Refactor workflow routing and greeting management in Brain classes

- Update WorkflowBrain to handle greeting playback more effectively, ensuring that the initial greeting completes before transitioning to the first node.
- Introduce new methods for managing greeting states and conditions, enhancing the interaction flow for user turns.
- Refactor WorkflowLLMRouter to improve routing logic and ensure proper handling of conditional paths.
- Enhance tests to verify the correct behavior of greeting management and routing under various scenarios, including waiting for audio playback to finish.
- Update frontend components to reflect changes in edge handling and improve user experience in workflow configurations.
This commit is contained in:
Xin Wang
2026-07-17 22:01:42 +08:00
parent 34c0d12d2a
commit 162a3d8bec
15 changed files with 826 additions and 147 deletions

View File

@@ -284,6 +284,111 @@ class WorkflowGraphTests(unittest.TestCase):
)
)
def test_all_source_nodes_allow_multiple_conditional_paths(self):
expression = {
"combinator": "and",
"rules": [{"variable": "route", "operator": "eq", "value": "yes"}],
}
cases = {
"start": {
"nodes": [
{"id": "start", "type": "start", "data": {}},
{"id": "agent", "type": "agent", "data": {}},
{"id": "action", "type": "action", "data": {}},
{"id": "end", "type": "end", "data": {}},
],
"edges": [
{"id": "default", "source": "start", "target": "agent", "data": {"mode": "always", "priority": 10}},
{"id": "llm", "source": "start", "target": "end", "data": {"mode": "llm", "priority": 20, "condition": "应当结束"}},
{"id": "expr", "source": "start", "target": "action", "data": {"mode": "expression", "priority": 30, "expression": expression}},
{"id": "action-end", "source": "action", "target": "end", "data": {"mode": "always", "priority": 10}},
],
},
"agent": {
"nodes": [
{"id": "start", "type": "start", "data": {}},
{"id": "agent", "type": "agent", "data": {}},
{"id": "action", "type": "action", "data": {}},
{"id": "handoff", "type": "handoff", "data": {}},
{"id": "end", "type": "end", "data": {}},
],
"edges": [
{"id": "begin", "source": "start", "target": "agent", "data": {"mode": "always", "priority": 10}},
{"id": "default", "source": "agent", "target": "end", "data": {"mode": "always", "priority": 10}},
{"id": "llm", "source": "agent", "target": "action", "data": {"mode": "llm", "priority": 20, "condition": "执行操作"}},
{"id": "expr", "source": "agent", "target": "handoff", "data": {"mode": "expression", "priority": 30, "expression": expression}},
{"id": "action-end", "source": "action", "target": "end", "data": {"mode": "always", "priority": 10}},
],
},
"action": {
"nodes": [
{"id": "start", "type": "start", "data": {}},
{"id": "action", "type": "action", "data": {}},
{"id": "agent", "type": "agent", "data": {}},
{"id": "handoff", "type": "handoff", "data": {}},
{"id": "end", "type": "end", "data": {}},
],
"edges": [
{"id": "begin", "source": "start", "target": "action", "data": {"mode": "always", "priority": 10}},
{"id": "default", "source": "action", "target": "agent", "data": {"mode": "always", "priority": 10}},
{"id": "llm", "source": "action", "target": "end", "data": {"mode": "llm", "priority": 20, "condition": "执行失败"}},
{"id": "expr", "source": "action", "target": "handoff", "data": {"mode": "expression", "priority": 30, "expression": expression}},
],
},
"handoff": {
"nodes": [
{"id": "start", "type": "start", "data": {}},
{"id": "handoff", "type": "handoff", "data": {}},
{"id": "agent", "type": "agent", "data": {}},
{"id": "action", "type": "action", "data": {}},
{"id": "end", "type": "end", "data": {}},
],
"edges": [
{"id": "begin", "source": "start", "target": "handoff", "data": {"mode": "always", "priority": 10}},
{"id": "default", "source": "handoff", "target": "agent", "data": {"mode": "always", "priority": 10}},
{"id": "llm", "source": "handoff", "target": "end", "data": {"mode": "llm", "priority": 20, "condition": "转接完成"}},
{"id": "expr", "source": "handoff", "target": "action", "data": {"mode": "expression", "priority": 30, "expression": expression}},
{"id": "action-end", "source": "action", "target": "end", "data": {"mode": "always", "priority": 10}},
],
},
}
for source_type, parts in cases.items():
graph = {"specVersion": 3, "settings": {}, **parts}
with self.subTest(source_type=source_type):
self.assertEqual(validate_graph(graph), [])
graph["edges"].append(
{
"id": "duplicate-default",
"source": source_type,
"target": "end",
"data": {"mode": "always", "priority": 40},
}
)
self.assertTrue(
any("最多只能有一条默认路径" in error for error in validate_graph(graph))
)
def test_v2_condition_on_non_agent_becomes_llm_path(self):
graph = normalize_graph(
{
"nodes": [
{"id": "start", "type": "start", "data": {}},
{"id": "handoff", "type": "handoff", "data": {}},
],
"edges": [
{
"id": "route",
"source": "start",
"target": "handoff",
"data": {"condition": "用户需要人工服务"},
}
],
}
)
self.assertEqual(graph["edges"][0]["data"]["mode"], "llm")
def test_v2_start_prompt_is_preserved_in_synthetic_agent(self):
graph = normalize_graph(
{