feat(workflow): add edge-tool routing and realtime runtime

This commit is contained in:
Xin Wang
2026-08-04 22:29:04 +08:00
parent 1902ffc240
commit 59588ba88d
25 changed files with 1952 additions and 107 deletions
+68
View File
@@ -24,6 +24,8 @@ NODE_TYPES = {
"end",
}
EDGE_MODES = {"llm", "expression", "always"}
WORKFLOW_RUNTIME_MODES = {"pipeline", "realtime"}
WORKFLOW_LLM_ROUTING_MODES = {"llm_router", "edge_tool"}
AGENT_ENTRY_MODES = {"wait_user", "generate"}
ACTION_RESULT_ASSIGNMENT_MODES = {"inherit", "override", "none"}
ACTION_USER_INPUT_POLICIES = {"queue", "block"}
@@ -254,7 +256,12 @@ def _normalize_message_data(data: dict[str, Any]) -> None:
def _normalize_settings(settings: dict[str, Any], *, global_prompt: str = "") -> None:
# Missing values belong to graphs saved before workflow runtime/routing
# became explicit. Preserve their dedicated-router behavior.
settings.setdefault("runtimeMode", "pipeline")
settings.setdefault("llmRoutingMode", "llm_router")
settings.setdefault("globalPrompt", global_prompt)
settings.setdefault("defaultRealtimeResourceId", "")
settings.setdefault("defaultLlmResourceId", "")
settings.setdefault("defaultAsrResourceId", "")
settings.setdefault("defaultTtsResourceId", "")
@@ -418,6 +425,27 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
return []
errors: list[str] = []
settings = graph.get("settings") or {}
runtime_mode = str(settings.get("runtimeMode") or "pipeline")
llm_routing_mode = str(settings.get("llmRoutingMode") or "llm_router")
if runtime_mode not in WORKFLOW_RUNTIME_MODES:
errors.append(f"工作流运行模式无效:{runtime_mode}")
if llm_routing_mode not in WORKFLOW_LLM_ROUTING_MODES:
errors.append(f"大模型判断路由模式无效:{llm_routing_mode}")
if runtime_mode == "realtime" and llm_routing_mode != "edge_tool":
errors.append("Realtime 工作流的大模型判断路由只能使用边工具模式")
if runtime_mode == "realtime" and not settings.get(
"defaultRealtimeResourceId"
):
errors.append("Realtime 工作流必须选择 Realtime 模型")
if runtime_mode == "realtime" and settings.get("visionEnabled"):
errors.append("Realtime 工作流暂不支持视觉能力")
if (
runtime_mode == "realtime"
and settings.get("knowledgeBaseId")
and settings.get("knowledgeMode") != "on_demand"
):
errors.append("Realtime 工作流的知识库只能使用按需模式")
node_by_id: dict[str, dict] = {}
counts: dict[str, int] = defaultdict(int)
for node in nodes:
@@ -443,6 +471,34 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
not isinstance(name, str) for name in state_names
):
errors.append(f"Agent 节点 {node_id} 的状态变量授权必须是列表")
if runtime_mode == "realtime":
if data.get("contextPolicy", "inherit") != "inherit":
errors.append(
f"Realtime Agent 节点 {node_id} 只能继承会话上下文"
)
if any(
data.get(key)
for key in (
"llmResourceId",
"asrResourceId",
"ttsResourceId",
)
) or any(key in data for key in ("enableInterrupt", "turnConfig")):
errors.append(
f"Realtime Agent 节点 {node_id} 不支持节点级模型、语音或交互策略覆盖"
)
source = settings if data.get("inheritGlobalConfig", True) else data
if source.get("visionEnabled"):
errors.append(
f"Realtime Agent 节点 {node_id} 暂不支持视觉能力"
)
if (
source.get("knowledgeBaseId")
and source.get("knowledgeMode") != "on_demand"
):
errors.append(
f"Realtime Agent 节点 {node_id} 的知识库只能使用按需模式"
)
elif node_type == "message":
data = node.get("data") or {}
speech = data.get("speech")
@@ -500,6 +556,10 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
errors.append(
f"Action 节点 {node_id} 的用户输入策略无效:{input_policy}"
)
elif runtime_mode == "realtime" and input_policy != "block":
errors.append(
f"Realtime Action 节点 {node_id} 必须阻止执行期间的用户输入"
)
elif node_type == "update_state":
data = node.get("data") or {}
assignments = data.get("assignments")
@@ -550,6 +610,13 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
if priority in priorities[source_id]:
errors.append(f"节点 {source_id} 的条件边优先级不能重复:{priority}")
priorities[source_id].add(priority)
source_type = node_by_id[source_id].get("type")
if llm_routing_mode == "edge_tool" and mode == "llm" and source_type != "agent":
errors.append(f"边工具模式的大模型判断边只能从 Agent 发出:{edge_id}")
if llm_routing_mode == "edge_tool" and mode == "always" and source_type == "agent":
errors.append(
f"边工具模式的 Agent 不能使用默认路径:{source_id}"
)
incoming[target_id] += 1
outgoing[source_id] += 1
adj[source_id].append(target_id)
@@ -633,6 +700,7 @@ def graph_references(graph: dict[str, Any]) -> dict[str, set[str]]:
str(value)
for value in (
settings.get("defaultLlmResourceId"),
settings.get("defaultRealtimeResourceId"),
settings.get("defaultAsrResourceId"),
settings.get("defaultTtsResourceId"),
(