feat: organize tools and enable qwen realtime calls
This commit is contained in:
+198
-149
@@ -5,7 +5,7 @@ import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from models import AssistantConfig, RuntimeTool
|
||||
from models import AssistantConfig, RuntimeTool, RuntimeToolBinding
|
||||
from pipecat.flows import FlowManager
|
||||
from pipecat.frames.frames import (
|
||||
LLMContextFrame,
|
||||
@@ -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, SystemToolConfig
|
||||
from schemas import AssistantUpsert, BuiltInTools, REALTIME_CAPABLE_TYPES
|
||||
from services.brains import BrainRuntime, SPECS, build_brain
|
||||
from services.brains.base import GREETING_CONTEXT_MARKER
|
||||
from services.brains.dify_llm import (
|
||||
@@ -130,6 +130,21 @@ def system_runtime_tool(kind: str, **config) -> RuntimeTool:
|
||||
)
|
||||
|
||||
|
||||
def built_in_config(kind: str, **config) -> dict:
|
||||
key = {
|
||||
"end_conversation": "end_conversation",
|
||||
"update_state": "update_state",
|
||||
"skip_turn": "skip_turn",
|
||||
"request_human_handoff": "request_human_handoff",
|
||||
}[kind]
|
||||
base = {"description": f"{kind} system tool", **config}
|
||||
return {key: base}
|
||||
|
||||
|
||||
def merge_builtins(*configs: dict) -> dict:
|
||||
return {key: value for config in configs for key, value in config.items()}
|
||||
|
||||
|
||||
class BrainRegistryTests(unittest.TestCase):
|
||||
def test_capability_matrix(self):
|
||||
self.assertEqual(
|
||||
@@ -139,7 +154,7 @@ class BrainRegistryTests(unittest.TestCase):
|
||||
},
|
||||
{
|
||||
"prompt": frozenset({"pipeline", "realtime"}),
|
||||
"workflow": frozenset({"pipeline"}),
|
||||
"workflow": frozenset({"pipeline", "realtime"}),
|
||||
"dify": frozenset({"pipeline"}),
|
||||
"fastgpt": frozenset({"pipeline"}),
|
||||
},
|
||||
@@ -157,13 +172,13 @@ class BrainRegistryTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "尚未实现"):
|
||||
build_brain(AssistantConfig(type="opencode"))
|
||||
|
||||
def test_workflow_realtime_is_rejected_at_schema_boundary(self):
|
||||
with self.assertRaises(ValueError):
|
||||
AssistantUpsert(
|
||||
name="workflow",
|
||||
type="workflow",
|
||||
runtimeMode="realtime",
|
||||
)
|
||||
def test_workflow_realtime_is_accepted_at_schema_boundary(self):
|
||||
assistant = AssistantUpsert(
|
||||
name="workflow",
|
||||
type="workflow",
|
||||
runtimeMode="realtime",
|
||||
)
|
||||
self.assertEqual(assistant.runtime_mode, "realtime")
|
||||
|
||||
def test_prompt_realtime_keeps_dynamic_variable_definitions(self):
|
||||
assistant = AssistantUpsert(
|
||||
@@ -180,36 +195,27 @@ class BrainRegistryTests(unittest.TestCase):
|
||||
)
|
||||
self.assertIn("user_name", assistant.dynamic_variable_definitions)
|
||||
|
||||
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_end_interruption_is_opt_in(self):
|
||||
self.assertFalse(SystemToolConfig().allow_end_interruption)
|
||||
self.assertTrue(
|
||||
SystemToolConfig(allowEndInterruption=True).allow_end_interruption
|
||||
def test_built_in_tools_are_typed_platform_config(self):
|
||||
tools = BuiltInTools(
|
||||
endConversation={"allowEndInterruption": True},
|
||||
updateState={"updates": []},
|
||||
skipTurn={},
|
||||
requestHumanHandoff={},
|
||||
)
|
||||
|
||||
def test_system_tool_config_rejects_unknown_kind(self):
|
||||
with self.assertRaises(ValueError):
|
||||
SystemToolConfig(kind="magic")
|
||||
self.assertTrue(tools.end_conversation.allow_end_interruption)
|
||||
self.assertEqual(tools.update_state.updates, [])
|
||||
|
||||
def test_workflow_keeps_dynamic_variables_and_tool_bindings(self):
|
||||
assistant = AssistantUpsert(
|
||||
name="workflow",
|
||||
type="workflow",
|
||||
toolIds=["tool_a"],
|
||||
toolBindings=[{"toolId": "tool_a"}],
|
||||
dynamicVariableDefinitions={
|
||||
"customer": {"type": "string", "required": False, "default": "王先生"}
|
||||
},
|
||||
graph={},
|
||||
)
|
||||
self.assertEqual(assistant.tool_ids, ["tool_a"])
|
||||
self.assertEqual(assistant.tool_bindings, [])
|
||||
self.assertIn("customer", assistant.dynamic_variable_definitions)
|
||||
|
||||
|
||||
@@ -310,13 +316,16 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[startup_tool, conversation_tool],
|
||||
llm_tool_ids=[conversation_tool.id],
|
||||
tool_bindings=[RuntimeToolBinding(tool_id=conversation_tool.id)],
|
||||
startup={
|
||||
"actions": [
|
||||
{
|
||||
"id": "opening_message",
|
||||
"phase": "opening",
|
||||
"tool_id": startup_tool.id,
|
||||
"toolBinding": {
|
||||
"toolId": startup_tool.id,
|
||||
"argumentBindings": {},
|
||||
},
|
||||
"required": True,
|
||||
}
|
||||
]
|
||||
@@ -361,7 +370,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
{
|
||||
"id": tool.id,
|
||||
"phase": "preflight",
|
||||
"tool_id": tool.id,
|
||||
"toolBinding": {
|
||||
"toolId": tool.id,
|
||||
"argumentBindings": {},
|
||||
},
|
||||
"required": True,
|
||||
}
|
||||
for tool in tools
|
||||
@@ -422,8 +434,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
{
|
||||
"id": "opening_data",
|
||||
"phase": "opening",
|
||||
"tool_id": "opening_data",
|
||||
"arguments": {},
|
||||
"toolBinding": {
|
||||
"toolId": "opening_data",
|
||||
"argumentBindings": {},
|
||||
},
|
||||
"required": True,
|
||||
}
|
||||
],
|
||||
@@ -848,26 +862,13 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(queued_frames, [])
|
||||
|
||||
async def test_end_conversation_system_tool_is_owned_by_prompt_brain(self):
|
||||
brain = build_brain(
|
||||
AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[
|
||||
RuntimeTool(
|
||||
id="end-call",
|
||||
name="结束通话",
|
||||
function_name="end_call",
|
||||
type="system",
|
||||
definition={
|
||||
"config": {
|
||||
"kind": "end_conversation",
|
||||
"message_type": "none",
|
||||
"capture_reason": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
built_in_tools=built_in_config(
|
||||
"end_conversation", capture_reason=True
|
||||
),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
call_end = FakeCallEnd()
|
||||
visible_tools = []
|
||||
@@ -876,23 +877,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
pass
|
||||
|
||||
await brain.setup(
|
||||
AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[
|
||||
RuntimeTool(
|
||||
id="end-call",
|
||||
name="结束通话",
|
||||
function_name="end_call",
|
||||
type="system",
|
||||
definition={
|
||||
"config": {
|
||||
"kind": "end_conversation",
|
||||
"capture_reason": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
cfg,
|
||||
BrainRuntime(
|
||||
context=LLMContext(messages=[]),
|
||||
llm=llm,
|
||||
@@ -903,10 +888,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(visible_tools[0].name, "end_call")
|
||||
self.assertEqual(visible_tools[0].name, "end_conversation")
|
||||
params = FakeFunctionParams({"reason": "用户已完成咨询"})
|
||||
await brain.on_assistant_text_start("closing-turn")
|
||||
await llm.functions["end_call"](params)
|
||||
await llm.functions["end_conversation"](params)
|
||||
self.assertEqual(call_end.reason, "用户已完成咨询")
|
||||
self.assertFalse(call_end.finished)
|
||||
await brain.on_assistant_text_end("closing-turn", "", False)
|
||||
@@ -916,19 +901,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(params.result["action"], "ending_call")
|
||||
|
||||
async def test_system_end_conversation_waits_for_generated_closing_speech(self):
|
||||
tool = RuntimeTool(
|
||||
id="end-call",
|
||||
name="结束通话",
|
||||
function_name="end_call",
|
||||
type="system",
|
||||
definition={
|
||||
"config": {
|
||||
"kind": "end_conversation",
|
||||
"message_type": "none",
|
||||
}
|
||||
},
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
built_in_tools=built_in_config("end_conversation"),
|
||||
)
|
||||
cfg = AssistantConfig(type="prompt", tools=[tool])
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
call_end = FakeCallEnd()
|
||||
@@ -945,7 +921,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
),
|
||||
)
|
||||
await brain.on_assistant_text_start("closing-turn")
|
||||
await llm.functions["end_call"](FakeFunctionParams({}))
|
||||
await llm.functions["end_conversation"](FakeFunctionParams({}))
|
||||
await brain.on_assistant_text_end(
|
||||
"closing-turn",
|
||||
"感谢您的来电。祝您生活愉快,再见。",
|
||||
@@ -966,12 +942,23 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"default": None,
|
||||
}
|
||||
},
|
||||
tools=[
|
||||
system_runtime_tool("end_conversation"),
|
||||
system_runtime_tool("update_state"),
|
||||
system_runtime_tool("skip_turn"),
|
||||
system_runtime_tool("request_human_handoff"),
|
||||
],
|
||||
built_in_tools=merge_builtins(
|
||||
built_in_config("end_conversation"),
|
||||
built_in_config(
|
||||
"update_state",
|
||||
updates=[{
|
||||
"variableName": "user_name",
|
||||
"expression": {
|
||||
"type": "llm",
|
||||
"parameterName": "user_name",
|
||||
"valueType": "string",
|
||||
"description": "更新动态变量 user_name。",
|
||||
},
|
||||
}],
|
||||
),
|
||||
built_in_config("skip_turn"),
|
||||
built_in_config("request_human_handoff"),
|
||||
),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1021,7 +1008,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_end_conversation_ends_call_after_generated_speech(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[system_runtime_tool("end_conversation")],
|
||||
built_in_tools=built_in_config("end_conversation"),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1059,12 +1046,9 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_end_conversation_passes_interruptible_tool_setting(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[
|
||||
system_runtime_tool(
|
||||
"end_conversation",
|
||||
allow_end_interruption=True,
|
||||
)
|
||||
],
|
||||
built_in_tools=built_in_config(
|
||||
"end_conversation", allow_end_interruption=True
|
||||
),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1089,7 +1073,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_end_conversation_finishes_when_no_speech(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[system_runtime_tool("end_conversation")],
|
||||
built_in_tools=built_in_config("end_conversation"),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1119,7 +1103,18 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
dynamic_variable_definitions={
|
||||
"user_name": {"type": "string", "required": False, "default": None}
|
||||
},
|
||||
tools=[system_runtime_tool("update_state")],
|
||||
built_in_tools=built_in_config(
|
||||
"update_state",
|
||||
updates=[{
|
||||
"variableName": "user_name",
|
||||
"expression": {
|
||||
"type": "llm",
|
||||
"parameterName": "user_name",
|
||||
"valueType": "string",
|
||||
"description": "更新用户名",
|
||||
},
|
||||
}],
|
||||
),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1160,10 +1155,24 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
self.assertTrue(prompts[-1].endswith("当前用户名: 王小明"))
|
||||
|
||||
async def test_update_state_rejects_undeclared_variable(self):
|
||||
async def test_update_state_rejects_missing_configured_value(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[system_runtime_tool("update_state")],
|
||||
dynamic_variable_definitions={
|
||||
"known_key": {"type": "string", "required": False, "default": None}
|
||||
},
|
||||
built_in_tools=built_in_config(
|
||||
"update_state",
|
||||
updates=[{
|
||||
"variableName": "known_key",
|
||||
"expression": {
|
||||
"type": "llm",
|
||||
"parameterName": "known_key",
|
||||
"valueType": "string",
|
||||
"description": "更新已知值",
|
||||
},
|
||||
}],
|
||||
),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1183,12 +1192,12 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
params = FakeFunctionParams({"unknown_key": "x"})
|
||||
await llm.functions["update_state"](params)
|
||||
self.assertEqual(params.result["status"], "error")
|
||||
self.assertIn("未声明", params.result["message"])
|
||||
self.assertIn("缺少", params.result["message"])
|
||||
|
||||
async def test_skip_turn_suppresses_response(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[system_runtime_tool("skip_turn")],
|
||||
built_in_tools=built_in_config("skip_turn"),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1214,7 +1223,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_request_human_handoff_keeps_call_available(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
tools=[system_runtime_tool("request_human_handoff")],
|
||||
built_in_tools=built_in_config("request_human_handoff"),
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
llm = FakeLLM()
|
||||
@@ -1320,14 +1329,23 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"required": False,
|
||||
},
|
||||
],
|
||||
"dynamic_variable_assignments": {
|
||||
"order_status": "response.order.status"
|
||||
},
|
||||
}
|
||||
},
|
||||
secrets={"dynamic_variables": {"secret__token": "server-token"}},
|
||||
)
|
||||
],
|
||||
tool_bindings=[
|
||||
RuntimeToolBinding(
|
||||
tool_id="lookup",
|
||||
argument_bindings={
|
||||
"order_id": {"source": "model"},
|
||||
"Authorization": {"source": "model"},
|
||||
},
|
||||
result_assignments={
|
||||
"order_status": "response.order.status"
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
{},
|
||||
assistant_id="asst_1",
|
||||
@@ -1380,12 +1398,23 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"type": "agent",
|
||||
"data": {
|
||||
"inheritGlobalConfig": False,
|
||||
"toolIds": [
|
||||
"tool_update_state",
|
||||
"tool_skip_turn",
|
||||
"tool_request_human_handoff",
|
||||
],
|
||||
"stateVariableNames": ["customer_name"],
|
||||
"toolBindings": [],
|
||||
"builtInTools": merge_builtins(
|
||||
built_in_config(
|
||||
"update_state",
|
||||
updates=[{
|
||||
"variableName": "customer_name",
|
||||
"expression": {
|
||||
"type": "llm",
|
||||
"parameterName": "customer_name",
|
||||
"valueType": "string",
|
||||
"description": "更新客户姓名",
|
||||
},
|
||||
}],
|
||||
),
|
||||
built_in_config("skip_turn"),
|
||||
built_in_config("request_human_handoff"),
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -1403,11 +1432,6 @@ 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",
|
||||
@@ -1435,6 +1459,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
brain._emit_variables = AsyncMock()
|
||||
brain._refresh_agent_prompt = AsyncMock()
|
||||
brain._state.enter("agent", WorkflowStatus.WAITING_USER)
|
||||
|
||||
node_config = brain._agent_config("agent")
|
||||
functions = {tool.name: tool for tool in node_config["functions"]}
|
||||
@@ -1465,7 +1490,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
None,
|
||||
)
|
||||
self.assertEqual(unauthorized["status"], "error")
|
||||
self.assertIn("未获当前节点授权", unauthorized["message"])
|
||||
self.assertIn("缺少", unauthorized["message"])
|
||||
|
||||
handoff = await functions["request_human_handoff"].handler(
|
||||
{"reason": "用户要求人工"},
|
||||
@@ -1494,10 +1519,19 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"id": "set_state",
|
||||
"type": "update_state",
|
||||
"data": {
|
||||
"assignments": {
|
||||
"confirmed": True,
|
||||
"display_name": "{{source_name}}",
|
||||
}
|
||||
"updates": [
|
||||
{
|
||||
"variableName": "confirmed",
|
||||
"expression": {"type": "constant", "value": True},
|
||||
},
|
||||
{
|
||||
"variableName": "display_name",
|
||||
"expression": {
|
||||
"type": "variable",
|
||||
"variableName": "source_name",
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -1640,8 +1674,15 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(brain._flow_tool(timeout_tool, "start").timeout_secs, 7.0)
|
||||
self.assertIsNone(brain._flow_tool(session_tool, "start").timeout_secs)
|
||||
timeout_binding = RuntimeToolBinding(tool_id=timeout_tool.id)
|
||||
session_binding = RuntimeToolBinding(tool_id=session_tool.id)
|
||||
self.assertEqual(
|
||||
brain._flow_tool(timeout_tool, "start", timeout_binding).timeout_secs,
|
||||
7.0,
|
||||
)
|
||||
self.assertIsNone(
|
||||
brain._flow_tool(session_tool, "start", session_binding).timeout_secs
|
||||
)
|
||||
|
||||
async def test_flow_transition_honors_strict_agent_entry_mode(self):
|
||||
brain = WorkflowBrain(
|
||||
@@ -1961,9 +2002,12 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"id": "lookup_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "lookup",
|
||||
"resultAssignments": {
|
||||
"order_status": "order.status"
|
||||
"toolBinding": {
|
||||
"toolId": "lookup",
|
||||
"argumentBindings": {},
|
||||
"resultAssignments": {
|
||||
"order_status": "order.status"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1984,8 +2028,8 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def queue_frame(frame):
|
||||
queued.append(frame)
|
||||
|
||||
async def execute(_tool, _arguments, *, result_assignments=None):
|
||||
self.assertEqual(result_assignments, {"order_status": "order.status"})
|
||||
async def execute(_tool, _arguments, *, binding=None, result_assignments=None):
|
||||
self.assertEqual(binding.result_assignments, {"order_status": "order.status"})
|
||||
brain._store.assign("order_status", "paid")
|
||||
return {
|
||||
"status": "ok",
|
||||
@@ -2032,7 +2076,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(completed_event["workflowRevision"], brain._engine.revision)
|
||||
self.assertNotIn("result", completed_event["outcome"])
|
||||
|
||||
async def test_action_result_assignment_modes_reach_tool_executor(self):
|
||||
async def test_action_binding_reaches_tool_executor(self):
|
||||
tool = RuntimeTool(
|
||||
id="client_action",
|
||||
name="客户端操作",
|
||||
@@ -2051,25 +2095,30 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"id": "inherit_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignmentMode": "inherit",
|
||||
"toolBinding": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignments": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "override_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignmentMode": "override",
|
||||
"resultAssignments": {"choice": "action"},
|
||||
"toolBinding": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignments": {"choice": "action"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "none_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignmentMode": "none",
|
||||
"toolBinding": {
|
||||
"toolId": "client_action",
|
||||
"resultAssignments": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -2080,8 +2129,8 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
captured = []
|
||||
|
||||
async def execute(_tool, _arguments, *, result_assignments=None):
|
||||
captured.append(result_assignments)
|
||||
async def execute(_tool, _arguments, *, binding=None, result_assignments=None):
|
||||
captured.append(binding.result_assignments)
|
||||
return {"status": "ok", "updated_variables": []}
|
||||
|
||||
brain._runtime = BrainRuntime(
|
||||
@@ -2098,7 +2147,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
await brain._enter_action("override_action")
|
||||
await brain._enter_action("none_action")
|
||||
|
||||
self.assertEqual(captured, [None, {"choice": "action"}, {}])
|
||||
self.assertEqual(captured, [{}, {"choice": "action"}, {}])
|
||||
|
||||
async def test_action_client_error_sets_error_status(self):
|
||||
tool = RuntimeTool(
|
||||
@@ -2118,7 +2167,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
{
|
||||
"id": "action",
|
||||
"type": "action",
|
||||
"data": {"toolId": "client_action"},
|
||||
"data": {"toolBinding": {"toolId": "client_action"}},
|
||||
},
|
||||
],
|
||||
"edges": [],
|
||||
@@ -2127,7 +2176,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
async def execute(_tool, _arguments, *, result_assignments=None):
|
||||
async def execute(_tool, _arguments, *, binding=None, result_assignments=None):
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "用户关闭了确认弹窗",
|
||||
@@ -2172,7 +2221,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
{
|
||||
"id": "action",
|
||||
"type": "action",
|
||||
"data": {"toolId": "client_action"},
|
||||
"data": {"toolBinding": {"toolId": "client_action"}},
|
||||
},
|
||||
{"id": "end", "type": "end", "data": {}},
|
||||
],
|
||||
@@ -2193,7 +2242,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def queue_frame(frame):
|
||||
queued.append(frame)
|
||||
|
||||
async def execute(_tool, _arguments, *, result_assignments=None):
|
||||
async def execute(_tool, _arguments, *, binding=None, result_assignments=None):
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "会话已结束",
|
||||
@@ -2244,7 +2293,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"id": "block_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "client_action",
|
||||
"toolBinding": {"toolId": "client_action"},
|
||||
"userInputPolicy": "block",
|
||||
},
|
||||
},
|
||||
@@ -2252,7 +2301,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
"id": "queue_action",
|
||||
"type": "action",
|
||||
"data": {
|
||||
"toolId": "client_action",
|
||||
"toolBinding": {"toolId": "client_action"},
|
||||
"userInputPolicy": "queue",
|
||||
},
|
||||
},
|
||||
@@ -2264,7 +2313,7 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
input_states = []
|
||||
|
||||
async def execute(_tool, _arguments, *, result_assignments=None):
|
||||
async def execute(_tool, _arguments, *, binding=None, result_assignments=None):
|
||||
input_states.append("executing")
|
||||
return {"status": "ok", "updated_variables": []}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user