feat: add workflow action runtime policies

This commit is contained in:
Xin Wang
2026-07-31 23:30:04 +08:00
parent c2f0f5eb04
commit f155f98e6e
10 changed files with 445 additions and 71 deletions

View File

@@ -92,6 +92,10 @@ class FakeFunctionParams:
self.properties = properties
async def noop_queue_frame(_frame):
return None
class BrainRegistryTests(unittest.TestCase):
def test_capability_matrix(self):
self.assertEqual(
@@ -806,6 +810,187 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(variable_events[-1]["changed"], ["order_status"])
self.assertEqual(variable_events[-1]["variables"], {"order_status": "paid"})
async def test_action_result_assignment_modes_reach_tool_executor(self):
tool = RuntimeTool(
id="client_action",
name="客户端操作",
function_name="show_message",
type="client",
)
brain = WorkflowBrain(
AssistantConfig(
type="workflow",
graph={
"specVersion": 3,
"settings": {},
"nodes": [
{"id": "start", "type": "start", "data": {}},
{
"id": "inherit_action",
"type": "action",
"data": {
"toolId": "client_action",
"resultAssignmentMode": "inherit",
},
},
{
"id": "override_action",
"type": "action",
"data": {
"toolId": "client_action",
"resultAssignmentMode": "override",
"resultAssignments": {"choice": "action"},
},
},
{
"id": "none_action",
"type": "action",
"data": {
"toolId": "client_action",
"resultAssignmentMode": "none",
},
},
],
"edges": [],
},
tools=[tool],
)
)
captured = []
async def execute(_tool, _arguments, *, result_assignments=None):
captured.append(result_assignments)
return {"status": "ok", "updated_variables": []}
brain._runtime = BrainRuntime(
context=LLMContext(messages=[]),
llm=FakeLLM(),
queue_frame=noop_queue_frame,
set_system_prompt=lambda _prompt: None,
set_tools=lambda _tools: None,
call_end=FakeCallEnd(),
)
brain._tools.execute = execute
await brain._enter_action("inherit_action")
await brain._enter_action("override_action")
await brain._enter_action("none_action")
self.assertEqual(captured, [None, {"choice": "action"}, {}])
async def test_action_client_error_sets_error_status(self):
tool = RuntimeTool(
id="client_action",
name="客户端操作",
function_name="show_message",
type="client",
)
brain = WorkflowBrain(
AssistantConfig(
type="workflow",
graph={
"specVersion": 3,
"settings": {},
"nodes": [
{"id": "start", "type": "start", "data": {}},
{
"id": "action",
"type": "action",
"data": {"toolId": "client_action"},
},
],
"edges": [],
},
tools=[tool],
)
)
async def execute(_tool, _arguments, *, result_assignments=None):
return {
"status": "error",
"message": "用户关闭了确认弹窗",
"updated_variables": [],
}
brain._runtime = BrainRuntime(
context=LLMContext(messages=[]),
llm=FakeLLM(),
queue_frame=noop_queue_frame,
set_system_prompt=lambda _prompt: None,
set_tools=lambda _tools: None,
call_end=FakeCallEnd(),
)
brain._tools.execute = execute
await brain._enter_action("action")
self.assertEqual(brain._store.values["system__last_action_status"], "error")
self.assertEqual(
brain._store.values["system__last_action_error"],
"用户关闭了确认弹窗",
)
async def test_action_block_policy_only_suppresses_input_while_running(self):
tool = RuntimeTool(
id="client_action",
name="客户端操作",
function_name="show_message",
type="client",
)
brain = WorkflowBrain(
AssistantConfig(
type="workflow",
graph={
"specVersion": 3,
"settings": {},
"nodes": [
{"id": "start", "type": "start", "data": {}},
{
"id": "block_action",
"type": "action",
"data": {
"toolId": "client_action",
"userInputPolicy": "block",
},
},
{
"id": "queue_action",
"type": "action",
"data": {
"toolId": "client_action",
"userInputPolicy": "queue",
},
},
],
"edges": [],
},
tools=[tool],
)
)
input_states = []
async def execute(_tool, _arguments, *, result_assignments=None):
input_states.append("executing")
return {"status": "ok", "updated_variables": []}
brain._runtime = BrainRuntime(
context=LLMContext(messages=[]),
llm=FakeLLM(),
queue_frame=noop_queue_frame,
set_system_prompt=lambda _prompt: None,
set_tools=lambda _tools: None,
call_end=FakeCallEnd(),
set_input_enabled=input_states.append,
)
brain._tools.execute = execute
await brain._enter_action("block_action")
self.assertEqual(input_states, [False, "executing", True])
input_states.clear()
await brain._enter_action("queue_action")
self.assertEqual(input_states, ["executing"])
async def test_nodes_without_outgoing_edges_remain_active(self):
queued = []