"""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)