fix(api): tighten agent v2 generated contracts (#36989)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh
2026-06-03 06:52:40 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent d6b4c800c2
commit 2d6c9300e3
15 changed files with 1458 additions and 484 deletions
@@ -91,7 +91,7 @@ class AgentAppConfigManager(BaseAppConfigManager):
"provider": model.model_provider,
"name": model.model,
"mode": "chat",
"completion_params": dict(model.model_settings or {}),
"completion_params": model.model_settings.model_dump(mode="json", exclude_none=True),
}
# The Agent Soul system prompt rides the EasyUI "simple" prompt slot; the
# agent backend is the real prompt authority, this only feeds the chat
@@ -112,7 +112,7 @@ class AgentAppRuntimeRequestBuilder:
model_provider=self._plugin_daemon_provider_name(agent_soul.model.model_provider),
model=agent_soul.model.model,
credentials=self._normalize_credentials(credentials),
model_settings=agent_soul.model.model_settings,
model_settings=agent_soul.model.model_settings.model_dump(mode="json", exclude_none=True),
),
execution_context=DifyExecutionContextLayerConfig(
tenant_id=context.dify_context.tenant_id,
@@ -31,7 +31,7 @@ RESERVED_AGENT_BACKEND_FEATURES = frozenset(
def build_runtime_feature_manifest(agent_soul: AgentSoulConfig) -> dict[str, Any]:
"""Describe PRD capabilities that are persisted but not executed in phase 3."""
warnings: list[dict[str, str]] = []
soul_dump = agent_soul.model_dump(mode="json")
soul_dump = agent_soul.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
for section in sorted(RESERVED_AGENT_BACKEND_FEATURES):
value = _get_nested(soul_dump, section)
has_value = bool(value)
@@ -25,6 +25,7 @@ from models.agent_config_entities import (
DeclaredOutputConfig,
DeclaredOutputType,
WorkflowNodeJobConfig,
WorkflowPreviousNodeOutputRef,
)
from models.agent_config_entities import (
effective_declared_outputs as _effective_declared_outputs,
@@ -139,7 +140,7 @@ class WorkflowAgentRuntimeRequestBuilder:
model_provider=self._plugin_daemon_provider_name(agent_soul.model.model_provider),
model=agent_soul.model.model,
credentials=self._normalize_credentials(credentials),
model_settings=agent_soul.model.model_settings,
model_settings=agent_soul.model.model_settings.model_dump(mode="json", exclude_none=True),
),
# The execution-context layer is now the only public protocol
# carrier for Dify tenant/user/run identifiers. ``user_id`` must
@@ -253,7 +254,7 @@ class WorkflowAgentRuntimeRequestBuilder:
def _resolve_previous_node_outputs(
self,
variable_pool: VariablePoolReader,
refs: Sequence[Mapping[str, Any]],
refs: Sequence[WorkflowPreviousNodeOutputRef],
) -> list[dict[str, Any]]:
resolved: list[dict[str, Any]] = []
for ref in refs:
@@ -279,7 +280,7 @@ class WorkflowAgentRuntimeRequestBuilder:
return resolved
@staticmethod
def _selector_from_ref(ref: Mapping[str, Any]) -> list[str] | None:
def _selector_from_ref(ref: WorkflowPreviousNodeOutputRef) -> list[str] | None:
for key in ("selector", "variable_selector", "value_selector"):
value = ref.get(key)
if isinstance(value, list) and all(isinstance(item, str) for item in value):
+18 -13
View File
@@ -9,7 +9,13 @@ from sqlalchemy.orm import Session
from graphon.enums import BuiltinNodeTypes
from models.agent import Agent, AgentConfigSnapshot, AgentStatus, WorkflowAgentNodeBinding
from models.agent_config_entities import AgentSoulConfig, WorkflowNodeJobConfig
from models.agent_config_entities import (
AgentFileRefConfig,
AgentHumanContactConfig,
AgentSoulConfig,
WorkflowNodeJobConfig,
WorkflowPreviousNodeOutputRef,
)
from models.model import UploadFile
from models.workflow import Workflow
@@ -183,16 +189,15 @@ class WorkflowAgentNodeValidator:
for human_ref in node_job.human_contacts:
cls._validate_human_ref(binding=binding, human_ref=human_ref)
file_refs = node_job.metadata.get("file_refs")
file_refs = node_job.metadata.file_refs
if isinstance(file_refs, list):
for file_ref in file_refs:
if isinstance(file_ref, Mapping):
cls._validate_file_ref(
session=session,
binding=binding,
file_ref=file_ref,
ref_context="metadata file ref",
)
cls._validate_file_ref(
session=session,
binding=binding,
file_ref=file_ref,
ref_context="metadata file ref",
)
@staticmethod
def iter_agent_v2_nodes(graph_dict: Mapping[str, Any]) -> Iterator[tuple[str, Mapping[str, Any]]]:
@@ -210,7 +215,7 @@ class WorkflowAgentNodeValidator:
yield node_id, node_data
@staticmethod
def selector_from_ref(ref: Mapping[str, Any]) -> list[str] | None:
def selector_from_ref(ref: WorkflowPreviousNodeOutputRef) -> list[str] | None:
for key in ("selector", "variable_selector", "value_selector"):
value = ref.get(key)
if isinstance(value, list) and all(isinstance(item, str) for item in value):
@@ -237,7 +242,7 @@ class WorkflowAgentNodeValidator:
binding: WorkflowAgentNodeBinding,
node_job: WorkflowNodeJobConfig,
) -> None:
forbidden_paths = cls._find_locked_agent_soul_paths(node_job.metadata)
forbidden_paths = cls._find_locked_agent_soul_paths(node_job.metadata.model_dump(mode="python"))
if forbidden_paths:
raise WorkflowAgentNodeValidationError(
f"Workflow Agent node {binding.node_id} cannot override locked Agent Soul fields: "
@@ -261,7 +266,7 @@ class WorkflowAgentNodeValidator:
cls,
*,
binding: WorkflowAgentNodeBinding,
human_ref: Mapping[str, Any],
human_ref: AgentHumanContactConfig,
) -> None:
contact_id = human_ref.get("contact_id") or human_ref.get("human_id") or human_ref.get("id")
if not isinstance(contact_id, str) or not contact_id:
@@ -306,7 +311,7 @@ class WorkflowAgentNodeValidator:
*,
session: Session,
binding: WorkflowAgentNodeBinding,
file_ref: Mapping[str, Any],
file_ref: AgentFileRefConfig,
ref_context: str,
) -> None:
tenant_id = file_ref.get("tenant_id")