- 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.
98 lines
3.4 KiB
Python
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)
|
|
|