Files
dify/api/services/workflow_tool_references.py
T

82 lines
3.4 KiB
Python

"""Locate and retarget Workflow Tool references inside App DSL data."""
from collections.abc import Iterator, Mapping
from dataclasses import dataclass
from typing import Any
import yaml
from graphon.enums import BuiltinNodeTypes
from graphon.nodes.tool.entities import ToolProviderType
@dataclass(frozen=True)
class WorkflowToolReference:
"""A tool configuration in a DSL whose ``id_key`` holds a Workflow Tool provider id."""
config: dict[str, Any]
id_key: str
@property
def provider_id(self) -> str:
return self.config[self.id_key]
@dataclass(frozen=True)
class WorkflowToolTarget:
"""The Workflow Tool that a packaged provider id resolves to in the importing workspace."""
provider_id: str
name: str
create: bool
def iter_workflow_tool_references(data: Mapping[str, Any]) -> Iterator[WorkflowToolReference]:
"""Yield the Workflow Tool references of tool nodes, legacy Agent nodes and agent-chat configs."""
tool_configs: list[Any] = []
workflow = data.get("workflow")
graph = workflow.get("graph") if isinstance(workflow, Mapping) else None
nodes = graph.get("nodes") if isinstance(graph, Mapping) else None
for node in nodes if isinstance(nodes, list) else []:
node_data = node.get("data") if isinstance(node, Mapping) else None
if not isinstance(node_data, Mapping):
continue
if node_data.get("type") == BuiltinNodeTypes.TOOL:
tool_configs.append(node_data)
agent_parameters = node_data.get("agent_parameters")
if node_data.get("type") == BuiltinNodeTypes.AGENT and isinstance(agent_parameters, Mapping):
for parameter in agent_parameters.values():
value = parameter.get("value") if isinstance(parameter, Mapping) else None
tool_configs.extend(value if isinstance(value, list) else [])
model_config = data.get("model_config")
agent_mode = model_config.get("agent_mode") if isinstance(model_config, Mapping) else None
agent_tools = agent_mode.get("tools") if isinstance(agent_mode, Mapping) else None
tool_configs.extend(agent_tools if isinstance(agent_tools, list) else [])
for config in tool_configs:
if not isinstance(config, dict):
continue
# Agent node tools name the provider type ``type`` and keep the provider id in ``provider_name``.
if "provider_type" in config:
provider_type, id_key = config["provider_type"], "provider_id"
else:
provider_type, id_key = config.get("type"), "provider_name"
provider_id = config.get(id_key)
if provider_type == ToolProviderType.WORKFLOW and isinstance(provider_id, str) and provider_id:
yield WorkflowToolReference(config=config, id_key=id_key)
def retarget_workflow_tool_dsl(dsl: str, targets: Mapping[str, WorkflowToolTarget]) -> str:
"""Point the DSL's Workflow Tool references at their targets, keeping unknown providers as they are."""
data = yaml.safe_load(dsl)
if not isinstance(data, dict):
return dsl
for reference in iter_workflow_tool_references(data):
target = targets.get(reference.provider_id)
if target is None:
continue
reference.config[reference.id_key] = target.provider_id
# The editor matches a tool node's outputs by the tool name, which follows a renamed provider.
reference.config["tool_name"] = target.name
return yaml.dump(data, allow_unicode=True)