From 2f2bde9856cc13b060cecd34a60cc75d106906ae Mon Sep 17 00:00:00 2001 From: ivaaan Date: Mon, 17 Nov 2025 13:40:03 +0100 Subject: [PATCH] add timestamps to example --- .../foundational/07ae-interruptible-hume.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/examples/foundational/07ae-interruptible-hume.py b/examples/foundational/07ae-interruptible-hume.py index 046f2d4c8..501eafad3 100644 --- a/examples/foundational/07ae-interruptible-hume.py +++ b/examples/foundational/07ae-interruptible-hume.py @@ -13,13 +13,17 @@ from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams 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.frames.frames import LLMRunFrame +from pipecat.frames.frames import Frame, LLMRunFrame, TTSTextFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineParams, PipelineTask from pipecat.processors.aggregators.llm_context import LLMContext -from pipecat.processors.aggregators.llm_response_universal import LLMContextAggregatorPair +from pipecat.processors.aggregators.llm_response_universal import ( + LLMContextAggregatorPair, +) +from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor +from pipecat.processors.transcript_processor import TranscriptProcessor from pipecat.runner.types import RunnerArguments from pipecat.runner.utils import create_transport from pipecat.services.deepgram.stt import DeepgramSTTService @@ -28,9 +32,25 @@ from pipecat.services.openai.llm import OpenAILLMService from pipecat.transports.base_transport import BaseTransport, TransportParams from pipecat.transports.daily.transport import DailyParams from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams +from pipecat.frames.frames import format_pts load_dotenv(override=True) + +class TimestampLogger(FrameProcessor): + """Frame processor that logs TTSTextFrame objects with their timestamps. + + This helps verify that word timestamps are working correctly by showing + when each word is spoken with its presentation timestamp (PTS). + """ + + async def process_frame(self, frame: Frame, direction: FrameDirection): + if isinstance(frame, TTSTextFrame): + pts_str = format_pts(frame.pts) if frame.pts else "no PTS" + logger.info(f"🎯 Word timestamp: '{frame.text}' at {pts_str}") + await self.push_frame(frame, direction) + + # We store functions so objects (e.g. SileroVADAnalyzer) don't get # instantiated. The function will be called when the desired transport gets # selected. @@ -81,15 +101,24 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): rtvi = RTVIProcessor(config=RTVIConfig(config=[])) + # Add transcript processor to show timestamps in conversation history + transcript = TranscriptProcessor() + + # Add timestamp logger to verify word timestamps are being generated + timestamp_logger = TimestampLogger() + pipeline = Pipeline( [ transport.input(), # Transport user input rtvi, stt, + transcript.user(), # User transcripts context_aggregator.user(), # User responses llm, # LLM - tts, # TTS + tts, # TTS (HumeTTSService with word timestamps) + timestamp_logger, # Log word timestamps for verification transport.output(), # Transport bot output + transcript.assistant(), # Assistant transcripts with timestamps context_aggregator.assistant(), # Assistant spoken responses ] ) @@ -109,11 +138,23 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): async def on_client_ready(rtvi): await rtvi.set_bot_ready() + @transcript.event_handler("on_transcript_update") + async def on_transcript_update(processor, frame): + """Log transcript updates to show timestamps in conversation.""" + for msg in frame.messages: + timestamp_str = f"[{msg.timestamp}] " if msg.timestamp else "" + logger.info(f"📝 Transcript: {timestamp_str}{msg.role}: {msg.content}") + @transport.event_handler("on_client_connected") async def on_client_connected(transport, client): logger.info(f"Client connected") + logger.info( + "💡 Word timestamps are enabled! Watch for '🎯 Word timestamp' logs showing each word with its PTS." + ) # Kick off the conversation. - messages.append({"role": "system", "content": "Please introduce yourself to the user."}) + messages.append( + {"role": "system", "content": "Please introduce yourself to the user."} + ) await task.queue_frames([LLMRunFrame()]) @transport.event_handler("on_client_disconnected")