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:
Xin Wang
2026-07-14 11:08:11 +08:00
parent 665f727796
commit f74040adf3
18 changed files with 848 additions and 194 deletions

View File

@@ -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,
),
)

View File

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