refactor: unify system tools as resources

This commit is contained in:
Xin Wang
2026-08-04 17:05:26 +08:00
parent d1b05f16c7
commit 74a8be2357
26 changed files with 788 additions and 507 deletions

View File

@@ -57,7 +57,7 @@ from services.message_stage import (
MessageStageSpec,
)
from services.runtime_variables import DynamicVariableError, DynamicVariableStore
from services.system_tools import SYSTEM_TOOL_KINDS, state_update_properties
from services.system_tools import state_update_properties, system_tool_kind
from services.tool_executor import ToolExecutionError, ToolExecutor
from services.tool_policy import policy_for_tool
from services.workflow.agent import WorkflowAgentStage
@@ -564,22 +564,21 @@ class WorkflowBrain(BaseBrain):
for tool_id in stage.tool_ids:
tool = self._tool_by_id.get(str(tool_id))
if tool and tool.type in {"http", "mcp", "client"}:
if not tool:
continue
if tool.type == "system":
append_function(
self._workflow_system_tool(
tool,
node_id=node_id,
state_variable_names=stage.state_variable_names,
)
)
elif tool.type in {"http", "mcp", "client"}:
append_function(self._flow_tool(tool, node_id))
append_function(self._knowledge_function(node_id))
if stage.vision_enabled and self._require_runtime().vision_function:
append_function(self._require_runtime().vision_function)
for kind in stage.system_tools:
if kind not in SYSTEM_TOOL_KINDS:
logger.warning(f"忽略 Agent {node_id} 的未知系统工具: {kind}")
continue
append_function(
self._workflow_system_tool(
kind,
node_id=node_id,
state_variable_names=stage.state_variable_names,
)
)
return self._require_agent_stage().node_config(
node_id,
functions=functions,
@@ -738,27 +737,30 @@ class WorkflowBrain(BaseBrain):
def _workflow_system_tool(
self,
kind: str,
tool: RuntimeTool,
*,
node_id: str,
state_variable_names: tuple[str, ...],
) -> FlowsFunctionSchema:
"""Build one platform-owned tool scoped to the active Agent node."""
kind = system_tool_kind(tool.definition or {})
if kind == "update_state":
return self._workflow_update_state_tool(
tool,
node_id,
state_variable_names=state_variable_names,
)
if kind == "skip_turn":
return self._workflow_skip_turn_tool()
return self._workflow_skip_turn_tool(tool)
if kind == "request_human_handoff":
return self._workflow_handoff_tool(node_id)
return self._workflow_handoff_tool(tool, node_id)
if kind == "end_conversation":
return self._workflow_end_conversation_tool()
raise ValueError(f"未知系统工具: {kind}")
return self._workflow_end_conversation_tool(tool, node_id)
raise ValueError(f"系统工具 {tool.id} 缺少有效 kind")
def _workflow_update_state_tool(
self,
tool: RuntimeTool,
node_id: str,
*,
state_variable_names: tuple[str, ...],
@@ -791,8 +793,8 @@ class WorkflowBrain(BaseBrain):
}
return FlowsFunctionSchema(
name="update_state",
description=(
name=tool.function_name,
description=tool.description or (
"静默更新当前阶段明确授权的动态变量。"
"只提交本轮获得或确认的信息,更新后继续当前回答。"
),
@@ -805,7 +807,7 @@ class WorkflowBrain(BaseBrain):
)
@staticmethod
def _workflow_skip_turn_tool() -> FlowsFunctionSchema:
def _workflow_skip_turn_tool(tool: RuntimeTool) -> FlowsFunctionSchema:
async def handler(args, _flow_manager):
reason = str((args or {}).get("reason") or "").strip()
result = {"status": "success", "action": "skip_turn"}
@@ -815,8 +817,8 @@ class WorkflowBrain(BaseBrain):
setattr(handler, "_suppress_followup_llm", True)
return FlowsFunctionSchema(
name="skip_turn",
description=(
name=tool.function_name,
description=tool.description or (
"跳过当前轮次,不生成任何语音回复。"
"仅当用户明确要求稍等、话还没说完,或输入可确认只是噪音时调用。"
),
@@ -830,7 +832,9 @@ class WorkflowBrain(BaseBrain):
handler=handler,
)
def _workflow_handoff_tool(self, node_id: str) -> FlowsFunctionSchema:
def _workflow_handoff_tool(
self, tool: RuntimeTool, node_id: str
) -> FlowsFunctionSchema:
async def handler(args, _flow_manager):
reason = str((args or {}).get("reason") or "human_handoff").strip()
await self._require_runtime().queue_frame(
@@ -852,8 +856,8 @@ class WorkflowBrain(BaseBrain):
}
return FlowsFunctionSchema(
name="request_human_handoff",
description=(
name=tool.function_name,
description=tool.description or (
"提交人工接管请求。当用户明确要求人工服务、投诉升级或 AI 无法"
"解决时调用。该工具只提交请求,不代表人工已经接通;调用后继续"
"回复用户并说明正在等待人工响应。"
@@ -865,27 +869,55 @@ class WorkflowBrain(BaseBrain):
handler=handler,
)
def _workflow_end_conversation_tool(self) -> FlowsFunctionSchema:
def _workflow_end_conversation_tool(
self, tool: RuntimeTool, node_id: str
) -> FlowsFunctionSchema:
config = (tool.definition or {}).get("config") or {}
message_type = str(config.get("message_type") or "none")
custom_message = str(config.get("custom_message") or "").strip()
capture_reason = bool(config.get("capture_reason", True))
async def handler(args, _flow_manager):
reason = str((args or {}).get("reason") or "end_conversation").strip()
self._waiting_for_generated_end_speech = True
self._require_runtime().call_end.begin(reason)
uses_custom_message = message_type == "custom" and bool(custom_message)
self._waiting_for_generated_end_speech = not uses_custom_message
runtime = self._require_runtime()
runtime.call_end.begin(reason)
if uses_custom_message:
await self._queue_visible_speech(
custom_message,
source="workflow-system-tool",
node_id=node_id,
)
arm_tracked = getattr(
runtime.call_end,
"arm_after_tracked_speech",
None,
)
if callable(arm_tracked):
await arm_tracked()
else:
runtime.call_end.arm_after_speech()
return {"status": "success", "action": "ending_call"}
setattr(handler, "_suppress_followup_llm", True)
return FlowsFunctionSchema(
name="end_conversation",
description=(
name=tool.function_name,
description=tool.description or (
"礼貌地结束本次对话。当用户明确告别、表示任务已完成"
"或要求挂断时调用。"
),
properties={
"reason": {
"type": "string",
"description": "结束对话的简短原因。",
properties=(
{
"reason": {
"type": "string",
"description": "结束对话的简短原因。",
}
}
},
required=[],
if capture_reason
else {}
),
required=["reason"] if capture_reason else [],
handler=handler,
)