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

@@ -116,7 +116,7 @@ def node_types_response() -> dict[str, Any]:
return {"specVersion": SPEC_VERSION, "nodeTypes": NODE_SPECS}
def _edge_data_v3(edge: dict, source_type: str) -> dict:
def _edge_data_v3(edge: dict) -> dict:
data = deepcopy(edge.get("data") or {})
if data.get("mode") in EDGE_MODES:
data.setdefault("priority", 10)
@@ -125,7 +125,7 @@ def _edge_data_v3(edge: dict, source_type: str) -> dict:
transition = data.pop("transition_speech", data.get("transitionSpeech", ""))
data.update(
{
"mode": "llm" if condition and source_type == "agent" else "always",
"mode": "llm" if condition else "always",
"priority": 10,
"condition": condition,
"transitionSpeech": transition,
@@ -185,7 +185,6 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
edges = source.get("edges") or []
global_prompt = ""
mapped_nodes: list[dict] = []
type_by_id: dict[str, str] = {}
start_prompt_nodes: dict[str, str] = {}
type_map = {
@@ -220,15 +219,11 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
data.pop(key, None)
migrated["data"] = data
mapped_nodes.append(migrated)
if migrated.get("id"):
type_by_id[str(migrated["id"])] = new_type
mapped_edges: list[dict] = []
for edge in edges:
migrated = deepcopy(edge)
migrated["data"] = _edge_data_v3(
migrated, type_by_id.get(str(migrated.get("source")), "")
)
migrated["data"] = _edge_data_v3(migrated)
mapped_edges.append(migrated)
# A v2 Start was conversational. Insert a synthetic Agent so its prompt remains active.
@@ -254,7 +249,7 @@ def normalize_graph(graph: dict[str, Any] | None) -> dict[str, Any]:
for edge in mapped_edges:
if edge.get("source") == start_id:
edge["source"] = synthetic_id
edge["data"] = _edge_data_v3(edge, "agent")
edge["data"] = _edge_data_v3(edge)
if str(edge["data"].get("condition") or "").strip():
edge["data"]["mode"] = "llm"
mapped_edges.append(
@@ -352,10 +347,8 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
mode = data.get("mode")
if mode not in EDGE_MODES:
errors.append(f"{edge_id} 的判断模式无效:{mode}")
if mode == "llm" and node_by_id[source_id].get("type") != "agent":
errors.append(f"LLM 判断边只能从 Agent 发出:{edge_id}")
if mode == "llm" and not str(data.get("condition") or "").strip():
errors.append(f"LLM 判断边缺少自然语言条件:{edge_id}")
errors.append(f"大模型判断边缺少自然语言条件:{edge_id}")
if mode == "expression":
expression_errors = _validate_expression(data.get("expression"))
errors.extend(f"{edge_id}:{item}" for item in expression_errors)
@@ -370,7 +363,7 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
if mode == "always":
always_counts[source_id] += 1
if always_counts[source_id] > 1:
errors.append(f"节点 {source_id} 最多只能有一条默认")
errors.append(f"节点 {source_id} 最多只能有一条默认路径")
incoming[target_id] += 1
outgoing[source_id] += 1
adj[source_id].append(target_id)
@@ -394,14 +387,6 @@ def validate_graph(graph: dict[str, Any]) -> list[str]:
errors.append(f"节点 {node_id}{label}不能少于 {lo}")
if hi is not None and actual > hi:
errors.append(f"节点 {node_id}{label}不能多于 {hi}")
node_type = node.get("type")
if (
node_type in AUTOMATIC_NODE_TYPES
and outgoing[node_id] > 0
and always_counts[node_id] != 1
):
errors.append(f"自动节点 {node_id} 存在出边时必须有且仅有一条默认边")
start_id = next(
(node_id for node_id, node in node_by_id.items() if node.get("type") == "start"),
None,