- 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.
135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""Resolve and apply one Workflow Agent's complete stage configuration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from copy import deepcopy
|
|
|
|
from models import AssistantConfig
|
|
from pipecat.flows import ContextStrategy, ContextStrategyConfig, NodeConfig
|
|
from pipecat.frames.frames import LLMUpdateSettingsFrame
|
|
from pipecat.services.settings import LLMSettings
|
|
|
|
from services.brains.base import BrainRuntime
|
|
from services.runtime_variables import DynamicVariableStore
|
|
from services.workflow_engine import WorkflowEngine
|
|
from services.workflow_router import WorkflowLLMRouter
|
|
|
|
|
|
AGENT_STAGE_INSTRUCTION = (
|
|
"工作流路由已在用户一轮输入结束时完成。只执行当前阶段任务,"
|
|
"不要自行解释、模拟或宣布节点切换。"
|
|
)
|
|
|
|
|
|
class WorkflowAgentStage:
|
|
"""Translate graph-level Agent data into Pipecat Flows configuration."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
cfg: AssistantConfig,
|
|
engine: WorkflowEngine,
|
|
store: DynamicVariableStore,
|
|
runtime: BrainRuntime,
|
|
) -> None:
|
|
self._cfg = cfg
|
|
self._engine = engine
|
|
self._store = store
|
|
self._runtime = runtime
|
|
|
|
def role_message(self, node_id: str) -> str:
|
|
stage_prompt = self._engine.prompt_for(node_id, self._store)
|
|
return (
|
|
f"{stage_prompt}\n\n[工作流执行规则]\n"
|
|
f"{AGENT_STAGE_INSTRUCTION}"
|
|
)
|
|
|
|
async def refresh_prompt(self, node_id: str) -> None:
|
|
await self._runtime.queue_frame(
|
|
LLMUpdateSettingsFrame(
|
|
delta=LLMSettings(
|
|
system_instruction=self.role_message(node_id)
|
|
)
|
|
)
|
|
)
|
|
|
|
def router_for_node(
|
|
self,
|
|
node_id: str,
|
|
default_router: WorkflowLLMRouter,
|
|
) -> WorkflowLLMRouter:
|
|
resource_id = self._engine.agent_stage_config(node_id).llm_resource_id
|
|
resource = self._cfg.workflow_model_resources.get(resource_id)
|
|
if not resource:
|
|
return default_router
|
|
from services.pipecat.service_factory import config_with_resource
|
|
|
|
return WorkflowLLMRouter(config_with_resource(self._cfg, resource))
|
|
|
|
async def apply(self, node_id: str) -> None:
|
|
stage = self._engine.agent_stage_config(node_id)
|
|
if self._runtime.set_input_enabled:
|
|
self._runtime.set_input_enabled(True)
|
|
if self._runtime.apply_turn_config:
|
|
await self._runtime.apply_turn_config(
|
|
stage.enable_interrupt,
|
|
stage.turn_config,
|
|
)
|
|
if self._runtime.switch_services:
|
|
await self._runtime.switch_services(
|
|
stage.llm_resource_id or None,
|
|
stage.asr_resource_id or None,
|
|
stage.tts_resource_id or None,
|
|
)
|
|
if self._runtime.set_knowledge_scope:
|
|
self._runtime.set_knowledge_scope(
|
|
{
|
|
"knowledge_base_id": stage.knowledge_base_id,
|
|
"mode": stage.knowledge_mode,
|
|
"top_n": stage.knowledge_top_n,
|
|
"score_threshold": stage.knowledge_score_threshold,
|
|
}
|
|
)
|
|
|
|
def node_config(
|
|
self,
|
|
node_id: str,
|
|
*,
|
|
functions: list,
|
|
greeting_context_message: dict[str, str] | None,
|
|
leading_messages: list[dict[str, str]] | None = None,
|
|
) -> NodeConfig:
|
|
data = self._engine.data(node_id)
|
|
entry_mode = str(data.get("entryMode") or "wait_user")
|
|
entry_speech = self._store.render(str(data.get("entrySpeech") or ""))
|
|
strategy = (
|
|
ContextStrategy.RESET
|
|
if data.get("contextPolicy") == "fresh"
|
|
else ContextStrategy.APPEND
|
|
)
|
|
greeting_messages = (
|
|
[deepcopy(greeting_context_message)]
|
|
if strategy == ContextStrategy.RESET and greeting_context_message
|
|
else []
|
|
)
|
|
fixed_reply_messages = (
|
|
[{"role": "assistant", "content": entry_speech}]
|
|
if entry_mode == "fixed_speech" and entry_speech
|
|
else []
|
|
)
|
|
return {
|
|
"name": node_id,
|
|
"role_message": self.role_message(node_id),
|
|
"task_messages": [
|
|
*greeting_messages,
|
|
*(leading_messages or []),
|
|
*fixed_reply_messages,
|
|
],
|
|
"functions": functions,
|
|
"context_strategy": ContextStrategyConfig(strategy=strategy),
|
|
# Direct node activations let WorkflowRuntime decide whether to run
|
|
# the LLM. A Pipecat function transition may explicitly override
|
|
# this because FlowManager must coordinate the tool result first.
|
|
"respond_immediately": False,
|
|
}
|