Files
ai-video-fullstack/backend/services/workflow/routing.py
Xin Wang bdf3d3dd9c Refactor workflow agent and routing components for improved functionality
- Introduce WorkflowAgentStage to manage agent stage configurations and enhance interaction with the workflow engine.
- Implement WorkflowEdgeEvaluator for priority-aware edge evaluation, improving routing decisions based on conditions and user turns.
- Update WorkflowBrain to handle user turns and routing more effectively, ensuring agents cannot have only one default path.
- Enhance CallEndCoordinator to track speech events and manage call termination based on queued speech.
- Add new models and output handling for workflow interactions, improving clarity and maintainability.
- Update tests to validate the new routing logic and agent behavior under various scenarios.
2026-07-17 22:37:15 +08:00

98 lines
3.4 KiB
Python

"""Priority-aware Workflow edge evaluation."""
from __future__ import annotations
from collections.abc import Callable
from services.runtime_variables import DynamicVariableStore
from services.workflow.models import EdgeEvaluation, RouteStatus
from services.workflow_engine import WorkflowEngine
from services.workflow_router import WorkflowLLMRouter
class WorkflowEdgeEvaluator:
"""Evaluate one node's paths without changing runtime state."""
def __init__(
self,
engine: WorkflowEngine,
store: DynamicVariableStore,
router_for_node: Callable[[str], WorkflowLLMRouter],
) -> None:
self._engine = engine
self._store = store
self._router_for_node = router_for_node
async def evaluate(self, node_id: str) -> EdgeEvaluation:
"""Select the first matching conditional path, then the default path."""
outgoing = self._engine.outgoing(node_id)
expression_edge = self._engine.deterministic_edge(
node_id,
self._store,
include_default=False,
)
default_edge = next(
(
edge
for edge in outgoing
if self._engine.edge_mode(edge) == "always"
),
None,
)
llm_edges = [
edge for edge in outgoing if self._engine.edge_mode(edge) == "llm"
]
# A matching expression is a priority boundary. A later LLM condition
# cannot bypass it, while an earlier LLM condition still gets one chance.
if expression_edge:
expression_index = outgoing.index(expression_edge)
llm_edges = [
edge
for edge in llm_edges
if outgoing.index(edge) < expression_index
]
if not llm_edges:
return self._matched_or_no_match(expression_edge or default_edge)
result = await self._router_for_node(node_id).select_edge(
node_name=self._engine.name(node_id),
node_prompt=self._engine.routing_prompt(node_id, self._store),
edges=llm_edges,
history=self._store.history,
variables={
key: value
for key, value in self._store.values.items()
if not key.startswith(("system__", "secret__"))
},
edge_name=self._engine.edge_fn_name,
edge_description=self._engine.edge_description,
)
if result.status == RouteStatus.ERROR:
return EdgeEvaluation(status=RouteStatus.ERROR, error=result.error)
if result.status == RouteStatus.NO_MATCH:
return self._matched_or_no_match(expression_edge or default_edge)
selected = next(
(
edge
for edge in llm_edges
if self._engine.edge_fn_name(edge) == result.function_name
),
None,
)
if selected is None:
return EdgeEvaluation(
status=RouteStatus.ERROR,
error="路由模型返回了不属于当前节点的连接",
)
return EdgeEvaluation(status=RouteStatus.MATCHED, edge=selected)
@staticmethod
def _matched_or_no_match(edge: dict | None) -> EdgeEvaluation:
if edge is None:
return EdgeEvaluation(status=RouteStatus.NO_MATCH)
return EdgeEvaluation(status=RouteStatus.MATCHED, edge=edge)