Files
ai-video-fullstack/backend/services/workflow/routing.py
2026-08-03 10:55:57 +08:00

104 lines
3.6 KiB
Python

"""Priority-aware Workflow edge evaluation."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
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,
*,
current_user_message: dict[str, Any] | None = None,
) -> 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,
current_user_message=current_user_message,
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)