feat: add system tools and state updates

This commit is contained in:
Xin Wang
2026-08-04 15:27:39 +08:00
parent 5bf5987fe4
commit 731a372df9
33 changed files with 1787 additions and 124 deletions
+68 -2
View File
@@ -11,15 +11,30 @@ from services.message_policy import (
MESSAGE_CONFIRMATION,
MESSAGE_PLAYBACK,
)
from services.system_tools import SYSTEM_TOOL_KINDS, normalize_system_tools
SPEC_VERSION = "3"
NODE_TYPES = {"start", "agent", "message", "action", "handoff", "end"}
NODE_TYPES = {
"start",
"agent",
"message",
"action",
"update_state",
"handoff",
"end",
}
EDGE_MODES = {"llm", "expression", "always"}
AGENT_ENTRY_MODES = {"wait_user", "generate"}
ACTION_RESULT_ASSIGNMENT_MODES = {"inherit", "override", "none"}
ACTION_USER_INPUT_POLICIES = {"queue", "block"}
AUTOMATIC_NODE_TYPES = {"start", "message", "action", "handoff"}
AUTOMATIC_NODE_TYPES = {
"start",
"message",
"action",
"update_state",
"handoff",
}
EXPRESSION_OPERATORS = {
"eq",
"neq",
@@ -98,6 +113,24 @@ NODE_SPECS: list[dict[str, Any]] = [
{"key": "name", "label": "节点名称", "type": "text", "default": "Action"},
],
},
{
"name": "update_state",
"displayName": "Update State",
"category": "execution_node",
"description": "原子更新已声明的动态变量,然后使用新状态继续路由。",
"icon": "Braces",
"accent": "mint",
"addable": True,
"constraints": {"minIncoming": 1, "minOutgoing": 0},
"fields": [
{
"key": "name",
"label": "节点名称",
"type": "text",
"default": "Update State",
},
],
},
{
"name": "handoff",
"displayName": "Handoff",
@@ -160,6 +193,11 @@ def _normalize_agent_data(data: dict[str, Any]) -> None:
if data.get("entryMode") not in AGENT_ENTRY_MODES:
data["entryMode"] = "wait_user"
data.pop("entrySpeech", None)
data["systemTools"] = list(normalize_system_tools(data.get("systemTools")))
state_names = data.get("stateVariableNames")
data["stateVariableNames"] = list(
dict.fromkeys(str(name) for name in state_names or [] if str(name))
)
if "inheritGlobalConfig" not in data:
has_node_overrides = any(
(
@@ -194,6 +232,12 @@ def _normalize_action_data(data: dict[str, Any]) -> None:
data.pop("speech", None)
def _normalize_update_state_data(data: dict[str, Any]) -> None:
"""Keep deterministic state updates as one explicit assignment object."""
assignments = data.get("assignments")
data["assignments"] = dict(assignments) if isinstance(assignments, dict) else {}
def _normalize_message_data(data: dict[str, Any]) -> None:
"""Fill the small built-in Message contract used by runtime and editor."""
data.setdefault("speech", "")
@@ -246,6 +290,8 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
_normalize_message_data(data)
elif node.get("type") == "action":
_normalize_action_data(data)
elif node.get("type") == "update_state":
_normalize_update_state_data(data)
return source
nodes = source.get("nodes") or []
@@ -262,6 +308,7 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
"agent": "agent",
"message": "message",
"action": "action",
"update_state": "update_state",
"handoff": "handoff",
"end": "end",
}
@@ -283,6 +330,8 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
_normalize_message_data(data)
elif new_type == "action":
_normalize_action_data(data)
elif new_type == "update_state":
_normalize_update_state_data(data)
elif new_type == "start":
prompt = str(data.pop("prompt", "") or "").strip()
data.pop("greeting", None)
@@ -390,6 +439,16 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
entry_mode = data.get("entryMode", "wait_user")
if entry_mode not in AGENT_ENTRY_MODES:
errors.append(f"Agent 节点 {node_id} 的进入模式无效:{entry_mode}")
system_tools = data.get("systemTools", [])
if not isinstance(system_tools, list) or any(
tool not in SYSTEM_TOOL_KINDS for tool in system_tools
):
errors.append(f"Agent 节点 {node_id} 的系统工具配置无效")
state_names = data.get("stateVariableNames", [])
if not isinstance(state_names, list) or any(
not isinstance(name, str) for name in state_names
):
errors.append(f"Agent 节点 {node_id} 的状态变量授权必须是列表")
elif node_type == "message":
data = node.get("data") or {}
speech = data.get("speech")
@@ -447,6 +506,13 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
errors.append(
f"Action 节点 {node_id} 的用户输入策略无效:{input_policy}"
)
elif node_type == "update_state":
data = node.get("data") or {}
assignments = data.get("assignments")
if not isinstance(assignments, dict) or not assignments:
errors.append(
f"Update State 节点 {node_id} 必须配置至少一个变量赋值"
)
if counts["start"] != 1:
errors.append("工作流必须有且仅有一个 Start 节点")