feat: add workflow_version to workflow_agent_node_bindings (#36603)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
zyssyz123
2026-05-25 06:26:19 +00:00
committed by GitHub
co-authored by Claude Opus 4.7 autofix-ci[bot]
parent eb41c9b769
commit 5f7eb7bde9
9 changed files with 121 additions and 11 deletions
+6 -3
View File
@@ -55,10 +55,14 @@ class AgentBackendModelConfig(BaseModel):
class AgentBackendOutputConfig(BaseModel):
"""API-side structured output declaration for the conventional output layer."""
"""API-side structured output declaration for the conventional output layer.
The structured-output tool name is fixed to ``final_output`` inside
``dify_agent.layers.output`` so callers only control the JSON Schema plus
optional description/strictness metadata.
"""
json_schema: dict[str, JsonValue]
name: str = "final_result"
description: str | None = None
strict: bool | None = None
@@ -153,7 +157,6 @@ class AgentBackendRunRequestBuilder:
metadata=run_input.metadata,
config=DifyOutputLayerConfig(
json_schema=run_input.output.json_schema,
name=run_input.output.name,
description=run_input.output.description,
strict=run_input.output.strict,
),
@@ -0,0 +1,65 @@
"""add workflow_version to workflow_agent_node_bindings
Restores the stage 1 §5.3 unique key
``(tenant_id, workflow_id, workflow_version, node_id)`` so draft and published
workflow bindings can coexist at the same workflow_id once we want to track
them per workflow version. ``workflow_version`` mirrors ``workflows.version``
("draft" or a published version string).
Because the New Agent Experience feature is pre-release, this table is empty
in every environment that matters; the ``server_default='draft'`` only exists
to keep developer-local rows valid during the alter and is dropped immediately
afterward so application code must specify ``workflow_version`` explicitly.
Revision ID: 97e2e1a644e8
Revises: f8b6b7e9c421
Create Date: 2026-05-25 11:43:37.611300
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '97e2e1a644e8'
down_revision = 'f8b6b7e9c421'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('workflow_agent_node_bindings', schema=None) as batch_op:
batch_op.add_column(
sa.Column(
'workflow_version',
sa.String(length=255),
nullable=False,
server_default='draft',
)
)
batch_op.alter_column('workflow_version', server_default=None)
batch_op.drop_constraint(
batch_op.f('workflow_agent_node_binding_node_unique'), type_='unique'
)
batch_op.create_unique_constraint(
'workflow_agent_node_binding_node_version_unique',
['tenant_id', 'workflow_id', 'workflow_version', 'node_id'],
)
batch_op.create_index(
'workflow_agent_node_binding_workflow_version_idx',
['tenant_id', 'workflow_id', 'workflow_version'],
unique=False,
)
def downgrade():
with op.batch_alter_table('workflow_agent_node_bindings', schema=None) as batch_op:
batch_op.drop_index('workflow_agent_node_binding_workflow_version_idx')
batch_op.drop_constraint(
'workflow_agent_node_binding_node_version_unique', type_='unique'
)
batch_op.create_unique_constraint(
batch_op.f('workflow_agent_node_binding_node_unique'),
['tenant_id', 'workflow_id', 'node_id'],
postgresql_nulls_not_distinct=False,
)
batch_op.drop_column('workflow_version')
+13 -1
View File
@@ -231,17 +231,29 @@ class WorkflowAgentNodeBinding(DefaultFieldsMixin, Base):
UniqueConstraint(
"tenant_id",
"workflow_id",
"workflow_version",
"node_id",
name="workflow_agent_node_binding_node_unique",
name="workflow_agent_node_binding_node_version_unique",
),
Index("workflow_agent_node_binding_agent_idx", "tenant_id", "agent_id"),
Index("workflow_agent_node_binding_current_snapshot_idx", "tenant_id", "current_snapshot_id"),
Index("workflow_agent_node_binding_app_idx", "tenant_id", "app_id"),
Index(
"workflow_agent_node_binding_workflow_version_idx",
"tenant_id",
"workflow_id",
"workflow_version",
),
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
workflow_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
# Tracks which workflow version (draft or a published version string) this
# binding belongs to. Mirrors ``Workflow.version`` and lets us keep separate
# rows for the draft workflow and each published copy under the same
# workflow_id, restoring the stage 1 §5.3 unique key.
workflow_version: Mapped[str] = mapped_column(String(255), nullable=False)
node_id: Mapped[str] = mapped_column(String(255), nullable=False)
binding_type: Mapped[WorkflowAgentBindingType] = mapped_column(
EnumText(WorkflowAgentBindingType, length=32), nullable=False
+11
View File
@@ -28,6 +28,10 @@ from services.entities.agent_entities import (
WorkflowNodeJobConfig,
)
# WorkflowAgentNodeBinding.workflow_version tag for the draft workflow row.
# Mirrors Workflow.version when it is "draft" (see models/workflow.py).
_DRAFT_WORKFLOW_VERSION = "draft"
class AgentComposerService:
@classmethod
@@ -284,6 +288,7 @@ class AgentComposerService:
tenant_id=tenant_id,
app_id=app_id,
workflow_id=workflow_id,
workflow_version=_DRAFT_WORKFLOW_VERSION,
node_id=node_id,
binding_type=WorkflowAgentBindingType.INLINE_AGENT,
agent_id=agent.id,
@@ -387,6 +392,7 @@ class AgentComposerService:
tenant_id=tenant_id,
app_id=app_id,
workflow_id=workflow_id,
workflow_version=_DRAFT_WORKFLOW_VERSION,
node_id=node_id,
created_by=account_id,
)
@@ -606,11 +612,16 @@ class AgentComposerService:
def _get_workflow_binding(
cls, *, tenant_id: str, workflow_id: str, node_id: str
) -> WorkflowAgentNodeBinding | None:
# Composer always operates against the draft workflow row, so this lookup
# is scoped to ``workflow_version="draft"``. Published bindings are
# materialized by WorkflowAgentPublishService.copy_agent_node_bindings_to_published
# and are not edited through the Composer.
return db.session.scalar(
select(WorkflowAgentNodeBinding)
.where(
WorkflowAgentNodeBinding.tenant_id == tenant_id,
WorkflowAgentNodeBinding.workflow_id == workflow_id,
WorkflowAgentNodeBinding.workflow_version == _DRAFT_WORKFLOW_VERSION,
WorkflowAgentNodeBinding.node_id == node_id,
)
.limit(1)
@@ -39,6 +39,7 @@ class WorkflowAgentPublishService:
WorkflowAgentNodeBinding.tenant_id == draft_workflow.tenant_id,
WorkflowAgentNodeBinding.app_id == draft_workflow.app_id,
WorkflowAgentNodeBinding.workflow_id == draft_workflow.id,
WorkflowAgentNodeBinding.workflow_version == draft_workflow.version,
WorkflowAgentNodeBinding.node_id.in_(node_ids),
)
).all()
@@ -48,6 +49,7 @@ class WorkflowAgentPublishService:
tenant_id=binding.tenant_id,
app_id=binding.app_id,
workflow_id=published_workflow.id,
workflow_version=published_workflow.version,
node_id=binding.node_id,
binding_type=binding.binding_type,
agent_id=binding.agent_id,