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

@@ -21,7 +21,7 @@ from pipecat.frames.frames import (
)
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.frame_processor import FrameDirection
from schemas import AssistantUpsert, REALTIME_CAPABLE_TYPES
from schemas import AssistantUpsert, REALTIME_CAPABLE_TYPES, SystemToolConfig
from services.brains import BrainRuntime, SPECS, build_brain
from services.brains.base import GREETING_CONTEXT_MARKER
from services.brains.dify_llm import (
@@ -104,6 +104,17 @@ async def noop_queue_frame(_frame):
return None
def system_runtime_tool(kind: str) -> RuntimeTool:
return RuntimeTool(
id=f"tool_{kind}",
name=kind,
function_name=kind,
type="system",
description=f"{kind} system tool",
definition={"type": "system", "config": {"kind": kind}},
)
class BrainRegistryTests(unittest.TestCase):
def test_capability_matrix(self):
self.assertEqual(
@@ -154,45 +165,18 @@ class BrainRegistryTests(unittest.TestCase):
)
self.assertIn("user_name", assistant.dynamic_variable_definitions)
def test_system_tools_are_prompt_pipeline_only(self):
assistant = AssistantUpsert(
name="prompt",
type="prompt",
systemTools=["end_conversation", "skip_turn", "end_conversation"],
)
self.assertEqual(assistant.system_tools, ["end_conversation", "skip_turn"])
workflow = AssistantUpsert(
name="workflow",
type="workflow",
systemTools=["update_state"],
graph={},
)
self.assertEqual(workflow.system_tools, [])
def test_system_tool_config_accepts_all_platform_actions(self):
for kind in (
"end_conversation",
"update_state",
"skip_turn",
"request_human_handoff",
):
self.assertEqual(SystemToolConfig(kind=kind).kind, kind)
def test_system_tool_config_rejects_unknown_kind(self):
with self.assertRaises(ValueError):
AssistantUpsert(
name="realtime prompt",
type="prompt",
runtimeMode="realtime",
systemTools=["skip_turn"],
)
def test_system_tools_reject_unknown_kind(self):
with self.assertRaises(ValueError):
AssistantUpsert(
name="prompt",
type="prompt",
systemTools=["update_state", "magic"],
)
def test_prompt_update_state_requires_a_declared_variable(self):
with self.assertRaisesRegex(ValueError, "必须声明至少一个动态变量"):
AssistantUpsert(
name="prompt",
type="prompt",
systemTools=["update_state"],
)
SystemToolConfig(kind="magic")
def test_workflow_keeps_dynamic_variables_and_tool_bindings(self):
assistant = AssistantUpsert(
@@ -916,11 +900,11 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
"default": None,
}
},
system_tools=[
"end_conversation",
"update_state",
"skip_turn",
"request_human_handoff",
tools=[
system_runtime_tool("end_conversation"),
system_runtime_tool("update_state"),
system_runtime_tool("skip_turn"),
system_runtime_tool("request_human_handoff"),
],
)
brain = build_brain(cfg)
@@ -971,7 +955,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_end_conversation_ends_call_after_generated_speech(self):
cfg = AssistantConfig(
type="prompt",
system_tools=["end_conversation"],
tools=[system_runtime_tool("end_conversation")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1009,7 +993,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_end_conversation_finishes_when_no_speech(self):
cfg = AssistantConfig(
type="prompt",
system_tools=["end_conversation"],
tools=[system_runtime_tool("end_conversation")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1039,7 +1023,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
dynamic_variable_definitions={
"user_name": {"type": "string", "required": False, "default": None}
},
system_tools=["update_state"],
tools=[system_runtime_tool("update_state")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1083,7 +1067,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_update_state_rejects_undeclared_variable(self):
cfg = AssistantConfig(
type="prompt",
system_tools=["update_state"],
tools=[system_runtime_tool("update_state")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1108,7 +1092,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_skip_turn_suppresses_response(self):
cfg = AssistantConfig(
type="prompt",
system_tools=["skip_turn"],
tools=[system_runtime_tool("skip_turn")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1134,7 +1118,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_request_human_handoff_keeps_call_available(self):
cfg = AssistantConfig(
type="prompt",
system_tools=["request_human_handoff"],
tools=[system_runtime_tool("request_human_handoff")],
)
brain = build_brain(cfg)
llm = FakeLLM()
@@ -1299,10 +1283,11 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
"id": "agent",
"type": "agent",
"data": {
"systemTools": [
"update_state",
"skip_turn",
"request_human_handoff",
"inheritGlobalConfig": False,
"toolIds": [
"tool_update_state",
"tool_skip_turn",
"tool_request_human_handoff",
],
"stateVariableNames": ["customer_name"],
},
@@ -1322,6 +1307,11 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
"default": None,
},
},
tools=[
system_runtime_tool("update_state"),
system_runtime_tool("skip_turn"),
system_runtime_tool("request_human_handoff"),
],
),
{},
assistant_id="asst_workflow_system_tools",
@@ -1462,7 +1452,10 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
set_tools=lambda _tools: None,
call_end=call_end,
)
tool = brain._workflow_end_conversation_tool()
tool = brain._workflow_end_conversation_tool(
system_runtime_tool("end_conversation"),
"agent",
)
await brain.on_assistant_text_start("turn-1")
result = await tool.handler({"reason": "用户告别"}, None)