Enhance conversation history and runtime variable management
- Update ConversationRecorder to include source and nodeId metadata in transcripts for better context tracking. - Introduce optional variable handling in DynamicVariableStore, allowing for unset variables to be rendered as empty without raising errors. - Refactor WorkflowBrain to apply turn configurations and manage interaction policies dynamically, improving agent responsiveness. - Implement tests to ensure proper handling of updated session variables and workflow metadata in various scenarios.
This commit is contained in:
@@ -10,6 +10,7 @@ import asyncio
|
||||
import base64
|
||||
from collections.abc import Callable
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from loguru import logger
|
||||
@@ -50,6 +51,7 @@ from pipecat.frames.frames import (
|
||||
TTSSpeakFrame,
|
||||
UserImageRawFrame,
|
||||
UserImageRequestFrame,
|
||||
VADParamsUpdateFrame,
|
||||
)
|
||||
from pipecat.pipeline.pipeline import Pipeline
|
||||
from pipecat.pipeline.llm_switcher import LLMSwitcher
|
||||
@@ -58,7 +60,6 @@ from pipecat.pipeline.worker import PipelineParams, PipelineWorker
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
from pipecat.processors.aggregators.llm_response_universal import (
|
||||
LLMAssistantAggregator,
|
||||
LLMUserAggregator,
|
||||
LLMUserAggregatorParams,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -72,8 +73,10 @@ from pipecat.turns.user_mute.function_call_user_mute_strategy import (
|
||||
FunctionCallUserMuteStrategy,
|
||||
)
|
||||
from services.pipecat.turn_config import (
|
||||
ConfigurableLLMUserAggregator,
|
||||
create_user_turn_strategies,
|
||||
create_vad_analyzer,
|
||||
create_vad_params,
|
||||
)
|
||||
from pipecat.utils.time import time_now_iso8601
|
||||
from pipecat.workers.runner import WorkerRunner
|
||||
@@ -794,7 +797,7 @@ async def run_pipeline(
|
||||
current_llm_service = llm
|
||||
if cfg.type == "workflow":
|
||||
llm, llm_services, current_llm_service = _workflow_llm_switcher(cfg, llm)
|
||||
user_aggregator = LLMUserAggregator(
|
||||
user_aggregator = ConfigurableLLMUserAggregator(
|
||||
context,
|
||||
params=LLMUserAggregatorParams(
|
||||
vad_analyzer=create_vad_analyzer(cfg.turnConfig),
|
||||
@@ -1063,6 +1066,31 @@ async def run_pipeline(
|
||||
)
|
||||
)
|
||||
|
||||
current_enable_interrupt = cfg.enableInterrupt
|
||||
current_turn_config = dict(cfg.turnConfig)
|
||||
|
||||
async def apply_workflow_turn_config(
|
||||
enable_interrupt: bool,
|
||||
turn_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Apply one Agent's interaction policy before its next user turn."""
|
||||
nonlocal current_enable_interrupt, current_turn_config
|
||||
normalized = dict(turn_config or {})
|
||||
if (
|
||||
current_enable_interrupt == enable_interrupt
|
||||
and current_turn_config == normalized
|
||||
):
|
||||
return
|
||||
await user_aggregator.apply_turn_strategies(
|
||||
normalized,
|
||||
enable_interruptions=enable_interrupt,
|
||||
)
|
||||
await worker.queue_frame(
|
||||
VADParamsUpdateFrame(params=create_vad_params(normalized))
|
||||
)
|
||||
current_enable_interrupt = enable_interrupt
|
||||
current_turn_config = normalized
|
||||
|
||||
async def queue_transcript(role: str, content: str, timestamp: str) -> None:
|
||||
if content:
|
||||
await worker.queue_frame(
|
||||
@@ -1107,6 +1135,7 @@ async def run_pipeline(
|
||||
switch_services=switch_workflow_services,
|
||||
set_knowledge_scope=knowledge_retrieval.set_scope,
|
||||
set_input_enabled=lambda enabled: input_state.__setitem__("enabled", enabled),
|
||||
apply_turn_config=apply_workflow_turn_config,
|
||||
flow_global_functions=flow_global_functions,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -7,6 +7,11 @@ from typing import Any
|
||||
from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
|
||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||
from pipecat.audio.vad.vad_analyzer import VADParams
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
from pipecat.processors.aggregators.llm_response_universal import (
|
||||
LLMUserAggregator,
|
||||
LLMUserAggregatorParams,
|
||||
)
|
||||
from pipecat.turns.user_start import (
|
||||
TranscriptionUserTurnStartStrategy,
|
||||
VADUserTurnStartStrategy,
|
||||
@@ -39,18 +44,21 @@ def _value(config: dict[str, Any], snake: str, camel: str, default: Any) -> Any:
|
||||
return config.get(snake, config.get(camel, default))
|
||||
|
||||
|
||||
def create_vad_analyzer(turn_config: dict[str, Any]) -> SileroVADAnalyzer:
|
||||
def create_vad_params(turn_config: dict[str, Any]) -> VADParams:
|
||||
"""Translate product settings into Pipecat's runtime VAD parameters."""
|
||||
vad = _section(turn_config, "vad", "vad")
|
||||
return SileroVADAnalyzer(
|
||||
params=VADParams(
|
||||
confidence=float(vad.get("confidence", DEFAULT_VAD["confidence"])),
|
||||
start_secs=float(_value(vad, "start_secs", "startSecs", 0.2)),
|
||||
stop_secs=float(_value(vad, "stop_secs", "stopSecs", 0.2)),
|
||||
min_volume=float(_value(vad, "min_volume", "minVolume", 0.6)),
|
||||
)
|
||||
return VADParams(
|
||||
confidence=float(vad.get("confidence", DEFAULT_VAD["confidence"])),
|
||||
start_secs=float(_value(vad, "start_secs", "startSecs", 0.2)),
|
||||
stop_secs=float(_value(vad, "stop_secs", "stopSecs", 0.2)),
|
||||
min_volume=float(_value(vad, "min_volume", "minVolume", 0.6)),
|
||||
)
|
||||
|
||||
|
||||
def create_vad_analyzer(turn_config: dict[str, Any]) -> SileroVADAnalyzer:
|
||||
return SileroVADAnalyzer(params=create_vad_params(turn_config))
|
||||
|
||||
|
||||
def create_user_turn_strategies(
|
||||
turn_config: dict[str, Any], *, enable_interruptions: bool
|
||||
) -> UserTurnStrategies:
|
||||
@@ -87,3 +95,34 @@ def create_user_turn_strategies(
|
||||
)
|
||||
]
|
||||
return UserTurnStrategies(start=start, stop=stop)
|
||||
|
||||
|
||||
class ConfigurableLLMUserAggregator(LLMUserAggregator):
|
||||
"""LLM user aggregator with one stable project-level runtime update API.
|
||||
|
||||
Pipecat 1.5 exposes ``UserTurnController.update_strategies`` but does not
|
||||
surface it on ``LLMUserAggregator``. Keeping that version-specific bridge
|
||||
here prevents Workflow orchestration from depending on Pipecat internals.
|
||||
VAD threshold updates still use Pipecat's public ``VADParamsUpdateFrame``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
params: LLMUserAggregatorParams | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(context, params=params, **kwargs)
|
||||
|
||||
async def apply_turn_strategies(
|
||||
self,
|
||||
turn_config: dict[str, Any],
|
||||
*,
|
||||
enable_interruptions: bool,
|
||||
) -> None:
|
||||
strategies = create_user_turn_strategies(
|
||||
turn_config,
|
||||
enable_interruptions=enable_interruptions,
|
||||
)
|
||||
await self._user_turn_controller.update_strategies(strategies)
|
||||
|
||||
Reference in New Issue
Block a user