From bd8c6267bc0011609738fcf3d9db4372144eef30 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 21:58:31 +0100 Subject: [PATCH 001/215] Initial version 0.1.0. --- Makefile | 52 +- README.md | 14 + examples/example2.wav | 3 + examples/voice/README.md | 89 ++ examples/voice/file_transcription.py | 188 ++++ examples/voice/microphone_transcription.py | 140 +++ examples/voice/requirements-examples.txt | 2 + sdk/batch/speechmatics/batch/_transport.py | 6 +- sdk/voice/OVERVIEW.md | 281 ++++++ sdk/voice/README.md | 105 +++ sdk/voice/pyproject.toml | 67 ++ sdk/voice/speechmatics/__init__.py | 1 + sdk/voice/speechmatics/voice/__init__.py | 59 ++ sdk/voice/speechmatics/voice/_client.py | 945 +++++++++++++++++++++ sdk/voice/speechmatics/voice/_logging.py | 52 ++ sdk/voice/speechmatics/voice/_models.py | 793 +++++++++++++++++ tests/voice/test_setup.py | 2 + 17 files changed, 2784 insertions(+), 15 deletions(-) create mode 100644 examples/example2.wav create mode 100644 examples/voice/README.md create mode 100644 examples/voice/file_transcription.py create mode 100644 examples/voice/microphone_transcription.py create mode 100644 examples/voice/requirements-examples.txt create mode 100644 sdk/voice/OVERVIEW.md create mode 100644 sdk/voice/README.md create mode 100644 sdk/voice/pyproject.toml create mode 100644 sdk/voice/speechmatics/__init__.py create mode 100644 sdk/voice/speechmatics/voice/__init__.py create mode 100644 sdk/voice/speechmatics/voice/_client.py create mode 100644 sdk/voice/speechmatics/voice/_logging.py create mode 100644 sdk/voice/speechmatics/voice/_models.py create mode 100644 tests/voice/test_setup.py diff --git a/Makefile b/Makefile index 575f3514..7bb6694b 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ # Makefile for Speechmatics Python SDKs .PHONY: help -.PHONY: test-all test-rt test-batch test-flow -.PHONY: format-all format-rt format-batch format-flow -.PHONY: lint-all lint-rt lint-batch lint-flow -.PHONY: type-check-all type-check-rt type-check-batch type-check-flow -.PHONY: build-all build-rt build-batch build-flow -.PHONY: clean-all clean-rt clean-batch clean-flow clean-flow +.PHONY: test-all test-rt test-batch test-flow test-voice +.PHONY: format-all format-rt format-batch format-flow format-voice +.PHONY: lint-all lint-rt lint-batch lint-flow lint-voice +.PHONY: type-check-all type-check-rt type-check-batch type-check-flow type-check-voice +.PHONY: build-all build-rt build-batch build-flow build-voice +.PHONY: clean-all clean-rt clean-batch clean-flow clean-voice help: @echo "Available commands:" @@ -16,40 +16,46 @@ help: @echo " test-rt Run tests for RT SDK" @echo " test-batch Run tests for Batch SDK" @echo " test-flow Run tests for Flow SDK" + @echo " test-voice Run tests for Voice Agent SDK" @echo "" @echo "Code formatting:" @echo " format-all Auto-fix formatting for all SDKs" @echo " format-rt Auto-fix formatting for RT SDK" @echo " format-batch Auto-fix formatting for Batch SDK" @echo " format-flow Auto-fix formatting for Flow SDK" + @echo " format-voice Auto-fix formatting for Voice Agent SDK" @echo "" @echo "Linting:" @echo " lint-all Run linting for all SDKs" @echo " lint-rt Run linting for RT SDK" @echo " lint-batch Run linting for Batch SDK" @echo " lint-flow Run linting for Flow SDK" + @echo " lint-voice Run linting for Voice Agent SDK" @echo "" @echo "Type checking:" @echo " type-check-all Run type checking for all SDKs" @echo " type-check-rt Run type checking for RT SDK" @echo " type-check-batch Run type checking for Batch SDK" @echo " type-check-flow Run type checking for Flow SDK" + @echo " type-check-voice Run type checking for Voice Agent SDK" @echo "" @echo "Building:" @echo " build-all Build all SDKs" @echo " build-rt Build RT SDK" @echo " build-batch Build Batch SDK" @echo " build-flow Build Flow SDK" + @echo " build-voice Build Voice Agent SDK" @echo "" @echo "Cleaning:" @echo " clean-all Clean all SDKs" @echo " clean-rt Clean RT SDK build artifacts" @echo " clean-batch Clean Batch SDK build artifacts" @echo " clean-flow Clean Flow SDK build artifacts" + @echo " clean-voice Clean Voice Agent SDK build artifacts" @echo "" # Testing targets -test-all: test-rt test-batch test-flow +test-all: test-rt test-batch test-flow test-voice test-rt: pytest tests/rt/ -v @@ -60,8 +66,11 @@ test-batch: test-flow: pytest tests/flow/ -v +test-voice: + pytest tests/voice/ -v + # Formatting targets -format-all: format-rt format-batch format-flow +format-all: format-rt format-batch format-flow format-voice format-rt: cd sdk/rt/speechmatics && black . @@ -75,8 +84,12 @@ format-flow: cd sdk/flow/speechmatics && black . cd sdk/flow/speechmatics && ruff check --fix . +format-voice: + cd sdk/voice/speechmatics && black . + cd sdk/voice/speechmatics && ruff check --fix . + # Linting targets -lint-all: lint-rt lint-batch lint-flow +lint-all: lint-rt lint-batch lint-flow lint-voice lint-rt: cd sdk/rt/speechmatics && ruff check . @@ -87,8 +100,11 @@ lint-batch: lint-flow: cd sdk/flow/speechmatics && ruff check . +lint-voice: + cd sdk/voice/speechmatics && ruff check . + # Type checking targets -type-check-all: type-check-rt type-check-batch type-check-flow +type-check-all: type-check-rt type-check-batch type-check-flow type-check-voice type-check-rt: cd sdk/rt/speechmatics && mypy . @@ -99,18 +115,22 @@ type-check-batch: type-check-flow: cd sdk/flow/speechmatics && mypy . +type-check-voice: + cd sdk/voice/speechmatics && mypy . + # Installation targets install-dev: python -m pip install --upgrade pip python -m pip install -e sdk/rt[dev] python -m pip install -e sdk/batch[dev] python -m pip install -e sdk/flow[dev] + python -m pip install -e sdk/voice[dev] install-build: python -m pip install --upgrade build # Building targets -build-all: build-rt build-batch build-flow +build-all: build-rt build-batch build-flow build-voice build-rt: install-build cd sdk/rt && python -m build @@ -121,8 +141,11 @@ build-batch: install-build build-flow: install-build cd sdk/flow && python -m build +build-voice: install-build + cd sdk/voice && python -m build + # Cleaning targets -clean-all: clean-rt clean-batch clean-flow +clean-all: clean-rt clean-batch clean-flow clean-voice clean-rt: rm -rf sdk/rt/dist sdk/rt/build sdk/rt/*.egg-info @@ -135,3 +158,8 @@ clean-batch: clean-flow: rm -rf sdk/flow/dist sdk/flow/build sdk/flow/*.egg-info find sdk/flow -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + +clean-voice: + rm -rf sdk/voice/dist sdk/voice/build sdk/voice/*.egg-info + find sdk/voice -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + \ No newline at end of file diff --git a/README.md b/README.md index 4d085317..bf660863 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,14 @@ An async Python client for Speechmatics Flow API. pip install speechmatics-flow ``` +### (Beta) Voice Agent Client (`speechmatics-voice`) + +A Voice Agent Python client for Speechmatics Real-Time API. + +```bash +pip install speechmatics-voice +``` + ## Development ### Repository Structure @@ -53,10 +61,15 @@ speechmatics-python-sdk/ │ │ ├── pyproject.toml │ │ └── README.md │ +│ ├── voice/ +│ │ ├── pyproject.toml +│ │ └── README.md +│ ├── tests/ │ ├── batch/ │ └── rt/ │ └── flow/ +│ └── voice/ │ ├── examples/ ├── Makefile @@ -97,6 +110,7 @@ Each package can be installed separately: pip install speechmatics-rt pip install speechmatics-batch pip install speechmatics-flow +pip install speechmatics-voice ``` ## Docs diff --git a/examples/example2.wav b/examples/example2.wav new file mode 100644 index 00000000..67209392 --- /dev/null +++ b/examples/example2.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c744f7e3f3d45fc1d64000670da99c223bcdec348cbf014385eaf98b0913016f +size 901352 diff --git a/examples/voice/README.md b/examples/voice/README.md new file mode 100644 index 00000000..c95de58a --- /dev/null +++ b/examples/voice/README.md @@ -0,0 +1,89 @@ +# Speechmatics Voice Agent SDK Examples + +This directory contains practical demonstrations of the Speechmatics Voice Agent SDK for real-time speech-to-text applications. + +## Installation + +```bash +pip install -r requirements-examples.txt +``` + +**Requirements:** + +- Python 3.9+ +- PyAudio for audio I/O +- Valid Speechmatics API key in `SPEECHMATICS_API_KEY` environment variable + +## Examples + +### File Transcription + +An example that takes an audio file and sends it in real-time to the SDK and outputs the interim and final transcription to the console. The audio will also be played through the selected audio output device. + +Audio files should be in Mono 16kHz 16-bit WAV format. + +```bash +cd examples/voice +pip install -r requirements-examples.txt + +# Transcribe the default example audio file +python file_transcription.py + +# Transcribe a custom audio file (16kHz Mono 16-bit WAV) +python file_transcription.py path/to/your/audio.wav +``` + +**Features:** + +- File streaming +- Audio playback through selected device +- Real-time transcription +- Speaker diarization +- Speaker start / stop events + +### Microphone Transcription + +An example that takes audio from a microphone and sends it in real-time to the SDK and outputs the interim and final transcription to the console. + +```bash +cd examples/voice +pip install -r requirements-examples.txt + +# Real-time microphone transcription +python microphone_transcription.py +``` + +**Features:** + +- Live microphone input with device selection +- Real-time transcription +- Speaker diarization +- Speaker start / stop events + +## Troubleshooting + +**PyAudio installation issues:** + +```bash +# macOS +brew install portaudio +pip install pyaudio + +# Ubuntu/Debian +sudo apt-get install portaudio19-dev +pip install pyaudio +``` + +**No audio devices found:** + +- Ensure PyAudio is properly installed +- Check system audio permissions +- Verify audio devices are connected and recognised + +**API key issues:** + +```bash +export SPEECHMATICS_API_KEY="your_api_key_here" +``` + +For more information, see the main SDK documentation in [OVERVIEW.md](../OVERVIEW.md). diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py new file mode 100644 index 00000000..c6ea5e1f --- /dev/null +++ b/examples/voice/file_transcription.py @@ -0,0 +1,188 @@ +import asyncio +import sys +import wave +from pathlib import Path + +from colorama import init as colorama_init +from utils import AudioPlayer +from utils import CustomLevels +from utils import get_logger +from utils import select_audio_output_device + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationSpeakerConfig +from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeakerSegment +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + +colorama_init(autoreset=True) +logger = get_logger(__name__) + + +async def main() -> None: + """ + Transcribe audio files with real-time playback and speaker diarisation. + + This example demonstrates: + - Loading and validating WAV audio files + - Real-time audio playback during transcription + - Speaker diarisation with voice activity detection + - Comprehensive event handling and logging + """ + # Parse command line arguments for audio file path + # Default to example.wav if no file specified + audio_file_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent / "../example2.wav" + + # Validate that the audio file exists + if not audio_file_path.exists(): + print(f"Error: Audio file not found: {audio_file_path}") + print("Usage: python file_transcription.py [audio_file.wav]") + return + + # Load and validate the audio file format + # Only mono 16-bit WAV files are supported + try: + sample_rate, channels, sample_width, duration = _load_audio_file(audio_file_path) + print(f"Loading: {audio_file_path.name} ({duration:.1f}s, {sample_rate}Hz)") + except (wave.Error, ValueError) as e: + print(f"Error: {e}") + return + + # Setup audio playback device for real-time playback + # User can select from available output devices + audio_player = _setup_audio_player(sample_rate, channels, sample_width) + + # Configure Voice Agent with transcription settings + config = VoiceAgentConfig( + sample_rate=sample_rate, + end_of_utterance_silence_trigger=0.5, + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + speaker_config=DiarizationSpeakerConfig(focus_speakers=["S2"], focus_mode=DiarizationFocusMode.IGNORE), + ) + + # Create Voice Agent client and start transcription + async with VoiceAgentClient(config=config) as client: + # Register event handlers for transcription events + _register_event_handlers(client, logger) + + try: + print("Starting transcription... (Press Ctrl+C to stop)\n") + # Connect to the Voice Agent service + await client.connect() + + # Stream the audio file with real-time playback + await _stream_audio_file(audio_file_path, client, audio_player, sample_rate, sample_width) + except asyncio.CancelledError: + print("\nTranscription cancelled") + except Exception as e: + print(f"Error: {e}") + finally: + # Clean up audio playback resources + if audio_player: + audio_player.stop() + + +def _load_audio_file(file_path: Path) -> tuple[int, int, int, float]: + """Load and validate audio file. + + Returns: + Tuple of (sample_rate, channels, sample_width, duration) + """ + with wave.open(str(file_path), "rb") as wav_file: + sample_rate = wav_file.getframerate() + channels = wav_file.getnchannels() + sample_width = wav_file.getsampwidth() + frames = wav_file.getnframes() + duration = frames / sample_rate + + if channels != 1: + raise ValueError("Only mono audio files are supported") + if sample_width != 2: + raise ValueError("Only 16-bit audio files are supported") + + return sample_rate, channels, sample_width, duration + + +def _setup_audio_player(sample_rate: int, channels: int, sample_width: int) -> AudioPlayer | None: + """Setup audio player with device selection.""" + print("\nSelect audio output device for playback:") + output_device = select_audio_output_device() + + audio_player = AudioPlayer(sample_rate, channels, sample_width, output_device) + if not audio_player.start(): + print("Warning: Audio playback unavailable - continuing with transcription only") + return None + return audio_player + + +def _register_event_handlers(client: VoiceAgentClient, logger) -> None: + """Register event handlers for transcription events with detailed logging.""" + + def _format_segment(segment: SpeakerSegment) -> str: + """Format speaker segment for display.""" + template = "@{speaker_id} -> {text}" if segment.is_active else "@{speaker_id} (background) -> {text}" + return segment.format_text(template) + + @client.on(AgentServerMessageType.ADD_SEGMENTS) + def handle_final_segments(message): + """Handle final transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + logger.log(CustomLevels.FINAL, f"🚀 {segments}") + + @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + def handle_partial_segments(message): + """Handle partial transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + delay = message.get("delay_to_finalize") + if delay is not None: + logger.log(CustomLevels.PARTIAL, f"⚡ {segments} ({delay}s to finals)") + else: + logger.log(CustomLevels.PARTIAL, f"⚡ {segments}") + + @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + def handle_speech_started(message): + """Handle speech start events.""" + logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") + + @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + def handle_speech_ended(message): + """Handle speech end events.""" + logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") + + @client.on(AgentServerMessageType.TTFB_METRICS) + def handle_metrics(message): + """Handle time-to-first-byte metrics.""" + logger.debug(f"📊 Metrics: {message}") + + +async def _stream_audio_file( + file_path: Path, + client: VoiceAgentClient, + audio_player: AudioPlayer | None, + sample_rate: int, + sample_width: int, +) -> None: + """Stream audio file in real-time to client and player.""" + CHUNK_SIZE = 320 + chunk_duration = CHUNK_SIZE / sample_rate / sample_width + + with wave.open(str(file_path), "rb") as wav_file: + while True: + audio_data = wav_file.readframes(CHUNK_SIZE) + if not audio_data: + print("\nEnd of file reached") + break + + # Send to transcription and play audio simultaneously + asyncio.create_task(client.send_audio(audio_data)) + if audio_player: + audio_player.play(audio_data) + + await asyncio.sleep(chunk_duration) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py new file mode 100644 index 00000000..6f195e96 --- /dev/null +++ b/examples/voice/microphone_transcription.py @@ -0,0 +1,140 @@ +import asyncio + +from colorama import init as colorama_init +from utils import CustomLevels +from utils import get_logger +from utils import select_audio_device + +from speechmatics.rt import Microphone +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationSpeakerConfig +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeakerSegment +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + +colorama_init(autoreset=True) + +logger = get_logger(__name__) + + +async def main() -> None: + """ + Live microphone transcription with speaker diarisation. + + This example demonstrates: + - Real-time microphone input with device selection + - Speaker diarisation and voice activity detection + - Performance metrics (TTFB) + - Works with any PyAudio-compatible input device + """ + + # Audio configuration for real-time processing + sample_rate = 16000 + chunk_size = 320 + + # Setup microphone with user device selection + mic = _setup_microphone(sample_rate, chunk_size) + if not mic: + return + + # Configure Voice Agent with microphone-specific settings + config = VoiceAgentConfig( + sample_rate=sample_rate, + end_of_utterance_silence_trigger=0.5, + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + speaker_config=DiarizationSpeakerConfig(focus_speakers=["S1"]), + ) + + # Create Voice Agent client and start transcription + async with VoiceAgentClient(config=config) as client: + # Register event handlers for real-time transcription events + _register_event_handlers(client, logger) + + try: + print("Microphone ready - speak now... (Press Ctrl+C to stop)\n") + # Connect to the Voice Agent service + await client.connect() + + # Start streaming microphone audio + await _stream_microphone(mic, client, chunk_size) + except asyncio.CancelledError: + print("\nTranscription cancelled") + except Exception as e: + print(f"Error: {e}") + + +def _register_event_handlers(client: VoiceAgentClient, logger) -> None: + """Register event handlers for real-time transcription events.""" + + def _format_segment(segment: SpeakerSegment) -> str: + """Format speaker segment for display.""" + template = ( + "@{speaker_id}: {text}" + if segment.is_active + else "@{speaker_id} (background): {text}" + ) + return segment.format_text(template) + + @client.on(AgentServerMessageType.ADD_SEGMENTS) + def handle_final_segments(message): + """Handle final transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") + + @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + def handle_partial_segments(message): + """Handle partial transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + delay = message.get("delay_to_finalize") + if delay is not None: + logger.log( + CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)" + ) + else: + logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") + + @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + def handle_speech_started(message): + """Handle speech start events.""" + logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") + + @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + def handle_speech_ended(message): + """Handle speech end events.""" + logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") + + @client.on(AgentServerMessageType.TTFB_METRICS) + def handle_metrics(message): + """Handle time-to-first-byte metrics.""" + logger.debug(f"📊 Metrics: {message}") + + +def _setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: + """Setup microphone with device selection.""" + selected_device = select_audio_device() + + mic = Microphone( + sample_rate=sample_rate, + chunk_size=chunk_size, + device_index=selected_device, + ) + + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return None + return mic + + +async def _stream_microphone( + mic: Microphone, client: VoiceAgentClient, chunk_size: int +) -> None: + """Stream microphone audio to client.""" + while True: + frame = await mic.read(chunk_size) + await client.send_audio(frame) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/requirements-examples.txt b/examples/voice/requirements-examples.txt new file mode 100644 index 00000000..593081ce --- /dev/null +++ b/examples/voice/requirements-examples.txt @@ -0,0 +1,2 @@ +pyaudio +colorama diff --git a/sdk/batch/speechmatics/batch/_transport.py b/sdk/batch/speechmatics/batch/_transport.py index cfe65ff3..bab82750 100644 --- a/sdk/batch/speechmatics/batch/_transport.py +++ b/sdk/batch/speechmatics/batch/_transport.py @@ -297,9 +297,9 @@ async def _prepare_headers(self) -> dict[str, str]: Headers dictionary with authentication and tracking info """ auth_headers = await self._auth.get_auth_headers() - auth_headers[ - "User-Agent" - ] = f"speechmatics-batch-v{get_version()} python/{sys.version_info.major}.{sys.version_info.minor}" + auth_headers["User-Agent"] = ( + f"speechmatics-batch-v{get_version()} python/{sys.version_info.major}.{sys.version_info.minor}" + ) if self._request_id: auth_headers["X-Request-Id"] = self._request_id diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md new file mode 100644 index 00000000..4ad6cc7f --- /dev/null +++ b/sdk/voice/OVERVIEW.md @@ -0,0 +1,281 @@ +# Speechmatics Voice Agent Client Overview + +The Speechmatics Voice Agent Client provides a comprehensive set of utilities for building voice agents with real-time speech-to-text capabilities, speaker diarization, and advanced transcription processing. + +## Key Features + +- **Real-time transcription** with partial and final results +- **Speaker diarization** with configurable focus modes +- **Voice activity detection** (VAD) with adaptive end-of-utterance detection +- **Event-driven architecture** with callback registration +- **TTFB (Time to First Byte) metrics** for performance monitoring + +## Installation + +```bash +pip install speechmatics-voice +``` + +## Quick Start + +### Basic Voice Agent Setup + +```python +import asyncio +from speechmatics.rt import Microphone +from speechmatics.voice import ( + VoiceAgentClient, + VoiceAgentConfig, + EndOfUtteranceMode, + AgentServerMessageType, + SpeakerSegment, +) + +async def main(): + # Configure the voice agent + config = VoiceAgentConfig( + end_of_utterance_silence_trigger=0.5, + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + ) + + # Initialize microphone + mic = Microphone( + sample_rate=16000, + chunk_size=160, + ) + + if not mic.start(): + print("Microphone not available") + return + + # Create client and register event handlers + async with VoiceAgentClient(config=config) as client: + + # Handle interim transcription segments + @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + def handle_interim_segments(message): + segments: list[SpeakerSegment] = message["segments"] + for segment in segments: + print(f"Speaker {segment.speaker_id}: {segment.text}") + + # Handle finalized transcription segments + @client.on(AgentServerMessageType.ADD_SEGMENTS) + def handle_final_segments(message): + segments: list[SpeakerSegment] = message["segments"] + for segment in segments: + print(f"Speaker {segment.speaker_id}: {segment.text}") + + # Handle user started speaking event + @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + def handle_speech_started(message): + print("User started speaking") + + # Handle user stopped speaking event + @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + def handle_speech_ended(message): + print("User stopped speaking") + + # Connect and start processing audio + await client.connect() + + while True: + frame = await mic.read(160) + await client.send_audio(frame) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Core Components + +### VoiceAgentClient + +The main client class is the `VoiceAgentClient` which extends `AsyncClient` from the Speechmatics Real-Time SDK. + +```python +VoiceAgentClient( + api_key: Optional[str] = None, # Speechmatics API key + url: Optional[str] = None, # REST API endpoint URL + app: Optional[str] = None, # Optional application name + config: Optional[VoiceAgentConfig] = None, # Optional voice agent config +) +``` + +**Key Methods:** + +- `connect()` - Establish WebSocket connection +- `disconnect()` - Disconnect from WebSocket connection +- `update_diarization_config(config)` - Update the diarization configuration +- `send_audio(frame)` - Send audio data for transcription +- `on(event_type)` - Register event callbacks + +**Authentication:** + +- Uses `SPEECHMATICS_API_KEY` environment variable +- Custom API key via constructor parameter + +### VoiceAgentConfig + +Configuration class for voice agent behaviour. + +```python +from speechmatics.voice import ( + AdditionalVocabEntry, + AudioEncoding, + DiarizationSpeakerConfig, + EndOfUtteranceMode, + OperatingPoint, + VoiceAgentConfig, +) + +config = VoiceAgentConfig( + # Service Configuration + operating_point=OperatingPoint.ENHANCED, # Accuracy vs latency tradeoff + domain=None, # Optional domain specification + language="en", # Language code (ISO 639-1) + output_locale="en-GB", # Output locale format + + # Timing & Finalisation Control + max_delay=1.0, # Max delay for transcription (seconds) + end_of_utterance_silence_trigger=0.5, # Silence threshold for EOU (seconds) + end_of_utterance_mode=EndOfUtteranceMode.FIXED, # EOU detection mode + + # Vocabulary Enhancement + additional_vocab=[ # Custom vocabulary entries + AdditionalVocabEntry( + content="Speechmatics", + sounds_like=["speech matters", "speech magic"] + ) + ], + punctuation_overrides={}, # Custom punctuation rules + + # Speaker diarization + enable_diarization=True, # Enable speaker separation + speaker_sensitivity=0.5, # diarization sensitivity (0.0-1.0) + max_speakers=None, # Max speakers to detect + prefer_current_speaker=False, # Prefer grouping words to current speaker + speaker_config=DiarizationSpeakerConfig(), # Advanced speaker configuration + known_speakers=[], # Pre-identified speakers + + # Audio Configuration + sample_rate=16000, # Audio sample rate (Hz) + chunk_size=160, # Audio chunk size (samples) + audio_encoding=AudioEncoding.PCM_S16LE, # Audio encoding format +) +``` + +### Event Types + +The SDK provides comprehensive event handling through `AgentServerMessageType` (shadows `ServerMessageType` from the RT SDK): + +**API Control Messages:** + +- `RECOGNITION_STARTED` - Recognition session has started +- `END_OF_TRANSCRIPT` - Recognition session has ended +- `INFO` - Informational message +- `WARNING` - Warning message +- `ERROR` - Error message + +**Transcription Messages:** + +- `ADD_TRANSCRIPT` - Final transcription results +- `ADD_PARTIAL_TRANSCRIPT` - Partial transcription updates + +**Speaker Segments:** + +- `ADD_SEGMENTS` - Final speaker segments +- `ADD_INTERIM_SEGMENTS` - Interim speaker segments + +**Voice Activity Detection (VAD):** + +- `USER_SPEECH_STARTED` - Voice activity detected +- `USER_SPEECH_ENDED` - Voice activity ended + +**Speaker Information:** + +- `SPEAKERS_RESULT` - Speaker identification results + +**Performance Metrics:** + +- `METRICS` - General STT engine metrics +- `TTFB_METRICS` - Time to First Byte metrics + +### SpeakerSegment + +Represents a segment of speech from a specific speaker: + +```python +def format_segment(segment: SpeakerSegment) -> str: + if segment.is_active: + return segment.format_text("@{speaker_id}: {text}") + else: + return segment.format_text("@{speaker_id} (background): {text}") +``` + +The `format_text()` method uses the following placeholders: + +- `{speaker_id}`: The speaker ID (empty if no speaker) (e.g. `S1`). +- `{text}`: The formatted text of the segment (e.g. `Hello, how are you?`). +- `{ts}`: The timestamp of the segment (e.g. `2025-09-03T16:05:01.000Z`). +- `{lang}`: The language of the segment (e.g. `en`). + +## Advanced Configuration + +### End of Utterance Modes + +The end of utterance mode controls how the STT engine detects the end of a user's speech. This can either be fixed silence threshold (defaults to `0.5s`) or it can be adaptive based on speech patterns, rate, pauses, and disfluencies. + +```python +from speechmatics.voice import EndOfUtteranceMode + +# Fixed silence threshold +EndOfUtteranceMode.FIXED + +# Adaptive silence detection (recommended) +EndOfUtteranceMode.ADAPTIVE +``` + +### Speaker Diarization + +The configuration below would: + +- focus on speakers `S1` and `S2` +- ignore speakers `S3` and `S4` +- retain words from all other speakers + +```python +from speechmatics.voice import DiarizationSpeakerConfig, DiarizationKnownSpeaker + +config = VoiceAgentConfig( + enable_diarization=True, + diarization_config=DiarizationSpeakerConfig( + focus_speakers=["S1", "S2"], + ignore_speakers=["S3", "S4"], + focus_mode=DiarizationFocusMode.RETAIN, + ) +) +``` + +### Additional Vocabulary + +If you have specific words that you need to make sure are transcribed properly, you can add in up to 1,000 additional vocabulary entries. You can optionally provide a list of words that sound like the word you are adding. + +```python +from speechmatics.voice import AdditionalVocabEntry + +config = VoiceAgentConfig( + additional_vocab=[ + AdditionalVocabEntry( + content="Speechmatics", + sounds_like=["speech matters", "speech magic"] + ) + ] +) +``` + +## Environment Variables + +- `SPEECHMATICS_API_KEY` - Your Speechmatics API key +- `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) +- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging diff --git a/sdk/voice/README.md b/sdk/voice/README.md new file mode 100644 index 00000000..506b148d --- /dev/null +++ b/sdk/voice/README.md @@ -0,0 +1,105 @@ +# Voice Agent Python client for Speechmatics Real-Time API + +[![License](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/speechmatics/speechmatics-python-voice/blob/master/LICENSE) + +An SDK for working with the Speechmatics Real-Time API optimised for use in voice agents or transcription services. + +This uses the Python Real-Time API to process the transcription results from the STT engine and combine them into manageable segments of audio. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. + +See [OVERVIEW.md](OVERVIEW.md) for more information. + +## Installation + +```bash +pip install speechmatics-voice +``` + +## Requirements + +You must have a valid Speechmatics API key to use this SDK. You can get one from the [Speechmatics Console](https://console.speechmatics.com). + +Store this as `SPEECHMATICS_API_KEY` environment variable in your `.env` file or use `export SPEECHMATICS_API_KEY="your_api_key_here"` in your terminal. + +## Quick Start + +Below is a basic example of how to use the SDK to transcribe audio from a microphone. + +```python +import asyncio +from speechmatics.rt import Microphone +from speechmatics.voice import ( + VoiceAgentClient, + VoiceAgentConfig, + EndOfUtteranceMode, + AgentServerMessageType, + SpeakerSegment, +) + +async def main(): + # Configure the voice agent + config = VoiceAgentConfig( + end_of_utterance_silence_trigger=0.5, + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + ) + + # Initialize microphone + mic = Microphone( + sample_rate=16000, + chunk_size=160, + ) + + if not mic.start(): + print("Microphone not available") + return + + # Create client and register event handlers + async with VoiceAgentClient(config=config) as client: + + # Handle interim transcription segments + @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + def handle_interim_segments(message): + segments: list[SpeakerSegment] = message["segments"] + for segment in segments: + print(f"Speaker {segment.speaker_id}: {segment.text}") + + # Handle finalized transcription segments + @client.on(AgentServerMessageType.ADD_SEGMENTS) + def handle_final_segments(message): + segments: list[SpeakerSegment] = message["segments"] + for segment in segments: + print(f"Speaker {segment.speaker_id}: {segment.text}") + + # Handle user started speaking event + @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + def handle_speech_started(message): + print("User started speaking") + + # Handle user stopped speaking event + @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + def handle_speech_ended(message): + print("User stopped speaking") + + # Connect and start processing audio + await client.connect() + + while True: + frame = await mic.read(160) + await client.send_audio(frame) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Examples + +The `examples/` directory contains practical demonstrations of the Voice Agent SDK. See the README in the `examples/voice` directory for more information. + +## Documentation + +- **SDK Overview**: See [OVERVIEW.md](OVERVIEW.md) for comprehensive API documentation +- **Speechmatics API**: https://docs.speechmatics.com + +## License + +[MIT](LICENSE) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml new file mode 100644 index 00000000..2002efee --- /dev/null +++ b/sdk/voice/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +requires = ["setuptools>=61.0.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "speechmatics-voice" +dynamic = ["version"] +description = "Speechmatics Voice Agent Python client for Real-Time API" +readme = "README.md" +authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] +license = "MIT" +requires-python = ">=3.9" +dependencies = ["websockets>=10.0"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Topic :: Multimedia :: Sound/Audio :: Speech", + "Topic :: Software Development :: Libraries :: Python Modules", +] +keywords = [ + "speechmatics", + "conversational-ai", + "voice", + "agents", + "websocket", + "real-time", + "pipecat", + "livekit" +] + +[project.optional-dependencies] +dev = [ + "black", + "ruff", + "mypy", + "pre-commit", + "pytest", + "pytest-asyncio", + "pytest-cov", + "pytest-mock", + "build", +] + +[project.urls] +homepage = "https://github.com/speechmatics/speechmatics-python-sdk" +documentation = "https://docs.speechmatics.com/" +repository = "https://github.com/speechmatics/speechmatics-python-sdk" +issues = "https://github.com/speechmatics/speechmatics-python-sdk/issues" + +[tool.setuptools.dynamic] +version = { attr = "speechmatics.voice.__version__" } + +[tool.setuptools.package-data] +"speechmatics.voice" = ["py.typed"] + +[tool.setuptools.packages.find] +where = ["."] + +[[tool.mypy.overrides]] +module = ["speechmatics.rt.*"] +ignore_missing_imports = true diff --git a/sdk/voice/speechmatics/__init__.py b/sdk/voice/speechmatics/__init__.py new file mode 100644 index 00000000..8db66d3d --- /dev/null +++ b/sdk/voice/speechmatics/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py new file mode 100644 index 00000000..3b9b554a --- /dev/null +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + + +"""Voice Agents SDK. + +A comprehensive set of utility classes tailored for Voice Agents and +using the Speechmatics Python Real-Time SDK, including the processing of +partial and final transcription from the STT engine into accumulated +transcriptions with flags to indicate changes between messages, etc. +""" + +__version__ = "0.1.0" + +from speechmatics.rt import AudioEncoding +from speechmatics.rt import AudioFormat +from speechmatics.rt import OperatingPoint + +from ._client import VoiceAgentClient +from ._models import AdditionalVocabEntry +from ._models import AgentClientMessageType +from ._models import AgentServerMessageType +from ._models import AnnotationFlags +from ._models import AnnotationResult +from ._models import DiarizationFocusMode +from ._models import DiarizationKnownSpeaker +from ._models import DiarizationSpeakerConfig +from ._models import EndOfUtteranceMode +from ._models import SpeakerSegment +from ._models import SpeakerVADStatus +from ._models import SpeechFragment +from ._models import VoiceAgentConfig + +__all__ = [ + # SDK + "__version__", + # Conversation config + "VoiceAgentConfig", + "EndOfUtteranceMode", + "DiarizationSpeakerConfig", + "DiarizationFocusMode", + "AdditionalVocabEntry", + "DiarizationKnownSpeaker", + "AudioEncoding", + "AudioFormat", + "OperatingPoint", + # Transcription models + "AnnotationFlags", + "AnnotationResult", + "SpeakerSegment", + "SpeechFragment", + # Events + "SpeakerVADStatus", + # Client + "VoiceAgentClient", + "AgentClientMessageType", + "AgentServerMessageType", +] diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py new file mode 100644 index 00000000..09c03dbc --- /dev/null +++ b/sdk/voice/speechmatics/voice/_client.py @@ -0,0 +1,945 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +import asyncio +import datetime +import os +import re +import time +from typing import Any +from typing import Optional +from urllib.parse import urlencode + +from speechmatics.rt import AsyncClient +from speechmatics.rt import AudioEncoding +from speechmatics.rt import AudioFormat +from speechmatics.rt import ConversationConfig +from speechmatics.rt import ServerMessageType +from speechmatics.rt import TranscriptionConfig + +from . import __version__ +from ._logging import get_logger +from ._models import AgentServerMessageType +from ._models import AnnotationFlags +from ._models import AnnotationResult +from ._models import DiarizationFocusMode +from ._models import DiarizationSpeakerConfig +from ._models import EndOfUtteranceMode +from ._models import SpeakerFragmentView +from ._models import SpeakerVADStatus +from ._models import SpeechFragment +from ._models import VoiceAgentConfig + +DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] + +if DEBUG_MORE: + import json + + +class VoiceAgentClient(AsyncClient): + """Voice Agent client. + + This class extends the AsyncClient class from the Speechmatics Real-Time SDK + and provides additional functionality for processing partial and final + transcription from the STT engine into accumulated transcriptions with + flags to indicate changes between messages, etc. + """ + + def __init__( + self, + api_key: Optional[str] = None, + url: Optional[str] = None, + app: Optional[str] = None, + config: Optional[VoiceAgentConfig] = None, + ): + """Initialize the Voice Agent client. + + Args: + api_key: Speechmatics API key. If None, uses SPEECHMATICS_API_KEY env var. + url: REST API endpoint URL. If None, uses SPEECHMATICS_RT_URL env var + or defaults to production endpoint. + app: Optional application name to use in the endpoint URL. + config: Optional voice agent configuration. + """ + # Default URL + if not url: + url = os.getenv("SPEECHMATICS_RT_URL") or "wss://eu2.rt.speechmatics.com/v2" + + # Initialize the client + super().__init__(api_key=api_key, url=self._get_endpoint_url(url, app)) + + # Logger + self._logger = get_logger(__name__) + + # Process the config + self._config, self._transcription_config, self._audio_format = self._process_config(config) + + # Connection status + self._is_connected: bool = False + self._is_ready_for_audio: bool = False + + # Timing info + self._start_time: datetime.datetime = datetime.datetime.now(datetime.timezone.utc) + self._total_time: float = 0 + self._total_bytes: int = 0 + + # TTFB metrics + self._last_ttfb_time: Optional[float] = None + self._last_ttfb: float = 0 + + # Time to disregard speech fragments before + self._trim_before_time: float = 0 + + # Current utterance speech data + self._speech_fragments: list[SpeechFragment] = [] + self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() + self._last_speech_fragments: list[SpeechFragment] = [] + self._current_view: Optional[SpeakerFragmentView] = None + + # Speaking states + self._is_speaking: bool = False + self._current_speaker: Optional[str] = None + self._last_vad_time: float = 0 + + # Diarization / speaker focus + self._end_of_utterance_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode + self._end_of_utterance_delay: float = self._config.end_of_utterance_silence_trigger + self._dz_enabled: bool = self._config.enable_diarization + self._dz_config = self._config.speaker_config + + # EndOfUtterance fallback timer + self._finalize_task: Optional[asyncio.Task] = None + + # Segment processor + self._processor_wait_time: float = 0.005 + self._processor_task: Optional[asyncio.Task] = None + + # Emitter task + self._finals_emitter_task: Optional[asyncio.Task] = None + + # Metrics emitter task + self._metrics_emitter_interval: float = 10.0 + self._metrics_emitter_task: Optional[asyncio.Task] = None + + # Audi sampling info + self._audio_sample_rate: float = self._audio_format.sample_rate * 1.0 + self._audio_sample_width: float = { + AudioEncoding.PCM_F32LE: 4.0, + AudioEncoding.PCM_S16LE: 2.0, + }.get(self._audio_format.encoding, 1.0) + + # Register handlers + self._register_event_handlers() + + # def on(self, event: ServerMessageType, callback: Optional[Callable] = None) -> Callable: + + def _process_config( + self, config: Optional[VoiceAgentConfig] = None + ) -> tuple[VoiceAgentConfig, TranscriptionConfig, AudioFormat]: + """Create a formatted STT transcription and audio config. + + Creates a transcription config object based on the service parameters. Aligns + with the Speechmatics RT API transcription config. + + Args: + config: Optional VoiceAgentConfig object to process. + + Returns: + A tuple of (VoiceAgentConfig, TranscriptionConfig, AudioFormat). + """ + # Default config + if config is None: + config = VoiceAgentConfig() + + # Transcription config + transcription_config = TranscriptionConfig( + language=config.language, + domain=config.domain, + output_locale=config.output_locale, + operating_point=config.operating_point, + diarization="speaker" if config.enable_diarization else None, + enable_partials=True, + max_delay=config.max_delay, + ) + + # Additional vocab + if config.additional_vocab: + transcription_config.additional_vocab = [ + { + "content": e.content, + "sounds_like": e.sounds_like, + } + for e in config.additional_vocab + ] + + # Diarization + if config.enable_diarization: + dz_cfg: dict[str, Any] = {} + if config.speaker_sensitivity is not None: + dz_cfg["speaker_sensitivity"] = config.speaker_sensitivity + if config.prefer_current_speaker is not None: + dz_cfg["prefer_current_speaker"] = config.prefer_current_speaker + if config.known_speakers: + dz_cfg["speakers"] = {s.label: s.speaker_identifiers for s in config.known_speakers} + if config.max_speakers is not None: + dz_cfg["max_speakers"] = config.max_speakers + if dz_cfg: + transcription_config.speaker_diarization_config = dz_cfg + + # End of Utterance (for fixed) + if config.end_of_utterance_silence_trigger and config.end_of_utterance_mode == EndOfUtteranceMode.FIXED: + transcription_config.conversation_config = ConversationConfig( + end_of_utterance_silence_trigger=config.end_of_utterance_silence_trigger, + ) + + # Punctuation overrides + if config.punctuation_overrides: + transcription_config.punctuation_overrides = config.punctuation_overrides + + # Configure the audio + audio_format = AudioFormat( + encoding=config.audio_encoding, + sample_rate=config.sample_rate, + chunk_size=config.chunk_size, + ) + + # Return the config objects + return config, transcription_config, audio_format + + def _register_event_handlers(self) -> None: + """Register event handlers. + + Specific event handlers that we need to deal with. All other events + from the STT API will be available to clients to use themselves. + """ + + # Recognition started event + @self.once(ServerMessageType.RECOGNITION_STARTED) + def _evt_on_recognition_started(message: dict[str, Any]): + self._start_time = datetime.datetime.now(datetime.timezone.utc) + self._is_ready_for_audio = True + + # Partial transcript event + @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) + def _evt_on_partial_transcript(message: dict[str, Any]): + self._handle_transcript(message, is_final=False) + + # Final transcript event + @self.on(ServerMessageType.ADD_TRANSCRIPT) + def _evt_on_final_transcript(message: dict[str, Any]): + self._handle_transcript(message, is_final=True) + + # End of utterance event + if self._end_of_utterance_mode == EndOfUtteranceMode.FIXED: + + @self.on(ServerMessageType.END_OF_UTTERANCE) + def _evt_on_end_of_utterance(message: dict[str, Any]): + self._logger.debug("End of utterance") + self.finalize_segments() + + async def connect(self) -> None: + """Connect to the Speechmatics API. + + Args: + transcription_config: Transcription configuration. + audio_format: Audio format. + """ + # Check if we are already connected + if self._is_connected: + self.emit( + AgentServerMessageType.ERROR, + {"reason": "Already connected"}, + ) + return + + # Connect to API + try: + await self.start_session( + transcription_config=self._transcription_config, + audio_format=self._audio_format, + ) + self._is_connected = True + self._start_metrics_task() + except Exception as e: + self._logger.error(f"Exception: {e}") + raise + + async def disconnect(self) -> None: + """Disconnect from the Speechmatics API.""" + # Check if we are already connected + if not self._is_connected: + return + + # Disconnect from API + try: + await asyncio.wait_for(self.close(), timeout=5.0) + except asyncio.TimeoutError: + self._logger.warning(f"{self} Timeout while closing Speechmatics client connection") + raise + except Exception as e: + self._logger.error(f"{self} Error closing Speechmatics client: {e}") + raise + finally: + self._is_connected = False + self._is_ready_for_audio = False + self._stop_metrics_task() + + def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: + """Update the diarization configuration. + + You can update the speakers that needs to be focussed on or ignored during + a session. The new config will overwrite the existing configuration and become + active immediately. + + Args: + config: The new diarization configuration. + """ + self._dz_config = config + + async def send_audio(self, payload: bytes) -> None: + """Send an audio frame through the WebSocket. + + Args: + payload: The audio frame to send. + + Examples: + >>> audio_chunk = b"" + >>> await client.send_audio(audio_chunk) + """ + # Skip if not ready for audio + if not self._is_ready_for_audio: + return + + # Send to the AsyncClient + await super().send_audio(payload) + + # Calculate the time (in seconds) for the payload + if self._audio_format is not None: + self._total_bytes += len(payload) + self._total_time += len(payload) / self._audio_sample_rate / self._audio_sample_width + + def _start_metrics_task(self) -> None: + """Start the metrics task.""" + + # Task to send metrics + async def emit_metrics() -> None: + while True: + # Interval between emitting metrics + await asyncio.sleep(self._metrics_emitter_interval) + + # Check if there are any listeners for AgentServerMessageType.METRICS + if not self.listeners(AgentServerMessageType.METRICS): + break + + # Calculations + time_s = round(self._total_time, 3) + + # Emit metrics + self.emit( + AgentServerMessageType.METRICS, + { + "total_time": time_s, + "total_time_str": time.strftime("%H:%M:%S", time.gmtime(time_s)), + "total_bytes": self._total_bytes, + "last_ttfb": self._last_ttfb, + }, + ) + + # Trigger the task + self._metrics_emitter_task = asyncio.create_task(emit_metrics()) + + def _stop_metrics_task(self) -> None: + """Stop the metrics task.""" + if self._metrics_emitter_task: + self._metrics_emitter_task.cancel() + self._metrics_emitter_task = None + + def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> None: + """Handle the partial and final transcript events. + + Args: + message: The new Partial or Final from the STT engine. + is_final: Whether the data is final or partial. + """ + # Handle async + asyncio.create_task(self._handle_transcript_async(message, is_final)) + + async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool) -> None: + """Handle the partial and final transcript events (async). + + Args: + message: The new Partial or Final from the STT engine. + is_final: Whether the data is final or partial. + """ + + # Add the speech fragments + fragments_available = await self._add_speech_fragments( + message=message, + is_final=is_final, + ) + + # Skip if unchanged + if not fragments_available: + return + + # Clear any existing timer + if self._processor_task is not None: + self._processor_task.cancel() + + # Send transcription frames after delay + async def process_after_delay(delay: float): + await asyncio.sleep(delay) + await self._process_speech_fragments() + self._processor_task = None + + # Send frames after delay + self._processor_task = asyncio.create_task(process_after_delay(self._processor_wait_time)) + + def _calculate_ttfb(self, end_time: float) -> None: + """Calculate the time to first text. + + The TTFB is calculated by taking the end time of the payload from the STT + engine and then calculating the difference between the total time of bytes + sent to the engine from the client. + + Args: + end_time: The end time of the payload from the STT engine. + """ + # Skip if no fragments are words + if len(self._speech_fragments) == 0 or all(f._type != "word" for f in self._speech_fragments): + return + + # Get start of the first fragment + fragments_start_time = self._speech_fragments[0].start_time + + # Skip if no partial word or if we have already calculated the TTFB for this word + if self._last_ttfb_time and fragments_start_time <= self._last_ttfb_time: + return + + # Calculate the time difference (convert to ms) + ttfb = (self._total_time - end_time) * 1000.0 + + # Skip if zero or less + if ttfb <= 0: + return + + # Save TTFB and end time + self._last_ttfb = ttfb + self._last_ttfb_time = end_time + + # Emit the TTFB + self.emit( + AgentServerMessageType.TTFB_METRICS, + { + "ttfb": self._last_ttfb, + }, + ) + + async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = False) -> bool: + """Takes a new Partial or Final from the STT engine. + + Accumulates it into the _speech_data list. As new final data is added, all + partials are removed from the list. + + Note: If a known speaker is `__[A-Z0-9_]{2,}__`, then the words are skipped, + as this is used to protect against self-interruption by the assistant or to + block out specific known voices. + + Args: + message: The new Partial or Final from the STT engine. + is_final: Whether the data is final or partial. + + Returns: + True if the speech fragments were updated, False otherwise. + """ + async with self._speech_fragments_lock: + # Parsed new speech data from the STT engine + fragments: list[SpeechFragment] = [] + + # Metadata + metadata = message.get("metadata", {}) + payload_start_time = metadata.get("start_time", 0) + payload_end_time = metadata.get("end_time", 0) + + # Iterate over the results in the payload + for result in message.get("results", []): + alt = result.get("alternatives", [{}])[0] + if alt.get("content", None): + # Create the new fragment + fragment = SpeechFragment( + start_time=result.get("start_time", 0), + end_time=result.get("end_time", 0), + language=alt.get("language", "en"), + _type=result.get("type", "word"), + is_eos=result.get("is_eos", False), + is_disfluency="disfluency" in alt.get("tags", []), + is_punctuation=result.get("type", "") == "punctuation", + is_final=is_final, + attaches_to=result.get("attaches_to", ""), + content=alt.get("content", ""), + speaker=alt.get("speaker", None), + confidence=alt.get("confidence", 1.0), + result=result, + ) + + # Check fragment is after trim time + if fragment.start_time < self._trim_before_time: + continue + + # Speaker filtering + if fragment.speaker: + # Drop `__XX__` speakers + if re.match(r"^__[A-Z0-9_]{2,}__$", fragment.speaker): + continue + + # Drop speakers not focussed on + if ( + self._dz_config.focus_mode == DiarizationFocusMode.IGNORE + and self._dz_config.focus_speakers + and fragment.speaker not in self._dz_config.focus_speakers + ): + continue + + # Drop ignored speakers + if self._dz_config.ignore_speakers and fragment.speaker in self._dz_config.ignore_speakers: + continue + + # Add the fragment + fragments.append(fragment) + + # Evaluate for VAD (only done on partials) + if not is_final: + self._vad_evaluation(fragments) + + # Fragments to retain + # - Outside of the bounds of the payload + # - Not a trailing fragment with same start time as payload end time (end of sentence etc.) + # retained_fragments = [ + # frag + # for frag in self._speech_fragments + # if not ( + # frag.start_time >= payload_start_time + # and frag.end_time <= payload_end_time + # and frag.is_final is False + # # and frag.start_time != payload_end_time + # ) + # ] + retained_fragments = [frag for frag in self._speech_fragments if frag.is_final] + + # Re-structure the speech fragments + self._speech_fragments = retained_fragments.copy() + self._speech_fragments.extend(fragments) + self._speech_fragments.sort(key=lambda x: x.start_time) + + # Debug the fragments + if DEBUG_MORE: + debug_payload = { + "final": is_final, + "start_time": payload_start_time, + "end_time": payload_end_time, + "keeping": [f.content for f in retained_fragments], + "adding": [f.content for f in fragments], + "transcript": metadata.get("transcript", ""), + "full": [[f.content, f.start_time, f.end_time, f.is_final] for f in self._speech_fragments], + } + self._logger.debug(json.dumps(debug_payload)) + + # Update TTFB + if not is_final: + self._calculate_ttfb(end_time=payload_end_time) + + # Fragments available + return len(self._speech_fragments) > 0 + + async def _process_speech_fragments(self) -> None: + """Process the speech fragments. + + Compares the current speech fragments against the last set of speech fragments. + When segments are emitted, they are then removed from the buffer of fragments + so the next comparison is based on the remaining + new fragments. + """ + async with self._speech_fragments_lock: + # Current transcription + last_complete_view = SpeakerFragmentView( + fragments=self._last_speech_fragments.copy(), + base_time=self._start_time, + focus_speakers=self._dz_config.focus_speakers, + annotate_segments=False, + ) + last_active_view = SpeakerFragmentView( + fragments=[f for s in last_complete_view.segments if s.is_active for f in s.fragments], + base_time=self._start_time, + focus_speakers=self._dz_config.focus_speakers, + annotate_segments=False, + ) + + # New transcript + current_complete_view = SpeakerFragmentView( + fragments=self._speech_fragments.copy(), + base_time=self._start_time, + focus_speakers=self._dz_config.focus_speakers, + ) + current_active_view = SpeakerFragmentView( + fragments=[f for s in current_complete_view.segments if s.is_active for f in s.fragments], + base_time=self._start_time, + focus_speakers=self._dz_config.focus_speakers, + ) + + # If no segments, return + if current_complete_view.segment_count == 0: + return + + # Compare this against the previous transcript + complete_annotation_result = current_complete_view.compare_and_annotate(last_complete_view) + active_annotation_result = current_active_view.compare_and_annotate(last_active_view) + + # TODO - Consider a config option to split segments into smaller chunks for long transcripts? + + # Determine if there has been a change and delay before finalizing + active_view_changed, delay_to_finalize = self._calc_delay_to_finalize( + view=current_complete_view, view_changes=active_annotation_result + ) + + # Emit partial segments + if complete_annotation_result.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL_LCASE): + asyncio.create_task(self._emit_partial_segments(current_complete_view, delay_to_finalize)) + + # Emit segments + if active_view_changed: + # Update the active view + self._current_view = current_complete_view + + # Stop any existing emitter task + if self._finals_emitter_task is not None: + self._finals_emitter_task.cancel() + + # Emit final segments + if delay_to_finalize is not None: + asyncio.create_task(self._emit_final_segments(delay=delay_to_finalize)) + + # Copy the data + self._last_speech_fragments = self._speech_fragments.copy() + + def _calc_delay_to_finalize( + self, + view: SpeakerFragmentView, + view_changes: AnnotationResult, + ) -> tuple[bool, float | None]: + """Calculate the delay before finalizing segments. + + Process the most recent segment and view to determine how long to delay before emitting + the segments to the client. + + Args: + view: The speaker fragment to evaluate. + view_changes: The annotation result to use for evaluation. + + Returns: + Tuple of (should_emit, emit_final_delay) + """ + # Skip if no segments + if view.segment_count == 0: + return False, None + + # Emit segments + emit_final_delay: Optional[float] = None + should_emit: bool = False + + # Last active segment + last_active_segment_index = view.last_active_segment_index + last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None + + # Elapsed time from last segment to now + # elapsed_time = ( + # self._total_time - last_active_segment.end_time + # if last_active_segment + # else view.end_time + # ) + + # If this is NEW or UPDATED_FULL_LCASE + if view_changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL_LCASE): + """Process the annotation flags to determine how long before sending a final segment.""" + + # Fallback when using FIXED + if self._end_of_utterance_mode == EndOfUtteranceMode.FIXED: + emit_final_delay = self._end_of_utterance_delay * 5.0 + + # Timer for when ADAPTIVE + elif self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE and last_active_segment: + """Check the contents of the last segment." + + Check for: + - ends with a disfluency + """ + + # Delay multiplier + multiplier = 1.5 + + # Very speaking + if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): + multiplier *= 3.0 + + # Slow speaking + if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): + multiplier *= 1.5 + + # Has a disfluency + if last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): + multiplier *= 1.5 + + # Ends with a disfluency + if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): + multiplier *= 4.0 + + # Calculate the delay + emit_final_delay = self._end_of_utterance_delay * multiplier + + # TODO - Other checks / end of turn detection + + # Emit segments + should_emit = True + + # Remove any elapsed time from the delay + # if emit_final_delay is not None: + # emit_final_delay = max( + # emit_final_delay - elapsed_time, + # self._end_of_utterance_delay - elapsed_time, + # 0.001, + # ) + + # Return the result + return should_emit, emit_final_delay + + def finalize_segments(self, ttl: Optional[float] = None): + """Finalize segments. + + This function will emit segments in the buffer without any further checks + on the contents of the segments. If the ttl is set to zero, then finalization + will be forced through without yielding for any remaining STT messages. + + Args: + ttl: Optional delay before finalizing partial segments (defaults to 0.01 seconds). + """ + + async def _emit(): + await asyncio.sleep(ttl or 0.01) + await self._emit_final_segments() + + asyncio.create_task(_emit()) + + async def _emit_partial_segments( + self, view: SpeakerFragmentView, delay_to_finalize: Optional[float] = None + ) -> None: + """Emit partial segments to listeners. + + This function will emit segments in the view without any further checks + on the contents of the segments. + + Args: + view: The speaker fragment view to emit. + delay_to_finalize: Optional delay before finalizing the segments. + """ + # Skip if no segments + if view.segment_count == 0: + return + + # Emit partial segments + self.emit( + AgentServerMessageType.ADD_INTERIM_SEGMENTS, + {"segments": view.segments, "delay_to_finalize": delay_to_finalize}, + ) + + async def _emit_final_segments(self, delay: float = 0) -> None: + """Emit final segments to listeners. + + At the point of emitting the result, this will use the most current view, + unlike emit_partial_segments which uses the view that was passed in. + + Args: + delay: Delay before finalizing partial segments. + """ + # Check delay within bounds + if delay < 0: + raise ValueError("Delay must be non-negative") + elif delay > 30: + raise ValueError("Delay must be less than 30 seconds") + + # Skip if no segments + if self._current_view is None or self._current_view.segment_count == 0: + return + + # Stop any existing emitter task + if self._finals_emitter_task is not None: + self._finals_emitter_task.cancel() + + async def emit_finals_now(): + """Emit final segments.""" + + # Skip if no segments + if self._current_view is None or self._current_view.segment_count == 0: + return + + # Get the active view + view = self._current_view + + # Find out if we have segments to emit + segments_to_emit = view.last_active_segment_index + 1 + + # Only finalize if there are valid segments + if segments_to_emit > 0: + # Accumulate all of the fragments for segments up to and including the last_active_index + fragments_to_emit = [] + for i in range(segments_to_emit): + fragments_to_emit.extend(view.segments[i].fragments) + + # Only include segments up to the last active segment + trimmed_view = SpeakerFragmentView( + fragments=fragments_to_emit, + base_time=view.base_time, + focus_speakers=view.focus_speakers, + annotate_segments=False, + ) + + # Emit the segments + self.emit( + AgentServerMessageType.ADD_SEGMENTS, + {"segments": trimmed_view.segments}, + ) + + # Last end time + self._trim_before_time = trimmed_view.end_time + + # Filter segments after the trim time + async with self._speech_fragments_lock: + self._speech_fragments = [ + fragment for fragment in self._speech_fragments if fragment.start_time >= self._trim_before_time + ] + + # Update the current view + self._current_view = SpeakerFragmentView( + fragments=self._speech_fragments, + base_time=self._trim_before_time, + focus_speakers=self._dz_config.focus_speakers, + annotate_segments=False, + ) + + # Reset previous fragments + self._last_speech_fragments = self._speech_fragments.copy() + + async def emit_finals_after_delay(_delay: float): + """Yield before emitting final segments after a delay.""" + + # Wait for the delay (can be cancelled) + if _delay > 0: + await asyncio.sleep(_delay) + + # Emit the segments + await emit_finals_now() + + # Reset the task + self._finals_emitter_task = None + + if DEBUG_MORE: + self._logger.debug(f"Will emit final segments after {delay} seconds") + + # Emit with delay or immediately + if delay > 0: + self._finals_emitter_task = asyncio.create_task(emit_finals_after_delay(delay)) + else: + self._finals_emitter_task = asyncio.create_task(emit_finals_now()) + + def _vad_evaluation(self, fragments: list[SpeechFragment]): + """Emit a VAD event. + + This will emit `SPEECH_STARTED` and `SPEECH_ENDED` events to the client and is + based on valid transcription for active speakers. Ignored or speakers not in + focus will not be considered an active participant. + + This should only run on partial / non-final words. + + Args: + fragments: The list of fragments to use for evaluation. + """ + # Find the valid list of partial words + if self._dz_enabled and self._dz_config.focus_speakers: + partial_words = [ + frag + for frag in fragments + if frag.speaker in self._dz_config.focus_speakers and frag._type == "word" and not frag.is_final + ] + else: + partial_words = [frag for frag in fragments if frag._type == "word" and not frag.is_final] + + # Evaluate if any valid partial words exist + has_valid_partial = len(partial_words) > 0 + + # Are we already speaking + already_speaking = self._is_speaking + + # Speakers + current_speaker = self._current_speaker + speaker = partial_words[-1].speaker if has_valid_partial else self._current_speaker + speaker_changed = speaker != current_speaker and current_speaker is not None + + # If diarization is enabled, indicate speaker switching + if self._dz_enabled and speaker is not None: + """When enabled, we send a speech events if the speaker has changed. + + For any client that wishes to show _which_ speaker is speaking, this will + emit events to indicate when speakers switch. + """ + + # Check if speaker is different to the current speaker + if already_speaking and speaker_changed: + self.emit( + AgentServerMessageType.USER_SPEECH_ENDED, + {"status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False)}, + ) + self.emit( + AgentServerMessageType.USER_SPEECH_STARTED, + {"status": SpeakerVADStatus(speaker_id=speaker, is_active=True)}, + ) + + # Update current speaker + self._current_speaker = speaker + + # No change required + if has_valid_partial == already_speaking: + return + + # Set the speaking state + self._is_speaking = not self._is_speaking + + # Emit the event for latest speaker + self.emit( + ( + AgentServerMessageType.USER_SPEECH_STARTED + if self._is_speaking + else AgentServerMessageType.USER_SPEECH_ENDED + ), + {"status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, + ) + + # Reset the current speaker + if not self._is_speaking: + self._current_speaker = None + + def _get_endpoint_url(self, url: str, app: Optional[str] = None) -> str: + """Format the endpoint URL with the SDK and app versions. + + Args: + url: The base URL for the endpoint. + app: The application name to use in the endpoint URL. + + Returns: + str: The formatted endpoint URL. + """ + query_params = {} + query_params["sm-app"] = app or f"voice-sdk/{__version__}" + query_params["sm-voice-sdk"] = f"{__version__}" + query = urlencode(query_params) + + return f"{url}?{query}" diff --git a/sdk/voice/speechmatics/voice/_logging.py b/sdk/voice/speechmatics/voice/_logging.py new file mode 100644 index 00000000..18f7419f --- /dev/null +++ b/sdk/voice/speechmatics/voice/_logging.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +import logging + +logger = logging.getLogger(__name__) +logger.addHandler(logging.NullHandler()) + + +def get_logger(name: str) -> logging.Logger: + """Get a logger that stays silent by default. + + The logger uses Python's standard logging module and includes NullHandler + by default to avoid unwanted output. Users can configure logging levels + and handlers as needed. + + Args: + name: Logger name, typically __name__ from the calling module. + + Returns: + Configured logger instance. + + Examples: + Basic usage in SDK modules: + logger = get_logger(__name__) + logger.debug("HTTP request sent %s %s", method, url) + logger.info("Job submitted (job_id=%s)", job_id) + logger.warning("Job failed (job_id=%s): %s", job_id, error) + logger.error("Connection failed: %s", e) + + Enable debug logging in user code: + import logging + logging.basicConfig(level=logging.DEBUG) + # Now all SDK debug messages will be visible + + Custom logging configuration: + import logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # Or for specific components: + logging.getLogger('speechmatics.batch').setLevel(logging.DEBUG) + """ + module_logger = logging.getLogger(name) + module_logger.addHandler(logging.NullHandler()) + return module_logger + + +__all__ = ["get_logger"] diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py new file mode 100644 index 00000000..a79a34b0 --- /dev/null +++ b/sdk/voice/speechmatics/voice/_models.py @@ -0,0 +1,793 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + + +import datetime +from dataclasses import dataclass +from dataclasses import field +from enum import Enum +from enum import IntFlag +from enum import auto +from typing import Any +from typing import Optional + +from speechmatics.rt import AudioEncoding +from speechmatics.rt import OperatingPoint + +__version__ = "0.1.0" + + +class EndOfUtteranceMode(str, Enum): + """End of turn delay options for transcription.""" + + NONE = "none" + FIXED = "fixed" + ADAPTIVE = "adaptive" + + +class DiarizationFocusMode(str, Enum): + """Speaker focus mode for diarization.""" + + RETAIN = "retain" + IGNORE = "ignore" + + +@dataclass +class AdditionalVocabEntry: + """Additional vocabulary entry. + + Parameters: + content: The word to add to the dictionary. + sounds_like: Similar words to the word. + """ + + content: str + sounds_like: list[str] = field(default_factory=list) + + +@dataclass +class DiarizationKnownSpeaker: + """Known speakers for speaker diarization. + + Parameters: + label: The label of the speaker. + speaker_identifiers: One or more data strings for the speaker. + """ + + label: str + speaker_identifiers: list[str] + + +@dataclass +class DiarizationSpeakerConfig: + """Speaker Diarization Config. + + List of speakers to focus on, ignore and how to deal with speakers that are not + in focus. These settings can be changed during a session. Other changes may require + a new session. + + Parameters: + focus_speakers: List of speaker IDs to focus on. When enabled, only these speakers are + emitted as finalized frames and other speakers are considered passive. Words from + other speakers are still processed, but only emitted when a focussed speaker has + also said new words. A list of labels (e.g. `S1`, `S2`) or identifiers of known + speakers (e.g. `speaker_1`, `speaker_2`) can be used. + Defaults to []. + + ignore_speakers: List of speaker IDs to ignore. When enabled, these speakers are + excluded from the transcription and their words are not processed. Their speech + will not trigger any VAD or end of utterance detection. By default, any speaker + with a label starting and ending with double underscores will be excluded (e.g. + `__ASSISTANT__`). + Defaults to []. + + focus_mode: Speaker focus mode for diarization. When set to `DiarizationFocusMode.RETAIN`, + the STT engine will retain words spoken by other speakers (not listed in `ignore_speakers`) + and process them as passive speaker frames. When set to `DiarizationFocusMode.IGNORE`, + the STT engine will ignore words spoken by other speakers and they will not be processed. + Defaults to `DiarizationFocusMode.RETAIN`. + """ + + focus_speakers: list[str] = field(default_factory=list) + ignore_speakers: list[str] = field(default_factory=list) + focus_mode: DiarizationFocusMode = DiarizationFocusMode.RETAIN + + +@dataclass +class VoiceAgentConfig: + """Voice Agent configuration. + + A framework-independent configuration object for the Speechmatics Voice Agent. This uses + utility functions to create `TranscriptionConfig` and `AudioConfig` objects and also retain + agent configuration for the `VoiceAgentClient`. + + Parameters: + operating_point: Operating point for transcription accuracy vs. latency tradeoff. It is + recommended to use OperatingPoint.ENHANCED for most use cases. Defaults to + OperatingPoint.ENHANCED. + + domain: Domain for Speechmatics API. Defaults to None. + + language: Language code for transcription. Defaults to `en`. + + output_locale: Output locale for transcription, e.g. `en-GB`. Defaults to None. + + max_delay: Maximum delay in seconds for transcription. This forces the STT engine to + speed up the processing of transcribed words and reduces the interval between partial + and final results. Lower values can have an impact on accuracy. Defaults to 1.0. + + end_of_utterance_silence_trigger: Maximum delay in seconds for end of utterance trigger. + The delay is used to wait for any further transcribed words before emitting the final + word frames. The value must be lower than max_delay. + Defaults to 0.5. + + end_of_utterance_mode: End of utterance delay mode. When ADAPTIVE is used, the delay + can be adjusted on the content of what the most recent speaker has said, such as + rate of speech and whether they have any pauses or disfluencies. When FIXED is used, + the delay is fixed to the value of `end_of_utterance_delay`. Use of NONE disables + end of utterance detection and uses a fallback timer. + Defaults to `EndOfUtteranceMode.FIXED`. + + additional_vocab: List of additional vocabulary entries. If you supply a list of + additional vocabulary entries, the this will increase the weight of the words in the + vocabulary and help the STT engine to better transcribe the words. + Defaults to []. + + punctuation_overrides: Punctuation overrides. This allows you to override the punctuation + in the STT engine. This is useful for languages that use different punctuation + than English. See documentation for more information. + Defaults to None. + + enable_diarization: Enable speaker diarization. When enabled, the STT engine will + determine and attribute words to unique speakers. The speaker_sensitivity + parameter can be used to adjust the sensitivity of diarization. + Defaults to False. + + speaker_sensitivity: Diarization sensitivity. A higher value increases the sensitivity + of diarization and helps when two or more speakers have similar voices. + Defaults to 0.5. + + max_speakers: Maximum number of speakers to detect. This forces the STT engine to cluster + words into a fixed number of speakers. It should not be used to limit the number of + speakers, unless it is clear that there will only be a known number of speakers. + Defaults to None. + + prefer_current_speaker: Prefer current speaker ID. When set to true, groups of words close + together are given extra weight to be identified as the same speaker. + Defaults to False. + + speaker_config: DiarizationSpeakerConfig to configure the speakers to focus on, ignore and + how to deal with speakers that are not in focus. + + known_speakers: List of known speaker labels and identifiers. If you supply a list of + labels and identifiers for speakers, then the STT engine will use them to attribute + any spoken words to that speaker. This is useful when you want to attribute words + to a specific speaker, such as the assistant or a specific user. Labels and identifiers + can be obtained from a running STT session and then used in subsequent sessions. + Identifiers are unique to each Speechmatics account and cannot be used across accounts. + Refer to our examples on the format of the known_speakers parameter. + Defaults to []. + + sample_rate: Audio sample rate for streaming. Defaults to 16000. + chunk_size: Audio chunk size for streaming. Defaults to 160. + audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. + """ + + # Service configuration + operating_point: OperatingPoint = OperatingPoint.ENHANCED + domain: Optional[str] = None + language: str = "en" + output_locale: Optional[str] = None + + # Features + max_delay: float = 1.0 + end_of_utterance_silence_trigger: float = 0.5 + end_of_utterance_mode: EndOfUtteranceMode = EndOfUtteranceMode.FIXED + additional_vocab: list[AdditionalVocabEntry] = field(default_factory=list) + punctuation_overrides: Optional[dict] = None + + # Diarization + enable_diarization: bool = False + speaker_sensitivity: float = 0.5 + max_speakers: Optional[int] = None + prefer_current_speaker: bool = False + speaker_config: DiarizationSpeakerConfig = field(default_factory=DiarizationSpeakerConfig) + known_speakers: list[DiarizationKnownSpeaker] = field(default_factory=list) + + # Audio + sample_rate: int = 16000 + chunk_size: int = 160 + audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE + + +class AgentClientMessageType(str, Enum): + """Message types that can be sent from client to server. + + These enum values represent the different types of messages that the + client can send to the Speechmatics RT API during a transcription session. + + Attributes: + EndOfStream: Signals that no more audio data will be sent. + GetSpeakers: Internal, Speechmatics only message. Allows the client to request speaker data. + + Examples: + >>> # Ending the session + >>> end_message = { + ... "message": AgentClientMessageType.END_OF_STREAM, + ... "last_seq_no": sequence_number + ... } + """ + + END_OF_STREAM = "EndOfStream" + GET_SPEAKERS = "GetSpeakers" + + +class AgentServerMessageType(str, Enum): + """Message types that can be received from the server / agent. + + These enum values represent the different types of messages that the + Speechmatics RT API / Voice Agent SDK can send to the client. + + Attributes: + RecognitionStarted: The recognition session has started. + EndOfTranscript: The recognition session has ended. + Info: Informational message. + Warning: Warning message. + Error: Error message. + AddPartialTranscript: Partial transcript has been added. + AddTranscript: Transcript has been added. + UserSpeechStarted: Speech has started. + UserSpeechEnded: Speech has ended. + TurnDetected: A turn has been detected. + AddSegments: A final segment has been detected. + AddInterimSegments: An interim segment has been detected. + SpeakersResult: Speakers result has been detected. + Metrics: Metrics for the STT engine. + SpeakerMetrics: Metrics relating to speakers. + + Examples: + >>> # Register event handlers for different message types + >>> @client.on(AgentServerMessageType.INTERIM_SEGMENTS) + >>> def handle_interim(message): + ... segments: list[SpeakerSegment] = message['segments'] + ... print(f"Interim: {segments}") + >>> + >>> @client.on(AgentServerMessageType.FINAL_SEGMENTS) + >>> def handle_final(message): + ... segments: list[SpeakerSegment] = message['segments'] + ... print(f"Final: {segments}") + >>> + >>> @client.on(AgentServerMessageType.ERROR) + >>> def handle_error(message): + ... print(f"Error: {message['reason']}") + """ + + # API messages + RECOGNITION_STARTED = "RecognitionStarted" + END_OF_TRANSCRIPT = "EndOfTranscript" + INFO = "Info" + WARNING = "Warning" + ERROR = "Error" + + # Raw transcription messages + ADD_PARTIAL_TRANSCRIPT = "AddPartialTranscript" + ADD_TRANSCRIPT = "AddTranscript" + + # VAD messages + USER_SPEECH_STARTED = "UserSpeechStarted" + USER_SPEECH_ENDED = "UserSpeechEnded" + + # Turn / segment messages + ADD_SEGMENTS = "AddSegments" + ADD_INTERIM_SEGMENTS = "AddInterimSegments" + TURN_DETECTED = "TurnDetected" + + # Speaker messages + SPEAKERS_RESULT = "SpeakersResult" + + # Metrics + METRICS = "Metrics" + TTFB_METRICS = "TTFBMetrics" + SPEAKER_METRICS = "SpeakerMetrics" + + +class AnnotationFlags(IntFlag): + """Flags to apply when processing speech / objects.""" + + # High-level segment updates + NEW = auto() + UPDATED_FULL = auto() + UPDATED_FULL_LCASE = auto() + UPDATED_STRIPPED = auto() + UPDATED_STRIPPED_LCASE = auto() + UPDATED_FINALS = auto() + UPDATED_PARTIALS = auto() + UPDATED_SPEAKERS = auto() + + # Content of segments + ONLY_ACTIVE_SPEAKERS = auto() + CONTAINS_INACTIVE_SPEAKERS = auto() + + # More granular details on the word content + HAS_PARTIAL = auto() + HAS_FINAL = auto() + STARTS_WITH_FINAL = auto() + ENDS_WITH_FINAL = auto() + HAS_EOS = auto() + ENDS_WITH_EOS = auto() + HAS_DISFLUENCY = auto() + STARTS_WITH_DISFLUENCY = auto() + ENDS_WITH_DISFLUENCY = auto() + HIGH_DISFLUENCY_COUNT = auto() + ENDS_WITH_PUNCTUATION = auto() + VERY_SLOW_SPEAKER = auto() + SLOW_SPEAKER = auto() + FAST_SPEAKER = auto() + ONLY_PUNCTUATION = auto() + MULTIPLE_SPEAKERS = auto() + NO_TEXT = auto() + + # End of utterance detection + END_OF_UTTERANCE = auto() + + +@dataclass +class AnnotationResult: + """Processing result.""" + + flags: int = 0 + + def __init__(self, *flags: AnnotationFlags): + """Initialize the object. + + Args: + flags: The initial flags to set. + """ + self.flags = 0 + for flag in flags: + self.add(flag) + + def has(self, *flags: AnnotationFlags) -> bool: + """Check if the object has all given flags.""" + return all(self.flags & flag == flag for flag in flags) + + def any(self, *flags: AnnotationFlags) -> bool: + """Check if the object has any of the given flags.""" + return any(self.flags & flag == flag for flag in flags) + + def add(self, flag: AnnotationFlags) -> None: + """Add a flag to the object.""" + self.flags |= flag + + def remove(self, flag: AnnotationFlags) -> None: + """Remove a flag from the object.""" + self.flags &= ~flag + + def __eq__(self, o: object) -> bool: + """Check if the object is equal to another.""" + if isinstance(o, AnnotationResult): + return self.flags == o.flags + elif isinstance(o, (AnnotationFlags, int)): + return self.flags == o + return False + + def __str__(self) -> str: + """String representation of the flags.""" + flags: list[str] = [] + for flag in AnnotationFlags: + if self.flags & flag == flag and flag.name is not None: + flags.append(flag.name) + return f"{type(self).__name__}({', '.join(flags)})" + + +@dataclass +class SpeechFragment: + """Fragment of a speech event. + + As the transcript is processed (partials and finals), a list of SpeechFragments + objects are accumulated and then used to form SpeechSegments objects. + + Parameters: + start_time: Start time of the fragment in seconds (from session start). + end_time: End time of the fragment in seconds (from session start). + language: Language of the fragment. Defaults to `en`. + _type: Type of the fragment. Defaults to `word`. + is_eos: Whether the fragment is the end of a sentence. Defaults to `False`. + is_final: Whether the fragment is the final fragment. Defaults to `False`. + is_disfluency: Whether the fragment is a disfluency. Defaults to `False`. + is_punctuation: Whether the fragment is a punctuation. Defaults to `False`. + attaches_to: Whether the fragment attaches to the previous or next fragment (punctuation). Defaults to empty string. + content: Content of the fragment. Defaults to empty string. + speaker: Speaker of the fragment (if diarization is enabled). Defaults to `None`. + confidence: Confidence of the fragment (0.0 to 1.0). Defaults to `1.0`. + result: Raw result of the fragment from the TTS. + annotation: Annotation for the fragment. + """ + + start_time: float + end_time: float + language: str = "en" + _type: str = "word" + is_eos: bool = False + is_final: bool = False + is_disfluency: bool = False + is_punctuation: bool = False + attaches_to: str = "" + content: str = "" + speaker: Optional[str] = None + confidence: float = 1.0 + result: Optional[Any] = None + annotation: Optional[AnnotationResult] = None + + +@dataclass +class SpeakerSegment: + """SpeechFragment items grouped by speaker_id and whether the speaker is active. + + Parameters: + speaker_id: The ID of the speaker. + is_active: Whether the speaker is active (emits frame). + timestamp: The timestamp of the frame. + language: The language of the frame. + fragments: The list of SpeechFragment items. + annotation: The annotation associated with the segment. + """ + + speaker_id: Optional[str] = None + is_active: bool = False + timestamp: Optional[str] = None + language: Optional[str] = None + fragments: list[SpeechFragment] = field(default_factory=list) + annotation: AnnotationResult = field(default_factory=AnnotationResult) + + @property + def start_time(self) -> float: + """Return the start time of the segment.""" + return self.fragments[0].start_time + + @property + def end_time(self) -> float: + """Return the end time of the segment.""" + return self.fragments[-1].end_time + + def __str__(self): + """Return a string representation of the object.""" + meta = { + "speaker_id": self.speaker_id, + "timestamp": self.timestamp, + "language": self.language, + "annotation": str(self.annotation), + "text": self.format_text(), + } + return f"SpeakerSegment({', '.join(f'{k}={v}' for k, v in meta.items())})" + + def format_text(self, format: Optional[str] = None, words_only: bool = False) -> str: + """Wrap text with speaker ID in an optional f-string format. + + Supported format variables: + speaker_id: The ID of the speaker. + text: The text of the fragment. + ts: The timestamp of the fragment. + lang: The language of the fragment. + + Args: + format: Format to wrap the text with. + words_only: Whether to include only word fragments. + + Returns: + str: The wrapped text. + """ + # Cumulative contents + content = "" + + # Select fragments to include + if not words_only: + fragments = self.fragments + else: + fragments = [frag for frag in self.fragments if frag._type == "word"] + + # Assemble the text + for frag in fragments: + if content == "" or frag.attaches_to == "previous": + content += frag.content + else: + content += " " + frag.content + + # Format the text, if format is provided + if format is None: + return content + return format.format( + **{ + "speaker_id": self.speaker_id, + "text": content, + "ts": self.timestamp, + "lang": self.language, + } + ) + + +@dataclass +class SpeakerFragmentView: + """View for speaker fragments. + + Parameters: + fragments: List of fragments. + base_time: Base time for the fragments. + focus_speakers: List of speakers to focus on or None. + """ + + fragments: list[SpeechFragment] + segments: list[SpeakerSegment] + + def __init__( + self, + fragments: list[SpeechFragment], + base_time: datetime.datetime, + focus_speakers: Optional[list[str]] = None, + annotate_segments: bool = True, + ): + self.fragments = fragments + self.base_time = base_time + self.focus_speakers = focus_speakers + self.segments = self._get_segments_from_fragments(annotate_segments=annotate_segments) + + @property + def start_time(self) -> float: + return self.fragments[0].start_time + + @property + def end_time(self) -> float: + return self.fragments[-1].end_time + + @property + def final_count(self) -> int: + return sum(1 for frag in self.fragments if frag.is_final) + + @property + def partial_count(self) -> int: + return sum(1 for frag in self.fragments if not frag.is_final) + + @property + def segment_count(self) -> int: + return len(self.segments) + + @property + def last_active_segment_index(self) -> int: + idx = next( + (i for i, segment in enumerate(reversed(self.segments)) if segment.is_active), + None, + ) + if idx is None: + return -1 + return len(self.segments) - idx - 1 + + def format_text( + self, + format: str = "|{speaker_id}|{text}|", + separator: str = "", + words_only: bool = False, + ) -> str: + return separator.join(segment.format_text(format, words_only) for segment in self.segments) + + def _get_segments_from_fragments(self, annotate_segments: bool = True) -> list[SpeakerSegment]: + # Speaker groups + current_speaker: Optional[str] = None + speaker_groups: list[list[SpeechFragment]] = [[]] + + # Group by speakers + for frag in self.fragments: + if frag.speaker != current_speaker: + current_speaker = frag.speaker + if speaker_groups[-1]: + speaker_groups.append([]) + speaker_groups[-1].append(frag) + + # Create SpeakerFragments objects + segments: list[SpeakerSegment] = [] + for group in speaker_groups: + sd = self._get_speaker_segment_from_fragment_group(group, annotate=annotate_segments) + if sd: + segments.append(sd) + + # Return the grouped SpeakerFragments objects + return segments + + def _get_speaker_segment_from_fragment_group( + self, + group: list[SpeechFragment], + annotate: bool = True, + ) -> Optional[SpeakerSegment]: + """Take a group of fragments and piece together into SpeakerSegment. + + Each fragment for a given speaker is assembled into a string, + taking into consideration whether words are attached to the + previous or next word (notably punctuation). This ensures that + the text does not have extra spaces. This will also check for + any straggling punctuation from earlier utterances that should + be removed. + + Args: + group: List of SpeechFragment objects. + annotate: Whether to annotate the segment. + + Returns: + SpeakerSegment: The object for the group. + """ + # Check for starting fragments that are attached to previous + if group and group[0].attaches_to == "previous": + group = group[1:] + + # Check for trailing fragments that are attached to next + if group and group[-1].attaches_to == "next": + group = group[:-1] + + # Check there are results + if not group: + return None + + # Get the timing extremes + start_time = min(frag.start_time for frag in group) + + # Timestamp + ts = (self.base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") + + # Determine if the speaker is considered active + is_active = True + if self.focus_speakers: + is_active = group[0].speaker in self.focus_speakers + + # New SpeakerSegment + segment = SpeakerSegment( + speaker_id=group[0].speaker, + timestamp=ts, + language=group[0].language, + fragments=group, + is_active=is_active, + ) + + # Annotate + if annotate: + segment.annotation = self._annotate_segment(segment) + + # Return the SpeakerSegment object + return segment + + def _annotate_segment(self, segment: SpeakerSegment) -> AnnotationResult: + """Annotate the segment. + + This will annotate the segment with any additional information. + """ + # Annotation result + result = AnnotationResult() + + # References + segment_length: int = len(segment.fragments) + first_fragment: SpeechFragment = segment.fragments[0] + last_fragment: SpeechFragment = segment.fragments[-1] + penultimate_fragment: Optional[SpeechFragment] = segment.fragments[-2] if segment_length > 1 else None + + # Count of words + words = [frag for frag in segment.fragments if frag._type == "word"] + word_count = len(words) + if word_count == 0: + result.add(AnnotationFlags.NO_TEXT) + + # Only punctuation + if all(frag.is_punctuation for frag in segment.fragments): + result.add(AnnotationFlags.ONLY_PUNCTUATION) + + # Partials and finals + if any(not frag.is_final for frag in segment.fragments): + result.add(AnnotationFlags.HAS_PARTIAL) + + # Finals + if any(frag.is_final for frag in segment.fragments): + result.add(AnnotationFlags.HAS_FINAL) + if first_fragment.is_final: + result.add(AnnotationFlags.STARTS_WITH_FINAL) + if last_fragment.is_final: + result.add(AnnotationFlags.ENDS_WITH_FINAL) + + # End of sentence + if last_fragment.is_eos: + result.add(AnnotationFlags.ENDS_WITH_EOS) + + # Punctuation + if last_fragment.is_punctuation: + result.add(AnnotationFlags.ENDS_WITH_PUNCTUATION) + + # Disfluency + if any(frag.is_disfluency for frag in segment.fragments): + result.add(AnnotationFlags.HAS_DISFLUENCY) + if first_fragment.is_disfluency: + result.add(AnnotationFlags.STARTS_WITH_DISFLUENCY) + if last_fragment.is_disfluency: + result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) + if ( + penultimate_fragment + and result.any(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_PUNCTUATION) + and penultimate_fragment.is_disfluency + ): + result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) + + # Rate of speech + if len(words) > 1: + # Calculate the approximate words-per-minute (for last 5 words) + last_5_words = words[-5:] + wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) + + # Categorize the speaker + if wpm < 100: + result.add(AnnotationFlags.VERY_SLOW_SPEAKER) + elif wpm < 200: + result.add(AnnotationFlags.SLOW_SPEAKER) + elif wpm > 350: + result.add(AnnotationFlags.FAST_SPEAKER) + + # Return the annotation result + return result + + def compare_and_annotate(self, other: Optional["SpeakerFragmentView"]) -> AnnotationResult: + """Compare two SpeakerFragmentView objects and return the differences. + + Args: + other: The other SpeakerFragmentView object to compare to or None. + + Returns: + AnnotationResult: The annotation result. + """ + # Result + result = AnnotationResult() + + # If we have a previous view, compare it + if other and other.segment_count > 0: + # Compare full string + self_full_str: str = self.format_text() + other_full_str: str = other.format_text() + if self_full_str != other_full_str: + result.add(AnnotationFlags.UPDATED_FULL) + if self_full_str.lower() != other_full_str.lower(): + result.add(AnnotationFlags.UPDATED_FULL_LCASE) + + # Stripped string (without punctuation) + self_stripped_str: str = self.format_text(words_only=True) + other_stripped_str: str = other.format_text(words_only=True) + if self_stripped_str != other_stripped_str: + result.add(AnnotationFlags.UPDATED_STRIPPED) + if self_stripped_str.lower() != other_stripped_str.lower(): + result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) + + # Partials, finals and speakers + if self.final_count != other.final_count: + result.add(AnnotationFlags.UPDATED_FINALS) + if self.partial_count != other.partial_count: + result.add(AnnotationFlags.UPDATED_PARTIALS) + if self.segment_count != other.segment_count: + result.add(AnnotationFlags.UPDATED_SPEAKERS) + + # Assume this is new + elif self.segment_count > 0: + result.add(AnnotationFlags.NEW) + + # TODO - contents of LAST segment? + + # Return the result + self.annotation = result + return result + + +@dataclass +class SpeakerVADStatus: + """Emitted when a speaker starts or ends speaking. + + The speaker id is taken from the last word in the segment when + the event is emitted. + + Parameters: + is_active: Whether the speaker is active. + speaker_id: The ID of the speaker. + """ + + is_active: bool + speaker_id: Optional[str] = None diff --git a/tests/voice/test_setup.py b/tests/voice/test_setup.py new file mode 100644 index 00000000..4fc590d2 --- /dev/null +++ b/tests/voice/test_setup.py @@ -0,0 +1,2 @@ +def test_setup(): + assert True From 03165ad6349aaabbd83d5e1916c925e0316e285c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 22:02:28 +0100 Subject: [PATCH 002/215] missing type fix for rt and batch --- sdk/batch/pyproject.toml | 4 ++++ sdk/rt/pyproject.toml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/sdk/batch/pyproject.toml b/sdk/batch/pyproject.toml index 7c10a8f1..7d5f9ea7 100644 --- a/sdk/batch/pyproject.toml +++ b/sdk/batch/pyproject.toml @@ -50,3 +50,7 @@ version = { attr = "speechmatics.batch.__version__" } [tool.setuptools.packages.find] where = ["."] + +[[tool.mypy.overrides]] +module = ["aiofiles.*"] +ignore_missing_imports = true diff --git a/sdk/rt/pyproject.toml b/sdk/rt/pyproject.toml index eca9c4c9..ab707803 100644 --- a/sdk/rt/pyproject.toml +++ b/sdk/rt/pyproject.toml @@ -56,3 +56,7 @@ version = { attr = "speechmatics.rt.__version__" } [tool.setuptools.packages.find] where = ["."] + +[[tool.mypy.overrides]] +module = ["pyaudio.*"] +ignore_missing_imports = true From a353400cf654b176f7d5910b77c309ff88808722 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 22:08:31 +0100 Subject: [PATCH 003/215] Fix to README in voice examples --- examples/voice/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voice/README.md b/examples/voice/README.md index c95de58a..2d84090d 100644 --- a/examples/voice/README.md +++ b/examples/voice/README.md @@ -86,4 +86,4 @@ pip install pyaudio export SPEECHMATICS_API_KEY="your_api_key_here" ``` -For more information, see the main SDK documentation in [OVERVIEW.md](../OVERVIEW.md). +For more information, see the main SDK documentation in [OVERVIEW.md](../../OVERVIEW.md). From 39d8eb4aca551a21659dac59b45aaec5641d9d14 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 22:18:43 +0100 Subject: [PATCH 004/215] added missing github tests --- .github/workflows/release-voice.yaml | 90 ++++++++++++++++++++++++++++ .github/workflows/test.yaml | 20 +++++++ 2 files changed, 110 insertions(+) create mode 100644 .github/workflows/release-voice.yaml diff --git a/.github/workflows/release-voice.yaml b/.github/workflows/release-voice.yaml new file mode 100644 index 00000000..c80b36c4 --- /dev/null +++ b/.github/workflows/release-voice.yaml @@ -0,0 +1,90 @@ +name: Release Voice Agent SDK + +on: + push: + tags: + - "voice/v*" + +permissions: + contents: read + id-token: write + +jobs: + extract-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract.outputs.version }} + steps: + - name: Extract version from tag + id: extract + run: | + # Extract version from tag (voice/v1.0.0 -> 1.0.0) + VERSION=${GITHUB_REF#refs/tags/voice/v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + test-voice: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Test Voice Agent SDK + run: | + make install-dev + make lint-voice + make test-voice + + release-build: + runs-on: ubuntu-latest + needs: [extract-version, test-voice] + outputs: + version: ${{ needs.extract-version.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Update package version in sdk/voice/speechmatics/voice/__init__.py + run: | + VERSION="${{ needs.extract-version.outputs.version }}" + sed -i "s/0\.0\.0/$VERSION/g" ./sdk/voice/speechmatics/voice/__init__.py + echo "Updated version to: $VERSION" + cat ./sdk/voice/speechmatics/voice/__init__.py | grep __version__ + + - name: Build Voice Agent SDK + run: | + make install-dev + make build-voice + + - name: Upload dist + uses: actions/upload-artifact@v4 + with: + name: voice-release-dist + path: sdk/voice/dist/ + + pypi-publish: + runs-on: ubuntu-latest + needs: [release-build] + environment: + name: pypi-voice + url: https://pypi.org/project/speechmatics-voice/${{ needs.release-build.outputs.version }} + + steps: + - name: Retrieve release dist + uses: actions/download-artifact@v4 + with: + name: voice-release-dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + password: ${{ secrets.PYPI_ORG_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b0ca76c2..49d61fe7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -69,3 +69,23 @@ jobs: run: make test-flow - name: Build Flow SDK run: make build-flow + + test-voice: + name: Test Voice Agent SDK + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: make install-dev + - name: Lint Voice Agent SDK + run: make lint-voice + - name: Test Voice Agent SDK + run: make test-voice + - name: Build Voice Agent SDK + run: make build-voice From 1f8cd405d6c662828c9ca3958cfaa92794d468e4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 22:25:55 +0100 Subject: [PATCH 005/215] missing release info in `RELEASE.md` --- .github/RELEASE.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/.github/RELEASE.md b/.github/RELEASE.md index adccaec6..9b6ef51f 100644 --- a/.github/RELEASE.md +++ b/.github/RELEASE.md @@ -5,9 +5,11 @@ This document outlines the release process for the Speechmatics Python SDK packa ## Overview The Speechmatics Python SDK repository contains two separate packages: + - `speechmatics-rt` - Real-Time API Client - `speechmatics-batch` - Batch API Client - `speechmatics-flow` - Flow API Client +- `speechmatics-voice` - Voice Agent API Client Each package is released independently with its own versioning and release workflow. @@ -16,6 +18,7 @@ Each package is released independently with its own versioning and release workf Before creating a release, ensure the following steps are completed: ### Code Quality + - [ ] All tests pass locally (`make test-all`) - [ ] Linting passes (`make lint-all`) - [ ] Type checking passes (`make type-check-all`) @@ -23,10 +26,12 @@ Before creating a release, ensure the following steps are completed: - [ ] Documentation is up to date ### Version Management + - [ ] Review and update README files if needed - [ ] Verify dependencies are correct in `pyproject.toml` ### Testing + - [ ] Test examples with fresh installations - [ ] Verify environment variables work correctly - [ ] Test error handling scenarios @@ -39,6 +44,7 @@ Before creating a release, ensure the following steps are completed: To release a new version of the RT SDK: 1. **Create a Release Tag** + ```bash git tag rt/v1.0.0 git push origin rt/v1.0.0 @@ -46,6 +52,7 @@ To release a new version of the RT SDK: 2. **Automated Workflow** The `release-rt.yaml` workflow will automatically: + - Extract version from tag (e.g., `rt/v1.0.0` → `1.0.0`) - Run comprehensive tests across Python versions - Update version in `sdk/rt/speechmatics/rt/__init__.py` @@ -63,6 +70,7 @@ To release a new version of the RT SDK: To release a new version of the Batch SDK: 1. **Create a Release Tag** + ```bash git tag batch/v1.0.0 git push origin batch/v1.0.0 @@ -70,6 +78,7 @@ To release a new version of the Batch SDK: 2. **Automated Workflow** The `release-batch.yaml` workflow will automatically: + - Extract version from tag (e.g., `batch/v1.0.0` → `1.0.0`) - Run comprehensive tests across Python versions - Update version in `sdk/batch/speechmatics/batch/__init__.py` @@ -87,6 +96,7 @@ To release a new version of the Batch SDK: To release a new version of the Flow SDK: 1. **Create a Release Tag** + ```bash git tag flow/v1.0.0 git push origin flow/v1.0.0 @@ -94,6 +104,7 @@ To release a new version of the Flow SDK: 2. **Automated Workflow** The `release-flow.yaml` workflow will automatically: + - Extract version from tag (e.g., `flow/v1.0.0` → `1.0.0`) - Run comprehensive tests across Python versions - Update version in `sdk/flow/speechmatics/flow/__init__.py` @@ -106,36 +117,72 @@ To release a new version of the Flow SDK: - Update GitHub release notes - Announce the release +### 4. Voice Agent SDK Release + +To release a new version of the Voice Agent SDK: + +1. **Create a Release Tag** + + ```bash + git tag voice/v1.0.0 + git push origin voice/v1.0.0 + ``` + +2. **Automated Workflow** + The `release-voice.yaml` workflow will automatically: + + - Extract version from tag (e.g., `voice/v1.0.0` → `1.0.0`) + - Run comprehensive tests across Python versions + - Update version in `sdk/voice/speechmatics/voice/__init__.py` + - Build the package + - Publish to PyPI + +3. **Manual Steps After Release** + - Verify the package is available on PyPI + - Test installation: `pip install speechmatics-voice==1.0.0` + - Update GitHub release notes + - Announce the release + ## Version Management ### Version Format + Both packages follow semantic versioning (SemVer): + - `MAJOR.MINOR.PATCH` (e.g., `1.2.3`) - `MAJOR.MINOR.PATCH-beta.N` for beta releases (e.g., `1.2.3-beta.1`) ### Version Update Process + 1. **Development**: Versions remain as `0.0.0` in `__init__.py` files 2. **Release**: GitHub Actions automatically updates the version during release 3. **Post-Release**: The updated version remains in the repository ### Tag Naming Convention + - RT SDK: `rt/v{version}` (e.g., `rt/v1.0.0`) - Batch SDK: `batch/v{version}` (e.g., `batch/v1.0.0`) - Flow SDK: `flow/v{version}` (e.g., `flow/v1.0.0`) +- Voice Agent SDK: `voice/v{version}` (e.g., `voice/v1.0.0`) ## Environment Setup ### PyPI Configuration + Both packages are published to PyPI using GitHub Actions with OpenID Connect (OIDC): + - RT SDK: Uses `pypi-rt` environment - Batch SDK: Uses `pypi-batch` environment - Flow SDK: Uses `pypi-flow` environment +- Voice Agent SDK: Uses `pypi-voice` environment ### Required Secrets + No manual secrets are required as the workflows use OIDC for PyPI authentication. ## Testing Matrix Both packages are tested against: + - Python versions: 3.9, 3.10, 3.11, 3.12, 3.13 - Operating system: Ubuntu (latest) From 37c379ea9ec21458b5d59b5d3deeedee6fda1e00 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 22:57:47 +0100 Subject: [PATCH 006/215] fix missing utils package in voice examples --- examples/voice/file_transcription.py | 8 +- examples/voice/microphone_transcription.py | 14 +- examples/voice/utils.py | 222 +++++++++++++++++++++ 3 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 examples/voice/utils.py diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py index c6ea5e1f..941afeca 100644 --- a/examples/voice/file_transcription.py +++ b/examples/voice/file_transcription.py @@ -10,8 +10,8 @@ from utils import select_audio_output_device from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerSegment from speechmatics.voice import VoiceAgentClient @@ -166,12 +166,12 @@ async def _stream_audio_file( sample_width: int, ) -> None: """Stream audio file in real-time to client and player.""" - CHUNK_SIZE = 320 - chunk_duration = CHUNK_SIZE / sample_rate / sample_width + chunk_size = 320 + chunk_duration = chunk_size / sample_rate / sample_width with wave.open(str(file_path), "rb") as wav_file: while True: - audio_data = wav_file.readframes(CHUNK_SIZE) + audio_data = wav_file.readframes(chunk_size) if not audio_data: print("\nEnd of file reached") break diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index 6f195e96..57fba260 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -70,11 +70,7 @@ def _register_event_handlers(client: VoiceAgentClient, logger) -> None: def _format_segment(segment: SpeakerSegment) -> str: """Format speaker segment for display.""" - template = ( - "@{speaker_id}: {text}" - if segment.is_active - else "@{speaker_id} (background): {text}" - ) + template = "@{speaker_id}: {text}" if segment.is_active else "@{speaker_id} (background): {text}" return segment.format_text(template) @client.on(AgentServerMessageType.ADD_SEGMENTS) @@ -89,9 +85,7 @@ def handle_partial_segments(message): segments = [_format_segment(s) for s in message["segments"]] delay = message.get("delay_to_finalize") if delay is not None: - logger.log( - CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)" - ) + logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)") else: logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") @@ -127,9 +121,7 @@ def _setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: return mic -async def _stream_microphone( - mic: Microphone, client: VoiceAgentClient, chunk_size: int -) -> None: +async def _stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: """Stream microphone audio to client.""" while True: frame = await mic.read(chunk_size) diff --git a/examples/voice/utils.py b/examples/voice/utils.py new file mode 100644 index 00000000..03fc7c01 --- /dev/null +++ b/examples/voice/utils.py @@ -0,0 +1,222 @@ +import logging + +import pyaudio + +from speechmatics.rt import Microphone + + +def select_audio_device() -> int | None: + """Interactive microphone device selection. + + Returns: + Device index or None for default device + """ + devices = Microphone.list_devices() + if not devices: + return None + + print("Available microphones:") + for device in devices: + print(f" [{device['index']}] {device['name']} ({device['channels']} channels)") + print() + + return _get_device_choice( + [d["index"] for d in devices], + "Enter device index (or press Enter for default): ", + ) + + +def select_audio_output_device() -> int | None: + """Interactive audio output device selection. + + Returns: + Device index or None for default device + """ + try: + output_devices = _get_output_devices() + if not output_devices: + print("No audio output devices found.") + return None + + print("Available audio output devices:") + for device in output_devices: + print( + f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)" + ) + print() + + return _get_device_choice( + [d["index"] for d in output_devices], + "Enter output device index (or press Enter for default): ", + ) + + except Exception as e: + print(f"Error listing audio devices: {e}") + return None + + +def _get_output_devices() -> list[dict]: + """Get list of available output devices.""" + p = pyaudio.PyAudio() + try: + devices = [] + for i in range(p.get_device_count()): + info = p.get_device_info_by_index(i) + if info["maxOutputChannels"] > 0: + devices.append( + { + "index": i, + "name": info["name"], + "channels": info["maxOutputChannels"], + "sample_rate": int(info["defaultSampleRate"]), + } + ) + return devices + finally: + p.terminate() + + +def _get_device_choice(valid_indices: list[int], prompt: str) -> int | None: + """Get user device choice with validation.""" + while True: + try: + choice = input(prompt).strip() + if not choice: + return None + + device_index = int(choice) + if device_index in valid_indices: + return device_index + + print(f"Invalid device index. Choose from: {valid_indices}") + + except ValueError: + print("Please enter a valid number.") + except KeyboardInterrupt: + return None + + +class AudioPlayer: + """Real-time audio player using PyAudio.""" + + def __init__( + self, + sample_rate: int, + channels: int = 1, + sample_width: int = 2, + device_index: int | None = None, + ): + """Initialize audio player. + + Args: + sample_rate: Audio sample rate in Hz + channels: Number of audio channels (1 for mono) + sample_width: Sample width in bytes (2 for 16-bit) + device_index: Output device index (None for default) + """ + self.sample_rate = sample_rate + self.channels = channels + self.sample_width = sample_width + self.device_index = device_index + self.p = None + self.stream = None + + def start(self) -> bool: + """Start audio playback stream.""" + try: + self.p = pyaudio.PyAudio() + if not self.p: + return False + + audio_format = self._get_audio_format() + if not audio_format: + return False + + self.stream = self.p.open( + format=audio_format, + channels=self.channels, + rate=self.sample_rate, + output=True, + output_device_index=self.device_index, + ) + return True + + except Exception as e: + print(f"Error starting audio player: {e}") + return False + + def play(self, audio_data: bytes) -> None: + """Play audio data chunk.""" + if self.stream: + self.stream.write(audio_data) + + def stop(self) -> None: + """Stop and cleanup audio player.""" + if self.stream: + self.stream.stop_stream() + self.stream.close() + self.stream = None + + if self.p: + self.p.terminate() + self.p = None + + def _get_audio_format(self) -> int | None: + """Get PyAudio format from sample width.""" + format_map = {1: pyaudio.paInt8, 2: pyaudio.paInt16, 4: pyaudio.paInt32} + if self.sample_width not in format_map: + print(f"Unsupported sample width: {self.sample_width}") + return None + return format_map[self.sample_width] + + +class CustomLevels: + """Custom logging levels for transcription events.""" + + PARTIAL = 11 # Partial transcription results + FINAL = 12 # Final transcription results + SPEAKER = 15 # Speech activity events + + +class CustomTextFormatter(logging.Formatter): + """Coloured logging formatter for transcription events.""" + + # FORMAT = "%(asctime)s.%(msecs)03d %(levelname)-8s (%(filename)s:%(lineno)d) %(message)s" + FORMAT = "%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s" + + # ANSI colour codes + COLOURS = { + logging.DEBUG: "\033[90m", # Gray + CustomLevels.PARTIAL: "\033[32m", # Green + CustomLevels.FINAL: "\033[33m", # Yellow + CustomLevels.SPEAKER: "\033[36m", # Cyan + } + RESET = "\033[0m" + + def format(self, record): + colour = self.COLOURS.get(record.levelno, self.RESET) + message = super().format(record) + return f"{colour}{message}{self.RESET}" + + +def get_logger(name: str) -> logging.Logger: + """Setup coloured logger for transcription events.""" + logging.basicConfig(level=logging.INFO) + + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + logging.getLogger("speechmatics.voice").setLevel(logging.DEBUG) + + # Register custom level names + logging.addLevelName(CustomLevels.PARTIAL, "PARTIAL") + logging.addLevelName(CustomLevels.FINAL, "FINAL") + logging.addLevelName(CustomLevels.SPEAKER, "SPEAKER") + + # Apply custom formatter to all handlers + formatter = CustomTextFormatter( + CustomTextFormatter.FORMAT, datefmt="%Y-%m-%d %H:%M:%S" + ) + for handler in logging.root.handlers: + handler.setFormatter(formatter) + + return logger From f3cb7be9f54b0c2279be8112547ca58674b329d0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 3 Sep 2025 23:01:25 +0100 Subject: [PATCH 007/215] typo fix in README --- sdk/voice/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 506b148d..849e5441 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -4,7 +4,7 @@ An SDK for working with the Speechmatics Real-Time API optimised for use in voice agents or transcription services. -This uses the Python Real-Time API to process the transcription results from the STT engine and combine them into manageable segments of audio. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. +This uses the Python Real-Time API to process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. See [OVERVIEW.md](OVERVIEW.md) for more information. From f8bea005b32f6f2e56db22360a3265b9ca6d0bc8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 4 Sep 2025 00:20:10 +0100 Subject: [PATCH 008/215] support a max delay to emitting finals --- sdk/voice/speechmatics/voice/_client.py | 30 +++++++++++++++---------- sdk/voice/speechmatics/voice/_models.py | 8 ++++++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 09c03dbc..4f46258e 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -218,25 +218,25 @@ def _register_event_handlers(self) -> None: # Recognition started event @self.once(ServerMessageType.RECOGNITION_STARTED) - def _evt_on_recognition_started(message: dict[str, Any]): + def _evt_on_recognition_started(message: dict[str, Any]) -> None: self._start_time = datetime.datetime.now(datetime.timezone.utc) self._is_ready_for_audio = True # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) - def _evt_on_partial_transcript(message: dict[str, Any]): + def _evt_on_partial_transcript(message: dict[str, Any]) -> None: self._handle_transcript(message, is_final=False) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) - def _evt_on_final_transcript(message: dict[str, Any]): + def _evt_on_final_transcript(message: dict[str, Any]) -> None: self._handle_transcript(message, is_final=True) # End of utterance event if self._end_of_utterance_mode == EndOfUtteranceMode.FIXED: @self.on(ServerMessageType.END_OF_UTTERANCE) - def _evt_on_end_of_utterance(message: dict[str, Any]): + def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: self._logger.debug("End of utterance") self.finalize_segments() @@ -390,7 +390,7 @@ async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool self._processor_task.cancel() # Send transcription frames after delay - async def process_after_delay(delay: float): + async def process_after_delay(delay: float) -> None: await asyncio.sleep(delay) await self._process_speech_fragments() self._processor_task = None @@ -699,6 +699,12 @@ def _calc_delay_to_finalize( # TODO - Other checks / end of turn detection + # Constrain to `end_of_utterance_max_delay` + if emit_final_delay is not None: + emit_final_delay = min(emit_final_delay, self._config.end_of_utterance_max_delay) + else: + emit_final_delay = self._config.end_of_utterance_max_delay + # Emit segments should_emit = True @@ -713,7 +719,7 @@ def _calc_delay_to_finalize( # Return the result return should_emit, emit_final_delay - def finalize_segments(self, ttl: Optional[float] = None): + def finalize_segments(self, ttl: Optional[float] = None) -> None: """Finalize segments. This function will emit segments in the buffer without any further checks @@ -724,7 +730,7 @@ def finalize_segments(self, ttl: Optional[float] = None): ttl: Optional delay before finalizing partial segments (defaults to 0.01 seconds). """ - async def _emit(): + async def _emit() -> None: await asyncio.sleep(ttl or 0.01) await self._emit_final_segments() @@ -775,7 +781,7 @@ async def _emit_final_segments(self, delay: float = 0) -> None: if self._finals_emitter_task is not None: self._finals_emitter_task.cancel() - async def emit_finals_now(): + async def emit_finals_now() -> None: """Emit final segments.""" # Skip if no segments @@ -798,7 +804,7 @@ async def emit_finals_now(): # Only include segments up to the last active segment trimmed_view = SpeakerFragmentView( fragments=fragments_to_emit, - base_time=view.base_time, + base_time=self._start_time, focus_speakers=view.focus_speakers, annotate_segments=False, ) @@ -821,7 +827,7 @@ async def emit_finals_now(): # Update the current view self._current_view = SpeakerFragmentView( fragments=self._speech_fragments, - base_time=self._trim_before_time, + base_time=self._start_time, focus_speakers=self._dz_config.focus_speakers, annotate_segments=False, ) @@ -829,7 +835,7 @@ async def emit_finals_now(): # Reset previous fragments self._last_speech_fragments = self._speech_fragments.copy() - async def emit_finals_after_delay(_delay: float): + async def emit_finals_after_delay(_delay: float) -> None: """Yield before emitting final segments after a delay.""" # Wait for the delay (can be cancelled) @@ -851,7 +857,7 @@ async def emit_finals_after_delay(_delay: float): else: self._finals_emitter_task = asyncio.create_task(emit_finals_now()) - def _vad_evaluation(self, fragments: list[SpeechFragment]): + def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. This will emit `SPEECH_STARTED` and `SPEECH_ENDED` events to the client and is diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index a79a34b0..05ebf48d 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -122,6 +122,11 @@ class VoiceAgentConfig: word frames. The value must be lower than max_delay. Defaults to 0.5. + end_of_utterance_max_delay: Maximum delay in seconds for end of utterance delay. + The delay is used to wait for any further transcribed words before emitting the final + word frames. The value must be greater than end_of_utterance_silence_trigger. + Defaults to 10.0. + end_of_utterance_mode: End of utterance delay mode. When ADAPTIVE is used, the delay can be adjusted on the content of what the most recent speaker has said, such as rate of speech and whether they have any pauses or disfluencies. When FIXED is used, @@ -183,6 +188,7 @@ class VoiceAgentConfig: # Features max_delay: float = 1.0 end_of_utterance_silence_trigger: float = 0.5 + end_of_utterance_max_delay: float = 10.0 end_of_utterance_mode: EndOfUtteranceMode = EndOfUtteranceMode.FIXED additional_vocab: list[AdditionalVocabEntry] = field(default_factory=list) punctuation_overrides: Optional[dict] = None @@ -451,7 +457,7 @@ def end_time(self) -> float: """Return the end time of the segment.""" return self.fragments[-1].end_time - def __str__(self): + def __str__(self) -> str: """Return a string representation of the object.""" meta = { "speaker_id": self.speaker_id, From c417df0f5753ab5175e3455699ab5117e4777f81 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 9 Sep 2025 12:26:15 +0100 Subject: [PATCH 009/215] add RT as dependency --- sdk/voice/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index 2002efee..629e27ee 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" -dependencies = ["websockets>=10.0"] +dependencies = ["speechmatics-rt"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", From 9ea7199c05f939236ea9d779fa9245d4b3370f34 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 9 Sep 2025 12:33:50 +0100 Subject: [PATCH 010/215] add raw results to payload --- sdk/voice/speechmatics/voice/_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 4f46258e..a7403164 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -375,6 +375,10 @@ async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool is_final: Whether the data is final or partial. """ + # Debug + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + # Add the speech fragments fragments_available = await self._add_speech_fragments( message=message, @@ -482,7 +486,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = content=alt.get("content", ""), speaker=alt.get("speaker", None), confidence=alt.get("confidence", 1.0), - result=result, + result={"final": is_final, **result}, ) # Check fragment is after trim time From a658f7aa71317c9b5d02f26751e5ac638215533e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 10 Sep 2025 00:58:06 +0100 Subject: [PATCH 011/215] updated processing --- examples/voice/file_transcription.py | 2 +- examples/voice/microphone_transcription.py | 2 +- sdk/voice/speechmatics/voice/__init__.py | 2 +- sdk/voice/speechmatics/voice/_client.py | 266 ++++++--------------- sdk/voice/speechmatics/voice/_models.py | 28 ++- 5 files changed, 96 insertions(+), 204 deletions(-) diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py index 941afeca..0773349f 100644 --- a/examples/voice/file_transcription.py +++ b/examples/voice/file_transcription.py @@ -142,7 +142,7 @@ def handle_partial_segments(message): else: logger.log(CustomLevels.PARTIAL, f"⚡ {segments}") - @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + @client.on(AgentServerMessageType.SPEAKING_STARTED) def handle_speech_started(message): """Handle speech start events.""" logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index 57fba260..0f9d6a9c 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -89,7 +89,7 @@ def handle_partial_segments(message): else: logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") - @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + @client.on(AgentServerMessageType.SPEAKING_STARTED) def handle_speech_started(message): """Handle speech start events.""" logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 3b9b554a..36c0aed7 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.0" +__version__ = "0.1.2" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a7403164..230c4ea6 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -28,7 +28,7 @@ from ._models import DiarizationFocusMode from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode -from ._models import SpeakerFragmentView +from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment from ._models import VoiceAgentConfig @@ -97,7 +97,7 @@ def __init__( self._speech_fragments: list[SpeechFragment] = [] self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() self._last_speech_fragments: list[SpeechFragment] = [] - self._current_view: Optional[SpeakerFragmentView] = None + self._current_view: Optional[SpeakerSegmentView] = None # Speaking states self._is_speaking: bool = False @@ -237,8 +237,9 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - self._logger.debug("End of utterance") - self.finalize_segments() + # self._logger.debug("End of utterance") + # self.finalize_segments() + pass async def connect(self) -> None: """Connect to the Speechmatics API. @@ -519,19 +520,9 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = self._vad_evaluation(fragments) # Fragments to retain - # - Outside of the bounds of the payload - # - Not a trailing fragment with same start time as payload end time (end of sentence etc.) - # retained_fragments = [ - # frag - # for frag in self._speech_fragments - # if not ( - # frag.start_time >= payload_start_time - # and frag.end_time <= payload_end_time - # and frag.is_final is False - # # and frag.start_time != payload_end_time - # ) - # ] - retained_fragments = [frag for frag in self._speech_fragments if frag.is_final] + retained_fragments = [ + frag for frag in self._speech_fragments if frag.is_final and frag.start_time >= self._trim_before_time + ] # Re-structure the speech fragments self._speech_fragments = retained_fragments.copy() @@ -566,70 +557,38 @@ async def _process_speech_fragments(self) -> None: so the next comparison is based on the remaining + new fragments. """ async with self._speech_fragments_lock: - # Current transcription - last_complete_view = SpeakerFragmentView( - fragments=self._last_speech_fragments.copy(), - base_time=self._start_time, - focus_speakers=self._dz_config.focus_speakers, - annotate_segments=False, - ) - last_active_view = SpeakerFragmentView( - fragments=[f for s in last_complete_view.segments if s.is_active for f in s.fragments], - base_time=self._start_time, - focus_speakers=self._dz_config.focus_speakers, - annotate_segments=False, - ) - - # New transcript - current_complete_view = SpeakerFragmentView( - fragments=self._speech_fragments.copy(), - base_time=self._start_time, - focus_speakers=self._dz_config.focus_speakers, - ) - current_active_view = SpeakerFragmentView( - fragments=[f for s in current_complete_view.segments if s.is_active for f in s.fragments], - base_time=self._start_time, - focus_speakers=self._dz_config.focus_speakers, - ) + # Create a view of the current segments + self._update_current_view() - # If no segments, return - if current_complete_view.segment_count == 0: + # Check we have a valid view + if not self._current_view: return - # Compare this against the previous transcript - complete_annotation_result = current_complete_view.compare_and_annotate(last_complete_view) - active_annotation_result = current_active_view.compare_and_annotate(last_active_view) - - # TODO - Consider a config option to split segments into smaller chunks for long transcripts? - - # Determine if there has been a change and delay before finalizing - active_view_changed, delay_to_finalize = self._calc_delay_to_finalize( - view=current_complete_view, view_changes=active_annotation_result - ) + # Check we have at least one segment + if self._current_view.segment_count == 0 or self._current_view.last_active_segment_index == -1: + return - # Emit partial segments - if complete_annotation_result.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL_LCASE): - asyncio.create_task(self._emit_partial_segments(current_complete_view, delay_to_finalize)) + # Create a view of segments to emit + last_segment = self._current_view.segments[self._current_view.last_active_segment_index] + self._current_view.trim(start_time=self._current_view.start_time, end_time=last_segment.end_time) - # Emit segments - if active_view_changed: - # Update the active view - self._current_view = current_complete_view + # Emit the segments + asyncio.create_task(self._emit_segments()) - # Stop any existing emitter task - if self._finals_emitter_task is not None: - self._finals_emitter_task.cancel() + # TODO: process in-built end of utterance - e.g. `_calc_delay_to_end_of_turn`? + # TODO: process in-built VAD / end of speech - # Emit final segments - if delay_to_finalize is not None: - asyncio.create_task(self._emit_final_segments(delay=delay_to_finalize)) - - # Copy the data - self._last_speech_fragments = self._speech_fragments.copy() + def _update_current_view(self) -> None: + """Load the current view of the speech fragments.""" + self._current_view = SpeakerSegmentView( + fragments=self._speech_fragments.copy(), + base_time=self._start_time, + focus_speakers=self._dz_config.focus_speakers, + ) - def _calc_delay_to_finalize( + def _calc_delay_to_end_of_turn( self, - view: SpeakerFragmentView, + view: SpeakerSegmentView, view_changes: AnnotationResult, ) -> tuple[bool, float | None]: """Calculate the delay before finalizing segments. @@ -679,6 +638,9 @@ def _calc_delay_to_finalize( - ends with a disfluency """ + # Minimum delay + delay = max(self._end_of_utterance_delay, 0.5) + # Delay multiplier multiplier = 1.5 @@ -699,7 +661,7 @@ def _calc_delay_to_finalize( multiplier *= 4.0 # Calculate the delay - emit_final_delay = self._end_of_utterance_delay * multiplier + emit_final_delay = delay * multiplier # TODO - Other checks / end of turn detection @@ -734,132 +696,62 @@ def finalize_segments(self, ttl: Optional[float] = None) -> None: ttl: Optional delay before finalizing partial segments (defaults to 0.01 seconds). """ - async def _emit() -> None: - await asyncio.sleep(ttl or 0.01) - await self._emit_final_segments() + async def emit() -> None: + if ttl is not None: + await asyncio.sleep(ttl) + await self._emit_segments(finalize=True) - asyncio.create_task(_emit()) + asyncio.create_task(emit()) - async def _emit_partial_segments( - self, view: SpeakerFragmentView, delay_to_finalize: Optional[float] = None - ) -> None: - """Emit partial segments to listeners. + async def _emit_segments(self, finalize: bool = False) -> None: + """Emit segments to listeners. This function will emit segments in the view without any further checks - on the contents of the segments. - - Args: - view: The speaker fragment view to emit. - delay_to_finalize: Optional delay before finalizing the segments. - """ - # Skip if no segments - if view.segment_count == 0: - return - - # Emit partial segments - self.emit( - AgentServerMessageType.ADD_INTERIM_SEGMENTS, - {"segments": view.segments, "delay_to_finalize": delay_to_finalize}, - ) - - async def _emit_final_segments(self, delay: float = 0) -> None: - """Emit final segments to listeners. - - At the point of emitting the result, this will use the most current view, - unlike emit_partial_segments which uses the view that was passed in. + on the contents of the segments. Any segments that end with a final / EOS + will be emitted as finals and removed from the fragment buffer. Args: - delay: Delay before finalizing partial segments. + finalize: Whether to finalize all segments. """ - # Check delay within bounds - if delay < 0: - raise ValueError("Delay must be non-negative") - elif delay > 30: - raise ValueError("Delay must be less than 30 seconds") - # Skip if no segments - if self._current_view is None or self._current_view.segment_count == 0: - return - - # Stop any existing emitter task - if self._finals_emitter_task is not None: - self._finals_emitter_task.cancel() - - async def emit_finals_now() -> None: - """Emit final segments.""" - - # Skip if no segments - if self._current_view is None or self._current_view.segment_count == 0: + # Lock the speech fragments + async with self._speech_fragments_lock: + # Skip if no view + if not self._current_view or self._current_view.segment_count == 0: return - # Get the active view - view = self._current_view - - # Find out if we have segments to emit - segments_to_emit = view.last_active_segment_index + 1 - - # Only finalize if there are valid segments - if segments_to_emit > 0: - # Accumulate all of the fragments for segments up to and including the last_active_index - fragments_to_emit = [] - for i in range(segments_to_emit): - fragments_to_emit.extend(view.segments[i].fragments) - - # Only include segments up to the last active segment - trimmed_view = SpeakerFragmentView( - fragments=fragments_to_emit, - base_time=self._start_time, - focus_speakers=view.focus_speakers, - annotate_segments=False, - ) + # Force finalize + if finalize: + final_segments = self._current_view.segments + interim_segments = [] - # Emit the segments + # Split between finals and interim segments + else: + final_segments = [ + s + for s in self._current_view.segments + if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + ] + interim_segments = [s for s in self._current_view.segments if s not in final_segments] + + # Emit finals first + if final_segments: self.emit( AgentServerMessageType.ADD_SEGMENTS, - {"segments": trimmed_view.segments}, + {"segments": final_segments}, ) + self._trim_before_time = final_segments[-1].end_time + self._speech_fragments = [f for f in self._speech_fragments if f.start_time >= self._trim_before_time] - # Last end time - self._trim_before_time = trimmed_view.end_time - - # Filter segments after the trim time - async with self._speech_fragments_lock: - self._speech_fragments = [ - fragment for fragment in self._speech_fragments if fragment.start_time >= self._trim_before_time - ] - - # Update the current view - self._current_view = SpeakerFragmentView( - fragments=self._speech_fragments, - base_time=self._start_time, - focus_speakers=self._dz_config.focus_speakers, - annotate_segments=False, + # Emit interim segments + if interim_segments: + self.emit( + AgentServerMessageType.ADD_INTERIM_SEGMENTS, + {"segments": interim_segments}, ) - # Reset previous fragments - self._last_speech_fragments = self._speech_fragments.copy() - - async def emit_finals_after_delay(_delay: float) -> None: - """Yield before emitting final segments after a delay.""" - - # Wait for the delay (can be cancelled) - if _delay > 0: - await asyncio.sleep(_delay) - - # Emit the segments - await emit_finals_now() - - # Reset the task - self._finals_emitter_task = None - - if DEBUG_MORE: - self._logger.debug(f"Will emit final segments after {delay} seconds") - - # Emit with delay or immediately - if delay > 0: - self._finals_emitter_task = asyncio.create_task(emit_finals_after_delay(delay)) - else: - self._finals_emitter_task = asyncio.create_task(emit_finals_now()) + # Update the current view + self._update_current_view() def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. @@ -905,11 +797,11 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Check if speaker is different to the current speaker if already_speaking and speaker_changed: self.emit( - AgentServerMessageType.USER_SPEECH_ENDED, + AgentServerMessageType.SPEAKING_ENDED, {"status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False)}, ) self.emit( - AgentServerMessageType.USER_SPEECH_STARTED, + AgentServerMessageType.SPEAKING_STARTED, {"status": SpeakerVADStatus(speaker_id=speaker, is_active=True)}, ) @@ -925,11 +817,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Emit the event for latest speaker self.emit( - ( - AgentServerMessageType.USER_SPEECH_STARTED - if self._is_speaking - else AgentServerMessageType.USER_SPEECH_ENDED - ), + (AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED), {"status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 05ebf48d..33e980af 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -15,8 +15,6 @@ from speechmatics.rt import AudioEncoding from speechmatics.rt import OperatingPoint -__version__ = "0.1.0" - class EndOfUtteranceMode(str, Enum): """End of turn delay options for transcription.""" @@ -243,9 +241,9 @@ class AgentServerMessageType(str, Enum): Error: Error message. AddPartialTranscript: Partial transcript has been added. AddTranscript: Transcript has been added. - UserSpeechStarted: Speech has started. - UserSpeechEnded: Speech has ended. - TurnDetected: A turn has been detected. + SpeakingStarted: Speech has started. + SpeakingEnded: Speech has ended. + EndOfTurn: A turn has been detected (context-based prediction). AddSegments: A final segment has been detected. AddInterimSegments: An interim segment has been detected. SpeakersResult: Speakers result has been detected. @@ -281,13 +279,13 @@ class AgentServerMessageType(str, Enum): ADD_TRANSCRIPT = "AddTranscript" # VAD messages - USER_SPEECH_STARTED = "UserSpeechStarted" - USER_SPEECH_ENDED = "UserSpeechEnded" + SPEAKING_STARTED = "SpeakingStarted" + SPEAKING_ENDED = "SpeakingEnded" + END_OF_TURN = "EndOfTurn" # Turn / segment messages ADD_SEGMENTS = "AddSegments" ADD_INTERIM_SEGMENTS = "AddInterimSegments" - TURN_DETECTED = "TurnDetected" # Speaker messages SPEAKERS_RESULT = "SpeakersResult" @@ -514,7 +512,7 @@ def format_text(self, format: Optional[str] = None, words_only: bool = False) -> @dataclass -class SpeakerFragmentView: +class SpeakerSegmentView: """View for speaker fragments. Parameters: @@ -576,6 +574,12 @@ def format_text( ) -> str: return separator.join(segment.format_text(format, words_only) for segment in self.segments) + def trim(self, start_time: float, end_time: float, annotate_segments: bool = True) -> None: + self.fragments = [ + frag for frag in self.fragments if frag.start_time >= start_time and frag.end_time <= end_time + ] + self.segments = self._get_segments_from_fragments(annotate_segments=annotate_segments) + def _get_segments_from_fragments(self, annotate_segments: bool = True) -> list[SpeakerSegment]: # Speaker groups current_speaker: Optional[str] = None @@ -734,11 +738,11 @@ def _annotate_segment(self, segment: SpeakerSegment) -> AnnotationResult: # Return the annotation result return result - def compare_and_annotate(self, other: Optional["SpeakerFragmentView"]) -> AnnotationResult: - """Compare two SpeakerFragmentView objects and return the differences. + def compare_and_annotate(self, other: Optional["SpeakerSegmentView"]) -> AnnotationResult: + """Compare two SpeakerSegmentView objects and return the differences. Args: - other: The other SpeakerFragmentView object to compare to or None. + other: The other SpeakerSegmentView object to compare to or None. Returns: AnnotationResult: The annotation result. From 1d44298910dd229c6899d364512af04108a84dfa Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 10 Sep 2025 14:23:54 +0100 Subject: [PATCH 012/215] move static methods to FragmentUtils --- sdk/voice/speechmatics/voice/_client.py | 11 +- sdk/voice/speechmatics/voice/_models.py | 155 ++++++++++++++---------- 2 files changed, 101 insertions(+), 65 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 230c4ea6..d5f2b7ac 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -203,7 +203,6 @@ def _process_config( audio_format = AudioFormat( encoding=config.audio_encoding, sample_rate=config.sample_rate, - chunk_size=config.chunk_size, ) # Return the config objects @@ -237,9 +236,9 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - # self._logger.debug("End of utterance") + self._logger.debug("End of utterance") # self.finalize_segments() - pass + # pass async def connect(self) -> None: """Connect to the Speechmatics API. @@ -529,6 +528,8 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = self._speech_fragments.extend(fragments) self._speech_fragments.sort(key=lambda x: x.start_time) + # TODO: add in index to sort by + # Debug the fragments if DEBUG_MORE: debug_payload = { @@ -560,7 +561,7 @@ async def _process_speech_fragments(self) -> None: # Create a view of the current segments self._update_current_view() - # Check we have a valid view + # Check view exists if not self._current_view: return @@ -570,6 +571,8 @@ async def _process_speech_fragments(self) -> None: # Create a view of segments to emit last_segment = self._current_view.segments[self._current_view.last_active_segment_index] + + # Trim the view self._current_view.trim(start_time=self._current_view.start_time, end_time=last_segment.end_time) # Emit the segments diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 33e980af..4119f15f 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -113,12 +113,12 @@ class VoiceAgentConfig: max_delay: Maximum delay in seconds for transcription. This forces the STT engine to speed up the processing of transcribed words and reduces the interval between partial - and final results. Lower values can have an impact on accuracy. Defaults to 1.0. + and final results. Lower values can have an impact on accuracy. Defaults to 0.7. end_of_utterance_silence_trigger: Maximum delay in seconds for end of utterance trigger. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be lower than max_delay. - Defaults to 0.5. + Defaults to 0.2. end_of_utterance_max_delay: Maximum delay in seconds for end of utterance delay. The delay is used to wait for any further transcribed words before emitting the final @@ -173,7 +173,6 @@ class VoiceAgentConfig: Defaults to []. sample_rate: Audio sample rate for streaming. Defaults to 16000. - chunk_size: Audio chunk size for streaming. Defaults to 160. audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. """ @@ -184,8 +183,8 @@ class VoiceAgentConfig: output_locale: Optional[str] = None # Features - max_delay: float = 1.0 - end_of_utterance_silence_trigger: float = 0.5 + max_delay: float = 0.7 + end_of_utterance_silence_trigger: float = 0.2 end_of_utterance_max_delay: float = 10.0 end_of_utterance_mode: EndOfUtteranceMode = EndOfUtteranceMode.FIXED additional_vocab: list[AdditionalVocabEntry] = field(default_factory=list) @@ -201,7 +200,6 @@ class VoiceAgentConfig: # Audio sample_rate: int = 16000 - chunk_size: int = 160 audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE @@ -534,7 +532,11 @@ def __init__( self.fragments = fragments self.base_time = base_time self.focus_speakers = focus_speakers - self.segments = self._get_segments_from_fragments(annotate_segments=annotate_segments) + + # Process fragments into a list of segments + self.segments = FragmentUtils.segment_list_from_fragments( + base_time, fragments, focus_speakers, annotate_segments + ) @property def start_time(self) -> float: @@ -578,15 +580,54 @@ def trim(self, start_time: float, end_time: float, annotate_segments: bool = Tru self.fragments = [ frag for frag in self.fragments if frag.start_time >= start_time and frag.end_time <= end_time ] - self.segments = self._get_segments_from_fragments(annotate_segments=annotate_segments) + self.segments = FragmentUtils.segment_list_from_fragments( + self.base_time, self.fragments, focus_speakers=self.focus_speakers, annotate_segments=annotate_segments + ) + + +@dataclass +class SpeakerVADStatus: + """Emitted when a speaker starts or ends speaking. + + The speaker id is taken from the last word in the segment when + the event is emitted. + + Parameters: + is_active: Whether the speaker is active. + speaker_id: The ID of the speaker. + """ + + is_active: bool + speaker_id: Optional[str] = None + + +class FragmentUtils: + """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" + + @staticmethod + def segment_list_from_fragments( + base_time: datetime.datetime, + fragments: list[SpeechFragment], + focus_speakers: Optional[list[str]] = None, + annotate_segments: bool = True, + ) -> list[SpeakerSegment]: + """Create SpeakerSegment objects from a list of SpeechFragment objects. + + Args: + base_time: Base time for the fragments. + fragments: List of SpeechFragment objects. + focus_speakers: List of speakers to focus on or None. + annotate_segments: Whether to annotate segments. - def _get_segments_from_fragments(self, annotate_segments: bool = True) -> list[SpeakerSegment]: + Returns: + List of SpeakerSegment objects. + """ # Speaker groups current_speaker: Optional[str] = None speaker_groups: list[list[SpeechFragment]] = [[]] # Group by speakers - for frag in self.fragments: + for frag in fragments: if frag.speaker != current_speaker: current_speaker = frag.speaker if speaker_groups[-1]: @@ -596,16 +637,20 @@ def _get_segments_from_fragments(self, annotate_segments: bool = True) -> list[S # Create SpeakerFragments objects segments: list[SpeakerSegment] = [] for group in speaker_groups: - sd = self._get_speaker_segment_from_fragment_group(group, annotate=annotate_segments) + sd = FragmentUtils.segment_from_fragments( + base_time, group, focus_speakers=focus_speakers, annotate=annotate_segments + ) if sd: segments.append(sd) # Return the grouped SpeakerFragments objects return segments - def _get_speaker_segment_from_fragment_group( - self, - group: list[SpeechFragment], + @staticmethod + def segment_from_fragments( + base_time: datetime.datetime, + fragments: list[SpeechFragment], + focus_speakers: Optional[list[str]] = None, annotate: bool = True, ) -> Optional[SpeakerSegment]: """Take a group of fragments and piece together into SpeakerSegment. @@ -618,52 +663,55 @@ def _get_speaker_segment_from_fragment_group( be removed. Args: - group: List of SpeechFragment objects. + base_time: The base time for the segment. + fragments: List of SpeechFragment objects. + focus_speakers: List of speakers to focus on. annotate: Whether to annotate the segment. Returns: SpeakerSegment: The object for the group. """ # Check for starting fragments that are attached to previous - if group and group[0].attaches_to == "previous": - group = group[1:] + if fragments and fragments[0].attaches_to == "previous": + fragments = fragments[1:] # Check for trailing fragments that are attached to next - if group and group[-1].attaches_to == "next": - group = group[:-1] + if fragments and fragments[-1].attaches_to == "next": + fragments = fragments[:-1] # Check there are results - if not group: + if not fragments: return None # Get the timing extremes - start_time = min(frag.start_time for frag in group) + start_time = min(frag.start_time for frag in fragments) # Timestamp - ts = (self.base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") + ts = (base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") # Determine if the speaker is considered active is_active = True - if self.focus_speakers: - is_active = group[0].speaker in self.focus_speakers + if focus_speakers: + is_active = fragments[0].speaker in focus_speakers # New SpeakerSegment segment = SpeakerSegment( - speaker_id=group[0].speaker, + speaker_id=fragments[0].speaker, timestamp=ts, - language=group[0].language, - fragments=group, + language=fragments[0].language, + fragments=fragments, is_active=is_active, ) # Annotate if annotate: - segment.annotation = self._annotate_segment(segment) + segment.annotation = FragmentUtils._annotate_segment(segment) # Return the SpeakerSegment object return segment - def _annotate_segment(self, segment: SpeakerSegment) -> AnnotationResult: + @staticmethod + def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: """Annotate the segment. This will annotate the segment with any additional information. @@ -738,11 +786,13 @@ def _annotate_segment(self, segment: SpeakerSegment) -> AnnotationResult: # Return the annotation result return result - def compare_and_annotate(self, other: Optional["SpeakerSegmentView"]) -> AnnotationResult: + @staticmethod + def compare_views(view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView]) -> AnnotationResult: """Compare two SpeakerSegmentView objects and return the differences. Args: - other: The other SpeakerSegmentView object to compare to or None. + view1: The first SpeakerSegmentView object to compare. + view2: The second SpeakerSegmentView object to compare to or None. Returns: AnnotationResult: The annotation result. @@ -751,53 +801,36 @@ def compare_and_annotate(self, other: Optional["SpeakerSegmentView"]) -> Annotat result = AnnotationResult() # If we have a previous view, compare it - if other and other.segment_count > 0: + if view2 and view2.segment_count > 0: # Compare full string - self_full_str: str = self.format_text() - other_full_str: str = other.format_text() - if self_full_str != other_full_str: + view1_full_str: str = view1.format_text() + view2_full_str: str = view2.format_text() + if view1_full_str != view2_full_str: result.add(AnnotationFlags.UPDATED_FULL) - if self_full_str.lower() != other_full_str.lower(): + if view1_full_str.lower() != view2_full_str.lower(): result.add(AnnotationFlags.UPDATED_FULL_LCASE) # Stripped string (without punctuation) - self_stripped_str: str = self.format_text(words_only=True) - other_stripped_str: str = other.format_text(words_only=True) - if self_stripped_str != other_stripped_str: + view1_stripped_str: str = view1.format_text(words_only=True) + view2_stripped_str: str = view2.format_text(words_only=True) + if view1_stripped_str != view2_stripped_str: result.add(AnnotationFlags.UPDATED_STRIPPED) - if self_stripped_str.lower() != other_stripped_str.lower(): + if view1_stripped_str.lower() != view2_stripped_str.lower(): result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) # Partials, finals and speakers - if self.final_count != other.final_count: + if view1.final_count != view2.final_count: result.add(AnnotationFlags.UPDATED_FINALS) - if self.partial_count != other.partial_count: + if view1.partial_count != view2.partial_count: result.add(AnnotationFlags.UPDATED_PARTIALS) - if self.segment_count != other.segment_count: + if view1.segment_count != view2.segment_count: result.add(AnnotationFlags.UPDATED_SPEAKERS) # Assume this is new - elif self.segment_count > 0: + elif view1.segment_count > 0: result.add(AnnotationFlags.NEW) # TODO - contents of LAST segment? # Return the result - self.annotation = result return result - - -@dataclass -class SpeakerVADStatus: - """Emitted when a speaker starts or ends speaking. - - The speaker id is taken from the last word in the segment when - the event is emitted. - - Parameters: - is_active: Whether the speaker is active. - speaker_id: The ID of the speaker. - """ - - is_active: bool - speaker_id: Optional[str] = None From 2b3e13a7fa4107d19755caed74f1ba9c27be12d6 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 10 Sep 2025 14:24:11 +0100 Subject: [PATCH 013/215] add idx to fragments --- sdk/voice/speechmatics/voice/_client.py | 11 +++++++---- sdk/voice/speechmatics/voice/_models.py | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index d5f2b7ac..a4cde7a6 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -94,9 +94,9 @@ def __init__( self._trim_before_time: float = 0 # Current utterance speech data + self._fragment_idx: int = 0 self._speech_fragments: list[SpeechFragment] = [] self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() - self._last_speech_fragments: list[SpeechFragment] = [] self._current_view: Optional[SpeakerSegmentView] = None # Speaking states @@ -474,6 +474,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = if alt.get("content", None): # Create the new fragment fragment = SpeechFragment( + idx=self._frag_idx(), start_time=result.get("start_time", 0), end_time=result.get("end_time", 0), language=alt.get("language", "en"), @@ -526,9 +527,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Re-structure the speech fragments self._speech_fragments = retained_fragments.copy() self._speech_fragments.extend(fragments) - self._speech_fragments.sort(key=lambda x: x.start_time) - - # TODO: add in index to sort by + self._speech_fragments.sort(key=lambda x: x.idx) # Debug the fragments if DEBUG_MORE: @@ -828,6 +827,10 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: if not self._is_speaking: self._current_speaker = None + def _frag_idx(self): + self._fragment_idx += 1 + return self._fragment_idx + def _get_endpoint_url(self, url: str, app: Optional[str] = None) -> str: """Format the endpoint URL with the SDK and app versions. diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 4119f15f..1fb15f05 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -391,6 +391,7 @@ class SpeechFragment: objects are accumulated and then used to form SpeechSegments objects. Parameters: + idx: Index of the fragment in the list (used for sorting). start_time: Start time of the fragment in seconds (from session start). end_time: End time of the fragment in seconds (from session start). language: Language of the fragment. Defaults to `en`. @@ -407,6 +408,7 @@ class SpeechFragment: annotation: Annotation for the fragment. """ + idx: int start_time: float end_time: float language: str = "en" From 67c5493d54b701b03b1710d492a7f153a5ec346c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 10 Sep 2025 14:43:04 +0100 Subject: [PATCH 014/215] support for `Finalize` message + updated docs --- sdk/voice/OVERVIEW.md | 12 ++++++++---- sdk/voice/README.md | 10 +++++++--- sdk/voice/speechmatics/voice/_client.py | 12 +++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md index 4ad6cc7f..18be6058 100644 --- a/sdk/voice/OVERVIEW.md +++ b/sdk/voice/OVERVIEW.md @@ -34,9 +34,8 @@ from speechmatics.voice import ( async def main(): # Configure the voice agent config = VoiceAgentConfig( - end_of_utterance_silence_trigger=0.5, enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE ) # Initialize microphone @@ -67,15 +66,20 @@ async def main(): print(f"Speaker {segment.speaker_id}: {segment.text}") # Handle user started speaking event - @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + @client.on(AgentServerMessageType.SPEAKING_STARTED) def handle_speech_started(message): print("User started speaking") # Handle user stopped speaking event - @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + @client.on(AgentServerMessageType.SPEAKING_ENDED) def handle_speech_ended(message): print("User stopped speaking") + # End of turn / utterance(s) + @client.on(AgentServerMessageType.END_OF_TURN) + def handle_end_of_turn(message): + print("End of turn") + # Connect and start processing audio await client.connect() diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 849e5441..35ea8b37 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -38,7 +38,6 @@ from speechmatics.voice import ( async def main(): # Configure the voice agent config = VoiceAgentConfig( - end_of_utterance_silence_trigger=0.5, enable_diarization=True, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, ) @@ -71,15 +70,20 @@ async def main(): print(f"Speaker {segment.speaker_id}: {segment.text}") # Handle user started speaking event - @client.on(AgentServerMessageType.USER_SPEECH_STARTED) + @client.on(AgentServerMessageType.SPEAKING_STARTED) def handle_speech_started(message): print("User started speaking") # Handle user stopped speaking event - @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + @client.on(AgentServerMessageType.SPEAKING_ENDED) def handle_speech_ended(message): print("User stopped speaking") + # End of turn / utterance(s) + @client.on(AgentServerMessageType.END_OF_TURN) + def handle_end_of_turn(message): + print("End of turn") + # Connect and start processing audio await client.connect() diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a4cde7a6..f5789a9d 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -34,6 +34,8 @@ from ._models import VoiceAgentConfig DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] +PREVIEW_FEATURES = os.getenv("SPEECHMATICS_PREVIEW_FEATURES", "0").lower() in ["1", "true"] + if DEBUG_MORE: import json @@ -110,8 +112,9 @@ def __init__( self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config - # EndOfUtterance fallback timer + # Timers for EndOfUtterance and EndOfTurn self._finalize_task: Optional[asyncio.Task] = None + self._eot_task: Optional[asyncio.Task] = None # Segment processor self._processor_wait_time: float = 0.005 @@ -699,9 +702,12 @@ def finalize_segments(self, ttl: Optional[float] = None) -> None: """ async def emit() -> None: - if ttl is not None: + if ttl is not None and ttl > 0: await asyncio.sleep(ttl) - await self._emit_segments(finalize=True) + if PREVIEW_FEATURES: + await self.send_message({"message": "Finalize"}) + else: + await self._emit_segments(finalize=True) asyncio.create_task(emit()) From 0d2a8f8ae3eaf985a630a02f65a1e72fc498ccfd Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 10 Sep 2025 15:11:21 +0100 Subject: [PATCH 015/215] fix to example --- sdk/voice/OVERVIEW.md | 2 +- sdk/voice/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md index 18be6058..193fd894 100644 --- a/sdk/voice/OVERVIEW.md +++ b/sdk/voice/OVERVIEW.md @@ -35,7 +35,7 @@ async def main(): # Configure the voice agent config = VoiceAgentConfig( enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + end_of_utterance_mode=EndOfUtteranceMode.FIXED ) # Initialize microphone diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 35ea8b37..fc16a56b 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -39,7 +39,7 @@ async def main(): # Configure the voice agent config = VoiceAgentConfig( enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, ) # Initialize microphone From 4cf3036afbf85d110141fb9a5d05b57b41612e23 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 11 Sep 2025 23:41:14 +0100 Subject: [PATCH 016/215] support for some tests of the STT and voice SDK --- Makefile | 15 ++- sdk/voice/speechmatics/voice/_models.py | 7 ++ tests/voice/_utils.py | 107 +++++++++++++++++ tests/voice/assets/audio_01.wav | 3 + tests/voice/assets/chat_01.jsonl | 0 tests/voice/test_01_client.py | 29 +++++ tests/voice/test_02_messages.py | 70 +++++++++++ tests/voice/test_03_transcribe.py | 150 ++++++++++++++++++++++++ tests/voice/test_setup.py | 2 - 9 files changed, 375 insertions(+), 8 deletions(-) create mode 100644 tests/voice/_utils.py create mode 100644 tests/voice/assets/audio_01.wav create mode 100644 tests/voice/assets/chat_01.jsonl create mode 100644 tests/voice/test_01_client.py create mode 100644 tests/voice/test_02_messages.py create mode 100644 tests/voice/test_03_transcribe.py delete mode 100644 tests/voice/test_setup.py diff --git a/Makefile b/Makefile index 7bb6694b..e76f2e7c 100644 --- a/Makefile +++ b/Makefile @@ -58,19 +58,19 @@ help: test-all: test-rt test-batch test-flow test-voice test-rt: - pytest tests/rt/ -v + pytest tests/rt/ -v -s test-batch: - pytest tests/batch/ -v + pytest tests/batch/ -v -s test-flow: - pytest tests/flow/ -v + pytest tests/flow/ -v -s test-voice: - pytest tests/voice/ -v + pytest tests/voice/ -v -s # Formatting targets -format-all: format-rt format-batch format-flow format-voice +format-all: format-rt format-batch format-flow format-voice format-tests format-rt: cd sdk/rt/speechmatics && black . @@ -88,6 +88,10 @@ format-voice: cd sdk/voice/speechmatics && black . cd sdk/voice/speechmatics && ruff check --fix . +format-tests: + cd tests && black . + cd tests && ruff check --fix . + # Linting targets lint-all: lint-rt lint-batch lint-flow lint-voice @@ -162,4 +166,3 @@ clean-flow: clean-voice: rm -rf sdk/voice/dist sdk/voice/build sdk/voice/*.egg-info find sdk/voice -name __pycache__ -exec rm -rf {} + 2>/dev/null || true - \ No newline at end of file diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 1fb15f05..775fbf44 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -239,6 +239,7 @@ class AgentServerMessageType(str, Enum): Error: Error message. AddPartialTranscript: Partial transcript has been added. AddTranscript: Transcript has been added. + EndOfUtterance: End of utterance has been detected (from STT engine). SpeakingStarted: Speech has started. SpeakingEnded: Speech has ended. EndOfTurn: A turn has been detected (context-based prediction). @@ -275,6 +276,7 @@ class AgentServerMessageType(str, Enum): # Raw transcription messages ADD_PARTIAL_TRANSCRIPT = "AddPartialTranscript" ADD_TRANSCRIPT = "AddTranscript" + END_OF_UTTERANCE = "EndOfUtterance" # VAD messages SPEAKING_STARTED = "SpeakingStarted" @@ -455,6 +457,11 @@ def end_time(self) -> float: """Return the end time of the segment.""" return self.fragments[-1].end_time + @property + def text(self) -> str: + """Return the text of the segment.""" + return self.format_text() + def __str__(self) -> str: """Return a string representation of the object.""" meta = { diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py new file mode 100644 index 00000000..fa3fecce --- /dev/null +++ b/tests/voice/_utils.py @@ -0,0 +1,107 @@ +import asyncio +import os +import time +from typing import Callable +from typing import Optional + +import aiofiles + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + + +async def get_client( + api_key: Optional[str] = None, + url: Optional[str] = None, + app: Optional[str] = None, + config: Optional[VoiceAgentConfig] = None, + connect: bool = True, +) -> VoiceAgentClient: + """Get a client.""" + + # Create client + client = VoiceAgentClient(api_key=api_key, url=url, app=app, config=config) + + # Connect + if connect: + """Connect to the client and wait for the RECOGNITION_STARTED event.""" + + # Create an event to track when the callback is called + event_received = asyncio.Event() + received_message = None + + # Callback function for connection + def on_recognition_started(message): + nonlocal received_message + received_message = message + event_received.set() + + # Add listener for when recognition starts + client.once(AgentServerMessageType.RECOGNITION_STARTED, on_recognition_started) + + # Connect + await client.connect() + + # Wait for the callback with a 5-second timeout + try: + await asyncio.wait_for(event_received.wait(), timeout=5.0) + assert received_message is not None + except asyncio.TimeoutError: + raise TimeoutError("RECOGNITION_STARTED event was not received within 5 seconds") + + # Return client + return client + + +async def send_audio_file( + client: VoiceAgentClient, + audio_file: str, + event_received: Optional[asyncio.Event] = None, + chunk_size: int = 320, + progress_callback: Optional[Callable[[int], None]] = None, +) -> None: + """Send audio data to the API server.""" + + # Make sure client is connected + assert client._is_connected + + # Make sure file ends with .wav + assert audio_file.lower().endswith(".wav") + + # Check file exists + file = os.path.join(os.path.dirname(__file__), audio_file) + assert os.path.exists(file) + + # Delay is based off 16kHz int16 and chunk size + delay = chunk_size / 16000 / 2 + + # Load the file + async with aiofiles.open(file, "rb") as wav_file: + # Trim off the WAV file header + await wav_file.seek(44) + + # Send audio data + next_time = time.perf_counter() + delay + while not event_received.is_set() if event_received else True: + """Reads all chunks until the end of the file with precision delay.""" + + # Read chunk + chunk = await wav_file.read(chunk_size) + + # End of file + if not chunk: + break + + # Send audio to client + await client.send_audio(chunk) + + # Do any callbacks + if progress_callback: + progress_callback(len(chunk)) + + # Precision delay + sleep_time = next_time - time.perf_counter() + if sleep_time > 0: + await asyncio.sleep(sleep_time) + next_time += delay diff --git a/tests/voice/assets/audio_01.wav b/tests/voice/assets/audio_01.wav new file mode 100644 index 00000000..67209392 --- /dev/null +++ b/tests/voice/assets/audio_01.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c744f7e3f3d45fc1d64000670da99c223bcdec348cbf014385eaf98b0913016f +size 901352 diff --git a/tests/voice/assets/chat_01.jsonl b/tests/voice/assets/chat_01.jsonl new file mode 100644 index 00000000..e69de29b diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py new file mode 100644 index 00000000..4568aec5 --- /dev/null +++ b/tests/voice/test_01_client.py @@ -0,0 +1,29 @@ +import os + +import pytest +from _utils import get_client + + +@pytest.mark.asyncio +async def test_client(): + """Tests that a client can be created.""" + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Create client + client = await get_client(api_key=api_key, connect=False) + + # Check we are connected OK + await client.connect() + + # Check we are connected + assert client._is_connected + + # Disconnect + await client.disconnect() + + # Check we are disconnected + assert not client._is_connected diff --git a/tests/voice/test_02_messages.py b/tests/voice/test_02_messages.py new file mode 100644 index 00000000..bac44015 --- /dev/null +++ b/tests/voice/test_02_messages.py @@ -0,0 +1,70 @@ +import datetime +import json +import os + +import pytest +from _utils import get_client +from _utils import send_audio_file + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig + +api_key = os.getenv("SPEECHMATICS_API_KEY") + + +@pytest.mark.asyncio +async def test_log_messages(): + """Test transcription. + + This test will: + - log messages + """ + + # Client + client = await get_client( + api_key=api_key, + connect=True, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=0.2, max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED + ), + ) + + # Check we are connected + assert client._is_connected + + # Create an event to track when the callback is called + messages: list[str] = [] + + # Start time + start_time = datetime.datetime.now() + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + log = {"ts": ts, "payload": message} + messages.append(json.dumps(log)) + print(json.dumps(log)) + + # Add listeners + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, log_message) + client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) + client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + + # Load the audio file `./assets/audio_01.wav` + audio_file = "./assets/audio_01.wav" + print() + print(f"Loading audio file: {audio_file}") + print("---") + await send_audio_file(client, audio_file) + print("---") + print() + + # Close session + await client.disconnect() + assert not client._is_connected diff --git a/tests/voice/test_03_transcribe.py b/tests/voice/test_03_transcribe.py new file mode 100644 index 00000000..4b486ab7 --- /dev/null +++ b/tests/voice/test_03_transcribe.py @@ -0,0 +1,150 @@ +import asyncio +import os + +import pytest +from _utils import get_client +from _utils import send_audio_file + +from speechmatics.voice import AgentServerMessageType + +api_key = os.getenv("SPEECHMATICS_API_KEY") + + +@pytest.mark.asyncio +async def test_transcribe_partial(): + """Test transcription. + + This test will: + - send audio data to the API server + - wait for the first partial transcript (within 10 seconds) + """ + + # Client + client = await get_client(api_key=api_key, connect=True) + + # Check we are connected + assert client._is_connected + + # Create an event to track when the callback is called + event_received = asyncio.Event() + received_message = None + + # Callback function for connection + def on_partial_received(message): + nonlocal received_message + received_message = message + event_received.set() + + # Add listener for PARTIALS + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, on_partial_received) + + # Load the audio file `./assets/audio_01.wav` + await send_audio_file(client, "./assets/audio_01.wav", event_received) + + # Wait for the callback with timeout + try: + await asyncio.wait_for(event_received.wait(), timeout=5.0) + assert received_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_PARTIAL_TRANSCRIPT event was not received within 5 seconds of audio finish") + + # Close session + await client.disconnect() + assert not client._is_connected + + +@pytest.mark.asyncio +async def test_transcribe_final(): + """Test transcription. + + This test will: + - send audio data to the API server + - wait for the first final transcript (within 10 seconds) + """ + + # Client + client = await get_client(api_key=api_key, connect=True) + + # Check we are connected + assert client._is_connected + + # Create an event to track when the callback is called + event_received = asyncio.Event() + received_message = None + + # Callback function for connection + def on_final_received(message): + nonlocal received_message + received_message = message + event_received.set() + + # Add listener for PARTIALS + client.on(AgentServerMessageType.ADD_TRANSCRIPT, on_final_received) + + # Load the audio file `./assets/audio_01.wav` + await send_audio_file(client, "./assets/audio_01.wav", event_received) + + # Wait for the callback with timeout + try: + await asyncio.wait_for(event_received.wait(), timeout=5.0) + assert received_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_TRANSCRIPT event was not received within 5 seconds of audio finish") + + # Close session + await client.disconnect() + assert not client._is_connected + + +@pytest.mark.asyncio +async def test_interim_segment(): + """Test transcription. + + This test will: + - send audio data to the API server + - wait for the first interim segment (within 10 seconds) + """ + + # Client + client = await get_client(api_key=api_key, connect=True) + + # Check we are connected + assert client._is_connected + + # Create an event to track when the callback is called + event_received = asyncio.Event() + + # Callback function for connection + def on_segment_received(message): + # Segments from the message + segments = message.get("segments", []) + + # We need at least one segment + if not segments: + return + + # Get the first segment's text + transcription = segments[0].text + + # Check transcription starts with `Welcome to Speechmatics` + if not transcription.lower().startswith("welcome to speech"): + return + + # Set the event + event_received.set() + + # Add listener for PARTIALS + client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, on_segment_received) + + # Load the audio file `./assets/audio_01.wav` + await send_audio_file(client, "./assets/audio_01.wav", event_received) + + # Wait for the callback with timeout + try: + await asyncio.wait_for(event_received.wait(), timeout=5.0) + except asyncio.TimeoutError: + pytest.fail("ADD_INTERIM_SEGMENTS event was not received within 5 seconds of audio finish") + + # Close session + await client.disconnect() + assert not client._is_connected diff --git a/tests/voice/test_setup.py b/tests/voice/test_setup.py deleted file mode 100644 index 4fc590d2..00000000 --- a/tests/voice/test_setup.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_setup(): - assert True From 659ed5694e552a9e2916599f5a200d80679cbc18 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 01:24:17 +0100 Subject: [PATCH 017/215] updated tests --- sdk/voice/speechmatics/voice/_client.py | 24 +- tests/voice/_utils.py | 25 ++ tests/voice/assets/chat_01.jsonl | 228 ++++++++++++++++++ ...3_transcribe.py => test_02_transcriber.py} | 0 ...02_messages.py => test_03_conversation.py} | 28 ++- 5 files changed, 292 insertions(+), 13 deletions(-) rename tests/voice/{test_03_transcribe.py => test_02_transcriber.py} (100%) rename tests/voice/{test_02_messages.py => test_03_conversation.py} (64%) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index f5789a9d..ef15261d 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -206,6 +206,7 @@ def _process_config( audio_format = AudioFormat( encoding=config.audio_encoding, sample_rate=config.sample_rate, + chunk_size=320, ) # Return the config objects @@ -254,7 +255,7 @@ async def connect(self) -> None: if self._is_connected: self.emit( AgentServerMessageType.ERROR, - {"reason": "Already connected"}, + {"message": AgentServerMessageType.ERROR, "reason": "Already connected"}, ) return @@ -344,6 +345,7 @@ async def emit_metrics() -> None: self.emit( AgentServerMessageType.METRICS, { + "message": AgentServerMessageType.METRICS, "total_time": time_s, "total_time_str": time.strftime("%H:%M:%S", time.gmtime(time_s)), "total_bytes": self._total_bytes, @@ -441,6 +443,7 @@ def _calculate_ttfb(self, end_time: float) -> None: self.emit( AgentServerMessageType.TTFB_METRICS, { + "message": AgentServerMessageType.TTFB_METRICS, "ttfb": self._last_ttfb, }, ) @@ -746,7 +749,7 @@ async def _emit_segments(self, finalize: bool = False) -> None: if final_segments: self.emit( AgentServerMessageType.ADD_SEGMENTS, - {"segments": final_segments}, + {"message": AgentServerMessageType.ADD_SEGMENTS, "segments": final_segments}, ) self._trim_before_time = final_segments[-1].end_time self._speech_fragments = [f for f in self._speech_fragments if f.start_time >= self._trim_before_time] @@ -755,7 +758,7 @@ async def _emit_segments(self, finalize: bool = False) -> None: if interim_segments: self.emit( AgentServerMessageType.ADD_INTERIM_SEGMENTS, - {"segments": interim_segments}, + {"message": AgentServerMessageType.ADD_INTERIM_SEGMENTS, "segments": interim_segments}, ) # Update the current view @@ -806,11 +809,17 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: if already_speaking and speaker_changed: self.emit( AgentServerMessageType.SPEAKING_ENDED, - {"status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False)}, + { + "message": AgentServerMessageType.SPEAKING_ENDED, + "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False), + }, ) self.emit( AgentServerMessageType.SPEAKING_STARTED, - {"status": SpeakerVADStatus(speaker_id=speaker, is_active=True)}, + { + "message": AgentServerMessageType.SPEAKING_STARTED, + "status": SpeakerVADStatus(speaker_id=speaker, is_active=True), + }, ) # Update current speaker @@ -824,9 +833,10 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: self._is_speaking = not self._is_speaking # Emit the event for latest speaker + msg = AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED self.emit( - (AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED), - {"status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, + msg, + {"message": msg, "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, ) # Reset the current speaker diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index fa3fecce..6dcd93a8 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -1,6 +1,8 @@ import asyncio import os import time +from dataclasses import asdict +from dataclasses import is_dataclass from typing import Callable from typing import Optional @@ -105,3 +107,26 @@ async def send_audio_file( if sleep_time > 0: await asyncio.sleep(sleep_time) next_time += delay + + +def to_serializable(obj): + """Convert an object into a JSON-serializable form.""" + + # Dataclasses → dict + if is_dataclass(obj): + return {k: to_serializable(v) for k, v in asdict(obj).items()} + + # dicts → dict with serializable values + if isinstance(obj, dict): + return {k: to_serializable(v) for k, v in obj.items()} + + # lists/tuples → list with serializable items + if isinstance(obj, (list, tuple)): + return [to_serializable(v) for v in obj] + + # basic types → leave as is + if isinstance(obj, (str, int, float, bool)) or obj is None: + return obj + + # fallback → string + return str(obj) diff --git a/tests/voice/assets/chat_01.jsonl b/tests/voice/assets/chat_01.jsonl index e69de29b..3a65085f 100644 --- a/tests/voice/assets/chat_01.jsonl +++ b/tests/voice/assets/chat_01.jsonl @@ -0,0 +1,228 @@ +{"ts": 0.000115, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} +{"ts": 0.000314, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} +{"ts": 0.000428, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} +{"ts": 0.000491, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} +{"ts": 0.505819, "audio_ts": 0.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 0.506076, "audio_ts": 0.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} +{"ts": 0.886575, "audio_ts": 0.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.245033, "audio_ts": 1.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} +{"ts": 1.245772, "audio_ts": 1.25, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 1.251577, "audio_ts": 1.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} +{"ts": 1.64981, "audio_ts": 1.65, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} +{"ts": 1.650008, "audio_ts": 1.65, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} +{"ts": 1.657086, "audio_ts": 1.66, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 1.975388, "audio_ts": 1.98, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} +{"ts": 1.975628, "audio_ts": 1.98, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} +{"ts": 1.975884, "audio_ts": 1.98, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 1.981827, "audio_ts": 1.98, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4208640}}]}} +{"ts": 2.284597, "audio_ts": 2.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.290348, "audio_ts": 2.29, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4208640}}]}} +{"ts": 2.687002, "audio_ts": 2.69, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} +{"ts": 2.687193, "audio_ts": 2.69, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} +{"ts": 2.687711, "audio_ts": 2.69, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} +{"ts": 2.693148, "audio_ts": 2.7, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.32, "end_time": 1.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} +{"ts": 3.010142, "audio_ts": 3.01, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.44, "transcript": ""}}} +{"ts": 3.010291, "audio_ts": 3.01, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.38324, "audio_ts": 3.39, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.383357, "audio_ts": 3.39, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.77054, "audio_ts": 3.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.770768, "audio_ts": 3.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.52, "transcript": ""}}} +{"ts": 4.158163, "audio_ts": 4.16, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.44, "transcript": "We're"}}} +{"ts": 4.158496, "audio_ts": 4.16, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 4.164417, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 7, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} +{"ts": 4.449163, "audio_ts": 4.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.44, "transcript": "We're "}}} +{"ts": 4.449357, "audio_ts": 4.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} +{"ts": 4.455204, "audio_ts": 4.46, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 9, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 10, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 4.883937, "audio_ts": 4.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} +{"ts": 4.884232, "audio_ts": 4.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.71, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} +{"ts": 4.890195, "audio_ts": 4.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.71, "result": {"final": false, "alternatives": [{"confidence": 0.71, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 5.220628, "audio_ts": 5.22, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} +{"ts": 5.220879, "audio_ts": 5.22, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} +{"ts": 5.227434, "audio_ts": 5.23, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 5.593757, "audio_ts": 5.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} +{"ts": 5.594029, "audio_ts": 5.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} +{"ts": 5.599868, "audio_ts": 5.6, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 5.886625, "audio_ts": 5.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.98, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} +{"ts": 5.892631, "audio_ts": 5.9, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 6.296823, "audio_ts": 6.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} +{"ts": 6.297011, "audio_ts": 6.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech"}}} +{"ts": 6.302634, "audio_ts": 6.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 6.669202, "audio_ts": 6.67, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} +{"ts": 6.669474, "audio_ts": 6.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} +{"ts": 6.675638, "audio_ts": 6.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 7.021005, "audio_ts": 7.02, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} +{"ts": 7.02126, "audio_ts": 7.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} +{"ts": 7.021558, "audio_ts": 7.02, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 7.027727, "audio_ts": 7.03, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 14336}}]}} +{"ts": 7.335987, "audio_ts": 7.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} +{"ts": 7.336211, "audio_ts": 7.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 7.341872, "audio_ts": 7.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 7.771311, "audio_ts": 7.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} +{"ts": 7.771612, "audio_ts": 7.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} +{"ts": 7.777708, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 32, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 8.080594, "audio_ts": 8.08, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} +{"ts": 8.081095, "audio_ts": 8.08, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 8.081225, "audio_ts": 8.08, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 8.087489, "audio_ts": 8.09, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} +{"ts": 8.089333, "audio_ts": 8.09, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 33, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": {"flags": 4195328}}]}} +{"ts": 8.432364, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} +{"ts": 8.432691, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} +{"ts": 8.43878, "audio_ts": 8.44, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 39, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 8.82756, "audio_ts": 8.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} +{"ts": 8.828373, "audio_ts": 8.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} +{"ts": 8.834496, "audio_ts": 8.84, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 9.221652, "audio_ts": 9.22, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} +{"ts": 9.221862, "audio_ts": 9.22, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} +{"ts": 9.228307, "audio_ts": 9.23, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 47, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 9.488784, "audio_ts": 9.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} +{"ts": 9.489013, "audio_ts": 9.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} +{"ts": 9.495188, "audio_ts": 9.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 52, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 9.899314, "audio_ts": 9.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} +{"ts": 9.899588, "audio_ts": 9.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} +{"ts": 9.905964, "audio_ts": 9.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 56, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5282816}}]}} +{"ts": 10.290264, "audio_ts": 10.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} +{"ts": 10.29605, "audio_ts": 10.3, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 57, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 58, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 10.614414, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} +{"ts": 10.620351, "audio_ts": 10.62, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 59, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 10.94132, "audio_ts": 10.94, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} +{"ts": 10.947545, "audio_ts": 10.95, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 11.366284, "audio_ts": 11.37, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 11.0, "start_time": 9.0, "transcript": "three. One. Two three. "}}} +{"ts": 11.366833, "audio_ts": 11.37, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.367141, "audio_ts": 11.37, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 11.3675, "audio_ts": 11.37, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.08, "start_time": 11.08}}} +{"ts": 11.372948, "audio_ts": 11.38, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 61, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 62, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "One", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 64, "start_time": 9.88, "end_time": 9.88, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, "annotation": null}, {"idx": 65, "start_time": 9.92, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, "annotation": null}, {"idx": 66, "start_time": 10.2, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} +{"ts": 11.708067, "audio_ts": 11.71, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.708248, "audio_ts": 11.71, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.040609, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.040799, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} +{"ts": 12.40966, "audio_ts": 12.41, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} +{"ts": 12.409916, "audio_ts": 12.41, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 12.41579, "audio_ts": 12.42, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} +{"ts": 12.780649, "audio_ts": 12.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} +{"ts": 12.780909, "audio_ts": 12.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} +{"ts": 12.781768, "audio_ts": 12.78, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 12.782674, "audio_ts": 12.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 12.798136, "audio_ts": 12.8, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 13.11259, "audio_ts": 13.12, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} +{"ts": 13.112773, "audio_ts": 13.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} +{"ts": 13.119442, "audio_ts": 13.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1088512}}]}} +{"ts": 13.484031, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} +{"ts": 13.484207, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} +{"ts": 13.491362, "audio_ts": 13.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 13.86083, "audio_ts": 13.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} +{"ts": 13.861115, "audio_ts": 13.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} +{"ts": 13.867673, "audio_ts": 13.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 14.238783, "audio_ts": 14.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} +{"ts": 14.239052, "audio_ts": 14.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} +{"ts": 14.245175, "audio_ts": 14.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 14.520592, "audio_ts": 14.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} +{"ts": 14.520796, "audio_ts": 14.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} +{"ts": 14.526987, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 14.909253, "audio_ts": 14.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} +{"ts": 14.909547, "audio_ts": 14.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} +{"ts": 14.916065, "audio_ts": 14.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 15.330651, "audio_ts": 15.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} +{"ts": 15.330838, "audio_ts": 15.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} +{"ts": 15.337024, "audio_ts": 15.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 15.639239, "audio_ts": 15.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} +{"ts": 15.63953, "audio_ts": 15.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} +{"ts": 15.639798, "audio_ts": 15.64, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 15.645672, "audio_ts": 15.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 14336}}]}} +{"ts": 16.030186, "audio_ts": 16.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} +{"ts": 16.030552, "audio_ts": 16.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 16.036747, "audio_ts": 16.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 16.379245, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} +{"ts": 16.379467, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} +{"ts": 16.385789, "audio_ts": 16.39, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 16.748774, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} +{"ts": 16.749065, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} +{"ts": 16.755371, "audio_ts": 16.76, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 17.026278, "audio_ts": 17.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} +{"ts": 17.026559, "audio_ts": 17.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} +{"ts": 17.032572, "audio_ts": 17.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.75, "result": {"final": false, "alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5282816}}]}} +{"ts": 17.445589, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 17.12, "start_time": 16.2, "transcript": "API. "}}} +{"ts": 17.4459, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 17.2, "transcript": ""}}} +{"ts": 17.446008, "audio_ts": 17.45, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 17.2, "start_time": 17.2}}} +{"ts": 17.4463, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 17.452654, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} +{"ts": 17.774981, "audio_ts": 17.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 17.2, "transcript": ""}}} +{"ts": 18.185069, "audio_ts": 18.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 17.12, "transcript": "We "}}} +{"ts": 18.185883, "audio_ts": 18.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} +{"ts": 18.186174, "audio_ts": 18.19, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 18.192217, "audio_ts": 18.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 8395776}}]}} +{"ts": 18.502542, "audio_ts": 18.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} +{"ts": 18.502796, "audio_ts": 18.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} +{"ts": 18.508621, "audio_ts": 18.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 8395776}}]}} +{"ts": 18.914238, "audio_ts": 18.92, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} +{"ts": 18.914544, "audio_ts": 18.92, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} +{"ts": 18.920628, "audio_ts": 18.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.84, "result": {"final": false, "alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 19.259913, "audio_ts": 19.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} +{"ts": 19.260113, "audio_ts": 19.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} +{"ts": 19.266297, "audio_ts": 19.27, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 19.581366, "audio_ts": 19.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} +{"ts": 19.582053, "audio_ts": 19.58, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.5, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} +{"ts": 19.5886, "audio_ts": 19.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.5, "result": {"final": false, "alternatives": [{"confidence": 0.5, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 19.929443, "audio_ts": 19.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} +{"ts": 19.93549, "audio_ts": 19.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 20.30179, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}], "metadata": {"end_time": 20.0, "start_time": 18.8, "transcript": "results. "}}} +{"ts": 20.302086, "audio_ts": 20.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.302186, "audio_ts": 20.3, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 20.08, "start_time": 20.08}}} +{"ts": 20.302583, "audio_ts": 20.3, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 20.30866, "audio_ts": 20.31, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 19.52, "end_time": 19.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} +{"ts": 20.707935, "audio_ts": 20.71, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.708109, "audio_ts": 20.71, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.44, "transcript": ""}}} +{"ts": 21.020595, "audio_ts": 21.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.36, "transcript": "Results"}}} +{"ts": 21.020945, "audio_ts": 21.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 21.026903, "audio_ts": 21.03, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.140+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.36, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} +{"ts": 21.450397, "audio_ts": 21.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.36, "transcript": "Results. "}}} +{"ts": 21.450657, "audio_ts": 21.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} +{"ts": 21.450907, "audio_ts": 21.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 21.456571, "audio_ts": 21.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.140+00:00", "language": "en", "fragments": [{"idx": 128, "start_time": 20.36, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 0.99, "result": {"final": true, "alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, "annotation": null}, {"idx": 129, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} +{"ts": 21.788024, "audio_ts": 21.79, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} +{"ts": 21.788744, "audio_ts": 21.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are"}}} +{"ts": 21.789495, "audio_ts": 21.79, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 21.795425, "audio_ts": 21.8, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 22.087985, "audio_ts": 22.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} +{"ts": 22.088233, "audio_ts": 22.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} +{"ts": 22.094286, "audio_ts": 22.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 22.448874, "audio_ts": 22.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} +{"ts": 22.449068, "audio_ts": 22.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} +{"ts": 22.455057, "audio_ts": 22.46, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} +{"ts": 22.809949, "audio_ts": 22.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.52, "start_time": 21.56, "transcript": "amazing. "}}} +{"ts": 22.810272, "audio_ts": 22.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.6, "transcript": ""}}} +{"ts": 22.810411, "audio_ts": 22.81, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 22.6, "start_time": 22.6}}} +{"ts": 22.810674, "audio_ts": 22.81, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 22.816785, "audio_ts": 22.82, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} +{"ts": 23.172767, "audio_ts": 23.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.6, "transcript": ""}}} +{"ts": 23.17302, "audio_ts": 23.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.96, "transcript": ""}}} +{"ts": 23.586812, "audio_ts": 23.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, {"alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.88, "transcript": "Thank you"}}} +{"ts": 23.587241, "audio_ts": 23.59, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 23.592964, "audio_ts": 23.6, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 138, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.95, "result": {"final": false, "alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} +{"ts": 23.948459, "audio_ts": 23.95, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.88, "transcript": "Thank "}}} +{"ts": 23.948744, "audio_ts": 23.95, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you"}}} +{"ts": 23.955031, "audio_ts": 23.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 140, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 23.12, "end_time": 23.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} +{"ts": 24.28314, "audio_ts": 24.29, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.44, "is_eos": true, "start_time": 23.44, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} +{"ts": 24.283343, "audio_ts": 24.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.283409, "audio_ts": 24.29, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} +{"ts": 24.283606, "audio_ts": 24.29, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 24.289172, "audio_ts": 24.29, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 140, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 142, "start_time": 23.12, "end_time": 23.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.44, "end_time": 23.44, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.44, "is_eos": true, "start_time": 23.44, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} +{"ts": 24.607229, "audio_ts": 24.61, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.607397, "audio_ts": 24.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.4, "transcript": ""}}} +{"ts": 24.965649, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.4, "transcript": ""}}} +{"ts": 24.966291, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.340973, "audio_ts": 25.34, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.341086, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.73166, "audio_ts": 25.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.731855, "audio_ts": 25.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.059762, "audio_ts": 26.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.060025, "audio_ts": 26.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.401326, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.401424, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.781128, "audio_ts": 26.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.781359, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.117456, "audio_ts": 27.12, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.117699, "audio_ts": 27.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.487666, "audio_ts": 27.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.48785, "audio_ts": 27.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 27.28, "transcript": ""}}} +{"ts": 27.843804, "audio_ts": 27.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.28, "transcript": ""}}} +{"ts": 27.843956, "audio_ts": 27.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/test_03_transcribe.py b/tests/voice/test_02_transcriber.py similarity index 100% rename from tests/voice/test_03_transcribe.py rename to tests/voice/test_02_transcriber.py diff --git a/tests/voice/test_02_messages.py b/tests/voice/test_03_conversation.py similarity index 64% rename from tests/voice/test_02_messages.py rename to tests/voice/test_03_conversation.py index bac44015..6c0e6846 100644 --- a/tests/voice/test_02_messages.py +++ b/tests/voice/test_03_conversation.py @@ -5,6 +5,7 @@ import pytest from _utils import get_client from _utils import send_audio_file +from _utils import to_serializable from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode @@ -26,7 +27,10 @@ async def test_log_messages(): api_key=api_key, connect=True, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.2, max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED + end_of_utterance_silence_trigger=0.2, + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + enable_diarization=True, ), ) @@ -35,16 +39,23 @@ async def test_log_messages(): # Create an event to track when the callback is called messages: list[str] = [] + bytes_sent: int = 0 # Start time start_time = datetime.datetime.now() + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + # Callback for each message def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() - log = {"ts": ts, "payload": message} - messages.append(json.dumps(log)) - print(json.dumps(log)) + audio_ts = bytes_sent / 16000 / 2 + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + messages.append(log) + print(log) # Add listeners client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) @@ -59,11 +70,16 @@ def log_message(message): # Load the audio file `./assets/audio_01.wav` audio_file = "./assets/audio_01.wav" print() - print(f"Loading audio file: {audio_file}") + print() print("---") - await send_audio_file(client, audio_file) + log_message({"message": "AudioFile", "path": audio_file}) + log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) + log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) + log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) + await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) print("---") print() + print() # Close session await client.disconnect() From 5f09cb145c8d7c2af4a01a14615d658b60c6d454 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 01:32:54 +0100 Subject: [PATCH 018/215] Updated tests. --- tests/voice/test_02_transcriber.py | 15 +++++++++++++++ tests/voice/test_03_conversation.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index 4b486ab7..cc99fb09 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -19,6 +19,11 @@ async def test_transcribe_partial(): - wait for the first partial transcript (within 10 seconds) """ + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + # Client client = await get_client(api_key=api_key, connect=True) @@ -62,6 +67,11 @@ async def test_transcribe_final(): - wait for the first final transcript (within 10 seconds) """ + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + # Client client = await get_client(api_key=api_key, connect=True) @@ -105,6 +115,11 @@ async def test_interim_segment(): - wait for the first interim segment (within 10 seconds) """ + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + # Client client = await get_client(api_key=api_key, connect=True) diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 6c0e6846..035cae72 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -22,6 +22,11 @@ async def test_log_messages(): - log messages """ + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + # Client client = await get_client( api_key=api_key, From 269fddd5ec412f835ab57c9ce29721f3e54ee634 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 09:07:56 +0100 Subject: [PATCH 019/215] Added example for logging to JSONL file. --- Makefile | 6 +- examples/batch/transcribe_file_async.py | 20 +-- examples/{example.wav => example1.wav} | 0 examples/example3.wav | 3 + examples/example4.wav | 3 + examples/voice/file_to_jsonl.py | 191 ++++++++++++++++++++++++ examples/voice/utils.py | 8 +- 7 files changed, 211 insertions(+), 20 deletions(-) rename examples/{example.wav => example1.wav} (100%) create mode 100644 examples/example3.wav create mode 100644 examples/example4.wav create mode 100644 examples/voice/file_to_jsonl.py diff --git a/Makefile b/Makefile index e76f2e7c..eff2b794 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ test-voice: pytest tests/voice/ -v -s # Formatting targets -format-all: format-rt format-batch format-flow format-voice format-tests +format-all: format-rt format-batch format-flow format-voice format-tests format-examples format-rt: cd sdk/rt/speechmatics && black . @@ -92,6 +92,10 @@ format-tests: cd tests && black . cd tests && ruff check --fix . +format-examples: + cd examples && black . + cd examples && ruff check --fix . + # Linting targets lint-all: lint-rt lint-batch lint-flow lint-voice diff --git a/examples/batch/transcribe_file_async.py b/examples/batch/transcribe_file_async.py index 24508860..0d601e20 100644 --- a/examples/batch/transcribe_file_async.py +++ b/examples/batch/transcribe_file_async.py @@ -5,17 +5,13 @@ import asyncio import os -from speechmatics.batch import ( - AsyncClient, - JobConfig, - JobType, - Transcript, - TranscriptionConfig, -) +from speechmatics.batch import AsyncClient +from speechmatics.batch import JobConfig +from speechmatics.batch import JobType +from speechmatics.batch import Transcript +from speechmatics.batch import TranscriptionConfig -audio_file = os.getenv( - "AUDIO_FILE_PATH", os.path.join(os.path.dirname(__file__), "../example.wav") -) +audio_file = os.getenv("AUDIO_FILE_PATH", os.path.join(os.path.dirname(__file__), "../example.wav")) async def main() -> None: @@ -29,9 +25,7 @@ async def main() -> None: # Submit transcription job config = JobConfig( type=JobType.TRANSCRIPTION, - transcription_config=TranscriptionConfig( - language="en", enable_entities=True - ), + transcription_config=TranscriptionConfig(language="en", enable_entities=True), ) job = await client.submit_job(audio_file, config=config) diff --git a/examples/example.wav b/examples/example1.wav similarity index 100% rename from examples/example.wav rename to examples/example1.wav diff --git a/examples/example3.wav b/examples/example3.wav new file mode 100644 index 00000000..6a3536e1 --- /dev/null +++ b/examples/example3.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d214377882dd8a0472d44df3f9853f7cfb488ef429427efa03b7f2b5b6b7c37 +size 184964 diff --git a/examples/example4.wav b/examples/example4.wav new file mode 100644 index 00000000..21c5cff3 --- /dev/null +++ b/examples/example4.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96425f4f5f1d4db7b6dceadd99df889143e0ce00f0cbfd4b48a05335e4b43c62 +size 472300 diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py new file mode 100644 index 00000000..31209765 --- /dev/null +++ b/examples/voice/file_to_jsonl.py @@ -0,0 +1,191 @@ +import argparse +import asyncio +import datetime +import json +import os +import sys +import time +from dataclasses import asdict +from dataclasses import is_dataclass +from typing import Callable +from typing import Optional + +import aiofiles + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + + +async def main() -> None: + """Transcribe a file to a JSONL file.""" + + # Parse command line arguments + args = parse_args() + + # Validate API key + if not args.api_key: + print( + "Error: API key is required. Set SPEECHMATICS_API_KEY environment variable or use --api-key", + file=sys.stderr, + ) + sys.exit(1) + + # Use absolute path if provided, otherwise relative to script directory + if os.path.isabs(args.input_file): + file = args.input_file + else: + file = os.path.join(os.path.dirname(__file__), args.input_file) + + # Check file exists + if not os.path.exists(file): + print(f"Error: Input file '{file}' not found", file=sys.stderr) + sys.exit(1) + + # Client + client = VoiceAgentClient( + api_key=args.api_key, + url=args.url, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=0.2, + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + enable_diarization=True, + ), + ) + + # Create an event to track when the callback is called + messages: list[str] = [] + bytes_sent: int = 0 + + # Start time + start_time = datetime.datetime.now() + + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + audio_ts = bytes_sent / 16000 / 2 + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + messages.append(log) + + # Output to file or stdout + if args.output: + with open(args.output, "a") as f: + f.write(log + "\n") + else: + print(log) + + # Log script info + log_message({"message": "AudioFile", "path": file}) + log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) + log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) + log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) + + # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.once(AgentServerMessageType.INFO, log_message) + client.on(AgentServerMessageType.WARNING, log_message) + client.on(AgentServerMessageType.ERROR, log_message) + client.once(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, log_message) + client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) + client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + + # Clear output file if it exists + if args.output and os.path.exists(args.output): + open(args.output, "w").close() + + # Connect + await client.connect() + + # Use the input file from command line arguments + await send_audio_file(client, file, progress_callback=log_bytes_sent) + + # Close session + await client.disconnect() + assert not client._is_connected + + +def to_serializable(obj): + """Convert an object into a JSON-serializable form.""" + if is_dataclass(obj): + return {k: to_serializable(v) for k, v in asdict(obj).items()} + if isinstance(obj, dict): + return {k: to_serializable(v) for k, v in obj.items()} + if isinstance(obj, (list, tuple)): + return [to_serializable(v) for v in obj] + if isinstance(obj, (str, int, float, bool)) or obj is None: + return obj + return str(obj) + + +async def send_audio_file( + client: VoiceAgentClient, + file: str, + event_received: Optional[asyncio.Event] = None, + chunk_size: int = 320, + progress_callback: Optional[Callable[[int], None]] = None, +) -> None: + """Send audio data to the API server.""" + + # Delay is based off 16kHz int16 and chunk size + delay = chunk_size / 16000 / 2 + + # Load the file + async with aiofiles.open(file, "rb") as wav_file: + # Trim off the WAV file header + await wav_file.seek(44) + + # Send audio data + next_time = time.perf_counter() + delay + while not event_received.is_set() if event_received else True: + """Reads all chunks until the end of the file with precision delay.""" + + # Read chunk + chunk = await wav_file.read(chunk_size) + if not chunk: + break + + # Send audio to client + await client.send_audio(chunk) + + # Do any callbacks + if progress_callback: + progress_callback(len(chunk)) + + # Precision delay + sleep_time = next_time - time.perf_counter() + if sleep_time > 0: + await asyncio.sleep(sleep_time) + next_time += delay + + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Transcribe an audio file to JSONL format using Speechmatics Voice API" + ) + parser.add_argument("input_file", help="Path to the input audio file (WAV format)") + parser.add_argument( + "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), + help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", + ) + parser.add_argument("--url", help="Speechmatics server URL (optional)") + parser.add_argument("--output", "-o", help="Output JSONL file (defaults to stdout)") + return parser.parse_args() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/utils.py b/examples/voice/utils.py index 03fc7c01..9cc1f3d1 100644 --- a/examples/voice/utils.py +++ b/examples/voice/utils.py @@ -40,9 +40,7 @@ def select_audio_output_device() -> int | None: print("Available audio output devices:") for device in output_devices: - print( - f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)" - ) + print(f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)") print() return _get_device_choice( @@ -213,9 +211,7 @@ def get_logger(name: str) -> logging.Logger: logging.addLevelName(CustomLevels.SPEAKER, "SPEAKER") # Apply custom formatter to all handlers - formatter = CustomTextFormatter( - CustomTextFormatter.FORMAT, datefmt="%Y-%m-%d %H:%M:%S" - ) + formatter = CustomTextFormatter(CustomTextFormatter.FORMAT, datefmt="%Y-%m-%d %H:%M:%S") for handler in logging.root.handlers: handler.setFormatter(formatter) From 470bba6ca9afa45abab713d0033c6031de2a1d87 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 09:11:28 +0100 Subject: [PATCH 020/215] update example --- examples/voice/file_to_jsonl.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 31209765..078ce60d 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -24,14 +24,6 @@ async def main() -> None: # Parse command line arguments args = parse_args() - # Validate API key - if not args.api_key: - print( - "Error: API key is required. Set SPEECHMATICS_API_KEY environment variable or use --api-key", - file=sys.stderr, - ) - sys.exit(1) - # Use absolute path if provided, otherwise relative to script directory if os.path.isabs(args.input_file): file = args.input_file @@ -179,7 +171,6 @@ def parse_args(): parser.add_argument("input_file", help="Path to the input audio file (WAV format)") parser.add_argument( "--api-key", - default=os.getenv("SPEECHMATICS_API_KEY"), help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", ) parser.add_argument("--url", help="Speechmatics server URL (optional)") From 6f7777c181fd422887436cd33c24bb4aa768cc1a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 14:05:25 +0100 Subject: [PATCH 021/215] Updated model for flags. --- examples/voice/file_to_jsonl.py | 19 ++-- sdk/voice/OVERVIEW.md | 2 +- sdk/voice/README.md | 2 +- sdk/voice/speechmatics/voice/_client.py | 2 +- sdk/voice/speechmatics/voice/_models.py | 119 +++++++++++------------- tests/voice/test_03_conversation.py | 3 +- tests/voice/test_04_models.py | 54 +++++++++++ 7 files changed, 119 insertions(+), 82 deletions(-) create mode 100644 tests/voice/test_04_models.py diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 078ce60d..955c1198 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -24,15 +24,9 @@ async def main() -> None: # Parse command line arguments args = parse_args() - # Use absolute path if provided, otherwise relative to script directory - if os.path.isabs(args.input_file): - file = args.input_file - else: - file = os.path.join(os.path.dirname(__file__), args.input_file) - # Check file exists - if not os.path.exists(file): - print(f"Error: Input file '{file}' not found", file=sys.stderr) + if not os.path.exists(args.input_file): + print(f"Error: Input file '{args.input_file}' not found", file=sys.stderr) sys.exit(1) # Client @@ -74,7 +68,7 @@ def log_message(message): print(log) # Log script info - log_message({"message": "AudioFile", "path": file}) + log_message({"message": "AudioFile", "path": args.input_file}) log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) @@ -92,7 +86,8 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) - client.on(AgentServerMessageType.END_OF_TURN, log_message) + client.on(AgentServerMessageType.TURN_STARTED, log_message) + client.on(AgentServerMessageType.TURN_ENDED, log_message) # Clear output file if it exists if args.output and os.path.exists(args.output): @@ -102,14 +97,14 @@ def log_message(message): await client.connect() # Use the input file from command line arguments - await send_audio_file(client, file, progress_callback=log_bytes_sent) + await send_audio_file(client, args.input_file, progress_callback=log_bytes_sent) # Close session await client.disconnect() assert not client._is_connected -def to_serializable(obj): +def to_serializable(obj) -> str: """Convert an object into a JSON-serializable form.""" if is_dataclass(obj): return {k: to_serializable(v) for k, v in asdict(obj).items()} diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md index 193fd894..c8604fbb 100644 --- a/sdk/voice/OVERVIEW.md +++ b/sdk/voice/OVERVIEW.md @@ -76,7 +76,7 @@ async def main(): print("User stopped speaking") # End of turn / utterance(s) - @client.on(AgentServerMessageType.END_OF_TURN) + @client.on(AgentServerMessageType.TURN_ENDED) def handle_end_of_turn(message): print("End of turn") diff --git a/sdk/voice/README.md b/sdk/voice/README.md index fc16a56b..daaa5f2d 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -80,7 +80,7 @@ async def main(): print("User stopped speaking") # End of turn / utterance(s) - @client.on(AgentServerMessageType.END_OF_TURN) + @client.on(AgentServerMessageType.TURN_ENDED) def handle_end_of_turn(message): print("End of turn") diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index ef15261d..856d91f7 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -843,7 +843,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: if not self._is_speaking: self._current_speaker = None - def _frag_idx(self): + def _frag_idx(self) -> int: self._fragment_idx += 1 return self._fragment_idx diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 775fbf44..f036b98a 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -7,8 +7,6 @@ from dataclasses import dataclass from dataclasses import field from enum import Enum -from enum import IntFlag -from enum import auto from typing import Any from typing import Optional @@ -242,7 +240,8 @@ class AgentServerMessageType(str, Enum): EndOfUtterance: End of utterance has been detected (from STT engine). SpeakingStarted: Speech has started. SpeakingEnded: Speech has ended. - EndOfTurn: A turn has been detected (context-based prediction). + TurnStarted: A turn has been detected (context-based prediction). + TurnEnded: A turn has ended (context-based prediction). AddSegments: A final segment has been detected. AddInterimSegments: An interim segment has been detected. SpeakersResult: Speakers result has been detected. @@ -281,7 +280,10 @@ class AgentServerMessageType(str, Enum): # VAD messages SPEAKING_STARTED = "SpeakingStarted" SPEAKING_ENDED = "SpeakingEnded" - END_OF_TURN = "EndOfTurn" + + # Turn messages + TURN_STARTED = "TurnStarted" + TURN_ENDED = "TurnEnded" # Turn / segment messages ADD_SEGMENTS = "AddSegments" @@ -296,93 +298,78 @@ class AgentServerMessageType(str, Enum): SPEAKER_METRICS = "SpeakerMetrics" -class AnnotationFlags(IntFlag): +class AnnotationFlags(str, Enum): """Flags to apply when processing speech / objects.""" # High-level segment updates - NEW = auto() - UPDATED_FULL = auto() - UPDATED_FULL_LCASE = auto() - UPDATED_STRIPPED = auto() - UPDATED_STRIPPED_LCASE = auto() - UPDATED_FINALS = auto() - UPDATED_PARTIALS = auto() - UPDATED_SPEAKERS = auto() + NEW = "new" + UPDATED_FULL = "updated_full" + UPDATED_FULL_LCASE = "updated_full_lcase" + UPDATED_STRIPPED = "updated_stripped" + UPDATED_STRIPPED_LCASE = "updated_stripped_lcase" + UPDATED_FINALS = "updated_finals" + UPDATED_PARTIALS = "updated_partials" + UPDATED_SPEAKERS = "updated_speakers" # Content of segments - ONLY_ACTIVE_SPEAKERS = auto() - CONTAINS_INACTIVE_SPEAKERS = auto() + ONLY_ACTIVE_SPEAKERS = "only_active_speakers" + CONTAINS_INACTIVE_SPEAKERS = "contains_inactive_speakers" # More granular details on the word content - HAS_PARTIAL = auto() - HAS_FINAL = auto() - STARTS_WITH_FINAL = auto() - ENDS_WITH_FINAL = auto() - HAS_EOS = auto() - ENDS_WITH_EOS = auto() - HAS_DISFLUENCY = auto() - STARTS_WITH_DISFLUENCY = auto() - ENDS_WITH_DISFLUENCY = auto() - HIGH_DISFLUENCY_COUNT = auto() - ENDS_WITH_PUNCTUATION = auto() - VERY_SLOW_SPEAKER = auto() - SLOW_SPEAKER = auto() - FAST_SPEAKER = auto() - ONLY_PUNCTUATION = auto() - MULTIPLE_SPEAKERS = auto() - NO_TEXT = auto() + HAS_PARTIAL = "has_partial" + HAS_FINAL = "has_final" + STARTS_WITH_FINAL = "starts_with_final" + ENDS_WITH_FINAL = "ends_with_final" + HAS_EOS = "has_eos" + ENDS_WITH_EOS = "ends_with_eos" + HAS_DISFLUENCY = "has_disfluency" + STARTS_WITH_DISFLUENCY = "starts_with_disfluency" + ENDS_WITH_DISFLUENCY = "ends_with_disfluency" + HIGH_DISFLUENCY_COUNT = "high_disfluency_count" + ENDS_WITH_PUNCTUATION = "ends_with_punctuation" + VERY_SLOW_SPEAKER = "very_slow_speaker" + SLOW_SPEAKER = "slow_speaker" + FAST_SPEAKER = "fast_speaker" + ONLY_PUNCTUATION = "only_punctuation" + MULTIPLE_SPEAKERS = "multiple_speakers" + NO_TEXT = "no_text" # End of utterance detection - END_OF_UTTERANCE = auto() + END_OF_UTTERANCE = "end_of_utterance" -@dataclass -class AnnotationResult: +class AnnotationResult(list): """Processing result.""" - flags: int = 0 - - def __init__(self, *flags: AnnotationFlags): - """Initialize the object. + def add(self, *flags: AnnotationFlags) -> None: + """Add a flag(s) to the object.""" + for flag in flags: + if flag not in self: + self.append(flag.value) - Args: - flags: The initial flags to set. - """ - self.flags = 0 + def remove(self, *flags: AnnotationFlags) -> None: + """Remove a flag(s) from the object.""" for flag in flags: - self.add(flag) + if flag in self: + super().remove(flag.value) def has(self, *flags: AnnotationFlags) -> bool: """Check if the object has all given flags.""" - return all(self.flags & flag == flag for flag in flags) + return all(f.value in set(self) for f in flags) def any(self, *flags: AnnotationFlags) -> bool: """Check if the object has any of the given flags.""" - return any(self.flags & flag == flag for flag in flags) - - def add(self, flag: AnnotationFlags) -> None: - """Add a flag to the object.""" - self.flags |= flag + return any(f.value in set(self) for f in flags) - def remove(self, flag: AnnotationFlags) -> None: - """Remove a flag from the object.""" - self.flags &= ~flag - - def __eq__(self, o: object) -> bool: + def __eq__(self, other: object) -> bool: """Check if the object is equal to another.""" - if isinstance(o, AnnotationResult): - return self.flags == o.flags - elif isinstance(o, (AnnotationFlags, int)): - return self.flags == o + if isinstance(other, AnnotationResult): + return set(self) == set(other) return False - def __str__(self) -> str: - """String representation of the flags.""" - flags: list[str] = [] - for flag in AnnotationFlags: - if self.flags & flag == flag and flag.name is not None: - flags.append(flag.name) - return f"{type(self).__name__}({', '.join(flags)})" + # def __repr__(self) -> str: + # """Return a representation of the object.""" + # return repr([f.value for f in self]) @dataclass diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 035cae72..dc89bad2 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -70,7 +70,8 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) - client.on(AgentServerMessageType.END_OF_TURN, log_message) + client.on(AgentServerMessageType.TURN_STARTED, log_message) + client.on(AgentServerMessageType.TURN_ENDED, log_message) # Load the audio file `./assets/audio_01.wav` audio_file = "./assets/audio_01.wav" diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py new file mode 100644 index 00000000..885cd72e --- /dev/null +++ b/tests/voice/test_04_models.py @@ -0,0 +1,54 @@ +import json + +import pytest + +from speechmatics.voice import AnnotationFlags +from speechmatics.voice import AnnotationResult + + +@pytest.mark.asyncio +async def test_annotation_result(): + """Test AnnotationResult.""" + + # Create a new annotation + annotation = AnnotationResult() + assert annotation is not None + + # Add flags + annotation.add(AnnotationFlags.NO_TEXT) + annotation.add(AnnotationFlags.HAS_DISFLUENCY) + annotation.add(AnnotationFlags.MULTIPLE_SPEAKERS) + + # Has a flag + assert annotation.has(AnnotationFlags.NO_TEXT) + assert annotation.has(AnnotationFlags.HAS_DISFLUENCY) + assert annotation.has(AnnotationFlags.MULTIPLE_SPEAKERS) + + # Remove a flag + annotation.remove(AnnotationFlags.MULTIPLE_SPEAKERS) + assert not annotation.has(AnnotationFlags.MULTIPLE_SPEAKERS) + + # Add existing flag + annotation.add(AnnotationFlags.NO_TEXT) + assert annotation.has(AnnotationFlags.NO_TEXT) + assert str(annotation) == "['no_text', 'has_disfluency']" + + # Add multiple flags + annotation.add(AnnotationFlags.MULTIPLE_SPEAKERS, AnnotationFlags.STARTS_WITH_DISFLUENCY) + assert annotation.has(AnnotationFlags.MULTIPLE_SPEAKERS, AnnotationFlags.STARTS_WITH_DISFLUENCY) + + # Remove multiple flags + annotation.remove(AnnotationFlags.MULTIPLE_SPEAKERS, AnnotationFlags.STARTS_WITH_DISFLUENCY) + assert not annotation.has(AnnotationFlags.MULTIPLE_SPEAKERS, AnnotationFlags.STARTS_WITH_DISFLUENCY) + + # Compare + assert annotation == AnnotationResult([AnnotationFlags.HAS_DISFLUENCY, AnnotationFlags.NO_TEXT]) + + # Compare with non AnnotationResult + assert annotation != "string" + assert annotation != 123 + + # String representation + assert str(annotation) == "['no_text', 'has_disfluency']" + assert str({"annotation": annotation}) == "{'annotation': ['no_text', 'has_disfluency']}" + assert json.dumps({"annotation": annotation}) == '{"annotation": ["no_text", "has_disfluency"]}' From 1ee35a3d19936ac94e630b6bfab6ba39d3def894 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 22:39:55 +0100 Subject: [PATCH 022/215] updated tests --- examples/voice/file_to_jsonl.py | 7 +- sdk/voice/speechmatics/voice/_client.py | 4 +- sdk/voice/speechmatics/voice/_models.py | 4 +- tests/voice/_utils.py | 29 +++ .../assets/{chat_01.jsonl => chat1.jsonl} | 0 tests/voice/assets/chat2.jsonl | 225 ++++++++++++++++++ tests/voice/test_04_models.py | 135 ++++++++++- 7 files changed, 397 insertions(+), 7 deletions(-) rename tests/voice/assets/{chat_01.jsonl => chat1.jsonl} (100%) create mode 100644 tests/voice/assets/chat2.jsonl diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 955c1198..9d572646 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -15,7 +15,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice import VoiceAgentConfig, AdditionalVocabEntry async def main() -> None: @@ -34,10 +34,13 @@ async def main() -> None: api_key=args.api_key, url=args.url, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.2, + end_of_utterance_silence_trigger=0.5, max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matters", "speech magic"]) + ], ), ) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 856d91f7..b6dcab52 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -241,7 +241,7 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: self._logger.debug("End of utterance") - # self.finalize_segments() + # self.finalize() # pass async def connect(self) -> None: @@ -693,7 +693,7 @@ def _calc_delay_to_end_of_turn( # Return the result return should_emit, emit_final_delay - def finalize_segments(self, ttl: Optional[float] = None) -> None: + def finalize(self, ttl: Optional[float] = None) -> None: """Finalize segments. This function will emit segments in the buffer without any further checks diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index f036b98a..4fc4ab08 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -285,9 +285,9 @@ class AgentServerMessageType(str, Enum): TURN_STARTED = "TurnStarted" TURN_ENDED = "TurnEnded" - # Turn / segment messages - ADD_SEGMENTS = "AddSegments" + # Segment messages ADD_INTERIM_SEGMENTS = "AddInterimSegments" + ADD_SEGMENTS = "AddSegments" # Speaker messages SPEAKERS_RESULT = "SpeakersResult" diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index 6dcd93a8..b88ab437 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -1,8 +1,10 @@ import asyncio +import json import os import time from dataclasses import asdict from dataclasses import is_dataclass +from typing import Any from typing import Callable from typing import Optional @@ -130,3 +132,30 @@ def to_serializable(obj): # fallback → string return str(obj) + + +class ConversationLog: + """Load a JSONL past conversation.""" + + def __init__(self, file: str): + """Load a JSONL past conversation. + + Args: + file (str): Path to the JSONL file. + """ + self.file = file + self.conversation = self._load_conversation() + + def _load_conversation(self): + """Load a JSONL past conversation.""" + with open(self.file) as f: + return [json.loads(line) for line in f] + + def get_conversation(self, filter: Optional[list[str]] = None) -> list[dict[str, Any]]: + """Get the conversation.""" + try: + if filter: + return [line for line in self.conversation if line["payload"]["message"] in filter] + return list(self.conversation) + except KeyError: + return [] diff --git a/tests/voice/assets/chat_01.jsonl b/tests/voice/assets/chat1.jsonl similarity index 100% rename from tests/voice/assets/chat_01.jsonl rename to tests/voice/assets/chat1.jsonl diff --git a/tests/voice/assets/chat2.jsonl b/tests/voice/assets/chat2.jsonl new file mode 100644 index 00000000..1142058e --- /dev/null +++ b/tests/voice/assets/chat2.jsonl @@ -0,0 +1,225 @@ +{"ts": 2e-06, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./examples/example2.wav"}} +{"ts": 6.7e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.5, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} +{"ts": 0.000108, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.5}, "ctrl": null, "channel_diarization_labels": null}} +{"ts": 0.000132, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} +{"ts": 0.138386, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-12T15:51:53Z"}} +{"ts": 1.888186, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "09aaa112-9aee-4e1b-b7eb-b7e6016f09bd", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} +{"ts": 2.40306, "audio_ts": 0.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 2.922847, "audio_ts": 1.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.0, "transcript": ""}}} +{"ts": 3.221564, "audio_ts": 1.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.0, "transcript": "Welcome"}}} +{"ts": 3.222249, "audio_ts": 1.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 3.228478, "audio_ts": 1.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 3.52808, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.0, "transcript": "Welcome to "}}} +{"ts": 3.529091, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 3.535048, "audio_ts": 1.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 3.815655, "audio_ts": 1.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.64, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 3.821763, "audio_ts": 1.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 4.263167, "audio_ts": 2.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 2.0, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 4.269567, "audio_ts": 2.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 4.587797, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.96, "start_time": 1.0, "transcript": "Speechmatics "}}} +{"ts": 4.588038, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.588346, "audio_ts": 2.7, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 4.594102, "audio_ts": 2.71, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} +{"ts": 4.921012, "audio_ts": 3.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.04, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.921238, "audio_ts": 3.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.927861, "audio_ts": 3.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} +{"ts": 5.302842, "audio_ts": 3.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}], "metadata": {"end_time": 3.08, "start_time": 1.96, "transcript": ". "}}} +{"ts": 5.302946, "audio_ts": 3.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} +{"ts": 5.303833, "audio_ts": 3.42, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 3.16, "start_time": 3.16}}} +{"ts": 5.308695, "audio_ts": 3.42, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 8, "start_time": 1.96, "end_time": 1.96, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 5.64786, "audio_ts": 3.76, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} +{"ts": 6.053817, "audio_ts": 4.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.08, "transcript": "We're"}}} +{"ts": 6.054156, "audio_ts": 4.17, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 6.060356, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 9, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 6.382232, "audio_ts": 4.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.08, "transcript": "We're "}}} +{"ts": 6.382493, "audio_ts": 4.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} +{"ts": 6.388955, "audio_ts": 4.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 6.753409, "audio_ts": 4.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} +{"ts": 6.753704, "audio_ts": 4.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} +{"ts": 6.759998, "audio_ts": 4.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.064508, "audio_ts": 5.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} +{"ts": 7.064667, "audio_ts": 5.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} +{"ts": 7.071052, "audio_ts": 5.18, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.471408, "audio_ts": 5.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} +{"ts": 7.472128, "audio_ts": 5.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} +{"ts": 7.478006, "audio_ts": 5.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.792774, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} +{"ts": 7.79933, "audio_ts": 5.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.237617, "audio_ts": 6.35, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} +{"ts": 8.237751, "audio_ts": 6.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech to"}}} +{"ts": 8.243272, "audio_ts": 6.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 5.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.506788, "audio_ts": 6.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} +{"ts": 8.507043, "audio_ts": 6.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} +{"ts": 8.512609, "audio_ts": 6.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.93321, "audio_ts": 7.05, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} +{"ts": 8.933469, "audio_ts": 7.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} +{"ts": 8.933784, "audio_ts": 7.05, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 8.940048, "audio_ts": 7.05, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 9.240444, "audio_ts": 7.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} +{"ts": 9.240744, "audio_ts": 7.35, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 9.246894, "audio_ts": 7.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 9.663073, "audio_ts": 7.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} +{"ts": 9.663265, "audio_ts": 7.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} +{"ts": 9.669355, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 10.008257, "audio_ts": 8.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} +{"ts": 10.008693, "audio_ts": 8.12, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 10.008839, "audio_ts": 8.12, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 10.014702, "audio_ts": 8.13, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 10.016382, "audio_ts": 8.13, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "slow_speaker"]}]}} +{"ts": 10.329943, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} +{"ts": 10.330203, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} +{"ts": 10.335966, "audio_ts": 8.45, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 10.752302, "audio_ts": 8.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} +{"ts": 10.753003, "audio_ts": 8.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} +{"ts": 10.75884, "audio_ts": 8.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.065937, "audio_ts": 9.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} +{"ts": 11.066226, "audio_ts": 9.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} +{"ts": 11.072343, "audio_ts": 9.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.395428, "audio_ts": 9.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} +{"ts": 11.396274, "audio_ts": 9.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} +{"ts": 11.402234, "audio_ts": 9.52, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.771481, "audio_ts": 9.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} +{"ts": 11.772237, "audio_ts": 9.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} +{"ts": 11.778482, "audio_ts": 9.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 12.185364, "audio_ts": 10.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} +{"ts": 12.191717, "audio_ts": 10.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 61, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 12.510004, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} +{"ts": 12.51643, "audio_ts": 10.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 12.853021, "audio_ts": 10.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} +{"ts": 12.858984, "audio_ts": 10.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 13.308448, "audio_ts": 11.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "three. 123. "}}} +{"ts": 13.308567, "audio_ts": 11.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.308661, "audio_ts": 11.42, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 13.314061, "audio_ts": 11.43, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.6, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "123", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 13.52763, "audio_ts": 11.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 10.72, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.527825, "audio_ts": 11.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.922054, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.922129, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} +{"ts": 13.922158, "audio_ts": 12.04, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.8, "start_time": 11.8}}} +{"ts": 14.385998, "audio_ts": 12.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} +{"ts": 14.386353, "audio_ts": 12.5, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 14.391992, "audio_ts": 12.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 14.633662, "audio_ts": 12.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} +{"ts": 14.633813, "audio_ts": 12.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} +{"ts": 14.634032, "audio_ts": 12.75, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 14.634153, "audio_ts": 12.75, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 14.64005, "audio_ts": 12.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 14.976035, "audio_ts": 13.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} +{"ts": 14.976252, "audio_ts": 13.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} +{"ts": 14.981592, "audio_ts": 13.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 15.377371, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} +{"ts": 15.378387, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} +{"ts": 15.385407, "audio_ts": 13.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 15.737837, "audio_ts": 13.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} +{"ts": 15.73864, "audio_ts": 13.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} +{"ts": 15.744679, "audio_ts": 13.86, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 16.071516, "audio_ts": 14.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} +{"ts": 16.071747, "audio_ts": 14.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} +{"ts": 16.077695, "audio_ts": 14.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 16.414864, "audio_ts": 14.53, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} +{"ts": 16.415066, "audio_ts": 14.53, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} +{"ts": 16.421228, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 16.843428, "audio_ts": 14.96, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} +{"ts": 16.84362, "audio_ts": 14.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} +{"ts": 16.849775, "audio_ts": 14.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 17.287971, "audio_ts": 15.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} +{"ts": 17.288201, "audio_ts": 15.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} +{"ts": 17.29366, "audio_ts": 15.41, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 17.520679, "audio_ts": 15.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} +{"ts": 17.520938, "audio_ts": 15.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} +{"ts": 17.521447, "audio_ts": 15.63, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 17.527366, "audio_ts": 15.64, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 17.902758, "audio_ts": 16.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} +{"ts": 17.903008, "audio_ts": 16.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 17.909177, "audio_ts": 16.02, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 18.262821, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} +{"ts": 18.263012, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} +{"ts": 18.268827, "audio_ts": 16.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 18.633779, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} +{"ts": 18.634053, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} +{"ts": 18.640547, "audio_ts": 16.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 18.943305, "audio_ts": 17.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} +{"ts": 18.943422, "audio_ts": 17.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} +{"ts": 18.94966, "audio_ts": 17.06, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.74, "result": {"final": false, "alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 19.3389, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.72, "start_time": 16.2, "transcript": "API. "}}} +{"ts": 19.33907, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 16.8, "transcript": ""}}} +{"ts": 19.339197, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 19.344855, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 19.675487, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 16.8, "transcript": ""}}} +{"ts": 20.032894, "audio_ts": 18.15, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 16.72, "transcript": "We "}}} +{"ts": 20.033185, "audio_ts": 18.15, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} +{"ts": 20.03351, "audio_ts": 18.15, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 20.039813, "audio_ts": 18.15, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} +{"ts": 20.406231, "audio_ts": 18.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} +{"ts": 20.406479, "audio_ts": 18.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} +{"ts": 20.412281, "audio_ts": 18.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} +{"ts": 20.765333, "audio_ts": 18.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} +{"ts": 20.765568, "audio_ts": 18.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} +{"ts": 20.771675, "audio_ts": 18.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.83, "result": {"final": false, "alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.133457, "audio_ts": 19.25, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} +{"ts": 21.133646, "audio_ts": 19.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} +{"ts": 21.139587, "audio_ts": 19.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.461483, "audio_ts": 19.57, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} +{"ts": 21.461757, "audio_ts": 19.57, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} +{"ts": 21.468508, "audio_ts": 19.58, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.849236, "audio_ts": 19.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} +{"ts": 21.855117, "audio_ts": 19.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 22.189932, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.52, "start_time": 18.8, "transcript": "results "}}} +{"ts": 22.191317, "audio_ts": 20.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 19.6, "transcript": ""}}} +{"ts": 22.191722, "audio_ts": 20.31, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 22.197864, "audio_ts": 20.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 22.553322, "audio_ts": 20.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.36, "start_time": 19.52, "transcript": ". The"}}} +{"ts": 22.553734, "audio_ts": 20.67, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S3"}}} +{"ts": 22.560245, "audio_ts": 20.67, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}, {"speaker_id": "S3", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S3", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 22.917949, "audio_ts": 21.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.28, "start_time": 19.52, "transcript": ". The "}}} +{"ts": 22.918164, "audio_ts": 21.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.28, "transcript": "results"}}} +{"ts": 22.918499, "audio_ts": 21.03, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S3"}}} +{"ts": 22.918602, "audio_ts": 21.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 22.924306, "audio_ts": 21.04, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 128, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 22.924952, "audio_ts": 21.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.28, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 0.73, "result": {"final": false, "alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 23.370478, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.28, "transcript": "results? "}}} +{"ts": 23.370714, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} +{"ts": 23.370926, "audio_ts": 21.48, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 23.376688, "audio_ts": 21.49, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 20.28, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": "?", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 23.662326, "audio_ts": 21.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} +{"ts": 23.662507, "audio_ts": 21.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are pretty"}}} +{"ts": 23.662712, "audio_ts": 21.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 23.668783, "audio_ts": 21.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 24.000056, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} +{"ts": 24.000313, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} +{"ts": 24.006407, "audio_ts": 22.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 24.373133, "audio_ts": 22.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} +{"ts": 24.374785, "audio_ts": 22.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} +{"ts": 24.381632, "audio_ts": 22.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 24.707232, "audio_ts": 22.82, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.24, "start_time": 21.56, "transcript": "amazing. "}}} +{"ts": 24.707448, "audio_ts": 22.82, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.32, "transcript": ""}}} +{"ts": 24.707509, "audio_ts": 22.82, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 24.712719, "audio_ts": 22.83, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 25.077868, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.32, "start_time": 22.32, "transcript": ""}}} +{"ts": 25.077952, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.32, "transcript": ""}}} +{"ts": 25.565104, "audio_ts": 23.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.24, "transcript": "Thank you"}}} +{"ts": 25.56526, "audio_ts": 23.68, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 25.57096, "audio_ts": 23.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 142, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 25.800109, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.24, "transcript": "Thank "}}} +{"ts": 25.800404, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you."}}} +{"ts": 25.806547, "audio_ts": 23.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 145, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 146, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 26.145423, "audio_ts": 24.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} +{"ts": 26.145641, "audio_ts": 24.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.145718, "audio_ts": 24.26, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} +{"ts": 26.145902, "audio_ts": 24.26, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 26.151383, "audio_ts": 24.27, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 147, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 148, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 26.514257, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.859512, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.859743, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.224564, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.562289, "audio_ts": 25.68, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.56251, "audio_ts": 25.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} +{"ts": 27.935434, "audio_ts": 26.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} +{"ts": 28.289689, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.48, "transcript": ""}}} +{"ts": 28.289875, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.669148, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.997916, "audio_ts": 27.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.99816, "audio_ts": 27.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.392005, "audio_ts": 27.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.747696, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} \ No newline at end of file diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 885cd72e..702019b4 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -1,14 +1,29 @@ +import asyncio +import datetime import json +import os +from typing import Any +from typing import Optional import pytest +from _utils import ConversationLog +from _utils import get_client +from speechmatics.voice import AgentServerMessageType from speechmatics.voice import AnnotationFlags from speechmatics.voice import AnnotationResult +from speechmatics.voice import SpeakerSegment +from speechmatics.voice import SpeechFragment @pytest.mark.asyncio async def test_annotation_result(): - """Test AnnotationResult.""" + """Test AnnotationResult. + + - create new annotation + - add, remove, check for flags + - serialize to JSON + """ # Create a new annotation annotation = AnnotationResult() @@ -52,3 +67,121 @@ async def test_annotation_result(): assert str(annotation) == "['no_text', 'has_disfluency']" assert str({"annotation": annotation}) == "{'annotation': ['no_text', 'has_disfluency']}" assert json.dumps({"annotation": annotation}) == '{"annotation": ["no_text", "has_disfluency"]}' + + +@pytest.mark.asyncio +async def test_speech_fragments(): + """Test SpeechFragment. + + - create fragment(s) + - serialize to JSON + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + + # Start time + start_time = datetime.datetime.now() + + # Create a client + client = await get_client(api_key="NONE", connect=False) + assert client is not None + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Reset message + def message_reset(): + nonlocal last_message + last_message = None + event_rx.clear() + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.05) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + message_reset() + client.once(AgentServerMessageType.ADD_INTERIM_SEGMENTS, message_rx) + + # Inject first partial + await send_message(2, use_ttl=False) + + # Wait for partial transcript + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_INTERIM_SEGMENTS event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_INTERIM_SEGMENTS + + # Check the segment + segments: list[SpeakerSegment] = last_message.get("segments", []) + assert len(segments) == 1 + assert segments[0].speaker_id == "S1" + assert segments[0].text == "Welcome" + assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome" + + # Add listener for final segment + message_reset() + client.once(AgentServerMessageType.ADD_SEGMENTS, message_rx) + + # Send a more partials and finals + await send_message(3, count=11, use_ttl=False) + + # Wait for partial transcript + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_SEGMENTS event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENTS + + # Check the segment + segments: list[SpeakerSegment] = last_message.get("segments", []) + assert len(segments) == 1 + assert segments[0].speaker_id == "S1" + assert segments[0].text == "Welcome to Speechmatics." + assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome to Speechmatics." + + # Check the contents of the segment + fragments: list[SpeechFragment] = segments[0].fragments + assert len(fragments) == 4 + + +@pytest.mark.asyncio +async def test_speaker_segment(): + """Test SpeakerSegment. + + - create segment + - serialize to JSON + """ + + # Create a new segment + segment = SpeakerSegment() + assert segment is not None From 1e593e061bd0835aea40f8949456d5e8ca30f1b9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 22:40:21 +0100 Subject: [PATCH 023/215] File fix. --- tests/voice/assets/chat2.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/voice/assets/chat2.jsonl b/tests/voice/assets/chat2.jsonl index 1142058e..4f632e6e 100644 --- a/tests/voice/assets/chat2.jsonl +++ b/tests/voice/assets/chat2.jsonl @@ -222,4 +222,4 @@ {"ts": 28.99816, "audio_ts": 27.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} {"ts": 29.392005, "audio_ts": 27.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} {"ts": 29.747696, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} \ No newline at end of file +{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} From c9f79486868363f07e25b97929ef8c3eeab36848 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 12 Sep 2025 23:11:08 +0100 Subject: [PATCH 024/215] updated tests and move to `_helpers` file. --- examples/voice/file_to_jsonl.py | 14 +-------- sdk/voice/speechmatics/voice/_helpers.py | 40 ++++++++++++++++++++++++ sdk/voice/speechmatics/voice/_logging.py | 2 ++ sdk/voice/speechmatics/voice/_models.py | 1 + tests/voice/_utils.py | 25 --------------- tests/voice/test_03_conversation.py | 2 +- tests/voice/test_04_models.py | 1 + 7 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 sdk/voice/speechmatics/voice/_helpers.py diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 9d572646..836749ac 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -16,6 +16,7 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig, AdditionalVocabEntry +from speechmatics.voice._helpers import to_serializable async def main() -> None: @@ -107,19 +108,6 @@ def log_message(message): assert not client._is_connected -def to_serializable(obj) -> str: - """Convert an object into a JSON-serializable form.""" - if is_dataclass(obj): - return {k: to_serializable(v) for k, v in asdict(obj).items()} - if isinstance(obj, dict): - return {k: to_serializable(v) for k, v in obj.items()} - if isinstance(obj, (list, tuple)): - return [to_serializable(v) for v in obj] - if isinstance(obj, (str, int, float, bool)) or obj is None: - return obj - return str(obj) - - async def send_audio_file( client: VoiceAgentClient, file: str, diff --git a/sdk/voice/speechmatics/voice/_helpers.py b/sdk/voice/speechmatics/voice/_helpers.py new file mode 100644 index 00000000..305eb27e --- /dev/null +++ b/sdk/voice/speechmatics/voice/_helpers.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +from dataclasses import asdict +from dataclasses import is_dataclass +from typing import Any +from typing import cast + + +def to_serializable(obj: Any): + """Convert an object into a JSON-serializable form. + + Args: + obj: The object to convert. + + Returns: + The JSON-serializable form of the object. + """ + + # Dataclasses → dict + if is_dataclass(obj) and not isinstance(obj, type): + return {k: to_serializable(v) for k, v in asdict(cast(Any, obj)).items()} + + # dicts → dict with serializable values + if isinstance(obj, dict): + return {k: to_serializable(v) for k, v in obj.items()} + + # lists/tuples → list with serializable items + if isinstance(obj, (list, tuple)): + return [to_serializable(v) for v in obj] + + # basic types → leave as is + if isinstance(obj, (str, int, float, bool)) or obj is None: + return obj + + # fallback → string + return str(obj) diff --git a/sdk/voice/speechmatics/voice/_logging.py b/sdk/voice/speechmatics/voice/_logging.py index 18f7419f..5df400ef 100644 --- a/sdk/voice/speechmatics/voice/_logging.py +++ b/sdk/voice/speechmatics/voice/_logging.py @@ -2,6 +2,8 @@ # Copyright (c) 2025, Speechmatics / Cantab Research Ltd # +from __future__ import annotations + import logging logger = logging.getLogger(__name__) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 4fc4ab08..ece0fd2b 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -2,6 +2,7 @@ # Copyright (c) 2025, Speechmatics / Cantab Research Ltd # +from __future__ import annotations import datetime from dataclasses import dataclass diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index b88ab437..1aa1b847 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -2,8 +2,6 @@ import json import os import time -from dataclasses import asdict -from dataclasses import is_dataclass from typing import Any from typing import Callable from typing import Optional @@ -111,29 +109,6 @@ async def send_audio_file( next_time += delay -def to_serializable(obj): - """Convert an object into a JSON-serializable form.""" - - # Dataclasses → dict - if is_dataclass(obj): - return {k: to_serializable(v) for k, v in asdict(obj).items()} - - # dicts → dict with serializable values - if isinstance(obj, dict): - return {k: to_serializable(v) for k, v in obj.items()} - - # lists/tuples → list with serializable items - if isinstance(obj, (list, tuple)): - return [to_serializable(v) for v in obj] - - # basic types → leave as is - if isinstance(obj, (str, int, float, bool)) or obj is None: - return obj - - # fallback → string - return str(obj) - - class ConversationLog: """Load a JSONL past conversation.""" diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index dc89bad2..ed3cca5a 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -5,11 +5,11 @@ import pytest from _utils import get_client from _utils import send_audio_file -from _utils import to_serializable from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._helpers import to_serializable api_key = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 702019b4..0751264f 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -74,6 +74,7 @@ async def test_speech_fragments(): """Test SpeechFragment. - create fragment(s) + - check output from processing conversation - serialize to JSON """ From 108cf839c40ef26a262be22ebd0f418a23e893df Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:23:17 +0100 Subject: [PATCH 025/215] calculate end of utterance internally --- sdk/voice/speechmatics/voice/_client.py | 150 ++++++++++++++---------- sdk/voice/speechmatics/voice/_models.py | 11 +- 2 files changed, 98 insertions(+), 63 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index b6dcab52..5ce5e494 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -28,6 +28,7 @@ from ._models import DiarizationFocusMode from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode +from ._models import FragmentUtils from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment @@ -100,6 +101,7 @@ def __init__( self._speech_fragments: list[SpeechFragment] = [] self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() self._current_view: Optional[SpeakerSegmentView] = None + self._previous_view: Optional[SpeakerSegmentView] = None # Speaking states self._is_speaking: bool = False @@ -583,8 +585,33 @@ async def _process_speech_fragments(self) -> None: # Emit the segments asyncio.create_task(self._emit_segments()) - # TODO: process in-built end of utterance - e.g. `_calc_delay_to_end_of_turn`? - # TODO: process in-built VAD / end of speech + # Only do this for ADAPTIVE end of utterance + if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE: + """If ADAPTIVE, then calculate whether the turn has completed.""" + + # Compare previous view to this view + if self._previous_view: + changes = FragmentUtils.compare_views(self._previous_view, self._current_view) + else: + changes = AnnotationResult.from_flags(AnnotationFlags.NEW) + + # Update the previous view + self._previous_view = self._current_view + + # Calculate delay to end of utterance + should_emit, emit_final_delay = self._calc_delay_to_end_of_utterance(self._current_view, changes) + + # Emit finalized segments + async def emit(delay: float | None) -> None: + if delay and delay > 0: + await asyncio.sleep(delay) + asyncio.create_task(self._emit_segments(finalize=True, end_of_utterance=True)) + + # Finalize after delay + if should_emit: + if self._finalize_task: + self._finalize_task.cancel() + self._finalize_task = asyncio.create_task(emit(emit_final_delay)) def _update_current_view(self) -> None: """Load the current view of the speech fragments.""" @@ -594,7 +621,7 @@ def _update_current_view(self) -> None: focus_speakers=self._dz_config.focus_speakers, ) - def _calc_delay_to_end_of_turn( + def _calc_delay_to_end_of_utterance( self, view: SpeakerSegmentView, view_changes: AnnotationResult, @@ -623,13 +650,6 @@ def _calc_delay_to_end_of_turn( last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None - # Elapsed time from last segment to now - # elapsed_time = ( - # self._total_time - last_active_segment.end_time - # if last_active_segment - # else view.end_time - # ) - # If this is NEW or UPDATED_FULL_LCASE if view_changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL_LCASE): """Process the annotation flags to determine how long before sending a final segment.""" @@ -643,14 +663,17 @@ def _calc_delay_to_end_of_turn( """Check the contents of the last segment." Check for: + - has any disfluencies - ends with a disfluency + - speed of speech + - ends with finalizes end of sentence """ # Minimum delay delay = max(self._end_of_utterance_delay, 0.5) - # Delay multiplier - multiplier = 1.5 + # Delay multiplier (default to 2x) + multiplier = 2.0 # Very speaking if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): @@ -668,6 +691,10 @@ def _calc_delay_to_end_of_turn( if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): multiplier *= 4.0 + # Ends with an end of sentence + if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): + multiplier = 0.5 + # Calculate the delay emit_final_delay = delay * multiplier @@ -682,14 +709,6 @@ def _calc_delay_to_end_of_turn( # Emit segments should_emit = True - # Remove any elapsed time from the delay - # if emit_final_delay is not None: - # emit_final_delay = max( - # emit_final_delay - elapsed_time, - # self._end_of_utterance_delay - elapsed_time, - # 0.001, - # ) - # Return the result return should_emit, emit_final_delay @@ -701,7 +720,7 @@ def finalize(self, ttl: Optional[float] = None) -> None: will be forced through without yielding for any remaining STT messages. Args: - ttl: Optional delay before finalizing partial segments (defaults to 0.01 seconds). + ttl: Optional delay before finalizing partial segments. """ async def emit() -> None: @@ -714,7 +733,7 @@ async def emit() -> None: asyncio.create_task(emit()) - async def _emit_segments(self, finalize: bool = False) -> None: + async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = False) -> None: """Emit segments to listeners. This function will emit segments in the view without any further checks @@ -723,46 +742,53 @@ async def _emit_segments(self, finalize: bool = False) -> None: Args: finalize: Whether to finalize all segments. + end_of_utterance: Whether to emit an end of turn event. """ # Lock the speech fragments - async with self._speech_fragments_lock: - # Skip if no view - if not self._current_view or self._current_view.segment_count == 0: - return - - # Force finalize - if finalize: - final_segments = self._current_view.segments - interim_segments = [] - - # Split between finals and interim segments - else: - final_segments = [ - s - for s in self._current_view.segments - if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) - ] - interim_segments = [s for s in self._current_view.segments if s not in final_segments] - - # Emit finals first - if final_segments: - self.emit( - AgentServerMessageType.ADD_SEGMENTS, - {"message": AgentServerMessageType.ADD_SEGMENTS, "segments": final_segments}, - ) - self._trim_before_time = final_segments[-1].end_time - self._speech_fragments = [f for f in self._speech_fragments if f.start_time >= self._trim_before_time] + if self._current_view and self._current_view.segment_count > 0: + async with self._speech_fragments_lock: + # Force finalize + if finalize: + final_segments = self._current_view.segments + interim_segments = [] + + # Split between finals and interim segments + else: + final_segments = [ + s + for s in self._current_view.segments + if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + ] + interim_segments = [s for s in self._current_view.segments if s not in final_segments] + + # Emit finals first + if final_segments: + self.emit( + AgentServerMessageType.ADD_SEGMENTS, + {"message": AgentServerMessageType.ADD_SEGMENTS.value, "segments": final_segments}, + ) + self._trim_before_time = final_segments[-1].end_time + self._speech_fragments = [ + f for f in self._speech_fragments if f.start_time >= self._trim_before_time + ] + + # Emit interim segments + if interim_segments: + self.emit( + AgentServerMessageType.ADD_INTERIM_SEGMENTS, + {"message": AgentServerMessageType.ADD_INTERIM_SEGMENTS.value, "segments": interim_segments}, + ) - # Emit interim segments - if interim_segments: - self.emit( - AgentServerMessageType.ADD_INTERIM_SEGMENTS, - {"message": AgentServerMessageType.ADD_INTERIM_SEGMENTS, "segments": interim_segments}, - ) + # Update the current view + self._update_current_view() - # Update the current view - self._update_current_view() + # Emit end of utterance + if end_of_utterance: + self.emit( + AgentServerMessageType.END_OF_UTTERANCE, + {"message": AgentServerMessageType.END_OF_UTTERANCE.value}, + ) def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. @@ -810,14 +836,14 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: self.emit( AgentServerMessageType.SPEAKING_ENDED, { - "message": AgentServerMessageType.SPEAKING_ENDED, + "message": AgentServerMessageType.SPEAKING_ENDED.value, "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False), }, ) self.emit( AgentServerMessageType.SPEAKING_STARTED, { - "message": AgentServerMessageType.SPEAKING_STARTED, + "message": AgentServerMessageType.SPEAKING_STARTED.value, "status": SpeakerVADStatus(speaker_id=speaker, is_active=True), }, ) @@ -833,10 +859,12 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: self._is_speaking = not self._is_speaking # Emit the event for latest speaker - msg = AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED + msg: AgentServerMessageType = ( + AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED + ) self.emit( msg, - {"message": msg, "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, + {"message": msg.value, "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, ) # Reset the current speaker diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index ece0fd2b..f1973db6 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -342,6 +342,13 @@ class AnnotationFlags(str, Enum): class AnnotationResult(list): """Processing result.""" + @staticmethod + def from_flags(*flags: AnnotationFlags) -> AnnotationResult: + """Create an AnnotationResult from a list of flags.""" + r = AnnotationResult() + r.add(*flags) + return r + def add(self, *flags: AnnotationFlags) -> None: """Add a flag(s) to the object.""" for flag in flags: @@ -773,9 +780,9 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) # Categorize the speaker - if wpm < 100: + if wpm < 50: result.add(AnnotationFlags.VERY_SLOW_SPEAKER) - elif wpm < 200: + elif wpm < 100: result.add(AnnotationFlags.SLOW_SPEAKER) elif wpm > 350: result.add(AnnotationFlags.FAST_SPEAKER) From eda02c1ff8768a98e06c7b6788c56be94d653e62 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:23:44 +0100 Subject: [PATCH 026/215] ADAPTIVE end of utterance tests --- tests/voice/assets/chat2.jsonl | 212 ----------------------------- tests/voice/assets/chat3.jsonl | 225 +++++++++++++++++++++++++++++++ tests/voice/test_04_models.py | 240 +++++++++++++++++++++++++++++++-- 3 files changed, 457 insertions(+), 220 deletions(-) create mode 100644 tests/voice/assets/chat3.jsonl diff --git a/tests/voice/assets/chat2.jsonl b/tests/voice/assets/chat2.jsonl index 4f632e6e..99e0acf4 100644 --- a/tests/voice/assets/chat2.jsonl +++ b/tests/voice/assets/chat2.jsonl @@ -1,225 +1,13 @@ -{"ts": 2e-06, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./examples/example2.wav"}} -{"ts": 6.7e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.5, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} -{"ts": 0.000108, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.5}, "ctrl": null, "channel_diarization_labels": null}} -{"ts": 0.000132, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} -{"ts": 0.138386, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-12T15:51:53Z"}} -{"ts": 1.888186, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "09aaa112-9aee-4e1b-b7eb-b7e6016f09bd", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} {"ts": 2.40306, "audio_ts": 0.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} {"ts": 2.922847, "audio_ts": 1.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.0, "transcript": ""}}} {"ts": 3.221564, "audio_ts": 1.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.0, "transcript": "Welcome"}}} -{"ts": 3.222249, "audio_ts": 1.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 3.228478, "audio_ts": 1.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} {"ts": 3.52808, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.0, "transcript": "Welcome to "}}} {"ts": 3.529091, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 3.535048, "audio_ts": 1.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} {"ts": 3.815655, "audio_ts": 1.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.64, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 3.821763, "audio_ts": 1.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} {"ts": 4.263167, "audio_ts": 2.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 2.0, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 4.269567, "audio_ts": 2.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} {"ts": 4.587797, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.96, "start_time": 1.0, "transcript": "Speechmatics "}}} {"ts": 4.588038, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.588346, "audio_ts": 2.7, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 4.594102, "audio_ts": 2.71, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} {"ts": 4.921012, "audio_ts": 3.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.04, "start_time": 2.04, "transcript": ""}}} {"ts": 4.921238, "audio_ts": 3.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.927861, "audio_ts": 3.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} {"ts": 5.302842, "audio_ts": 3.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}], "metadata": {"end_time": 3.08, "start_time": 1.96, "transcript": ". "}}} -{"ts": 5.302946, "audio_ts": 3.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} {"ts": 5.303833, "audio_ts": 3.42, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 3.16, "start_time": 3.16}}} -{"ts": 5.308695, "audio_ts": 3.42, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 8, "start_time": 1.96, "end_time": 1.96, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 5.64786, "audio_ts": 3.76, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} -{"ts": 6.053817, "audio_ts": 4.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.08, "transcript": "We're"}}} -{"ts": 6.054156, "audio_ts": 4.17, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 6.060356, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 9, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 6.382232, "audio_ts": 4.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.08, "transcript": "We're "}}} -{"ts": 6.382493, "audio_ts": 4.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} -{"ts": 6.388955, "audio_ts": 4.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 6.753409, "audio_ts": 4.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} -{"ts": 6.753704, "audio_ts": 4.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} -{"ts": 6.759998, "audio_ts": 4.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.064508, "audio_ts": 5.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} -{"ts": 7.064667, "audio_ts": 5.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} -{"ts": 7.071052, "audio_ts": 5.18, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.471408, "audio_ts": 5.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} -{"ts": 7.472128, "audio_ts": 5.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} -{"ts": 7.478006, "audio_ts": 5.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.792774, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} -{"ts": 7.79933, "audio_ts": 5.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.237617, "audio_ts": 6.35, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} -{"ts": 8.237751, "audio_ts": 6.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech to"}}} -{"ts": 8.243272, "audio_ts": 6.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 5.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.506788, "audio_ts": 6.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} -{"ts": 8.507043, "audio_ts": 6.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} -{"ts": 8.512609, "audio_ts": 6.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.93321, "audio_ts": 7.05, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} -{"ts": 8.933469, "audio_ts": 7.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} -{"ts": 8.933784, "audio_ts": 7.05, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 8.940048, "audio_ts": 7.05, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 9.240444, "audio_ts": 7.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} -{"ts": 9.240744, "audio_ts": 7.35, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 9.246894, "audio_ts": 7.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 9.663073, "audio_ts": 7.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} -{"ts": 9.663265, "audio_ts": 7.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} -{"ts": 9.669355, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 10.008257, "audio_ts": 8.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} -{"ts": 10.008693, "audio_ts": 8.12, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 10.008839, "audio_ts": 8.12, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 10.014702, "audio_ts": 8.13, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 10.016382, "audio_ts": 8.13, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "slow_speaker"]}]}} -{"ts": 10.329943, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} -{"ts": 10.330203, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} -{"ts": 10.335966, "audio_ts": 8.45, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 10.752302, "audio_ts": 8.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} -{"ts": 10.753003, "audio_ts": 8.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} -{"ts": 10.75884, "audio_ts": 8.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.065937, "audio_ts": 9.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} -{"ts": 11.066226, "audio_ts": 9.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} -{"ts": 11.072343, "audio_ts": 9.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.395428, "audio_ts": 9.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} -{"ts": 11.396274, "audio_ts": 9.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} -{"ts": 11.402234, "audio_ts": 9.52, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.771481, "audio_ts": 9.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} -{"ts": 11.772237, "audio_ts": 9.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} -{"ts": 11.778482, "audio_ts": 9.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 12.185364, "audio_ts": 10.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} -{"ts": 12.191717, "audio_ts": 10.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 61, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 12.510004, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} -{"ts": 12.51643, "audio_ts": 10.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 12.853021, "audio_ts": 10.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} -{"ts": 12.858984, "audio_ts": 10.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 13.308448, "audio_ts": 11.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "three. 123. "}}} -{"ts": 13.308567, "audio_ts": 11.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.308661, "audio_ts": 11.42, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 13.314061, "audio_ts": 11.43, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.6, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "123", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 13.52763, "audio_ts": 11.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 10.72, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.527825, "audio_ts": 11.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.922054, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.922129, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} -{"ts": 13.922158, "audio_ts": 12.04, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.8, "start_time": 11.8}}} -{"ts": 14.385998, "audio_ts": 12.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} -{"ts": 14.386353, "audio_ts": 12.5, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 14.391992, "audio_ts": 12.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 14.633662, "audio_ts": 12.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} -{"ts": 14.633813, "audio_ts": 12.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} -{"ts": 14.634032, "audio_ts": 12.75, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 14.634153, "audio_ts": 12.75, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 14.64005, "audio_ts": 12.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 14.976035, "audio_ts": 13.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} -{"ts": 14.976252, "audio_ts": 13.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} -{"ts": 14.981592, "audio_ts": 13.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 15.377371, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} -{"ts": 15.378387, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} -{"ts": 15.385407, "audio_ts": 13.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 15.737837, "audio_ts": 13.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} -{"ts": 15.73864, "audio_ts": 13.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} -{"ts": 15.744679, "audio_ts": 13.86, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 16.071516, "audio_ts": 14.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} -{"ts": 16.071747, "audio_ts": 14.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} -{"ts": 16.077695, "audio_ts": 14.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 16.414864, "audio_ts": 14.53, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} -{"ts": 16.415066, "audio_ts": 14.53, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} -{"ts": 16.421228, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 16.843428, "audio_ts": 14.96, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} -{"ts": 16.84362, "audio_ts": 14.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} -{"ts": 16.849775, "audio_ts": 14.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 17.287971, "audio_ts": 15.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} -{"ts": 17.288201, "audio_ts": 15.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} -{"ts": 17.29366, "audio_ts": 15.41, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 17.520679, "audio_ts": 15.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} -{"ts": 17.520938, "audio_ts": 15.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} -{"ts": 17.521447, "audio_ts": 15.63, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 17.527366, "audio_ts": 15.64, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 17.902758, "audio_ts": 16.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} -{"ts": 17.903008, "audio_ts": 16.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 17.909177, "audio_ts": 16.02, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 18.262821, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} -{"ts": 18.263012, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} -{"ts": 18.268827, "audio_ts": 16.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 18.633779, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} -{"ts": 18.634053, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} -{"ts": 18.640547, "audio_ts": 16.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 18.943305, "audio_ts": 17.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} -{"ts": 18.943422, "audio_ts": 17.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} -{"ts": 18.94966, "audio_ts": 17.06, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.74, "result": {"final": false, "alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 19.3389, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.72, "start_time": 16.2, "transcript": "API. "}}} -{"ts": 19.33907, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 16.8, "transcript": ""}}} -{"ts": 19.339197, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 19.344855, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 19.675487, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 16.8, "transcript": ""}}} -{"ts": 20.032894, "audio_ts": 18.15, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 16.72, "transcript": "We "}}} -{"ts": 20.033185, "audio_ts": 18.15, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} -{"ts": 20.03351, "audio_ts": 18.15, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 20.039813, "audio_ts": 18.15, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} -{"ts": 20.406231, "audio_ts": 18.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} -{"ts": 20.406479, "audio_ts": 18.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} -{"ts": 20.412281, "audio_ts": 18.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} -{"ts": 20.765333, "audio_ts": 18.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} -{"ts": 20.765568, "audio_ts": 18.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} -{"ts": 20.771675, "audio_ts": 18.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.83, "result": {"final": false, "alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.133457, "audio_ts": 19.25, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} -{"ts": 21.133646, "audio_ts": 19.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} -{"ts": 21.139587, "audio_ts": 19.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.461483, "audio_ts": 19.57, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} -{"ts": 21.461757, "audio_ts": 19.57, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} -{"ts": 21.468508, "audio_ts": 19.58, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.849236, "audio_ts": 19.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} -{"ts": 21.855117, "audio_ts": 19.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 22.189932, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.52, "start_time": 18.8, "transcript": "results "}}} -{"ts": 22.191317, "audio_ts": 20.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 19.6, "transcript": ""}}} -{"ts": 22.191722, "audio_ts": 20.31, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 22.197864, "audio_ts": 20.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 22.553322, "audio_ts": 20.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.36, "start_time": 19.52, "transcript": ". The"}}} -{"ts": 22.553734, "audio_ts": 20.67, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S3"}}} -{"ts": 22.560245, "audio_ts": 20.67, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}, {"speaker_id": "S3", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S3", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 22.917949, "audio_ts": 21.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.28, "start_time": 19.52, "transcript": ". The "}}} -{"ts": 22.918164, "audio_ts": 21.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.28, "transcript": "results"}}} -{"ts": 22.918499, "audio_ts": 21.03, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S3"}}} -{"ts": 22.918602, "audio_ts": 21.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 22.924306, "audio_ts": 21.04, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 128, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 22.924952, "audio_ts": 21.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.28, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 0.73, "result": {"final": false, "alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 23.370478, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.28, "transcript": "results? "}}} -{"ts": 23.370714, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} -{"ts": 23.370926, "audio_ts": 21.48, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 23.376688, "audio_ts": 21.49, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 20.28, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": "?", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 23.662326, "audio_ts": 21.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} -{"ts": 23.662507, "audio_ts": 21.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are pretty"}}} -{"ts": 23.662712, "audio_ts": 21.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 23.668783, "audio_ts": 21.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 24.000056, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} -{"ts": 24.000313, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} -{"ts": 24.006407, "audio_ts": 22.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 24.373133, "audio_ts": 22.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} -{"ts": 24.374785, "audio_ts": 22.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} -{"ts": 24.381632, "audio_ts": 22.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 24.707232, "audio_ts": 22.82, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.24, "start_time": 21.56, "transcript": "amazing. "}}} -{"ts": 24.707448, "audio_ts": 22.82, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.32, "transcript": ""}}} -{"ts": 24.707509, "audio_ts": 22.82, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 24.712719, "audio_ts": 22.83, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 25.077868, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.32, "start_time": 22.32, "transcript": ""}}} -{"ts": 25.077952, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.32, "transcript": ""}}} -{"ts": 25.565104, "audio_ts": 23.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.24, "transcript": "Thank you"}}} -{"ts": 25.56526, "audio_ts": 23.68, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 25.57096, "audio_ts": 23.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 142, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 25.800109, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.24, "transcript": "Thank "}}} -{"ts": 25.800404, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you."}}} -{"ts": 25.806547, "audio_ts": 23.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 145, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 146, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 26.145423, "audio_ts": 24.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} -{"ts": 26.145641, "audio_ts": 24.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.145718, "audio_ts": 24.26, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} -{"ts": 26.145902, "audio_ts": 24.26, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 26.151383, "audio_ts": 24.27, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 147, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 148, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 26.514257, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.859512, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.859743, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.224564, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.562289, "audio_ts": 25.68, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.56251, "audio_ts": 25.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} -{"ts": 27.935434, "audio_ts": 26.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} -{"ts": 28.289689, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.48, "transcript": ""}}} -{"ts": 28.289875, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.669148, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.997916, "audio_ts": 27.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.99816, "audio_ts": 27.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.392005, "audio_ts": 27.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.747696, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/assets/chat3.jsonl b/tests/voice/assets/chat3.jsonl new file mode 100644 index 00000000..4f632e6e --- /dev/null +++ b/tests/voice/assets/chat3.jsonl @@ -0,0 +1,225 @@ +{"ts": 2e-06, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./examples/example2.wav"}} +{"ts": 6.7e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.5, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} +{"ts": 0.000108, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.5}, "ctrl": null, "channel_diarization_labels": null}} +{"ts": 0.000132, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} +{"ts": 0.138386, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-12T15:51:53Z"}} +{"ts": 1.888186, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "09aaa112-9aee-4e1b-b7eb-b7e6016f09bd", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} +{"ts": 2.40306, "audio_ts": 0.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 2.922847, "audio_ts": 1.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.0, "transcript": ""}}} +{"ts": 3.221564, "audio_ts": 1.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.0, "transcript": "Welcome"}}} +{"ts": 3.222249, "audio_ts": 1.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 3.228478, "audio_ts": 1.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 3.52808, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.0, "transcript": "Welcome to "}}} +{"ts": 3.529091, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 3.535048, "audio_ts": 1.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 3.815655, "audio_ts": 1.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.64, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 3.821763, "audio_ts": 1.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 4.263167, "audio_ts": 2.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 2.0, "start_time": 1.0, "transcript": "Speechmatics"}}} +{"ts": 4.269567, "audio_ts": 2.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 4.587797, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.96, "start_time": 1.0, "transcript": "Speechmatics "}}} +{"ts": 4.588038, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.588346, "audio_ts": 2.7, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 4.594102, "audio_ts": 2.71, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} +{"ts": 4.921012, "audio_ts": 3.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.04, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.921238, "audio_ts": 3.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.04, "transcript": ""}}} +{"ts": 4.927861, "audio_ts": 3.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} +{"ts": 5.302842, "audio_ts": 3.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}], "metadata": {"end_time": 3.08, "start_time": 1.96, "transcript": ". "}}} +{"ts": 5.302946, "audio_ts": 3.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} +{"ts": 5.303833, "audio_ts": 3.42, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 3.16, "start_time": 3.16}}} +{"ts": 5.308695, "audio_ts": 3.42, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 8, "start_time": 1.96, "end_time": 1.96, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 5.64786, "audio_ts": 3.76, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} +{"ts": 6.053817, "audio_ts": 4.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.08, "transcript": "We're"}}} +{"ts": 6.054156, "audio_ts": 4.17, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 6.060356, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 9, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 6.382232, "audio_ts": 4.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.08, "transcript": "We're "}}} +{"ts": 6.382493, "audio_ts": 4.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} +{"ts": 6.388955, "audio_ts": 4.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 6.753409, "audio_ts": 4.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} +{"ts": 6.753704, "audio_ts": 4.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} +{"ts": 6.759998, "audio_ts": 4.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.064508, "audio_ts": 5.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} +{"ts": 7.064667, "audio_ts": 5.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} +{"ts": 7.071052, "audio_ts": 5.18, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.471408, "audio_ts": 5.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} +{"ts": 7.472128, "audio_ts": 5.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} +{"ts": 7.478006, "audio_ts": 5.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 7.792774, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} +{"ts": 7.79933, "audio_ts": 5.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.237617, "audio_ts": 6.35, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} +{"ts": 8.237751, "audio_ts": 6.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech to"}}} +{"ts": 8.243272, "audio_ts": 6.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 5.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.506788, "audio_ts": 6.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} +{"ts": 8.507043, "audio_ts": 6.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} +{"ts": 8.512609, "audio_ts": 6.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 8.93321, "audio_ts": 7.05, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} +{"ts": 8.933469, "audio_ts": 7.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} +{"ts": 8.933784, "audio_ts": 7.05, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 8.940048, "audio_ts": 7.05, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 9.240444, "audio_ts": 7.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} +{"ts": 9.240744, "audio_ts": 7.35, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 9.246894, "audio_ts": 7.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 9.663073, "audio_ts": 7.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} +{"ts": 9.663265, "audio_ts": 7.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} +{"ts": 9.669355, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 10.008257, "audio_ts": 8.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} +{"ts": 10.008693, "audio_ts": 8.12, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 10.008839, "audio_ts": 8.12, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 10.014702, "audio_ts": 8.13, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 10.016382, "audio_ts": 8.13, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "slow_speaker"]}]}} +{"ts": 10.329943, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} +{"ts": 10.330203, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} +{"ts": 10.335966, "audio_ts": 8.45, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 10.752302, "audio_ts": 8.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} +{"ts": 10.753003, "audio_ts": 8.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} +{"ts": 10.75884, "audio_ts": 8.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.065937, "audio_ts": 9.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} +{"ts": 11.066226, "audio_ts": 9.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} +{"ts": 11.072343, "audio_ts": 9.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.395428, "audio_ts": 9.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} +{"ts": 11.396274, "audio_ts": 9.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} +{"ts": 11.402234, "audio_ts": 9.52, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 11.771481, "audio_ts": 9.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} +{"ts": 11.772237, "audio_ts": 9.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} +{"ts": 11.778482, "audio_ts": 9.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 12.185364, "audio_ts": 10.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} +{"ts": 12.191717, "audio_ts": 10.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 61, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 12.510004, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} +{"ts": 12.51643, "audio_ts": 10.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 12.853021, "audio_ts": 10.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} +{"ts": 12.858984, "audio_ts": 10.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 13.308448, "audio_ts": 11.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "three. 123. "}}} +{"ts": 13.308567, "audio_ts": 11.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.308661, "audio_ts": 11.42, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 13.314061, "audio_ts": 11.43, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.6, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "123", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 13.52763, "audio_ts": 11.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 10.72, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.527825, "audio_ts": 11.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.922054, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 10.72, "transcript": ""}}} +{"ts": 13.922129, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} +{"ts": 13.922158, "audio_ts": 12.04, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.8, "start_time": 11.8}}} +{"ts": 14.385998, "audio_ts": 12.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} +{"ts": 14.386353, "audio_ts": 12.5, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 14.391992, "audio_ts": 12.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 14.633662, "audio_ts": 12.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} +{"ts": 14.633813, "audio_ts": 12.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} +{"ts": 14.634032, "audio_ts": 12.75, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 14.634153, "audio_ts": 12.75, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 14.64005, "audio_ts": 12.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 14.976035, "audio_ts": 13.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} +{"ts": 14.976252, "audio_ts": 13.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} +{"ts": 14.981592, "audio_ts": 13.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 15.377371, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} +{"ts": 15.378387, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} +{"ts": 15.385407, "audio_ts": 13.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 15.737837, "audio_ts": 13.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} +{"ts": 15.73864, "audio_ts": 13.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} +{"ts": 15.744679, "audio_ts": 13.86, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 16.071516, "audio_ts": 14.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} +{"ts": 16.071747, "audio_ts": 14.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} +{"ts": 16.077695, "audio_ts": 14.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 16.414864, "audio_ts": 14.53, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} +{"ts": 16.415066, "audio_ts": 14.53, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} +{"ts": 16.421228, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 16.843428, "audio_ts": 14.96, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} +{"ts": 16.84362, "audio_ts": 14.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} +{"ts": 16.849775, "audio_ts": 14.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 17.287971, "audio_ts": 15.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} +{"ts": 17.288201, "audio_ts": 15.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} +{"ts": 17.29366, "audio_ts": 15.41, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 17.520679, "audio_ts": 15.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} +{"ts": 17.520938, "audio_ts": 15.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} +{"ts": 17.521447, "audio_ts": 15.63, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 17.527366, "audio_ts": 15.64, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 17.902758, "audio_ts": 16.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} +{"ts": 17.903008, "audio_ts": 16.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 17.909177, "audio_ts": 16.02, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 18.262821, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} +{"ts": 18.263012, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} +{"ts": 18.268827, "audio_ts": 16.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 18.633779, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} +{"ts": 18.634053, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} +{"ts": 18.640547, "audio_ts": 16.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 18.943305, "audio_ts": 17.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} +{"ts": 18.943422, "audio_ts": 17.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} +{"ts": 18.94966, "audio_ts": 17.06, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.74, "result": {"final": false, "alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 19.3389, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.72, "start_time": 16.2, "transcript": "API. "}}} +{"ts": 19.33907, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 16.8, "transcript": ""}}} +{"ts": 19.339197, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 19.344855, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 19.675487, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 16.8, "transcript": ""}}} +{"ts": 20.032894, "audio_ts": 18.15, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 16.72, "transcript": "We "}}} +{"ts": 20.033185, "audio_ts": 18.15, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} +{"ts": 20.03351, "audio_ts": 18.15, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 20.039813, "audio_ts": 18.15, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} +{"ts": 20.406231, "audio_ts": 18.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} +{"ts": 20.406479, "audio_ts": 18.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} +{"ts": 20.412281, "audio_ts": 18.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} +{"ts": 20.765333, "audio_ts": 18.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} +{"ts": 20.765568, "audio_ts": 18.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} +{"ts": 20.771675, "audio_ts": 18.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.83, "result": {"final": false, "alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.133457, "audio_ts": 19.25, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} +{"ts": 21.133646, "audio_ts": 19.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} +{"ts": 21.139587, "audio_ts": 19.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.461483, "audio_ts": 19.57, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} +{"ts": 21.461757, "audio_ts": 19.57, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} +{"ts": 21.468508, "audio_ts": 19.58, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 21.849236, "audio_ts": 19.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} +{"ts": 21.855117, "audio_ts": 19.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 22.189932, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.52, "start_time": 18.8, "transcript": "results "}}} +{"ts": 22.191317, "audio_ts": 20.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 19.6, "transcript": ""}}} +{"ts": 22.191722, "audio_ts": 20.31, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 22.197864, "audio_ts": 20.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} +{"ts": 22.553322, "audio_ts": 20.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.36, "start_time": 19.52, "transcript": ". The"}}} +{"ts": 22.553734, "audio_ts": 20.67, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S3"}}} +{"ts": 22.560245, "audio_ts": 20.67, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}, {"speaker_id": "S3", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S3", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 22.917949, "audio_ts": 21.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.28, "start_time": 19.52, "transcript": ". The "}}} +{"ts": 22.918164, "audio_ts": 21.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.28, "transcript": "results"}}} +{"ts": 22.918499, "audio_ts": 21.03, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S3"}}} +{"ts": 22.918602, "audio_ts": 21.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 22.924306, "audio_ts": 21.04, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 128, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 22.924952, "audio_ts": 21.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.28, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 0.73, "result": {"final": false, "alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 23.370478, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.28, "transcript": "results? "}}} +{"ts": 23.370714, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} +{"ts": 23.370926, "audio_ts": 21.48, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 23.376688, "audio_ts": 21.49, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 20.28, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": "?", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 23.662326, "audio_ts": 21.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} +{"ts": 23.662507, "audio_ts": 21.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are pretty"}}} +{"ts": 23.662712, "audio_ts": 21.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 23.668783, "audio_ts": 21.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 24.000056, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} +{"ts": 24.000313, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} +{"ts": 24.006407, "audio_ts": 22.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} +{"ts": 24.373133, "audio_ts": 22.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} +{"ts": 24.374785, "audio_ts": 22.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} +{"ts": 24.381632, "audio_ts": 22.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} +{"ts": 24.707232, "audio_ts": 22.82, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.24, "start_time": 21.56, "transcript": "amazing. "}}} +{"ts": 24.707448, "audio_ts": 22.82, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.32, "transcript": ""}}} +{"ts": 24.707509, "audio_ts": 22.82, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 24.712719, "audio_ts": 22.83, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} +{"ts": 25.077868, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.32, "start_time": 22.32, "transcript": ""}}} +{"ts": 25.077952, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.32, "transcript": ""}}} +{"ts": 25.565104, "audio_ts": 23.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.24, "transcript": "Thank you"}}} +{"ts": 25.56526, "audio_ts": 23.68, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 25.57096, "audio_ts": 23.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 142, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} +{"ts": 25.800109, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.24, "transcript": "Thank "}}} +{"ts": 25.800404, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you."}}} +{"ts": 25.806547, "audio_ts": 23.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 145, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 146, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 26.145423, "audio_ts": 24.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} +{"ts": 26.145641, "audio_ts": 24.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.145718, "audio_ts": 24.26, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} +{"ts": 26.145902, "audio_ts": 24.26, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 26.151383, "audio_ts": 24.27, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 147, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 148, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} +{"ts": 26.514257, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.859512, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.04, "transcript": ""}}} +{"ts": 26.859743, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.224564, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.562289, "audio_ts": 25.68, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 24.76, "transcript": ""}}} +{"ts": 27.56251, "audio_ts": 25.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} +{"ts": 27.935434, "audio_ts": 26.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} +{"ts": 28.289689, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.48, "transcript": ""}}} +{"ts": 28.289875, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.669148, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.997916, "audio_ts": 27.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.2, "transcript": ""}}} +{"ts": 28.99816, "audio_ts": 27.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.392005, "audio_ts": 27.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.747696, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 26.92, "transcript": ""}}} +{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 0751264f..e3277d1a 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -12,8 +12,10 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import AnnotationFlags from speechmatics.voice import AnnotationResult +from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerSegment from speechmatics.voice import SpeechFragment +from speechmatics.voice import VoiceAgentConfig @pytest.mark.asyncio @@ -26,12 +28,10 @@ async def test_annotation_result(): """ # Create a new annotation - annotation = AnnotationResult() + annotation = AnnotationResult.from_flags(AnnotationFlags.NO_TEXT, AnnotationFlags.HAS_DISFLUENCY) assert annotation is not None - # Add flags - annotation.add(AnnotationFlags.NO_TEXT) - annotation.add(AnnotationFlags.HAS_DISFLUENCY) + # Add extra flag annotation.add(AnnotationFlags.MULTIPLE_SPEAKERS) # Has a flag @@ -127,9 +127,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.ADD_INTERIM_SEGMENTS, message_rx) # Inject first partial - await send_message(2, use_ttl=False) + await send_message(0, count=3, use_ttl=False) - # Wait for partial transcript + # Wait for first segment try: await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None @@ -151,9 +151,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.ADD_SEGMENTS, message_rx) # Send a more partials and finals - await send_message(3, count=11, use_ttl=False) + await send_message(3, count=10, use_ttl=False) - # Wait for partial transcript + # Wait for final segment try: await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None @@ -175,6 +175,230 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert len(fragments) == 4 +@pytest.mark.asyncio +async def test_end_of_utterance_fixed(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (fast) + - wait for `EndOfUtterance` message + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + + # Start time + start_time = datetime.datetime.now() + + # Create a client + client = await get_client(api_key="NONE", connect=False) + assert client is not None + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + client.once(AgentServerMessageType.END_OF_UTTERANCE, message_rx) + + # Inject conversation + await send_message(0, count=13, use_ttl=False) + + # Wait for EndOfUtterance + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("END_OF_UTTERANCE event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE + + +@pytest.mark.asyncio +async def test_external_vad(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (realtime) + - finalizes based on external VAD (e.g. Pipecat's `UserStoppedSpeakingFrame` frame) + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + + # Start time + start_time = datetime.datetime.now() + + # Adaptive timeout + adaptive_timeout = 1.0 + + # Create a client + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.NONE + ), + ) + assert client is not None + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Inject conversation + await send_message(0, count=11, use_ttl=False) + + # Momentary pause + await asyncio.sleep(0.5) + + # Add listener for first interim segment + client.once(AgentServerMessageType.ADD_SEGMENTS, message_rx) + + # Send finalize + client.finalize() + + # Wait for AddSegments + try: + await asyncio.wait_for(event_rx.wait(), timeout=4) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_SEGMENTS event was not received within 4 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENTS + + # Check the segment + segments: list[SpeakerSegment] = last_message.get("segments", []) + assert len(segments) == 1 + assert segments[0].speaker_id == "S1" + assert segments[0].text == "Welcome to Speechmatics" + assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome to Speechmatics" + + # Check the contents of the segment + fragments: list[SpeechFragment] = segments[0].fragments + assert len(fragments) == 3 + + +@pytest.mark.asyncio +async def test_end_of_utterance_adaptive_vad(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (realtime) + - wait for `EndOfUtterance` message from SDK (adaptive) + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + + # Start time + start_time = datetime.datetime.now() + + # Adaptive timeout + adaptive_timeout = 0.5 + + # Create a client + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + ), + ) + assert client is not None + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + client.once(AgentServerMessageType.END_OF_UTTERANCE, message_rx) + + # Inject conversation + await send_message(0, count=12, use_ttl=False) + + # Wait for EndOfUtterance + try: + await asyncio.wait_for(event_rx.wait(), timeout=adaptive_timeout * 1.5) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail(f"END_OF_UTTERANCE event was not received within {adaptive_timeout * 1.5} seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE + + @pytest.mark.asyncio async def test_speaker_segment(): """Test SpeakerSegment. From 69e66103c624a584c8671528853a378dd49eabb2 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:28:46 +0100 Subject: [PATCH 027/215] function change for fragment index --- sdk/voice/speechmatics/voice/_client.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 5ce5e494..4ea56e77 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -67,6 +67,7 @@ def __init__( app: Optional application name to use in the endpoint URL. config: Optional voice agent configuration. """ + # Default URL if not url: url = os.getenv("SPEECHMATICS_RT_URL") or "wss://eu2.rt.speechmatics.com/v2" @@ -155,6 +156,7 @@ def _process_config( Returns: A tuple of (VoiceAgentConfig, TranscriptionConfig, AudioFormat). """ + # Default config if config is None: config = VoiceAgentConfig() @@ -253,6 +255,7 @@ async def connect(self) -> None: transcription_config: Transcription configuration. audio_format: Audio format. """ + # Check if we are already connected if self._is_connected: self.emit( @@ -275,6 +278,7 @@ async def connect(self) -> None: async def disconnect(self) -> None: """Disconnect from the Speechmatics API.""" + # Check if we are already connected if not self._is_connected: return @@ -467,6 +471,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = Returns: True if the speech fragments were updated, False otherwise. """ + async with self._speech_fragments_lock: # Parsed new speech data from the STT engine fragments: list[SpeechFragment] = [] @@ -482,7 +487,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = if alt.get("content", None): # Create the new fragment fragment = SpeechFragment( - idx=self._frag_idx(), + idx=self._next_fragment_id(), start_time=result.get("start_time", 0), end_time=result.get("end_time", 0), language=alt.get("language", "en"), @@ -564,6 +569,7 @@ async def _process_speech_fragments(self) -> None: When segments are emitted, they are then removed from the buffer of fragments so the next comparison is based on the remaining + new fragments. """ + async with self._speech_fragments_lock: # Create a view of the current segments self._update_current_view() @@ -638,6 +644,7 @@ def _calc_delay_to_end_of_utterance( Returns: Tuple of (should_emit, emit_final_delay) """ + # Skip if no segments if view.segment_count == 0: return False, None @@ -802,6 +809,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: Args: fragments: The list of fragments to use for evaluation. """ + # Find the valid list of partial words if self._dz_enabled and self._dz_config.focus_speakers: partial_words = [ @@ -871,7 +879,8 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: if not self._is_speaking: self._current_speaker = None - def _frag_idx(self) -> int: + def _next_fragment_id(self) -> int: + """Return the next fragment ID.""" self._fragment_idx += 1 return self._fragment_idx @@ -885,6 +894,7 @@ def _get_endpoint_url(self, url: str, app: Optional[str] = None) -> str: Returns: str: The formatted endpoint URL. """ + query_params = {} query_params["sm-app"] = app or f"voice-sdk/{__version__}" query_params["sm-voice-sdk"] = f"{__version__}" From 331b4fdb412d0001d35ba61d5857412c7ee91fce Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:32:06 +0100 Subject: [PATCH 028/215] remove end of turn messages --- examples/voice/file_to_jsonl.py | 2 -- sdk/voice/speechmatics/voice/_models.py | 6 ------ tests/voice/test_03_conversation.py | 2 -- 3 files changed, 10 deletions(-) diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 836749ac..22110bff 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -90,8 +90,6 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) - client.on(AgentServerMessageType.TURN_STARTED, log_message) - client.on(AgentServerMessageType.TURN_ENDED, log_message) # Clear output file if it exists if args.output and os.path.exists(args.output): diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index f1973db6..f00e24f9 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -241,8 +241,6 @@ class AgentServerMessageType(str, Enum): EndOfUtterance: End of utterance has been detected (from STT engine). SpeakingStarted: Speech has started. SpeakingEnded: Speech has ended. - TurnStarted: A turn has been detected (context-based prediction). - TurnEnded: A turn has ended (context-based prediction). AddSegments: A final segment has been detected. AddInterimSegments: An interim segment has been detected. SpeakersResult: Speakers result has been detected. @@ -282,10 +280,6 @@ class AgentServerMessageType(str, Enum): SPEAKING_STARTED = "SpeakingStarted" SPEAKING_ENDED = "SpeakingEnded" - # Turn messages - TURN_STARTED = "TurnStarted" - TURN_ENDED = "TurnEnded" - # Segment messages ADD_INTERIM_SEGMENTS = "AddInterimSegments" ADD_SEGMENTS = "AddSegments" diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index ed3cca5a..9d9907c4 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -70,8 +70,6 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) - client.on(AgentServerMessageType.TURN_STARTED, log_message) - client.on(AgentServerMessageType.TURN_ENDED, log_message) # Load the audio file `./assets/audio_01.wav` audio_file = "./assets/audio_01.wav" From d1366603bde2f7e22738923c5bf5d7ec023c226a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:44:48 +0100 Subject: [PATCH 029/215] test the receive interval --- tests/voice/test_04_models.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index e3277d1a..7be90bd1 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -388,16 +388,28 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Inject conversation await send_message(0, count=12, use_ttl=False) + # Timing info + timeout = adaptive_timeout * 1.5 + start_time = datetime.datetime.now() + receive_interval = None + # Wait for EndOfUtterance try: - await asyncio.wait_for(event_rx.wait(), timeout=adaptive_timeout * 1.5) + await asyncio.wait_for(event_rx.wait(), timeout=timeout) assert last_message is not None + receive_interval = (datetime.datetime.now() - start_time).total_seconds() except asyncio.TimeoutError: - pytest.fail(f"END_OF_UTTERANCE event was not received within {adaptive_timeout * 1.5} seconds") + pytest.fail(f"END_OF_UTTERANCE event was not received within {timeout} seconds") # Check the right message was received assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE + # Check the interval was within +/- 10% of the adaptive trigger of 0.5 the timeout (see client code) + expected_min_interval = adaptive_timeout * 0.5 * 0.9 + expected_max_interval = adaptive_timeout * 0.5 * 1.1 + assert receive_interval >= expected_min_interval + assert receive_interval <= expected_max_interval + @pytest.mark.asyncio async def test_speaker_segment(): From 45b0e36194cf5c9b0ebbed8df16f190b214877c1 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:45:22 +0100 Subject: [PATCH 030/215] version bump --- sdk/voice/speechmatics/voice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 36c0aed7..ee30c560 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.2" +__version__ = "0.1.3" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 8decac086e42f8a1d6be083976e6b692407c46af Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:46:07 +0100 Subject: [PATCH 031/215] version fix --- sdk/voice/speechmatics/voice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index ee30c560..36c0aed7 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.3" +__version__ = "0.1.2" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 32178bd654019390cc372628d9fb1dbb64a59631 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:49:45 +0100 Subject: [PATCH 032/215] version bump --- sdk/voice/speechmatics/voice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 36c0aed7..ee30c560 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.2" +__version__ = "0.1.3" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 35f1f03cd9f5a26a67386f67c08e28a498965b0f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 14 Sep 2025 15:54:34 +0100 Subject: [PATCH 033/215] fix error in docs --- sdk/voice/OVERVIEW.md | 2 +- sdk/voice/README.md | 2 +- sdk/voice/speechmatics/voice/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md index c8604fbb..ed64f6e8 100644 --- a/sdk/voice/OVERVIEW.md +++ b/sdk/voice/OVERVIEW.md @@ -76,7 +76,7 @@ async def main(): print("User stopped speaking") # End of turn / utterance(s) - @client.on(AgentServerMessageType.TURN_ENDED) + @client.on(AgentServerMessageType.END_OF_UTTERANCE) def handle_end_of_turn(message): print("End of turn") diff --git a/sdk/voice/README.md b/sdk/voice/README.md index daaa5f2d..084cd65b 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -80,7 +80,7 @@ async def main(): print("User stopped speaking") # End of turn / utterance(s) - @client.on(AgentServerMessageType.TURN_ENDED) + @client.on(AgentServerMessageType.END_OF_UTTERANCE) def handle_end_of_turn(message): print("End of turn") diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index ee30c560..78c92c13 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.3" +__version__ = "0.1.4" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 325481c8265499b57ce713f19834e834c5ff62e9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 11:47:46 +0100 Subject: [PATCH 034/215] fix to additional vocab entries --- sdk/voice/speechmatics/voice/_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 4ea56e77..55ae3d67 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -177,7 +177,7 @@ def _process_config( transcription_config.additional_vocab = [ { "content": e.content, - "sounds_like": e.sounds_like, + **({"sounds_like": e.sounds_like} if e.sounds_like else {}), } for e in config.additional_vocab ] From 4380515466fdb137c0d2661a49a828b0bc1568a5 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 16:38:15 +0100 Subject: [PATCH 035/215] updated tests --- tests/voice/_utils.py | 4 ++-- tests/voice/test_01_client.py | 16 ++++++++++++++-- tests/voice/test_02_transcriber.py | 2 +- tests/voice/test_04_models.py | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index 1aa1b847..ceca7950 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -59,7 +59,7 @@ def on_recognition_started(message): async def send_audio_file( client: VoiceAgentClient, audio_file: str, - event_received: Optional[asyncio.Event] = None, + terminate_event: Optional[asyncio.Event] = None, chunk_size: int = 320, progress_callback: Optional[Callable[[int], None]] = None, ) -> None: @@ -85,7 +85,7 @@ async def send_audio_file( # Send audio data next_time = time.perf_counter() + delay - while not event_received.is_set() if event_received else True: + while not terminate_event.is_set() if terminate_event else True: """Reads all chunks until the end of the file with precision delay.""" # Read chunk diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py index 4568aec5..0b5678bf 100644 --- a/tests/voice/test_01_client.py +++ b/tests/voice/test_01_client.py @@ -6,7 +6,11 @@ @pytest.mark.asyncio async def test_client(): - """Tests that a client can be created.""" + """Tests that a client can be created. + + - Checks for a valid session + - Checks that 'English' is the language pack info + """ # API key api_key = os.getenv("SPEECHMATICS_API_KEY") @@ -14,7 +18,10 @@ async def test_client(): pytest.skip("Valid API key required for test") # Create client - client = await get_client(api_key=api_key, connect=False) + client = await get_client( + api_key=api_key, + connect=False, + ) # Check we are connected OK await client.connect() @@ -27,3 +34,8 @@ async def test_client(): # Check we are disconnected assert not client._is_connected + + # Check session info + assert client._session_id is not None + assert client._language_pack_info is not None + assert client._language_pack_info.language_description == "English" diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index cc99fb09..84659ead 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -152,7 +152,7 @@ def on_segment_received(message): client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, on_segment_received) # Load the audio file `./assets/audio_01.wav` - await send_audio_file(client, "./assets/audio_01.wav", event_received) + await send_audio_file(client=client, audio_file="./assets/audio_01.wav", terminate_event=event_received) # Wait for the callback with timeout try: diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 7be90bd1..fe814fc1 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -364,6 +364,7 @@ def message_rx(message: dict[str, Any]): nonlocal last_message last_message = message event_rx.set() + print(message) # Send a message from the conversation async def send_message(idx: int, count: int = 1, use_ttl: bool = True): @@ -380,13 +381,14 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.sleep(0.005) # Emit the message + print(message) client.emit(message["payload"]["message"], message["payload"]) # Add listener for first interim segment client.once(AgentServerMessageType.END_OF_UTTERANCE, message_rx) # Inject conversation - await send_message(0, count=12, use_ttl=False) + await send_message(0, count=12, use_ttl=True) # Timing info timeout = adaptive_timeout * 1.5 @@ -405,6 +407,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE # Check the interval was within +/- 10% of the adaptive trigger of 0.5 the timeout (see client code) + print(f"receive_interval={round(receive_interval, 3)}") expected_min_interval = adaptive_timeout * 0.5 * 0.9 expected_max_interval = adaptive_timeout * 0.5 * 1.1 assert receive_interval >= expected_min_interval From 52cfd55462bc4dd5156eb42a970a9a936c43dd33 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 16:40:14 +0100 Subject: [PATCH 036/215] LanguagePackInfo added + tests --- sdk/voice/speechmatics/voice/_client.py | 7 +++++++ sdk/voice/speechmatics/voice/_models.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 55ae3d67..3759d44f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -29,6 +29,7 @@ from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode from ._models import FragmentUtils +from ._models import LanguagePackInfo from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment @@ -85,6 +86,10 @@ def __init__( self._is_connected: bool = False self._is_ready_for_audio: bool = False + # Session info + self._session_id: Optional[str] = None + self._language_pack_info: Optional[LanguagePackInfo] = None + # Timing info self._start_time: datetime.datetime = datetime.datetime.now(datetime.timezone.utc) self._total_time: float = 0 @@ -228,6 +233,8 @@ def _register_event_handlers(self) -> None: def _evt_on_recognition_started(message: dict[str, Any]) -> None: self._start_time = datetime.datetime.now(datetime.timezone.utc) self._is_ready_for_audio = True + self._session_id = message.get("id") + self._language_pack_info = LanguagePackInfo.from_dict(message.get("language_pack_info", {})) # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index f00e24f9..6f41b286 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -386,6 +386,7 @@ class SpeechFragment: start_time: Start time of the fragment in seconds (from session start). end_time: End time of the fragment in seconds (from session start). language: Language of the fragment. Defaults to `en`. + direction: Direction of the fragment. Defaults to `ltr`. _type: Type of the fragment. Defaults to `word`. is_eos: Whether the fragment is the end of a sentence. Defaults to `False`. is_final: Whether the fragment is the final fragment. Defaults to `False`. @@ -403,6 +404,7 @@ class SpeechFragment: start_time: float end_time: float language: str = "en" + direction: str = "ltr" _type: str = "word" is_eos: bool = False is_final: bool = False @@ -832,3 +834,25 @@ def compare_views(view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] # Return the result return result + + +@dataclass +class LanguagePackInfo: + """Information about the language pack used in a session.""" + + adapted: bool + itn: bool + language_description: str + word_delimiter: str + writing_direction: str + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> LanguagePackInfo: + """Create LanguagePackInfo from dictionary.""" + return cls( + adapted=data.get("adapted", False), + itn=data.get("itn", False), + language_description=data.get("language_description", "Unknown"), + word_delimiter=data.get("word_delimiter", ""), + writing_direction="rtl" if data.get("writing_direction", "left-to-right") == "right-to-left" else "ltr", + ) From a842f2880bbe5ba2cc9e11e837dc17239e3b256a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 21:38:09 +0100 Subject: [PATCH 037/215] Added `SessionInfo` with the language info. Updated examples and tests. --- examples/voice/file_to_jsonl.py | 8 +- examples/voice/file_transcription.py | 18 +- examples/voice/microphone_transcription.py | 18 +- sdk/voice/speechmatics/voice/_client.py | 63 ++- sdk/voice/speechmatics/voice/_models.py | 288 ++++++++----- tests/voice/assets/chat1.jsonl | 458 +++++++++++---------- tests/voice/test_01_client.py | 6 +- tests/voice/test_02_transcriber.py | 22 +- tests/voice/test_03_conversation.py | 30 +- tests/voice/test_04_models.py | 38 +- 10 files changed, 538 insertions(+), 411 deletions(-) diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 22110bff..83eb5a60 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -86,10 +86,10 @@ def log_message(message): client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) - client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, log_message) - client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) - client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) - client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) # Clear output file if it exists if args.output and os.path.exists(args.output): diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py index 0773349f..d44a9003 100644 --- a/examples/voice/file_transcription.py +++ b/examples/voice/file_transcription.py @@ -126,13 +126,7 @@ def _format_segment(segment: SpeakerSegment) -> str: template = "@{speaker_id} -> {text}" if segment.is_active else "@{speaker_id} (background) -> {text}" return segment.format_text(template) - @client.on(AgentServerMessageType.ADD_SEGMENTS) - def handle_final_segments(message): - """Handle final transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - logger.log(CustomLevels.FINAL, f"🚀 {segments}") - - @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def handle_partial_segments(message): """Handle partial transcription segments.""" segments = [_format_segment(s) for s in message["segments"]] @@ -142,12 +136,18 @@ def handle_partial_segments(message): else: logger.log(CustomLevels.PARTIAL, f"⚡ {segments}") - @client.on(AgentServerMessageType.SPEAKING_STARTED) + @client.on(AgentServerMessageType.ADD_SEGMENT) + def handle_final_segments(message): + """Handle final transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + logger.log(CustomLevels.FINAL, f"🚀 {segments}") + + @client.on(AgentServerMessageType.SPEAKER_STARTED) def handle_speech_started(message): """Handle speech start events.""" logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") - @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + @client.on(AgentServerMessageType.SPEAKER_ENDED) def handle_speech_ended(message): """Handle speech end events.""" logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index 0f9d6a9c..ad7bff3b 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -73,13 +73,7 @@ def _format_segment(segment: SpeakerSegment) -> str: template = "@{speaker_id}: {text}" if segment.is_active else "@{speaker_id} (background): {text}" return segment.format_text(template) - @client.on(AgentServerMessageType.ADD_SEGMENTS) - def handle_final_segments(message): - """Handle final transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") - - @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def handle_partial_segments(message): """Handle partial transcription segments.""" segments = [_format_segment(s) for s in message["segments"]] @@ -89,12 +83,18 @@ def handle_partial_segments(message): else: logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") - @client.on(AgentServerMessageType.SPEAKING_STARTED) + @client.on(AgentServerMessageType.ADD_SEGMENT) + def handle_final_segments(message): + """Handle final transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") + + @client.on(AgentServerMessageType.SPEAKER_STARTED) def handle_speech_started(message): """Handle speech start events.""" logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") - @client.on(AgentServerMessageType.USER_SPEECH_ENDED) + @client.on(AgentServerMessageType.SPEAKER_ENDED) def handle_speech_ended(message): """Handle speech end events.""" logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 3759d44f..4a4e45a0 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -30,6 +30,7 @@ from ._models import EndOfUtteranceMode from ._models import FragmentUtils from ._models import LanguagePackInfo +from ._models import SessionInfo from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment @@ -86,12 +87,14 @@ def __init__( self._is_connected: bool = False self._is_ready_for_audio: bool = False - # Session info - self._session_id: Optional[str] = None - self._language_pack_info: Optional[LanguagePackInfo] = None + # Session info (updated on session created) + self._session: SessionInfo = SessionInfo( + session_id="NOT_SET", + base_time=datetime.datetime.now(datetime.timezone.utc), + language_pack_info=LanguagePackInfo.from_dict({}), + ) # Timing info - self._start_time: datetime.datetime = datetime.datetime.now(datetime.timezone.utc) self._total_time: float = 0 self._total_bytes: int = 0 @@ -231,10 +234,12 @@ def _register_event_handlers(self) -> None: # Recognition started event @self.once(ServerMessageType.RECOGNITION_STARTED) def _evt_on_recognition_started(message: dict[str, Any]) -> None: - self._start_time = datetime.datetime.now(datetime.timezone.utc) self._is_ready_for_audio = True - self._session_id = message.get("id") - self._language_pack_info = LanguagePackInfo.from_dict(message.get("language_pack_info", {})) + self._session = SessionInfo( + session_id=message.get("id", "UNKNOWN"), + base_time=datetime.datetime.now(datetime.timezone.utc), + language_pack_info=LanguagePackInfo.from_dict(message.get("language_pack_info", {})), + ) # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) @@ -498,6 +503,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = start_time=result.get("start_time", 0), end_time=result.get("end_time", 0), language=alt.get("language", "en"), + direction=alt.get("direction", "ltr"), _type=result.get("type", "word"), is_eos=result.get("is_eos", False), is_disfluency="disfluency" in alt.get("tags", []), @@ -604,7 +610,7 @@ async def _process_speech_fragments(self) -> None: # Compare previous view to this view if self._previous_view: - changes = FragmentUtils.compare_views(self._previous_view, self._current_view) + changes = FragmentUtils.compare_views(self._session, self._previous_view, self._current_view) else: changes = AnnotationResult.from_flags(AnnotationFlags.NEW) @@ -629,8 +635,8 @@ async def emit(delay: float | None) -> None: def _update_current_view(self) -> None: """Load the current view of the speech fragments.""" self._current_view = SpeakerSegmentView( + session=self._session, fragments=self._speech_fragments.copy(), - base_time=self._start_time, focus_speakers=self._dz_config.focus_speakers, ) @@ -759,6 +765,14 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = end_of_utterance: Whether to emit an end of turn event. """ + # Metadata + if self._current_view: + metadata = {"start_time": self._current_view.start_time, "end_time": self._current_view.end_time} + elif self._previous_view: + metadata = {"start_time": self._previous_view.start_time, "end_time": self._previous_view.end_time} + else: + metadata = None + # Lock the speech fragments if self._current_view and self._current_view.segment_count > 0: async with self._speech_fragments_lock: @@ -779,8 +793,12 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = # Emit finals first if final_segments: self.emit( - AgentServerMessageType.ADD_SEGMENTS, - {"message": AgentServerMessageType.ADD_SEGMENTS.value, "segments": final_segments}, + AgentServerMessageType.ADD_SEGMENT, + { + "message": AgentServerMessageType.ADD_SEGMENT.value, + "segments": final_segments, + "metadata": metadata, + }, ) self._trim_before_time = final_segments[-1].end_time self._speech_fragments = [ @@ -790,8 +808,12 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = # Emit interim segments if interim_segments: self.emit( - AgentServerMessageType.ADD_INTERIM_SEGMENTS, - {"message": AgentServerMessageType.ADD_INTERIM_SEGMENTS.value, "segments": interim_segments}, + AgentServerMessageType.ADD_PARTIAL_SEGMENT, + { + "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, + "segments": interim_segments, + "metadata": metadata, + }, ) # Update the current view @@ -801,7 +823,10 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = if end_of_utterance: self.emit( AgentServerMessageType.END_OF_UTTERANCE, - {"message": AgentServerMessageType.END_OF_UTTERANCE.value}, + { + "message": AgentServerMessageType.END_OF_UTTERANCE.value, + "metadata": metadata, + }, ) def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: @@ -849,16 +874,16 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Check if speaker is different to the current speaker if already_speaking and speaker_changed: self.emit( - AgentServerMessageType.SPEAKING_ENDED, + AgentServerMessageType.SPEAKER_ENDED, { - "message": AgentServerMessageType.SPEAKING_ENDED.value, + "message": AgentServerMessageType.SPEAKER_ENDED.value, "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False), }, ) self.emit( - AgentServerMessageType.SPEAKING_STARTED, + AgentServerMessageType.SPEAKER_STARTED, { - "message": AgentServerMessageType.SPEAKING_STARTED.value, + "message": AgentServerMessageType.SPEAKER_STARTED.value, "status": SpeakerVADStatus(speaker_id=speaker, is_active=True), }, ) @@ -875,7 +900,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Emit the event for latest speaker msg: AgentServerMessageType = ( - AgentServerMessageType.SPEAKING_STARTED if self._is_speaking else AgentServerMessageType.SPEAKING_ENDED + AgentServerMessageType.SPEAKER_STARTED if self._is_speaking else AgentServerMessageType.SPEAKER_ENDED ) self.emit( msg, diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 6f41b286..dcf962e7 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -209,17 +209,26 @@ class AgentClientMessageType(str, Enum): client can send to the Speechmatics RT API during a transcription session. Attributes: + FinalizeTurn: Force the finalization of the current turn. EndOfStream: Signals that no more audio data will be sent. GetSpeakers: Internal, Speechmatics only message. Allows the client to request speaker data. Examples: + >>> # Finalizing the current turn + >>> finalize_turn_message = { + ... "message": AgentClientMessageType.FINALIZE_TURN + ... } + >>> await client.send_message(finalize_turn_message) + >>> >>> # Ending the session >>> end_message = { ... "message": AgentClientMessageType.END_OF_STREAM, ... "last_seq_no": sequence_number ... } + >>> await client.send_message(end_message) """ + FINALIZE_TURN = "FinalizeTurn" END_OF_STREAM = "EndOfStream" GET_SPEAKERS = "GetSpeakers" @@ -239,26 +248,31 @@ class AgentServerMessageType(str, Enum): AddPartialTranscript: Partial transcript has been added. AddTranscript: Transcript has been added. EndOfUtterance: End of utterance has been detected (from STT engine). - SpeakingStarted: Speech has started. - SpeakingEnded: Speech has ended. - AddSegments: A final segment has been detected. - AddInterimSegments: An interim segment has been detected. + SpeakerStarted: Speech has started. + SpeakerEnded: Speech has ended. + AddPartialSegment: A partial / interim segment has been detected. + AddSegment: A final segment has been detected. + EndOfTurn: End of turn has been detected. SpeakersResult: Speakers result has been detected. Metrics: Metrics for the STT engine. SpeakerMetrics: Metrics relating to speakers. Examples: >>> # Register event handlers for different message types - >>> @client.on(AgentServerMessageType.INTERIM_SEGMENTS) + >>> @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) >>> def handle_interim(message): ... segments: list[SpeakerSegment] = message['segments'] ... print(f"Interim: {segments}") >>> - >>> @client.on(AgentServerMessageType.FINAL_SEGMENTS) + >>> @client.on(AgentServerMessageType.ADD_SEGMENT) >>> def handle_final(message): ... segments: list[SpeakerSegment] = message['segments'] ... print(f"Final: {segments}") >>> + >>> @client.on(AgentServerMessageType.END_OF_TURN) + >>> def handle_end_of_turn(message): + ... print(f"End of turn") + >>> >>> @client.on(AgentServerMessageType.ERROR) >>> def handle_error(message): ... print(f"Error: {message['reason']}") @@ -277,12 +291,13 @@ class AgentServerMessageType(str, Enum): END_OF_UTTERANCE = "EndOfUtterance" # VAD messages - SPEAKING_STARTED = "SpeakingStarted" - SPEAKING_ENDED = "SpeakingEnded" + SPEAKER_STARTED = "SpeakerStarted" + SPEAKER_ENDED = "SpeakerEnded" # Segment messages - ADD_INTERIM_SEGMENTS = "AddInterimSegments" - ADD_SEGMENTS = "AddSegments" + ADD_PARTIAL_SEGMENT = "AddPartialSegment" + ADD_SEGMENT = "AddSegment" + END_OF_TURN = "EndOfTurn" # Speaker messages SPEAKERS_RESULT = "SpeakersResult" @@ -333,6 +348,51 @@ class AnnotationFlags(str, Enum): END_OF_UTTERANCE = "end_of_utterance" +@dataclass +class LanguagePackInfo: + """Information about the language pack used in a session. + + Attributes: + adapted (bool): Whether the language pack is adapted. + itn (bool): Whether the language pack has ITN enabled. + language_description (str): The language description. + word_delimiter (str): The word delimiter. + writing_direction (str): The writing direction ('ltr' or 'rtl'). + """ + + adapted: bool + itn: bool + language_description: str + word_delimiter: str + writing_direction: str + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> LanguagePackInfo: + """Create LanguagePackInfo from dictionary.""" + return cls( + adapted=data.get("adapted", False), + itn=data.get("itn", True), + language_description=data.get("language_description", "English"), + word_delimiter=data.get("word_delimiter", " "), + writing_direction="rtl" if data.get("writing_direction", "left-to-right") == "right-to-left" else "ltr", + ) + + +@dataclass +class SessionInfo: + """Information about the session. + + Attributes: + session_id (str): The session ID. + base_time (datetime.datetime): The base time for the session. + language_pack_info (LanguagePackInfo): The language pack info for the session. + """ + + session_id: str + base_time: datetime.datetime + language_pack_info: LanguagePackInfo + + class AnnotationResult(list): """Processing result.""" @@ -428,6 +488,7 @@ class SpeakerSegment: timestamp: The timestamp of the frame. language: The language of the frame. fragments: The list of SpeechFragment items. + text: The text of the segment. annotation: The annotation associated with the segment. """ @@ -436,6 +497,7 @@ class SpeakerSegment: timestamp: Optional[str] = None language: Optional[str] = None fragments: list[SpeechFragment] = field(default_factory=list) + text: str | None = None annotation: AnnotationResult = field(default_factory=AnnotationResult) @property @@ -448,11 +510,6 @@ def end_time(self) -> float: """Return the end time of the segment.""" return self.fragments[-1].end_time - @property - def text(self) -> str: - """Return the text of the segment.""" - return self.format_text() - def __str__(self) -> str: """Return a string representation of the object.""" meta = { @@ -460,82 +517,42 @@ def __str__(self) -> str: "timestamp": self.timestamp, "language": self.language, "annotation": str(self.annotation), - "text": self.format_text(), + "text": self.text, } return f"SpeakerSegment({', '.join(f'{k}={v}' for k, v in meta.items())})" - def format_text(self, format: Optional[str] = None, words_only: bool = False) -> str: - """Wrap text with speaker ID in an optional f-string format. - - Supported format variables: - speaker_id: The ID of the speaker. - text: The text of the fragment. - ts: The timestamp of the fragment. - lang: The language of the fragment. - - Args: - format: Format to wrap the text with. - words_only: Whether to include only word fragments. - - Returns: - str: The wrapped text. - """ - # Cumulative contents - content = "" - - # Select fragments to include - if not words_only: - fragments = self.fragments - else: - fragments = [frag for frag in self.fragments if frag._type == "word"] - - # Assemble the text - for frag in fragments: - if content == "" or frag.attaches_to == "previous": - content += frag.content - else: - content += " " + frag.content - - # Format the text, if format is provided - if format is None: - return content - return format.format( - **{ - "speaker_id": self.speaker_id, - "text": content, - "ts": self.timestamp, - "lang": self.language, - } - ) - @dataclass class SpeakerSegmentView: """View for speaker fragments. Parameters: + session: SessionInfo object. fragments: List of fragments. - base_time: Base time for the fragments. focus_speakers: List of speakers to focus on or None. """ + session: SessionInfo fragments: list[SpeechFragment] segments: list[SpeakerSegment] def __init__( self, + session: SessionInfo, fragments: list[SpeechFragment], - base_time: datetime.datetime, focus_speakers: Optional[list[str]] = None, annotate_segments: bool = True, ): + self.session = session self.fragments = fragments - self.base_time = base_time self.focus_speakers = focus_speakers # Process fragments into a list of segments self.segments = FragmentUtils.segment_list_from_fragments( - base_time, fragments, focus_speakers, annotate_segments + session=session, + fragments=fragments, + focus_speakers=focus_speakers, + annotate_segments=annotate_segments, ) @property @@ -568,20 +585,48 @@ def last_active_segment_index(self) -> int: return -1 return len(self.segments) - idx - 1 - def format_text( + def format_view_text( self, format: str = "|{speaker_id}|{text}|", separator: str = "", words_only: bool = False, ) -> str: - return separator.join(segment.format_text(format, words_only) for segment in self.segments) + """Format each segment into a single string. + + Args: + format: Format string. + separator: Separator string. + words_only: Whether to include only word fragments. + + Returns: + str: The formatted text. + """ + return separator.join( + FragmentUtils.format_segment_text( + session=self.session, + segment=segment, + format=format, + words_only=words_only, + ) + for segment in self.segments + ) def trim(self, start_time: float, end_time: float, annotate_segments: bool = True) -> None: + """Trim a segment view to a specific time range. + + Args: + start_time: Start time in seconds. + end_time: End time in seconds. + annotate_segments: Whether to annotate segments. + """ self.fragments = [ frag for frag in self.fragments if frag.start_time >= start_time and frag.end_time <= end_time ] self.segments = FragmentUtils.segment_list_from_fragments( - self.base_time, self.fragments, focus_speakers=self.focus_speakers, annotate_segments=annotate_segments + session=self.session, + fragments=self.fragments, + focus_speakers=self.focus_speakers, + annotate_segments=annotate_segments, ) @@ -604,9 +649,50 @@ class SpeakerVADStatus: class FragmentUtils: """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" + @staticmethod + def format_segment_text( + session: SessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False + ) -> str: + """Format a segment's text based on the language pack info. + + Args: + session: SessionInfo object. + segment: SpeakerSegment object. + words_only: Whether to include only word fragments. + + Returns: + str: The formatted text. + """ + + # Cumulative contents + content = "" + + # Select fragments to include + if words_only: + fragments = [frag for frag in segment.fragments if frag._type == "word"] + else: + fragments = segment.fragments + + # Assemble the text (LTR) + for frag in fragments: + if content == "" or frag.attaches_to == "previous": + content += frag.content + else: + content += session.language_pack_info.word_delimiter + frag.content + + # Return the formatted text + return format.format( + **{ + "speaker_id": segment.speaker_id, + "text": content, + "ts": segment.timestamp, + "lang": segment.language, + } + ) + @staticmethod def segment_list_from_fragments( - base_time: datetime.datetime, + session: SessionInfo, fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate_segments: bool = True, @@ -614,7 +700,7 @@ def segment_list_from_fragments( """Create SpeakerSegment objects from a list of SpeechFragment objects. Args: - base_time: Base time for the fragments. + session: SessionInfo object. fragments: List of SpeechFragment objects. focus_speakers: List of speakers to focus on or None. annotate_segments: Whether to annotate segments. @@ -622,6 +708,7 @@ def segment_list_from_fragments( Returns: List of SpeakerSegment objects. """ + # Speaker groups current_speaker: Optional[str] = None speaker_groups: list[list[SpeechFragment]] = [[]] @@ -637,18 +724,22 @@ def segment_list_from_fragments( # Create SpeakerFragments objects segments: list[SpeakerSegment] = [] for group in speaker_groups: - sd = FragmentUtils.segment_from_fragments( - base_time, group, focus_speakers=focus_speakers, annotate=annotate_segments + segment = FragmentUtils.segment_from_fragments( + session=session, + fragments=group, + focus_speakers=focus_speakers, + annotate=annotate_segments, ) - if sd: - segments.append(sd) + if segment: + segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) + segments.append(segment) # Return the grouped SpeakerFragments objects return segments @staticmethod def segment_from_fragments( - base_time: datetime.datetime, + session: SessionInfo, fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate: bool = True, @@ -663,7 +754,7 @@ def segment_from_fragments( be removed. Args: - base_time: The base time for the segment. + session: SessionInfo object. fragments: List of SpeechFragment objects. focus_speakers: List of speakers to focus on. annotate: Whether to annotate the segment. @@ -687,7 +778,7 @@ def segment_from_fragments( start_time = min(frag.start_time for frag in fragments) # Timestamp - ts = (base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") + ts = (session.base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") # Determine if the speaker is considered active is_active = True @@ -712,9 +803,13 @@ def segment_from_fragments( @staticmethod def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: - """Annotate the segment. + """Annotate the segment with any additional information. - This will annotate the segment with any additional information. + Args: + segment: SpeakerSegment object. + + Returns: + AnnotationResult: The annotation result. """ # Annotation result result = AnnotationResult() @@ -787,10 +882,13 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: return result @staticmethod - def compare_views(view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView]) -> AnnotationResult: + def compare_views( + session: SessionInfo, view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] + ) -> AnnotationResult: """Compare two SpeakerSegmentView objects and return the differences. Args: + session: SessionInfo object. view1: The first SpeakerSegmentView object to compare. view2: The second SpeakerSegmentView object to compare to or None. @@ -803,16 +901,16 @@ def compare_views(view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] # If we have a previous view, compare it if view2 and view2.segment_count > 0: # Compare full string - view1_full_str: str = view1.format_text() - view2_full_str: str = view2.format_text() + view1_full_str: str = view1.format_view_text() + view2_full_str: str = view2.format_view_text() if view1_full_str != view2_full_str: result.add(AnnotationFlags.UPDATED_FULL) if view1_full_str.lower() != view2_full_str.lower(): result.add(AnnotationFlags.UPDATED_FULL_LCASE) # Stripped string (without punctuation) - view1_stripped_str: str = view1.format_text(words_only=True) - view2_stripped_str: str = view2.format_text(words_only=True) + view1_stripped_str: str = view1.format_view_text(words_only=True) + view2_stripped_str: str = view2.format_view_text(words_only=True) if view1_stripped_str != view2_stripped_str: result.add(AnnotationFlags.UPDATED_STRIPPED) if view1_stripped_str.lower() != view2_stripped_str.lower(): @@ -830,29 +928,5 @@ def compare_views(view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] elif view1.segment_count > 0: result.add(AnnotationFlags.NEW) - # TODO - contents of LAST segment? - # Return the result return result - - -@dataclass -class LanguagePackInfo: - """Information about the language pack used in a session.""" - - adapted: bool - itn: bool - language_description: str - word_delimiter: str - writing_direction: str - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> LanguagePackInfo: - """Create LanguagePackInfo from dictionary.""" - return cls( - adapted=data.get("adapted", False), - itn=data.get("itn", False), - language_description=data.get("language_description", "Unknown"), - word_delimiter=data.get("word_delimiter", ""), - writing_direction="rtl" if data.get("writing_direction", "left-to-right") == "right-to-left" else "ltr", - ) diff --git a/tests/voice/assets/chat1.jsonl b/tests/voice/assets/chat1.jsonl index 3a65085f..d81d3284 100644 --- a/tests/voice/assets/chat1.jsonl +++ b/tests/voice/assets/chat1.jsonl @@ -1,228 +1,230 @@ -{"ts": 0.000115, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} -{"ts": 0.000314, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} -{"ts": 0.000428, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} -{"ts": 0.000491, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} -{"ts": 0.505819, "audio_ts": 0.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} -{"ts": 0.506076, "audio_ts": 0.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} -{"ts": 0.886575, "audio_ts": 0.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} -{"ts": 1.245033, "audio_ts": 1.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} -{"ts": 1.245772, "audio_ts": 1.25, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 1.251577, "audio_ts": 1.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} -{"ts": 1.64981, "audio_ts": 1.65, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} -{"ts": 1.650008, "audio_ts": 1.65, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} -{"ts": 1.657086, "audio_ts": 1.66, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 1.975388, "audio_ts": 1.98, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} -{"ts": 1.975628, "audio_ts": 1.98, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} -{"ts": 1.975884, "audio_ts": 1.98, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 1.981827, "audio_ts": 1.98, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4208640}}]}} -{"ts": 2.284597, "audio_ts": 2.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} -{"ts": 2.290348, "audio_ts": 2.29, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4208640}}]}} -{"ts": 2.687002, "audio_ts": 2.69, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} -{"ts": 2.687193, "audio_ts": 2.69, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} -{"ts": 2.687711, "audio_ts": 2.69, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} -{"ts": 2.693148, "audio_ts": 2.7, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:15.140+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.32, "end_time": 1.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} -{"ts": 3.010142, "audio_ts": 3.01, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.44, "transcript": ""}}} -{"ts": 3.010291, "audio_ts": 3.01, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.8, "transcript": ""}}} -{"ts": 3.38324, "audio_ts": 3.39, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 2.8, "transcript": ""}}} -{"ts": 3.383357, "audio_ts": 3.39, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} -{"ts": 3.77054, "audio_ts": 3.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} -{"ts": 3.770768, "audio_ts": 3.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.52, "transcript": ""}}} -{"ts": 4.158163, "audio_ts": 4.16, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.44, "transcript": "We're"}}} -{"ts": 4.158496, "audio_ts": 4.16, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 4.164417, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 7, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} -{"ts": 4.449163, "audio_ts": 4.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.44, "transcript": "We're "}}} -{"ts": 4.449357, "audio_ts": 4.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} -{"ts": 4.455204, "audio_ts": 4.46, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 9, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 10, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 4.883937, "audio_ts": 4.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} -{"ts": 4.884232, "audio_ts": 4.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.71, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} -{"ts": 4.890195, "audio_ts": 4.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.71, "result": {"final": false, "alternatives": [{"confidence": 0.71, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 5.220628, "audio_ts": 5.22, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} -{"ts": 5.220879, "audio_ts": 5.22, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} -{"ts": 5.227434, "audio_ts": 5.23, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 5.593757, "audio_ts": 5.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} -{"ts": 5.594029, "audio_ts": 5.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} -{"ts": 5.599868, "audio_ts": 5.6, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 5.886625, "audio_ts": 5.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.98, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} -{"ts": 5.892631, "audio_ts": 5.9, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 6.296823, "audio_ts": 6.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} -{"ts": 6.297011, "audio_ts": 6.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech"}}} -{"ts": 6.302634, "audio_ts": 6.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 6.669202, "audio_ts": 6.67, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} -{"ts": 6.669474, "audio_ts": 6.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} -{"ts": 6.675638, "audio_ts": 6.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 7.021005, "audio_ts": 7.02, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} -{"ts": 7.02126, "audio_ts": 7.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} -{"ts": 7.021558, "audio_ts": 7.02, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 7.027727, "audio_ts": 7.03, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 14336}}]}} -{"ts": 7.335987, "audio_ts": 7.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} -{"ts": 7.336211, "audio_ts": 7.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 7.341872, "audio_ts": 7.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 7.771311, "audio_ts": 7.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} -{"ts": 7.771612, "audio_ts": 7.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} -{"ts": 7.777708, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 32, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 8.080594, "audio_ts": 8.08, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} -{"ts": 8.081095, "audio_ts": 8.08, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 8.081225, "audio_ts": 8.08, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 8.087489, "audio_ts": 8.09, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:18.260+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} -{"ts": 8.089333, "audio_ts": 8.09, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 33, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": {"flags": 4195328}}]}} -{"ts": 8.432364, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} -{"ts": 8.432691, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} -{"ts": 8.43878, "audio_ts": 8.44, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 39, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 8.82756, "audio_ts": 8.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} -{"ts": 8.828373, "audio_ts": 8.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} -{"ts": 8.834496, "audio_ts": 8.84, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 9.221652, "audio_ts": 9.22, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} -{"ts": 9.221862, "audio_ts": 9.22, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} -{"ts": 9.228307, "audio_ts": 9.23, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 47, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 9.488784, "audio_ts": 9.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} -{"ts": 9.489013, "audio_ts": 9.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} -{"ts": 9.495188, "audio_ts": 9.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 52, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 9.899314, "audio_ts": 9.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} -{"ts": 9.899588, "audio_ts": 9.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} -{"ts": 9.905964, "audio_ts": 9.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 56, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5282816}}]}} -{"ts": 10.290264, "audio_ts": 10.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} -{"ts": 10.29605, "audio_ts": 10.3, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 57, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 58, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 10.614414, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} -{"ts": 10.620351, "audio_ts": 10.62, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 59, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 10.94132, "audio_ts": 10.94, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} -{"ts": 10.947545, "audio_ts": 10.95, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 11.366284, "audio_ts": 11.37, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 11.0, "start_time": 9.0, "transcript": "three. One. Two three. "}}} -{"ts": 11.366833, "audio_ts": 11.37, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 11.08, "transcript": ""}}} -{"ts": 11.367141, "audio_ts": 11.37, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 11.3675, "audio_ts": 11.37, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.08, "start_time": 11.08}}} -{"ts": 11.372948, "audio_ts": 11.38, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:21.860+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 61, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 62, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "One", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 64, "start_time": 9.88, "end_time": 9.88, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, "annotation": null}, {"idx": 65, "start_time": 9.92, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, "annotation": null}, {"idx": 66, "start_time": 10.2, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} -{"ts": 11.708067, "audio_ts": 11.71, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.08, "transcript": ""}}} -{"ts": 11.708248, "audio_ts": 11.71, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.44, "transcript": ""}}} -{"ts": 12.040609, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.44, "transcript": ""}}} -{"ts": 12.040799, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} -{"ts": 12.40966, "audio_ts": 12.41, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} -{"ts": 12.409916, "audio_ts": 12.41, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 12.41579, "audio_ts": 12.42, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} -{"ts": 12.780649, "audio_ts": 12.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} -{"ts": 12.780909, "audio_ts": 12.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} -{"ts": 12.781768, "audio_ts": 12.78, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 12.782674, "audio_ts": 12.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 12.798136, "audio_ts": 12.8, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 13.11259, "audio_ts": 13.12, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} -{"ts": 13.112773, "audio_ts": 13.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} -{"ts": 13.119442, "audio_ts": 13.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1088512}}]}} -{"ts": 13.484031, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} -{"ts": 13.484207, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} -{"ts": 13.491362, "audio_ts": 13.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 13.86083, "audio_ts": 13.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} -{"ts": 13.861115, "audio_ts": 13.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} -{"ts": 13.867673, "audio_ts": 13.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 14.238783, "audio_ts": 14.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} -{"ts": 14.239052, "audio_ts": 14.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} -{"ts": 14.245175, "audio_ts": 14.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 14.520592, "audio_ts": 14.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} -{"ts": 14.520796, "audio_ts": 14.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} -{"ts": 14.526987, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 14.909253, "audio_ts": 14.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} -{"ts": 14.909547, "audio_ts": 14.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} -{"ts": 14.916065, "audio_ts": 14.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 15.330651, "audio_ts": 15.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} -{"ts": 15.330838, "audio_ts": 15.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} -{"ts": 15.337024, "audio_ts": 15.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 15.639239, "audio_ts": 15.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} -{"ts": 15.63953, "audio_ts": 15.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} -{"ts": 15.639798, "audio_ts": 15.64, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 15.645672, "audio_ts": 15.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 14336}}]}} -{"ts": 16.030186, "audio_ts": 16.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} -{"ts": 16.030552, "audio_ts": 16.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 16.036747, "audio_ts": 16.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 16.379245, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} -{"ts": 16.379467, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} -{"ts": 16.385789, "audio_ts": 16.39, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 16.748774, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} -{"ts": 16.749065, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} -{"ts": 16.755371, "audio_ts": 16.76, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 17.026278, "audio_ts": 17.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} -{"ts": 17.026559, "audio_ts": 17.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} -{"ts": 17.032572, "audio_ts": 17.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.75, "result": {"final": false, "alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5282816}}]}} -{"ts": 17.445589, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 17.12, "start_time": 16.2, "transcript": "API. "}}} -{"ts": 17.4459, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 17.2, "transcript": ""}}} -{"ts": 17.446008, "audio_ts": 17.45, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 17.2, "start_time": 17.2}}} -{"ts": 17.4463, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 17.452654, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:26.660+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} -{"ts": 17.774981, "audio_ts": 17.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 17.2, "transcript": ""}}} -{"ts": 18.185069, "audio_ts": 18.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 17.12, "transcript": "We "}}} -{"ts": 18.185883, "audio_ts": 18.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} -{"ts": 18.186174, "audio_ts": 18.19, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 18.192217, "audio_ts": 18.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 8395776}}]}} -{"ts": 18.502542, "audio_ts": 18.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} -{"ts": 18.502796, "audio_ts": 18.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} -{"ts": 18.508621, "audio_ts": 18.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": {"flags": 8395776}}]}} -{"ts": 18.914238, "audio_ts": 18.92, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} -{"ts": 18.914544, "audio_ts": 18.92, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} -{"ts": 18.920628, "audio_ts": 18.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.84, "result": {"final": false, "alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 19.259913, "audio_ts": 19.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} -{"ts": 19.260113, "audio_ts": 19.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} -{"ts": 19.266297, "audio_ts": 19.27, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 19.581366, "audio_ts": 19.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} -{"ts": 19.582053, "audio_ts": 19.58, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.5, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} -{"ts": 19.5886, "audio_ts": 19.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.5, "result": {"final": false, "alternatives": [{"confidence": 0.5, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 19.929443, "audio_ts": 19.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} -{"ts": 19.93549, "audio_ts": 19.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 20.30179, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}], "metadata": {"end_time": 20.0, "start_time": 18.8, "transcript": "results. "}}} -{"ts": 20.302086, "audio_ts": 20.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 20.08, "transcript": ""}}} -{"ts": 20.302186, "audio_ts": 20.3, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 20.08, "start_time": 20.08}}} -{"ts": 20.302583, "audio_ts": 20.3, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 20.30866, "audio_ts": 20.31, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:32.180+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 19.52, "end_time": 19.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} -{"ts": 20.707935, "audio_ts": 20.71, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.08, "transcript": ""}}} -{"ts": 20.708109, "audio_ts": 20.71, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.44, "transcript": ""}}} -{"ts": 21.020595, "audio_ts": 21.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.36, "transcript": "Results"}}} -{"ts": 21.020945, "audio_ts": 21.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 21.026903, "audio_ts": 21.03, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.140+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.36, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} -{"ts": 21.450397, "audio_ts": 21.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.36, "transcript": "Results. "}}} -{"ts": 21.450657, "audio_ts": 21.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} -{"ts": 21.450907, "audio_ts": 21.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 21.456571, "audio_ts": 21.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.140+00:00", "language": "en", "fragments": [{"idx": 128, "start_time": 20.36, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 0.99, "result": {"final": true, "alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, "annotation": null}, {"idx": 129, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} -{"ts": 21.788024, "audio_ts": 21.79, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} -{"ts": 21.788744, "audio_ts": 21.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are"}}} -{"ts": 21.789495, "audio_ts": 21.79, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 21.795425, "audio_ts": 21.8, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 22.087985, "audio_ts": 22.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} -{"ts": 22.088233, "audio_ts": 22.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} -{"ts": 22.094286, "audio_ts": 22.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 22.448874, "audio_ts": 22.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} -{"ts": 22.449068, "audio_ts": 22.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} -{"ts": 22.455057, "audio_ts": 22.46, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": {"flags": 4201472}}]}} -{"ts": 22.809949, "audio_ts": 22.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.52, "start_time": 21.56, "transcript": "amazing. "}}} -{"ts": 22.810272, "audio_ts": 22.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.6, "transcript": ""}}} -{"ts": 22.810411, "audio_ts": 22.81, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 22.6, "start_time": 22.6}}} -{"ts": 22.810674, "audio_ts": 22.81, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 22.816785, "audio_ts": 22.82, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T00:22:35.580+00:00", "language": "en", "fragments": [{"idx": 130, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 5289984}}]}} -{"ts": 23.172767, "audio_ts": 23.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.6, "transcript": ""}}} -{"ts": 23.17302, "audio_ts": 23.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.96, "transcript": ""}}} -{"ts": 23.586812, "audio_ts": 23.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, {"alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.88, "transcript": "Thank you"}}} -{"ts": 23.587241, "audio_ts": 23.59, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 23.592964, "audio_ts": 23.6, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 138, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.95, "result": {"final": false, "alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 1024}}]}} -{"ts": 23.948459, "audio_ts": 23.95, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.88, "transcript": "Thank "}}} -{"ts": 23.948744, "audio_ts": 23.95, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you"}}} -{"ts": 23.955031, "audio_ts": 23.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 140, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 23.12, "end_time": 23.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": {"flags": 7168}}]}} -{"ts": 24.28314, "audio_ts": 24.29, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.44, "is_eos": true, "start_time": 23.44, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} -{"ts": 24.283343, "audio_ts": 24.29, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} -{"ts": 24.283409, "audio_ts": 24.29, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} -{"ts": 24.283606, "audio_ts": 24.29, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 24.289172, "audio_ts": 24.29, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T00:22:37.660+00:00", "language": "en", "fragments": [{"idx": 140, "start_time": 22.88, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 142, "start_time": 23.12, "end_time": 23.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.44, "end_time": 23.44, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.44, "is_eos": true, "start_time": 23.44, "type": "punctuation"}, "annotation": null}], "annotation": {"flags": 1095680}}]}} -{"ts": 24.607229, "audio_ts": 24.61, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} -{"ts": 24.607397, "audio_ts": 24.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.4, "transcript": ""}}} -{"ts": 24.965649, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.4, "transcript": ""}}} -{"ts": 24.966291, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} -{"ts": 25.340973, "audio_ts": 25.34, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} -{"ts": 25.341086, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 25.12, "transcript": ""}}} -{"ts": 25.73166, "audio_ts": 25.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.12, "transcript": ""}}} -{"ts": 25.731855, "audio_ts": 25.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} -{"ts": 26.059762, "audio_ts": 26.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} -{"ts": 26.060025, "audio_ts": 26.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.84, "transcript": ""}}} -{"ts": 26.401326, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.84, "transcript": ""}}} -{"ts": 26.401424, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} -{"ts": 26.781128, "audio_ts": 26.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} -{"ts": 26.781359, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.56, "transcript": ""}}} -{"ts": 27.117456, "audio_ts": 27.12, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.56, "transcript": ""}}} -{"ts": 27.117699, "audio_ts": 27.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} -{"ts": 27.487666, "audio_ts": 27.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} -{"ts": 27.48785, "audio_ts": 27.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 27.28, "transcript": ""}}} -{"ts": 27.843804, "audio_ts": 27.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.28, "transcript": ""}}} -{"ts": 27.843956, "audio_ts": 27.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} +{"ts": 1.8e-05, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} +{"ts": 7.8e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} +{"ts": 0.000114, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} +{"ts": 0.000134, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} +{"ts": 0.10114, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-15T19:47:28Z"}} +{"ts": 0.167755, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "a8779b0b-a238-43de-8211-c70f5fcbe191", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} +{"ts": 0.708136, "audio_ts": 0.54, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 0.708319, "audio_ts": 0.54, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.066432, "audio_ts": 0.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.401315, "audio_ts": 1.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} +{"ts": 1.401995, "audio_ts": 1.24, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 1.407782, "audio_ts": 1.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "text": "Welcome", "annotation": ["has_partial"]}], "metadata": {"start_time": 0.36, "end_time": 0.92}}} +{"ts": 1.803549, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} +{"ts": 1.803719, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} +{"ts": 1.81367, "audio_ts": 1.64, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.28}}} +{"ts": 2.196341, "audio_ts": 2.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} +{"ts": 2.196536, "audio_ts": 2.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.196743, "audio_ts": 2.03, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 2.202608, "audio_ts": 2.04, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.484286, "audio_ts": 2.32, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.492678, "audio_ts": 2.32, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.799758, "audio_ts": 2.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} +{"ts": 2.800017, "audio_ts": 2.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} +{"ts": 2.800089, "audio_ts": 2.64, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} +{"ts": 2.80614, "audio_ts": 2.64, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.32, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}, "annotation": null}], "text": "Welcome to speech.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 3.200744, "audio_ts": 3.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.44, "transcript": ""}}} +{"ts": 3.200932, "audio_ts": 3.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.528368, "audio_ts": 3.36, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.529249, "audio_ts": 3.36, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.881017, "audio_ts": 3.72, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.881107, "audio_ts": 3.72, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.52, "transcript": ""}}} +{"ts": 4.307043, "audio_ts": 4.14, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.44, "transcript": "We're"}}} +{"ts": 4.307265, "audio_ts": 4.14, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 4.312648, "audio_ts": 4.15, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 7, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "text": "We're", "annotation": ["has_partial"]}], "metadata": {"start_time": 1.32, "end_time": 3.72}}} +{"ts": 4.616071, "audio_ts": 4.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.44, "transcript": "We're "}}} +{"ts": 4.616139, "audio_ts": 4.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} +{"ts": 4.621818, "audio_ts": 4.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 9, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 10, "start_time": 4.08, "end_time": 4.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "text": "We're delighted that", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.16}}} +{"ts": 5.050354, "audio_ts": 4.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} +{"ts": 5.050534, "audio_ts": 4.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} +{"ts": 5.056403, "audio_ts": 4.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.44, "end_time": 4.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "text": "We're delighted that you've done", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.52}}} +{"ts": 5.356861, "audio_ts": 5.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} +{"ts": 5.357231, "audio_ts": 5.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} +{"ts": 5.365982, "audio_ts": 5.2, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.84}}} +{"ts": 5.767761, "audio_ts": 5.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} +{"ts": 5.768419, "audio_ts": 5.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} +{"ts": 5.774285, "audio_ts": 5.61, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.96, "end_time": 5.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.24}}} +{"ts": 6.071697, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.93, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} +{"ts": 6.077611, "audio_ts": 5.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 5.36, "end_time": 5.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.93, "result": {"final": false, "alternatives": [{"confidence": 0.93, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.6}}} +{"ts": 6.424714, "audio_ts": 6.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.56, "start_time": 4.96, "transcript": "try our "}}} +{"ts": 6.424806, "audio_ts": 6.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.56, "transcript": "speech to"}}} +{"ts": 6.430272, "audio_ts": 6.27, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.88, "end_time": 5.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.96}}} +{"ts": 6.817595, "audio_ts": 6.65, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.56, "transcript": "speech to "}}} +{"ts": 6.817904, "audio_ts": 6.65, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} +{"ts": 6.824999, "audio_ts": 6.66, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 6.32}}} +{"ts": 7.16476, "audio_ts": 7.0, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} +{"ts": 7.164862, "audio_ts": 7.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} +{"ts": 7.164948, "audio_ts": 7.0, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 7.170339, "audio_ts": 7.01, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 6.36}}} +{"ts": 7.484641, "audio_ts": 7.32, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} +{"ts": 7.484776, "audio_ts": 7.32, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 7.490157, "audio_ts": 7.33, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 7.0}}} +{"ts": 7.894987, "audio_ts": 7.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} +{"ts": 7.895187, "audio_ts": 7.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} +{"ts": 7.901366, "audio_ts": 7.74, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 7.0, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 33, "start_time": 7.08, "end_time": 7.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software. Hello", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 7.4}}} +{"ts": 8.294391, "audio_ts": 8.13, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} +{"ts": 8.294649, "audio_ts": 8.13, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 8.294704, "audio_ts": 8.13, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 8.3004, "audio_ts": 8.14, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 7.0, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 1.32, "end_time": 7.76}}} +{"ts": 8.301351, "audio_ts": 8.14, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 34, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 35, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 36, "start_time": 7.64, "end_time": 7.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "text": "Hello. This", "annotation": ["has_partial"]}], "metadata": {"start_time": 1.32, "end_time": 7.76}}} +{"ts": 8.64121, "audio_ts": 8.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} +{"ts": 8.641446, "audio_ts": 8.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} +{"ts": 8.648669, "audio_ts": 8.48, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 8.0, "end_time": 8.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "text": "Hello. This is a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.12}}} +{"ts": 8.963358, "audio_ts": 8.8, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} +{"ts": 8.964111, "audio_ts": 8.8, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} +{"ts": 8.970202, "audio_ts": 8.81, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "text": "Hello. This is a test", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.48}}} +{"ts": 9.331958, "audio_ts": 9.17, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} +{"ts": 9.332375, "audio_ts": 9.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} +{"ts": 9.339541, "audio_ts": 9.18, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 48, "start_time": 8.76, "end_time": 8.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.84}}} +{"ts": 9.684083, "audio_ts": 9.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} +{"ts": 9.684431, "audio_ts": 9.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} +{"ts": 9.691709, "audio_ts": 9.53, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 51, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 9.04, "end_time": 9.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, three", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 9.2}}} +{"ts": 10.021835, "audio_ts": 9.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} +{"ts": 10.022083, "audio_ts": 9.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} +{"ts": 10.028, "audio_ts": 9.86, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.52, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "text": "Hello. This is a test one, two, three.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.0, "end_time": 9.52}}} +{"ts": 10.434258, "audio_ts": 10.27, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} +{"ts": 10.439841, "audio_ts": 10.28, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.6, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, three one", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 9.88}}} +{"ts": 10.834333, "audio_ts": 10.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} +{"ts": 10.839834, "audio_ts": 10.68, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 10.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, 312", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 10.16}}} +{"ts": 11.129317, "audio_ts": 10.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} +{"ts": 11.136012, "audio_ts": 10.97, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 61, "start_time": 9.04, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, 3123", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 10.64}}} +{"ts": 11.521279, "audio_ts": 11.36, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 11.0, "start_time": 9.0, "transcript": "three. One. Two three. "}}} +{"ts": 11.521641, "audio_ts": 11.36, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.521703, "audio_ts": 11.36, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.08, "start_time": 11.08}}} +{"ts": 11.521897, "audio_ts": 11.36, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 11.527533, "audio_ts": 11.36, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 63, "start_time": 9.52, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.6, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "One", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.88, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.92, "end_time": 10.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.2, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, "annotation": null}, {"idx": 68, "start_time": 10.64, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "text": "Hello. This is a test one, two, three. One. Two three.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.0, "end_time": 10.64}}} +{"ts": 11.946521, "audio_ts": 11.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.946623, "audio_ts": 11.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.194132, "audio_ts": 12.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.194317, "audio_ts": 12.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} +{"ts": 12.607959, "audio_ts": 12.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} +{"ts": 12.608189, "audio_ts": 12.44, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 12.613619, "audio_ts": 12.45, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "text": "To", "annotation": ["has_partial"]}], "metadata": {"start_time": 10.64, "end_time": 12.08}}} +{"ts": 12.993248, "audio_ts": 12.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} +{"ts": 12.993443, "audio_ts": 12.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} +{"ts": 12.993762, "audio_ts": 12.83, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 12.993917, "audio_ts": 12.83, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 12.999893, "audio_ts": 12.84, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.28, "end_time": 12.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "text": "To get going", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 12.44}}} +{"ts": 13.333012, "audio_ts": 13.17, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} +{"ts": 13.333234, "audio_ts": 13.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} +{"ts": 13.339096, "audio_ts": 13.17, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "text": "To get going.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 12.76}}} +{"ts": 13.710176, "audio_ts": 13.55, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} +{"ts": 13.7104, "audio_ts": 13.55, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} +{"ts": 13.716376, "audio_ts": 13.55, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "text": "To get going. Just", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.0}}} +{"ts": 14.064707, "audio_ts": 13.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} +{"ts": 14.064963, "audio_ts": 13.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} +{"ts": 14.07142, "audio_ts": 13.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 81, "start_time": 13.32, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 82, "start_time": 13.36, "end_time": 13.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "text": "To get going. Just create. An", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.52}}} +{"ts": 14.401504, "audio_ts": 14.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} +{"ts": 14.401707, "audio_ts": 14.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} +{"ts": 14.407369, "audio_ts": 14.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.88}}} +{"ts": 14.760117, "audio_ts": 14.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} +{"ts": 14.760877, "audio_ts": 14.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} +{"ts": 14.76663, "audio_ts": 14.6, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.24}}} +{"ts": 15.098741, "audio_ts": 14.93, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} +{"ts": 15.0989, "audio_ts": 14.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} +{"ts": 15.105236, "audio_ts": 14.94, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.44}}} +{"ts": 15.493903, "audio_ts": 15.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} +{"ts": 15.494082, "audio_ts": 15.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} +{"ts": 15.499854, "audio_ts": 15.34, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.84}}} +{"ts": 15.833795, "audio_ts": 15.67, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} +{"ts": 15.834041, "audio_ts": 15.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} +{"ts": 15.834382, "audio_ts": 15.67, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 15.839965, "audio_ts": 15.68, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.84}}} +{"ts": 16.16751, "audio_ts": 16.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} +{"ts": 16.168197, "audio_ts": 16.0, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 16.174499, "audio_ts": 16.01, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 15.48}}} +{"ts": 16.616209, "audio_ts": 16.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} +{"ts": 16.616404, "audio_ts": 16.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} +{"ts": 16.622398, "audio_ts": 16.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.88, "end_time": 15.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.96, "end_time": 16.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 16.04}}} +{"ts": 16.959778, "audio_ts": 16.8, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} +{"ts": 16.960062, "audio_ts": 16.8, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} +{"ts": 16.967375, "audio_ts": 16.8, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 16.2}}} +{"ts": 17.24184, "audio_ts": 17.08, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} +{"ts": 17.24217, "audio_ts": 17.08, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} +{"ts": 17.247901, "audio_ts": 17.08, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.2, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.75, "result": {"final": false, "alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.72, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our API.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 16.72}}} +{"ts": 17.627421, "audio_ts": 17.46, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 17.12, "start_time": 16.2, "transcript": "API. "}}} +{"ts": 17.627528, "audio_ts": 17.46, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 17.2, "transcript": ""}}} +{"ts": 17.627559, "audio_ts": 17.46, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 17.2, "start_time": 17.2}}} +{"ts": 17.627684, "audio_ts": 17.46, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 17.633566, "audio_ts": 17.47, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.2, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 107, "start_time": 16.72, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our API.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 16.72}}} +{"ts": 17.955812, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 17.2, "transcript": ""}}} +{"ts": 18.36611, "audio_ts": 18.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 17.12, "transcript": "We "}}} +{"ts": 18.366187, "audio_ts": 18.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} +{"ts": 18.366359, "audio_ts": 18.18, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 18.372256, "audio_ts": 18.21, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.76, "end_time": 17.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "text": "We hope you", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 16.72, "end_time": 17.84}}} +{"ts": 18.774276, "audio_ts": 18.61, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} +{"ts": 18.774563, "audio_ts": 18.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} +{"ts": 18.780795, "audio_ts": 18.62, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "text": "We hope you'll be very", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 16.72, "end_time": 18.2}}} +{"ts": 19.067576, "audio_ts": 18.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} +{"ts": 19.067795, "audio_ts": 18.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} +{"ts": 19.074971, "audio_ts": 18.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.84, "result": {"final": false, "alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 18.56}}} +{"ts": 19.470369, "audio_ts": 19.31, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} +{"ts": 19.470632, "audio_ts": 19.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} +{"ts": 19.476703, "audio_ts": 19.31, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.68, "end_time": 18.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.76, "end_time": 18.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 18.92}}} +{"ts": 19.789004, "audio_ts": 19.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} +{"ts": 19.78923, "audio_ts": 19.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} +{"ts": 19.795785, "audio_ts": 19.63, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 19.28}}} +{"ts": 20.162161, "audio_ts": 20.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} +{"ts": 20.168371, "audio_ts": 20.0, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 19.52}}} +{"ts": 20.496429, "audio_ts": 20.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}], "metadata": {"end_time": 20.0, "start_time": 18.8, "transcript": "results. "}}} +{"ts": 20.496691, "audio_ts": 20.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.497783, "audio_ts": 20.33, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 20.499154, "audio_ts": 20.33, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 20.08, "start_time": 20.08}}} +{"ts": 20.505416, "audio_ts": 20.34, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 18.8, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 127, "start_time": 19.52, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}, "annotation": null}], "text": "We hope you'll be very impressed by the results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 16.72, "end_time": 19.52}}} +{"ts": 20.905258, "audio_ts": 20.74, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.90535, "audio_ts": 20.74, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.44, "transcript": ""}}} +{"ts": 21.28093, "audio_ts": 21.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.36, "transcript": "Results"}}} +{"ts": 21.281084, "audio_ts": 21.12, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 21.286308, "audio_ts": 21.12, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.096+00:00", "language": "en", "fragments": [{"idx": 128, "start_time": 20.36, "end_time": 20.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}, "annotation": null}], "text": "Results", "annotation": ["has_partial"]}], "metadata": {"start_time": 19.52, "end_time": 20.72}}} +{"ts": 21.640986, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.36, "transcript": "Results. "}}} +{"ts": 21.641212, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} +{"ts": 21.64134, "audio_ts": 21.48, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 21.646717, "audio_ts": 21.48, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.096+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.36, "end_time": 20.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 0.99, "result": {"final": true, "alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.76, "end_time": 20.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "text": "Results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 19.52, "end_time": 20.76}}} +{"ts": 21.995041, "audio_ts": 21.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} +{"ts": 21.995203, "audio_ts": 21.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are"}}} +{"ts": 21.995434, "audio_ts": 21.83, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 22.001087, "audio_ts": 21.84, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}], "text": "Really are", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 21.24}}} +{"ts": 22.276317, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} +{"ts": 22.276653, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} +{"ts": 22.283126, "audio_ts": 22.12, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "text": "Really are pretty", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 21.56}}} +{"ts": 22.62824, "audio_ts": 22.46, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} +{"ts": 22.62843, "audio_ts": 22.46, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} +{"ts": 22.634417, "audio_ts": 22.47, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.56, "end_time": 22.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "text": "Really are pretty amazing", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 22.16}}} +{"ts": 23.011228, "audio_ts": 22.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.52, "start_time": 21.56, "transcript": "amazing. "}}} +{"ts": 23.011483, "audio_ts": 22.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.6, "transcript": ""}}} +{"ts": 23.011722, "audio_ts": 22.85, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 23.012259, "audio_ts": 22.85, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 22.6, "start_time": 22.6}}} +{"ts": 23.017739, "audio_ts": 22.85, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.56, "end_time": 22.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 22.24, "end_time": 22.24, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "text": "Really are pretty amazing.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 20.76, "end_time": 22.24}}} +{"ts": 23.354514, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.6, "transcript": ""}}} +{"ts": 23.354677, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.96, "transcript": ""}}} +{"ts": 23.830358, "audio_ts": 23.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, {"alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.88, "transcript": "Thank you"}}} +{"ts": 23.830667, "audio_ts": 23.67, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 23.836925, "audio_ts": 23.67, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 139, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 23.12, "end_time": 23.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.95, "result": {"final": false, "alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "text": "Thank you", "annotation": ["has_partial"]}], "metadata": {"start_time": 22.24, "end_time": 23.24}}} +{"ts": 24.071311, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.88, "transcript": "Thank "}}} +{"ts": 24.071411, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you"}}} +{"ts": 24.076835, "audio_ts": 23.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 141, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 142, "start_time": 23.12, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}], "text": "Thank you", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 22.24, "end_time": 23.4}}} +{"ts": 24.491069, "audio_ts": 24.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} +{"ts": 24.491367, "audio_ts": 24.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.491418, "audio_ts": 24.33, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} +{"ts": 24.491545, "audio_ts": 24.33, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 24.496997, "audio_ts": 24.33, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 141, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 144, "start_time": 23.4, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "text": "Thank you.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 22.24, "end_time": 23.4}}} +{"ts": 24.794224, "audio_ts": 24.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.794376, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.4, "transcript": ""}}} +{"ts": 25.1636, "audio_ts": 25.0, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.4, "transcript": ""}}} +{"ts": 25.16374, "audio_ts": 25.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.496782, "audio_ts": 25.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.496917, "audio_ts": 25.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.897417, "audio_ts": 25.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.899313, "audio_ts": 25.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.257029, "audio_ts": 26.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.25724, "audio_ts": 26.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.559039, "audio_ts": 26.39, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.559141, "audio_ts": 26.39, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.9706, "audio_ts": 26.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.97071, "audio_ts": 26.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.31968, "audio_ts": 27.16, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.319783, "audio_ts": 27.16, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.72764, "audio_ts": 27.56, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.72774, "audio_ts": 27.56, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 27.28, "transcript": ""}}} +{"ts": 28.028009, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.28, "transcript": ""}}} +{"ts": 28.028179, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py index 0b5678bf..bd0838db 100644 --- a/tests/voice/test_01_client.py +++ b/tests/voice/test_01_client.py @@ -36,6 +36,6 @@ async def test_client(): assert not client._is_connected # Check session info - assert client._session_id is not None - assert client._language_pack_info is not None - assert client._language_pack_info.language_description == "English" + assert client._session.session_id != "NOT_SET" + assert client._session.language_pack_info is not None + assert client._session.language_pack_info.language_description == "English" diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index 84659ead..b671d091 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -5,7 +5,9 @@ from _utils import get_client from _utils import send_audio_file +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import VoiceAgentConfig api_key = os.getenv("SPEECHMATICS_API_KEY") @@ -25,7 +27,15 @@ async def test_transcribe_partial(): pytest.skip("Valid API key required for test") # Client - client = await get_client(api_key=api_key, connect=True) + client = await get_client( + api_key=api_key, + connect=True, + config=VoiceAgentConfig( + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ] + ), + ) # Check we are connected assert client._is_connected @@ -107,12 +117,12 @@ def on_final_received(message): @pytest.mark.asyncio -async def test_interim_segment(): +async def test_partial_segment(): """Test transcription. This test will: - send audio data to the API server - - wait for the first interim segment (within 10 seconds) + - wait for the first partial segment (within 10 seconds) """ # API key @@ -121,7 +131,7 @@ async def test_interim_segment(): pytest.skip("Valid API key required for test") # Client - client = await get_client(api_key=api_key, connect=True) + client = await get_client(api_key=api_key, connect=True, config=VoiceAgentConfig()) # Check we are connected assert client._is_connected @@ -149,7 +159,7 @@ def on_segment_received(message): event_received.set() # Add listener for PARTIALS - client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, on_segment_received) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, on_segment_received) # Load the audio file `./assets/audio_01.wav` await send_audio_file(client=client, audio_file="./assets/audio_01.wav", terminate_event=event_received) @@ -158,7 +168,7 @@ def on_segment_received(message): try: await asyncio.wait_for(event_received.wait(), timeout=5.0) except asyncio.TimeoutError: - pytest.fail("ADD_INTERIM_SEGMENTS event was not received within 5 seconds of audio finish") + pytest.fail("ADD_PARTIAL_SEGMENT event was not received within 5 seconds of audio finish") # Close session await client.disconnect() diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 9d9907c4..9f4c3e63 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -30,7 +30,7 @@ async def test_log_messages(): # Client client = await get_client( api_key=api_key, - connect=True, + connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=0.2, max_delay=0.7, @@ -39,9 +39,6 @@ async def test_log_messages(): ), ) - # Check we are connected - assert client._is_connected - # Create an event to track when the callback is called messages: list[str] = [] bytes_sent: int = 0 @@ -63,16 +60,23 @@ def log_message(message): print(log) # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.once(AgentServerMessageType.INFO, log_message) + client.on(AgentServerMessageType.WARNING, log_message) + client.on(AgentServerMessageType.ERROR, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) - client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS, log_message) - client.on(AgentServerMessageType.ADD_SEGMENTS, log_message) - client.on(AgentServerMessageType.SPEAKING_STARTED, log_message) - client.on(AgentServerMessageType.SPEAKING_ENDED, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) # Load the audio file `./assets/audio_01.wav` audio_file = "./assets/audio_01.wav" + + # HEADER print() print() print("---") @@ -80,7 +84,17 @@ def log_message(message): log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) + + # FOOTER print("---") print() print() diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index fe814fc1..ffdf4c93 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -124,7 +124,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Add listener for first interim segment message_reset() - client.once(AgentServerMessageType.ADD_INTERIM_SEGMENTS, message_rx) + client.once(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) # Inject first partial await send_message(0, count=3, use_ttl=False) @@ -137,18 +137,19 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): pytest.fail("ADD_INTERIM_SEGMENTS event was not received within 5 seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_INTERIM_SEGMENTS + assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT # Check the segment segments: list[SpeakerSegment] = last_message.get("segments", []) assert len(segments) == 1 - assert segments[0].speaker_id == "S1" - assert segments[0].text == "Welcome" - assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome" + seg0 = segments[0] + assert seg0.speaker_id == "S1" + assert seg0.text == "Welcome" + assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome" # Add listener for final segment message_reset() - client.once(AgentServerMessageType.ADD_SEGMENTS, message_rx) + client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) # Send a more partials and finals await send_message(3, count=10, use_ttl=False) @@ -161,17 +162,18 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): pytest.fail("ADD_SEGMENTS event was not received within 5 seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENTS + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT # Check the segment segments: list[SpeakerSegment] = last_message.get("segments", []) assert len(segments) == 1 - assert segments[0].speaker_id == "S1" - assert segments[0].text == "Welcome to Speechmatics." - assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome to Speechmatics." + seg0 = segments[0] + assert seg0.speaker_id == "S1" + assert seg0.text == "Welcome to Speechmatics." + assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome to Speechmatics." # Check the contents of the segment - fragments: list[SpeechFragment] = segments[0].fragments + fragments: list[SpeechFragment] = seg0.fragments assert len(fragments) == 4 @@ -227,7 +229,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Inject conversation await send_message(0, count=13, use_ttl=False) - # Wait for EndOfUtterance + # Wait for EndOfTurn try: await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None @@ -300,7 +302,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.sleep(0.5) # Add listener for first interim segment - client.once(AgentServerMessageType.ADD_SEGMENTS, message_rx) + client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) # Send finalize client.finalize() @@ -313,14 +315,15 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): pytest.fail("ADD_SEGMENTS event was not received within 4 seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENTS + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT # Check the segment segments: list[SpeakerSegment] = last_message.get("segments", []) assert len(segments) == 1 - assert segments[0].speaker_id == "S1" - assert segments[0].text == "Welcome to Speechmatics" - assert segments[0].format_text("{speaker_id}: {text}") == "S1: Welcome to Speechmatics" + seg0 = segments[0] + assert seg0.speaker_id == "S1" + assert seg0.text == "Welcome to Speechmatics" + assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome to Speechmatics" # Check the contents of the segment fragments: list[SpeechFragment] = segments[0].fragments @@ -381,7 +384,6 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.sleep(0.005) # Emit the message - print(message) client.emit(message["payload"]["message"], message["payload"]) # Add listener for first interim segment From b5485ad70f70cf9e5e84ee5764819b3cf2c50316 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 22:25:43 +0100 Subject: [PATCH 038/215] Updated to use EndOfTurn and not EndOfUtterance --- sdk/voice/speechmatics/voice/_client.py | 20 +++++----- sdk/voice/speechmatics/voice/_models.py | 2 +- tests/voice/test_04_models.py | 51 ++++++++++++++++--------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 4a4e45a0..c09975c7 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -257,8 +257,10 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: self._logger.debug("End of utterance") - # self.finalize() - # pass + self.emit( + AgentServerMessageType.END_OF_TURN, + {"message": AgentServerMessageType.END_OF_TURN, "metadata": message.get("metadata", {})}, + ) async def connect(self) -> None: """Connect to the Speechmatics API. @@ -624,7 +626,7 @@ async def _process_speech_fragments(self) -> None: async def emit(delay: float | None) -> None: if delay and delay > 0: await asyncio.sleep(delay) - asyncio.create_task(self._emit_segments(finalize=True, end_of_utterance=True)) + asyncio.create_task(self._emit_segments(finalize=True, end_of_turn=True)) # Finalize after delay if should_emit: @@ -753,7 +755,7 @@ async def emit() -> None: asyncio.create_task(emit()) - async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = False) -> None: + async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: """Emit segments to listeners. This function will emit segments in the view without any further checks @@ -762,7 +764,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = Args: finalize: Whether to finalize all segments. - end_of_utterance: Whether to emit an end of turn event. + end_of_turn: Whether to emit an end of turn event. """ # Metadata @@ -819,12 +821,12 @@ async def _emit_segments(self, finalize: bool = False, end_of_utterance: bool = # Update the current view self._update_current_view() - # Emit end of utterance - if end_of_utterance: + # Emit end of turn + if end_of_turn: self.emit( - AgentServerMessageType.END_OF_UTTERANCE, + AgentServerMessageType.END_OF_TURN, { - "message": AgentServerMessageType.END_OF_UTTERANCE.value, + "message": AgentServerMessageType.END_OF_TURN.value, "metadata": metadata, }, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index dcf962e7..d1216c14 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -18,7 +18,7 @@ class EndOfUtteranceMode(str, Enum): """End of turn delay options for transcription.""" - NONE = "none" + EXTERNAL = "external" FIXED = "fixed" ADAPTIVE = "adaptive" diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index ffdf4c93..e973f556 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -224,7 +224,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.emit(message["payload"]["message"], message["payload"]) # Add listener for first interim segment - client.once(AgentServerMessageType.END_OF_UTTERANCE, message_rx) + client.once(AgentServerMessageType.END_OF_TURN, message_rx) # Inject conversation await send_message(0, count=13, use_ttl=False) @@ -234,10 +234,10 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None except asyncio.TimeoutError: - pytest.fail("END_OF_UTTERANCE event was not received within 5 seconds") + pytest.fail("END_OF_TURN event was not received within 5 seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE + assert last_message.get("message") == AgentServerMessageType.END_OF_TURN @pytest.mark.asyncio @@ -263,7 +263,7 @@ async def test_external_vad(): api_key="NONE", connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.NONE + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL ), ) assert client is not None @@ -359,15 +359,26 @@ async def test_end_of_utterance_adaptive_vad(): assert client is not None # Event to wait - event_rx: asyncio.Event = asyncio.Event() + eot_received: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None - # Message receiver - def message_rx(message: dict[str, Any]): + # Time for last final (used to calculate the interval) + last_final_time: Optional[float] = None + receive_interval: Optional[float] = None + + # Transcript receiver + def transcript_rx(message: dict[str, Any]): + if not eot_received.is_set(): + nonlocal last_final_time + last_final_time = datetime.datetime.now() + + # End of turn receiver + def eot_rx(message: dict[str, Any]): nonlocal last_message + nonlocal receive_interval last_message = message - event_rx.set() - print(message) + receive_interval = (datetime.datetime.now() - last_final_time).total_seconds() + eot_received.set() # Send a message from the conversation async def send_message(idx: int, count: int = 1, use_ttl: bool = True): @@ -386,30 +397,32 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Emit the message client.emit(message["payload"]["message"], message["payload"]) - # Add listener for first interim segment - client.once(AgentServerMessageType.END_OF_UTTERANCE, message_rx) + # Add listener for partials (as these will trigger the adaptive timer)) + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, transcript_rx) - # Inject conversation + # Add listener for end of turn + client.once(AgentServerMessageType.END_OF_TURN, eot_rx) + + # Inject conversation up to the penultimate final from the STT await send_message(0, count=12, use_ttl=True) + # Check we have had a final + assert last_final_time is not None + # Timing info timeout = adaptive_timeout * 1.5 - start_time = datetime.datetime.now() - receive_interval = None # Wait for EndOfUtterance try: - await asyncio.wait_for(event_rx.wait(), timeout=timeout) + await asyncio.wait_for(eot_received.wait(), timeout=timeout) assert last_message is not None - receive_interval = (datetime.datetime.now() - start_time).total_seconds() except asyncio.TimeoutError: - pytest.fail(f"END_OF_UTTERANCE event was not received within {timeout} seconds") + pytest.fail(f"END_OF_TURN event was not received within {timeout} seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.END_OF_UTTERANCE + assert last_message.get("message") == AgentServerMessageType.END_OF_TURN # Check the interval was within +/- 10% of the adaptive trigger of 0.5 the timeout (see client code) - print(f"receive_interval={round(receive_interval, 3)}") expected_min_interval = adaptive_timeout * 0.5 * 0.9 expected_max_interval = adaptive_timeout * 0.5 * 1.1 assert receive_interval >= expected_min_interval From 0abd1fb1dcc39f716296c8cd20023cf48b7cc754 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 22:47:12 +0100 Subject: [PATCH 039/215] fix to message value --- sdk/voice/speechmatics/voice/_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c09975c7..ca261c30 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -259,7 +259,7 @@ def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: self._logger.debug("End of utterance") self.emit( AgentServerMessageType.END_OF_TURN, - {"message": AgentServerMessageType.END_OF_TURN, "metadata": message.get("metadata", {})}, + {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, ) async def connect(self) -> None: @@ -274,7 +274,7 @@ async def connect(self) -> None: if self._is_connected: self.emit( AgentServerMessageType.ERROR, - {"message": AgentServerMessageType.ERROR, "reason": "Already connected"}, + {"message": AgentServerMessageType.ERROR.value, "reason": "Already connected"}, ) return @@ -365,7 +365,7 @@ async def emit_metrics() -> None: self.emit( AgentServerMessageType.METRICS, { - "message": AgentServerMessageType.METRICS, + "message": AgentServerMessageType.METRICS.value, "total_time": time_s, "total_time_str": time.strftime("%H:%M:%S", time.gmtime(time_s)), "total_bytes": self._total_bytes, @@ -463,7 +463,7 @@ def _calculate_ttfb(self, end_time: float) -> None: self.emit( AgentServerMessageType.TTFB_METRICS, { - "message": AgentServerMessageType.TTFB_METRICS, + "message": AgentServerMessageType.TTFB_METRICS.value, "ttfb": self._last_ttfb, }, ) From 85e9a71fd09a62ca0ab917a535c90e7477384313 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 23:51:41 +0100 Subject: [PATCH 040/215] updated tests, examples and docs --- examples/voice/file_to_jsonl.py | 5 +-- examples/voice/file_transcription.py | 5 +-- examples/voice/microphone_transcription.py | 5 +-- sdk/voice/speechmatics/voice/__init__.py | 12 ----- sdk/voice/speechmatics/voice/_client.py | 6 ++- sdk/voice/speechmatics/voice/_models.py | 21 +++++++-- tests/voice/test_02_transcriber.py | 2 +- tests/voice/test_04_models.py | 51 ++++++---------------- 8 files changed, 42 insertions(+), 65 deletions(-) diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 83eb5a60..888d623f 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -5,17 +5,16 @@ import os import sys import time -from dataclasses import asdict -from dataclasses import is_dataclass from typing import Callable from typing import Optional import aiofiles +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig, AdditionalVocabEntry +from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._helpers import to_serializable diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py index d44a9003..97295a0a 100644 --- a/examples/voice/file_transcription.py +++ b/examples/voice/file_transcription.py @@ -13,7 +13,6 @@ from speechmatics.voice import DiarizationFocusMode from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import SpeakerSegment from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -121,10 +120,10 @@ def _setup_audio_player(sample_rate: int, channels: int, sample_width: int) -> A def _register_event_handlers(client: VoiceAgentClient, logger) -> None: """Register event handlers for transcription events with detailed logging.""" - def _format_segment(segment: SpeakerSegment) -> str: + def _format_segment(segment) -> str: """Format speaker segment for display.""" template = "@{speaker_id} -> {text}" if segment.is_active else "@{speaker_id} (background) -> {text}" - return segment.format_text(template) + return template.format(**segment) @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def handle_partial_segments(message): diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index ad7bff3b..95e281ac 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -9,7 +9,6 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import SpeakerSegment from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -68,10 +67,10 @@ async def main() -> None: def _register_event_handlers(client: VoiceAgentClient, logger) -> None: """Register event handlers for real-time transcription events.""" - def _format_segment(segment: SpeakerSegment) -> str: + def _format_segment(segment) -> str: """Format speaker segment for display.""" template = "@{speaker_id}: {text}" if segment.is_active else "@{speaker_id} (background): {text}" - return segment.format_text(template) + return template.format(**segment) @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def handle_partial_segments(message): diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 78c92c13..f83b0f1a 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -21,15 +21,10 @@ from ._models import AdditionalVocabEntry from ._models import AgentClientMessageType from ._models import AgentServerMessageType -from ._models import AnnotationFlags -from ._models import AnnotationResult from ._models import DiarizationFocusMode from ._models import DiarizationKnownSpeaker from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode -from ._models import SpeakerSegment -from ._models import SpeakerVADStatus -from ._models import SpeechFragment from ._models import VoiceAgentConfig __all__ = [ @@ -45,13 +40,6 @@ "AudioEncoding", "AudioFormat", "OperatingPoint", - # Transcription models - "AnnotationFlags", - "AnnotationResult", - "SpeakerSegment", - "SpeechFragment", - # Events - "SpeakerVADStatus", # Client "VoiceAgentClient", "AgentClientMessageType", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index ca261c30..d3ec022f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -89,6 +89,7 @@ def __init__( # Session info (updated on session created) self._session: SessionInfo = SessionInfo( + config=self._config, session_id="NOT_SET", base_time=datetime.datetime.now(datetime.timezone.utc), language_pack_info=LanguagePackInfo.from_dict({}), @@ -236,6 +237,7 @@ def _register_event_handlers(self) -> None: def _evt_on_recognition_started(message: dict[str, Any]) -> None: self._is_ready_for_audio = True self._session = SessionInfo( + config=self._config, session_id=message.get("id", "UNKNOWN"), base_time=datetime.datetime.now(datetime.timezone.utc), language_pack_info=LanguagePackInfo.from_dict(message.get("language_pack_info", {})), @@ -798,7 +800,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.ADD_SEGMENT, { "message": AgentServerMessageType.ADD_SEGMENT.value, - "segments": final_segments, + "segments": [s.to_dict(self._config.include_results) for s in final_segments], "metadata": metadata, }, ) @@ -813,7 +815,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.ADD_PARTIAL_SEGMENT, { "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, - "segments": interim_segments, + "segments": [s.to_dict(self._config.include_results) for s in interim_segments], "metadata": metadata, }, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index d1216c14..c1df3f65 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -171,6 +171,9 @@ class VoiceAgentConfig: Refer to our examples on the format of the known_speakers parameter. Defaults to []. + include_results: Include word data in the response. This is useful for debugging and + understanding the STT engine's behavior. Defaults to False. + sample_rate: Audio sample rate for streaming. Defaults to 16000. audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. """ @@ -197,6 +200,9 @@ class VoiceAgentConfig: speaker_config: DiarizationSpeakerConfig = field(default_factory=DiarizationSpeakerConfig) known_speakers: list[DiarizationKnownSpeaker] = field(default_factory=list) + # Advanced features + include_results: bool = False + # Audio sample_rate: int = 16000 audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE @@ -383,11 +389,13 @@ class SessionInfo: """Information about the session. Attributes: + config (VoiceAgentConfig): The configuration for the session. session_id (str): The session ID. base_time (datetime.datetime): The base time for the session. language_pack_info (LanguagePackInfo): The language pack info for the session. """ + config: VoiceAgentConfig session_id: str base_time: datetime.datetime language_pack_info: LanguagePackInfo @@ -510,15 +518,20 @@ def end_time(self) -> float: """Return the end time of the segment.""" return self.fragments[-1].end_time - def __str__(self) -> str: - """Return a string representation of the object.""" - meta = { + def to_dict(self, include_results: bool = False) -> dict[str, Any]: + """Return a dictionary representation of the object.""" + return { "speaker_id": self.speaker_id, + "is_active": self.is_active, "timestamp": self.timestamp, "language": self.language, - "annotation": str(self.annotation), "text": self.text, + **({"results": [f.result for f in self.fragments]} if include_results else {}), } + + def __str__(self) -> str: + """Return a string representation of the object.""" + meta = self.to_dict(include_results=True) return f"SpeakerSegment({', '.join(f'{k}={v}' for k, v in meta.items())})" diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index b671d091..77ba133c 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -149,7 +149,7 @@ def on_segment_received(message): return # Get the first segment's text - transcription = segments[0].text + transcription = segments[0]["text"] # Check transcription starts with `Welcome to Speechmatics` if not transcription.lower().startswith("welcome to speech"): diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index e973f556..3de3195a 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -10,12 +10,10 @@ from _utils import get_client from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import AnnotationFlags -from speechmatics.voice import AnnotationResult from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import SpeakerSegment -from speechmatics.voice import SpeechFragment from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import AnnotationFlags +from speechmatics.voice._models import AnnotationResult @pytest.mark.asyncio @@ -140,12 +138,12 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT # Check the segment - segments: list[SpeakerSegment] = last_message.get("segments", []) + segments = last_message.get("segments", []) assert len(segments) == 1 seg0 = segments[0] - assert seg0.speaker_id == "S1" - assert seg0.text == "Welcome" - assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome" + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome" + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome" # Add listener for final segment message_reset() @@ -165,16 +163,12 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT # Check the segment - segments: list[SpeakerSegment] = last_message.get("segments", []) + segments = last_message.get("segments", []) assert len(segments) == 1 seg0 = segments[0] - assert seg0.speaker_id == "S1" - assert seg0.text == "Welcome to Speechmatics." - assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome to Speechmatics." - - # Check the contents of the segment - fragments: list[SpeechFragment] = seg0.fragments - assert len(fragments) == 4 + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome to Speechmatics." + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics." @pytest.mark.asyncio @@ -318,16 +312,12 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT # Check the segment - segments: list[SpeakerSegment] = last_message.get("segments", []) + segments = last_message.get("segments", []) assert len(segments) == 1 seg0 = segments[0] - assert seg0.speaker_id == "S1" - assert seg0.text == "Welcome to Speechmatics" - assert f"{seg0.speaker_id}: {seg0.text}" == "S1: Welcome to Speechmatics" - - # Check the contents of the segment - fragments: list[SpeechFragment] = segments[0].fragments - assert len(fragments) == 3 + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome to Speechmatics" + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics" @pytest.mark.asyncio @@ -427,16 +417,3 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): expected_max_interval = adaptive_timeout * 0.5 * 1.1 assert receive_interval >= expected_min_interval assert receive_interval <= expected_max_interval - - -@pytest.mark.asyncio -async def test_speaker_segment(): - """Test SpeakerSegment. - - - create segment - - serialize to JSON - """ - - # Create a new segment - segment = SpeakerSegment() - assert segment is not None From 81399ed44d750881cd9948e2ccf72797befd362c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 15 Sep 2025 23:51:50 +0100 Subject: [PATCH 041/215] updated README --- sdk/voice/README.md | 156 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 143 insertions(+), 13 deletions(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 084cd65b..d3739b5a 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -31,8 +31,7 @@ from speechmatics.voice import ( VoiceAgentClient, VoiceAgentConfig, EndOfUtteranceMode, - AgentServerMessageType, - SpeakerSegment, + AgentServerMessageType ) async def main(): @@ -56,31 +55,33 @@ async def main(): async with VoiceAgentClient(config=config) as client: # Handle interim transcription segments - @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) + @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def handle_interim_segments(message): - segments: list[SpeakerSegment] = message["segments"] + segments = message["segments"] for segment in segments: - print(f"Speaker {segment.speaker_id}: {segment.text}") + print(f"[PARTIAL] Speaker {segment['speaker_id']}: {segment['text']}") # Handle finalized transcription segments - @client.on(AgentServerMessageType.ADD_SEGMENTS) + @client.on(AgentServerMessageType.ADD_SEGMENT) def handle_final_segments(message): - segments: list[SpeakerSegment] = message["segments"] + segments = message["segments"] for segment in segments: - print(f"Speaker {segment.speaker_id}: {segment.text}") + print(f"[FINAL] Speaker {segment['speaker_id']}: {segment['text']}") # Handle user started speaking event - @client.on(AgentServerMessageType.SPEAKING_STARTED) + @client.on(AgentServerMessageType.SPEAKER_STARTED) def handle_speech_started(message): - print("User started speaking") + status = message["status"] + print(f"User started speaking: {status}") # Handle user stopped speaking event - @client.on(AgentServerMessageType.SPEAKING_ENDED) + @client.on(AgentServerMessageType.SPEAKER_ENDED) def handle_speech_ended(message): - print("User stopped speaking") + status = message["status"] + print(f"User stopped speaking: {status}") # End of turn / utterance(s) - @client.on(AgentServerMessageType.END_OF_UTTERANCE) + @client.on(AgentServerMessageType.END_OF_TURN) def handle_end_of_turn(message): print("End of turn") @@ -99,6 +100,135 @@ if __name__ == "__main__": The `examples/` directory contains practical demonstrations of the Voice Agent SDK. See the README in the `examples/voice` directory for more information. +## Client Configuration + +The `VoiceAgentClient` can be configured with a number of options to control the behavior of the client. + +## Messages + +The async client will return messages of type `AgentServerMessageType`. To register for a message, use the `on` method. Optionally, you can use the `once` method to register a callback that will be called once and then removed. Use the `off` method to remove a callback. + +### `RECOGNITION_STARTED` + +Emitted when the recognition has started and contains the session ID and base time for when transcription started. It also contains the language pack information for the model being used. + +```json +{ + "message": "RecognitionStarted", + "orchestrator_version": "2025.08.29127+289170c022.HEAD", + "id": "a8779b0b-a238-43de-8211-c70f5fcbe191", + "language_pack_info": { + "adapted": false, + "itn": true, + "language_description": "English", + "word_delimiter": " ", + "writing_direction": "left-to-right" + } +} +``` + +### `SPEAKER_STARTED` + +Emitted when a speaker starts speaking. Contains the speaker ID and the VAD status. If there are multiple speakers, this will be emitted each time a speaker starts speaking. There will only be one active speaker at any given time. + +```json +{ + "message": "SpeakerStarted", + "status": { + "is_active": true, + "speaker_id": "S1" + } +} +``` + +### `SPEAKER_ENDED` + +Emitted when a speaker stops speaking. Contains the speaker ID (if diarization is enabled) and the VAD status. If there are multiple speakers, this will be emitted each time a speaker stops speaking. + +```json +{ + "message": "SpeakerEnded", + "status": { + "is_active": false, + "speaker_id": "S1" + } +} +``` + +### `ADD_PARTIAL_SEGMENT` + +Emitted when a partial segment has been detected. Contains the speaker ID, if diarization is enabled. If there are multiple speakers, this will be emitted each time a speaker starts speaking. Words from different speakers will be grouped into segments. + +If diarization is enabled and the client has been configured to focus on a specific speaker, the `is_active` will indicate whether the contents are from focused speakers. Ignored speakers will not have their words emitted. + +The `metadata` contains the start and end time for the segment. Each time the segment is updated as new partials are received, whole segment will be emitted again with updated `metadata`. The `annotation` field contains additional information about the contents of the segment. + +```json +{ + "message": "AddPartialSegment", + "segments": [ + { + "speaker_id": "S1", + "is_active": true, + "timestamp": "2025-09-15T19:47:29.096+00:00", + "language": "en", + "text": "Welcome", + "annotation": ["has_partial"] + } + ], + "metadata": { "start_time": 0.36, "end_time": 0.92 } +} +``` + +### `ADD_SEGMENT` + +Emitted when a final segment has been detected. Contains the speaker ID, if diarization is enabled. If there are multiple speakers, this will be emitted each time a speaker stops speaking. + +The `metadata` contains the start and end time for the segment. The `annotation` field contains additional information about the contents of the segment. + +```json +{ + "message": "AddSegment", + "segments": [ + { + "speaker_id": "S1", + "is_active": true, + "timestamp": "2025-09-15T19:47:29.096+00:00", + "language": "en", + "text": "Welcome to Speechmatics.", + "annotation": [ + "has_final", + "starts_with_final", + "ends_with_final", + "ends_with_eos", + "ends_with_punctuation" + ] + } + ], + "metadata": { "start_time": 0.36, "end_time": 1.32 } +} +``` + +### `END_OF_TURN` + +Emitted when a turn has ended. This is a signal that the user has finished speaking and the system has finished processing the turn. + +The message is emitted differently for the different `EndOfUtterance` modes: + +- `FIXED` -> emitted when the fixed delay has elapsed with the STT engine +- `ADAPTIVE` -> emitted when the adaptive delay has elapsed with the client +- `EXTERNAL` -> emitted when an external trigger forces the end of turn + +The `ADAPTIVE` mode takes into consideration a number of factors to determine whether the most recent speaker as completed their turn. These include: + +- The speed of speech +- Whether they have been using disfluencies (e.g. "um", "er", "ah") +- If the words spoken are considered to be a complete sentence + +The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED` and `ADAPTIVE` delays. As a fallback, the `end_of_utterance_max_delay` is used to trigger the end of turn after a fixed amount of time, regardless of the content of what the most recent speaker has said. + +When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. + ## Documentation - **SDK Overview**: See [OVERVIEW.md](OVERVIEW.md) for comprehensive API documentation From dfdd5b25bec1e80badabeb4c3cd2e65418cf39b9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 00:05:52 +0100 Subject: [PATCH 042/215] format payloads as dicts --- sdk/voice/speechmatics/voice/_client.py | 9 ++++++--- sdk/voice/speechmatics/voice/_models.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index d3ec022f..2b590bad 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -881,14 +881,14 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: AgentServerMessageType.SPEAKER_ENDED, { "message": AgentServerMessageType.SPEAKER_ENDED.value, - "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False), + "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False).to_dict(), }, ) self.emit( AgentServerMessageType.SPEAKER_STARTED, { "message": AgentServerMessageType.SPEAKER_STARTED.value, - "status": SpeakerVADStatus(speaker_id=speaker, is_active=True), + "status": SpeakerVADStatus(speaker_id=speaker, is_active=True).to_dict(), }, ) @@ -908,7 +908,10 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: ) self.emit( msg, - {"message": msg.value, "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking)}, + { + "message": msg.value, + "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking).to_dict(), + }, ) # Reset the current speaker diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index c1df3f65..fda92456 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -383,6 +383,15 @@ def from_dict(cls, data: dict[str, Any]) -> LanguagePackInfo: writing_direction="rtl" if data.get("writing_direction", "left-to-right") == "right-to-left" else "ltr", ) + def to_dict(self) -> dict[str, Any]: + return { + "adapted": self.adapted, + "itn": self.itn, + "language_description": self.language_description, + "word_delimiter": self.word_delimiter, + "writing_direction": self.writing_direction, + } + @dataclass class SessionInfo: @@ -400,6 +409,14 @@ class SessionInfo: base_time: datetime.datetime language_pack_info: LanguagePackInfo + def to_dict(self) -> dict[str, Any]: + return { + "config": self.config, + "session_id": self.session_id, + "base_time": self.base_time, + "language_pack_info": self.language_pack_info.to_dict(), + } + class AnnotationResult(list): """Processing result.""" @@ -658,6 +675,12 @@ class SpeakerVADStatus: is_active: bool speaker_id: Optional[str] = None + def to_dict(self) -> dict[str, Any]: + return { + "is_active": self.is_active, + "speaker_id": self.speaker_id, + } + class FragmentUtils: """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" From effb862d2c461c5a471451f38855854468688a58 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 00:45:02 +0100 Subject: [PATCH 043/215] README for 0.1.4 --- sdk/voice/OVERVIEW.md | 285 ------------------------ sdk/voice/README.md | 98 +++++++- sdk/voice/speechmatics/voice/_client.py | 1 - 3 files changed, 94 insertions(+), 290 deletions(-) delete mode 100644 sdk/voice/OVERVIEW.md diff --git a/sdk/voice/OVERVIEW.md b/sdk/voice/OVERVIEW.md deleted file mode 100644 index ed64f6e8..00000000 --- a/sdk/voice/OVERVIEW.md +++ /dev/null @@ -1,285 +0,0 @@ -# Speechmatics Voice Agent Client Overview - -The Speechmatics Voice Agent Client provides a comprehensive set of utilities for building voice agents with real-time speech-to-text capabilities, speaker diarization, and advanced transcription processing. - -## Key Features - -- **Real-time transcription** with partial and final results -- **Speaker diarization** with configurable focus modes -- **Voice activity detection** (VAD) with adaptive end-of-utterance detection -- **Event-driven architecture** with callback registration -- **TTFB (Time to First Byte) metrics** for performance monitoring - -## Installation - -```bash -pip install speechmatics-voice -``` - -## Quick Start - -### Basic Voice Agent Setup - -```python -import asyncio -from speechmatics.rt import Microphone -from speechmatics.voice import ( - VoiceAgentClient, - VoiceAgentConfig, - EndOfUtteranceMode, - AgentServerMessageType, - SpeakerSegment, -) - -async def main(): - # Configure the voice agent - config = VoiceAgentConfig( - enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.FIXED - ) - - # Initialize microphone - mic = Microphone( - sample_rate=16000, - chunk_size=160, - ) - - if not mic.start(): - print("Microphone not available") - return - - # Create client and register event handlers - async with VoiceAgentClient(config=config) as client: - - # Handle interim transcription segments - @client.on(AgentServerMessageType.ADD_INTERIM_SEGMENTS) - def handle_interim_segments(message): - segments: list[SpeakerSegment] = message["segments"] - for segment in segments: - print(f"Speaker {segment.speaker_id}: {segment.text}") - - # Handle finalized transcription segments - @client.on(AgentServerMessageType.ADD_SEGMENTS) - def handle_final_segments(message): - segments: list[SpeakerSegment] = message["segments"] - for segment in segments: - print(f"Speaker {segment.speaker_id}: {segment.text}") - - # Handle user started speaking event - @client.on(AgentServerMessageType.SPEAKING_STARTED) - def handle_speech_started(message): - print("User started speaking") - - # Handle user stopped speaking event - @client.on(AgentServerMessageType.SPEAKING_ENDED) - def handle_speech_ended(message): - print("User stopped speaking") - - # End of turn / utterance(s) - @client.on(AgentServerMessageType.END_OF_UTTERANCE) - def handle_end_of_turn(message): - print("End of turn") - - # Connect and start processing audio - await client.connect() - - while True: - frame = await mic.read(160) - await client.send_audio(frame) - -if __name__ == "__main__": - asyncio.run(main()) -``` - -## Core Components - -### VoiceAgentClient - -The main client class is the `VoiceAgentClient` which extends `AsyncClient` from the Speechmatics Real-Time SDK. - -```python -VoiceAgentClient( - api_key: Optional[str] = None, # Speechmatics API key - url: Optional[str] = None, # REST API endpoint URL - app: Optional[str] = None, # Optional application name - config: Optional[VoiceAgentConfig] = None, # Optional voice agent config -) -``` - -**Key Methods:** - -- `connect()` - Establish WebSocket connection -- `disconnect()` - Disconnect from WebSocket connection -- `update_diarization_config(config)` - Update the diarization configuration -- `send_audio(frame)` - Send audio data for transcription -- `on(event_type)` - Register event callbacks - -**Authentication:** - -- Uses `SPEECHMATICS_API_KEY` environment variable -- Custom API key via constructor parameter - -### VoiceAgentConfig - -Configuration class for voice agent behaviour. - -```python -from speechmatics.voice import ( - AdditionalVocabEntry, - AudioEncoding, - DiarizationSpeakerConfig, - EndOfUtteranceMode, - OperatingPoint, - VoiceAgentConfig, -) - -config = VoiceAgentConfig( - # Service Configuration - operating_point=OperatingPoint.ENHANCED, # Accuracy vs latency tradeoff - domain=None, # Optional domain specification - language="en", # Language code (ISO 639-1) - output_locale="en-GB", # Output locale format - - # Timing & Finalisation Control - max_delay=1.0, # Max delay for transcription (seconds) - end_of_utterance_silence_trigger=0.5, # Silence threshold for EOU (seconds) - end_of_utterance_mode=EndOfUtteranceMode.FIXED, # EOU detection mode - - # Vocabulary Enhancement - additional_vocab=[ # Custom vocabulary entries - AdditionalVocabEntry( - content="Speechmatics", - sounds_like=["speech matters", "speech magic"] - ) - ], - punctuation_overrides={}, # Custom punctuation rules - - # Speaker diarization - enable_diarization=True, # Enable speaker separation - speaker_sensitivity=0.5, # diarization sensitivity (0.0-1.0) - max_speakers=None, # Max speakers to detect - prefer_current_speaker=False, # Prefer grouping words to current speaker - speaker_config=DiarizationSpeakerConfig(), # Advanced speaker configuration - known_speakers=[], # Pre-identified speakers - - # Audio Configuration - sample_rate=16000, # Audio sample rate (Hz) - chunk_size=160, # Audio chunk size (samples) - audio_encoding=AudioEncoding.PCM_S16LE, # Audio encoding format -) -``` - -### Event Types - -The SDK provides comprehensive event handling through `AgentServerMessageType` (shadows `ServerMessageType` from the RT SDK): - -**API Control Messages:** - -- `RECOGNITION_STARTED` - Recognition session has started -- `END_OF_TRANSCRIPT` - Recognition session has ended -- `INFO` - Informational message -- `WARNING` - Warning message -- `ERROR` - Error message - -**Transcription Messages:** - -- `ADD_TRANSCRIPT` - Final transcription results -- `ADD_PARTIAL_TRANSCRIPT` - Partial transcription updates - -**Speaker Segments:** - -- `ADD_SEGMENTS` - Final speaker segments -- `ADD_INTERIM_SEGMENTS` - Interim speaker segments - -**Voice Activity Detection (VAD):** - -- `USER_SPEECH_STARTED` - Voice activity detected -- `USER_SPEECH_ENDED` - Voice activity ended - -**Speaker Information:** - -- `SPEAKERS_RESULT` - Speaker identification results - -**Performance Metrics:** - -- `METRICS` - General STT engine metrics -- `TTFB_METRICS` - Time to First Byte metrics - -### SpeakerSegment - -Represents a segment of speech from a specific speaker: - -```python -def format_segment(segment: SpeakerSegment) -> str: - if segment.is_active: - return segment.format_text("@{speaker_id}: {text}") - else: - return segment.format_text("@{speaker_id} (background): {text}") -``` - -The `format_text()` method uses the following placeholders: - -- `{speaker_id}`: The speaker ID (empty if no speaker) (e.g. `S1`). -- `{text}`: The formatted text of the segment (e.g. `Hello, how are you?`). -- `{ts}`: The timestamp of the segment (e.g. `2025-09-03T16:05:01.000Z`). -- `{lang}`: The language of the segment (e.g. `en`). - -## Advanced Configuration - -### End of Utterance Modes - -The end of utterance mode controls how the STT engine detects the end of a user's speech. This can either be fixed silence threshold (defaults to `0.5s`) or it can be adaptive based on speech patterns, rate, pauses, and disfluencies. - -```python -from speechmatics.voice import EndOfUtteranceMode - -# Fixed silence threshold -EndOfUtteranceMode.FIXED - -# Adaptive silence detection (recommended) -EndOfUtteranceMode.ADAPTIVE -``` - -### Speaker Diarization - -The configuration below would: - -- focus on speakers `S1` and `S2` -- ignore speakers `S3` and `S4` -- retain words from all other speakers - -```python -from speechmatics.voice import DiarizationSpeakerConfig, DiarizationKnownSpeaker - -config = VoiceAgentConfig( - enable_diarization=True, - diarization_config=DiarizationSpeakerConfig( - focus_speakers=["S1", "S2"], - ignore_speakers=["S3", "S4"], - focus_mode=DiarizationFocusMode.RETAIN, - ) -) -``` - -### Additional Vocabulary - -If you have specific words that you need to make sure are transcribed properly, you can add in up to 1,000 additional vocabulary entries. You can optionally provide a list of words that sound like the word you are adding. - -```python -from speechmatics.voice import AdditionalVocabEntry - -config = VoiceAgentConfig( - additional_vocab=[ - AdditionalVocabEntry( - content="Speechmatics", - sounds_like=["speech matters", "speech magic"] - ) - ] -) -``` - -## Environment Variables - -- `SPEECHMATICS_API_KEY` - Your Speechmatics API key -- `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) -- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging diff --git a/sdk/voice/README.md b/sdk/voice/README.md index d3739b5a..8cf2ddd0 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -6,8 +6,6 @@ An SDK for working with the Speechmatics Real-Time API optimised for use in voic This uses the Python Real-Time API to process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. -See [OVERVIEW.md](OVERVIEW.md) for more information. - ## Installation ```bash @@ -102,7 +100,94 @@ The `examples/` directory contains practical demonstrations of the Voice Agent S ## Client Configuration -The `VoiceAgentClient` can be configured with a number of options to control the behavior of the client. +The `VoiceAgentClient` can be configured with a number of options to control the behaviour of the client using the `VoiceAgentConfig` class. + +### VoiceAgentConfig Parameters + +#### Service Configuration + +- **`operating_point`** (`OperatingPoint`): Operating point for transcription accuracy vs. latency tradeoff. It is recommended to use `OperatingPoint.ENHANCED` for most use cases. Defaults to `OperatingPoint.ENHANCED`. +- **`domain`** (`Optional[str]`): Domain for Speechmatics API. Defaults to `None`. +- **`language`** (`str`): Language code for transcription. Defaults to `"en"`. +- **`output_locale`** (`Optional[str]`): Output locale for transcription, e.g. `"en-GB"`. Defaults to `None`. + +#### Timing and Latency Features + +- **`max_delay`** (`float`): Maximum delay in seconds for transcription. This forces the STT engine to speed up the processing of transcribed words and reduces the interval between partial and final results. Lower values can have an impact on accuracy. Defaults to `0.7`. +- **`end_of_utterance_silence_trigger`** (`float`): Maximum delay in seconds for end of utterance trigger. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be lower than `max_delay`. Defaults to `0.2`. +- **`end_of_utterance_max_delay`** (`float`): Maximum delay in seconds for end of utterance delay. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be greater than `end_of_utterance_silence_trigger`. Defaults to `10.0`. +- **`end_of_utterance_mode`** (`EndOfUtteranceMode`): End of utterance delay mode. When `ADAPTIVE` is used, the delay can be adjusted on the content of what the most recent speaker has said, such as rate of speech and whether they have any pauses or disfluencies. When `FIXED` is used, the delay is fixed to the value of `end_of_utterance_silence_trigger`. Use of `EXTERNAL` disables end of utterance detection and uses a fallback timer. Defaults to `EndOfUtteranceMode.FIXED`. + +#### Language and Vocabulary Features + +- **`additional_vocab`** (`list[AdditionalVocabEntry]`): List of additional vocabulary entries. If you supply a list of additional vocabulary entries, this will increase the weight of the words in the vocabulary and help the STT engine to better transcribe the words. Defaults to `[]`. +- **`punctuation_overrides`** (`Optional[dict]`): Punctuation overrides. This allows you to override the punctuation in the STT engine. This is useful for languages that use different punctuation than English. See documentation for more information. Defaults to `None`. + +#### Speaker Diarization + +- **`enable_diarization`** (`bool`): Enable speaker diarization. When enabled, the STT engine will determine and attribute words to unique speakers. The `speaker_sensitivity` parameter can be used to adjust the sensitivity of diarization. Defaults to `False`. +- **`speaker_sensitivity`** (`float`): Diarization sensitivity. A higher value increases the sensitivity of diarization and helps when two or more speakers have similar voices. Defaults to `0.5`. +- **`max_speakers`** (`Optional[int]`): Maximum number of speakers to detect. This forces the STT engine to cluster words into a fixed number of speakers. It should not be used to limit the number of speakers, unless it is clear that there will only be a known number of speakers. Defaults to `None`. +- **`prefer_current_speaker`** (`bool`): Prefer current speaker ID. When set to true, groups of words close together are given extra weight to be identified as the same speaker. Defaults to `False`. +- **`speaker_config`** (`DiarizationSpeakerConfig`): Configuration to specify speakers to focus on, ignore and how to deal with speakers that are not in focus. Defaults to `DiarizationSpeakerConfig()`. +- **`known_speakers`** (`list[DiarizationKnownSpeaker]`): List of known speaker labels and identifiers. If you supply a list of labels and identifiers for speakers, then the STT engine will use them to attribute any spoken words to that speaker. This is useful when you want to attribute words to a specific speaker, such as the assistant or a specific user. Labels and identifiers can be obtained from a running STT session and then used in subsequent sessions. Identifiers are unique to each Speechmatics account and cannot be used across accounts. Defaults to `[]`. + +#### Advanced Features + +- **`include_results`** (`bool`): Include word data in the response. This is useful for debugging and understanding the STT engine's behaviour. Defaults to `False`. + +#### Audio Configuration + +- **`sample_rate`** (`int`): Audio sample rate for streaming. Defaults to `16000`. +- **`audio_encoding`** (`AudioEncoding`): Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. + +### Example Configuration + +```python +from speechmatics.voice import ( + VoiceAgentConfig, + EndOfUtteranceMode, + DiarizationSpeakerConfig, + AdditionalVocabEntry +) +from speechmatics.rt import OperatingPoint, AudioEncoding + +# Basic configuration +config = VoiceAgentConfig( + language="en", + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, +) + +# Advanced configuration with custom settings +advanced_config = VoiceAgentConfig( + operating_point=OperatingPoint.ENHANCED, + language="en-GB", + output_locale="en-GB", + max_delay=0.5, + end_of_utterance_silence_trigger=0.15, + end_of_utterance_max_delay=8.0, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + enable_diarization=True, + speaker_sensitivity=0.7, + max_speakers=3, + prefer_current_speaker=True, + speaker_config=DiarizationSpeakerConfig( + focus_speakers=["S1", "S2"], + ignore_speakers=["S3"], + focus_mode=DiarizationFocusMode.RETAIN, + ), + additional_vocab=[ + AdditionalVocabEntry( + content="Speechmatics", + sounds_like=["speech matters", "speech magic"] + ) + ], + include_results=True, + sample_rate=16000, + audio_encoding=AudioEncoding.PCM_S16LE +) +``` ## Messages @@ -229,9 +314,14 @@ The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED` When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. +## Environment Variables + +- `SPEECHMATICS_API_KEY` - Your Speechmatics API key +- `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) +- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging + ## Documentation -- **SDK Overview**: See [OVERVIEW.md](OVERVIEW.md) for comprehensive API documentation - **Speechmatics API**: https://docs.speechmatics.com ## License diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 2b590bad..084e615b 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -258,7 +258,6 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - self._logger.debug("End of utterance") self.emit( AgentServerMessageType.END_OF_TURN, {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, From 187423642cbd88ba1cdcd66dd75be941c17c8177 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 00:56:53 +0100 Subject: [PATCH 044/215] README update --- sdk/voice/README.md | 28 +++++++++++++++--------- sdk/voice/speechmatics/voice/__init__.py | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 8cf2ddd0..17788b80 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -145,32 +145,33 @@ The `VoiceAgentClient` can be configured with a number of options to control the ```python from speechmatics.voice import ( - VoiceAgentConfig, - EndOfUtteranceMode, + AdditionalVocabEntry, + AudioEncoding, DiarizationSpeakerConfig, - AdditionalVocabEntry + EndOfUtteranceMode, + OperatingPoint, + VoiceAgentConfig ) -from speechmatics.rt import OperatingPoint, AudioEncoding # Basic configuration config = VoiceAgentConfig( language="en", enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, ) # Advanced configuration with custom settings advanced_config = VoiceAgentConfig( operating_point=OperatingPoint.ENHANCED, - language="en-GB", + language="en", output_locale="en-GB", - max_delay=0.5, - end_of_utterance_silence_trigger=0.15, + max_delay=0.7, + end_of_utterance_silence_trigger=0.25, end_of_utterance_max_delay=8.0, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, enable_diarization=True, - speaker_sensitivity=0.7, - max_speakers=3, + speaker_sensitivity=0.5, + max_speakers=6, prefer_current_speaker=True, speaker_config=DiarizationSpeakerConfig( focus_speakers=["S1", "S2"], @@ -314,6 +315,13 @@ The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED` When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. +```json +{ + "message": "EndOfTurn", + "metadata": { "end_time": 9.16, "start_time": 11.08 } +} +``` + ## Environment Variables - `SPEECHMATICS_API_KEY` - Your Speechmatics API key diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index f83b0f1a..d35e245b 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.4" +__version__ = "0.1.5" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From fc76dd3e59fa607e93cb671ff0743d97594b90c9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 09:33:22 +0100 Subject: [PATCH 045/215] Updated version info. --- sdk/voice/speechmatics/voice/__init__.py | 2 +- sdk/voice/speechmatics/voice/_models.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index d35e245b..14ea54dc 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.5" +__version__ = "0.1.6" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index fda92456..e1fa5d98 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -543,6 +543,7 @@ def to_dict(self, include_results: bool = False) -> dict[str, Any]: "timestamp": self.timestamp, "language": self.language, "text": self.text, + "annotation": self.annotation, **({"results": [f.result for f in self.fragments]} if include_results else {}), } From 95e97aa5b04223af2f74cad43e38e0179299a43e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 10:06:47 +0100 Subject: [PATCH 046/215] Updated examples from original PR. --- examples/voice/README.md | 135 ++++++++++++++++++++- examples/voice/file_to_jsonl.py | 79 ++++++++++-- examples/voice/file_transcription.py | 107 ++++++++++++++-- examples/voice/microphone_transcription.py | 94 +++++++++++++- examples/voice/requirements-examples.txt | 3 +- 5 files changed, 384 insertions(+), 34 deletions(-) diff --git a/examples/voice/README.md b/examples/voice/README.md index 2d84090d..71e7f48e 100644 --- a/examples/voice/README.md +++ b/examples/voice/README.md @@ -16,16 +16,64 @@ pip install -r requirements-examples.txt ## Examples +### Setup + +Install the Voice SDK to use the examples. Using a virtual environment is recommended. + +```bash +# Voice examples +cd examples/voice + +# Create a virtual environment +python -m venv .venv +source .venv/bin/activate + +# Install requirements for the examples +pip install -r requirements-examples.txt +``` + ### File Transcription An example that takes an audio file and sends it in real-time to the SDK and outputs the interim and final transcription to the console. The audio will also be played through the selected audio output device. +By default, this demo will focus on the second speaker and is set to ignore the first speaker altogether. This means that you may hear the first speaker's audio but not see their transcription. You can change this by modifying the `speaker_config` parameters in the `VoiceAgentConfig`. + Audio files should be in Mono 16kHz 16-bit WAV format. -```bash -cd examples/voice -pip install -r requirements-examples.txt +``` +usage: file_transcription.py [-h] [--api-key API_KEY] [--url URL] + [--focus-speakers [FOCUS_SPEAKERS ...]] + [--ignore-speakers [IGNORE_SPEAKERS ...]] + [--ignore-mode] [--max-delay MAX_DELAY] + [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] + [--end-of-utterance-mode {FIXED,ADAPTIVE}] + [audio_file] + +Transcribe an audio file with real-time playback and speaker diarisation using Speechmatics Voice API + +positional arguments: + audio_file Path to the input audio file (WAV format, mono 16-bit). Defaults to '../example2.wav' + +options: + -h, --help show this help message and exit + --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) + --url URL Speechmatics server URL (optional) + --focus-speakers [FOCUS_SPEAKERS ...] + Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead + --ignore-speakers [IGNORE_SPEAKERS ...] + Specific speakers to ignore (e.g., S1 S2) + --ignore-mode Use ignore mode instead of focus mode for --focus-speakers + --max-delay MAX_DELAY + Maximum delay for transcription results in seconds (default: 0.7) + --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER + Silence duration to trigger end of utterance in seconds (default: 0.5) + --end-of-utterance-mode {FIXED,ADAPTIVE} + End of utterance detection mode (default: ADAPTIVE) + +Example: python file_transcription.py audio.wav --focus-speakers S1 --max-delay 1.0 +``` +```bash # Transcribe the default example audio file python file_transcription.py @@ -45,10 +93,42 @@ python file_transcription.py path/to/your/audio.wav An example that takes audio from a microphone and sends it in real-time to the SDK and outputs the interim and final transcription to the console. -```bash -cd examples/voice -pip install -r requirements-examples.txt +By default this will focus on the first speaker only. Adjust the `speaker_config` parameters in the `VoiceAgentConfig` to change this. + +``` +usage: microphone_transcription.py [-h] [--api-key API_KEY] [--url URL] + [--sample-rate SAMPLE_RATE] [--chunk-size CHUNK_SIZE] + [--focus-speakers [FOCUS_SPEAKERS ...]] [--ignore-speakers [IGNORE_SPEAKERS ...]] + [--ignore-mode] [--max-delay MAX_DELAY] + [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] + [--end-of-utterance-mode {FIXED,ADAPTIVE}] + +Live microphone transcription with speaker diarisation using Speechmatics Voice API + +options: + -h, --help show this help message and exit + --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) + --url URL Speechmatics server URL (optional) + --sample-rate SAMPLE_RATE + Audio sample rate in Hz (default: 16000) + --chunk-size CHUNK_SIZE + Audio chunk size in bytes (default: 320) + --focus-speakers [FOCUS_SPEAKERS ...] + Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead + --ignore-speakers [IGNORE_SPEAKERS ...] + Specific speakers to ignore (e.g., S1 S2) + --ignore-mode Use ignore mode instead of focus mode for --focus-speakers + --max-delay MAX_DELAY + Maximum delay for transcription results in seconds (default: 0.7) + --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER + Silence duration to trigger end of utterance in seconds (default: 0.5) + --end-of-utterance-mode {FIXED,ADAPTIVE} + End of utterance detection mode (default: ADAPTIVE) + +Example: python microphone_transcription.py --focus-speakers S1 S2 --max-delay 1.0 +``` +```bash # Real-time microphone transcription python microphone_transcription.py ``` @@ -60,6 +140,49 @@ python microphone_transcription.py - Speaker diarization - Speaker start / stop events +### File to JSONL + +An example that takes an audio file and sends it in real-time to the SDK and outputs the transcription to a JSONL file. This can be useful for debugging or for processing the transcription results in a different way. + +``` +usage: file_to_jsonl.py input_file + [-h] [--api-key API_KEY] [--url URL] [--output OUTPUT] + [--focus-speakers [FOCUS_SPEAKERS ...]] [--ignore-speakers [IGNORE_SPEAKERS ...]] [--ignore-mode] + [--max-delay MAX_DELAY] + [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] + [--end-of-utterance-mode {FIXED,ADAPTIVE}] + +Transcribe an audio file to JSONL format using Speechmatics Voice API + +positional arguments: + input_file Path to the input audio file (WAV format) + +options: + -h, --help show this help message and exit + --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) + --url URL Speechmatics server URL (optional) + --output OUTPUT, -o OUTPUT + Output JSONL file (defaults to stdout) + --focus-speakers [FOCUS_SPEAKERS ...] + Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead + --ignore-speakers [IGNORE_SPEAKERS ...] + Specific speakers to ignore (e.g., S1 S2) + --ignore-mode Use ignore mode instead of focus mode for --focus-speakers + --max-delay MAX_DELAY + Maximum delay for transcription results in seconds (default: 0.7) + --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER + Silence duration to trigger end of utterance in seconds (default: 0.5) + --end-of-utterance-mode {FIXED,ADAPTIVE} + End of utterance detection mode (default: FIXED) + +Example: python file_to_jsonl.py --output transcription.jsonl --focus-speakers S1 example1.wav +``` + +```bash +# Transcribe the default example audio file +python file_to_jsonl.py ../example1.wav --api-key $SPEECHMATICS_API_KEY +``` + ## Troubleshooting **PyAudio installation issues:** diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index 888d623f..ccf3a831 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -12,6 +12,8 @@ from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -29,15 +31,26 @@ async def main() -> None: print(f"Error: Input file '{args.input_file}' not found", file=sys.stderr) sys.exit(1) + # Create speaker configuration if speaker options are provided + speaker_config = None + if args.focus_speakers or args.ignore_speakers: + focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.RETAIN + speaker_config = DiarizationSpeakerConfig( + focus_speakers=args.focus_speakers, + ignore_speakers=args.ignore_speakers, + focus_mode=focus_mode, + ) + # Client client = VoiceAgentClient( api_key=args.api_key, url=args.url, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.5, - max_delay=0.7, - end_of_utterance_mode=EndOfUtteranceMode.FIXED, + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, + max_delay=args.max_delay, + end_of_utterance_mode=args.end_of_utterance_mode, enable_diarization=True, + speaker_config=speaker_config, additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matters", "speech magic"]) ], @@ -78,17 +91,11 @@ def log_message(message): # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) - client.once(AgentServerMessageType.INFO, log_message) - client.on(AgentServerMessageType.WARNING, log_message) - client.on(AgentServerMessageType.ERROR, log_message) - client.once(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) - client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) - client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) - client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) # Clear output file if it exists if args.output and os.path.exists(args.output): @@ -149,16 +156,64 @@ async def send_audio_file( def parse_args(): """Parse command line arguments.""" parser = argparse.ArgumentParser( - description="Transcribe an audio file to JSONL format using Speechmatics Voice API" + description="Transcribe an audio file to JSONL format using Speechmatics Voice API", + epilog="Example: python file_to_jsonl.py audio.wav --output transcription.jsonl --focus-speakers S1 S2", ) parser.add_argument("input_file", help="Path to the input audio file (WAV format)") parser.add_argument( "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", ) parser.add_argument("--url", help="Speechmatics server URL (optional)") parser.add_argument("--output", "-o", help="Output JSONL file (defaults to stdout)") - return parser.parse_args() + + # Speaker configuration arguments + parser.add_argument( + "--focus-speakers", + nargs="*", + help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", + ) + parser.add_argument( + "--ignore-speakers", + nargs="*", + help="Specific speakers to ignore (e.g., S1 S2)", + ) + parser.add_argument( + "--ignore-mode", + action="store_true", + help="Use ignore mode instead of focus mode for --focus-speakers", + ) + + # Voice Agent configuration arguments + parser.add_argument( + "--max-delay", + type=float, + default=0.7, + help="Maximum delay for transcription results in seconds (default: 0.7)", + ) + parser.add_argument( + "--end-of-utterance-silence-trigger", + type=float, + default=0.5, + help="Silence duration to trigger end of utterance in seconds (default: 0.5)", + ) + parser.add_argument( + "--end-of-utterance-mode", + choices=["FIXED", "ADAPTIVE"], + default="FIXED", + help="End of utterance detection mode (default: FIXED)", + ) + + args = parser.parse_args() + + # Convert string to EndOfUtteranceMode enum + if args.end_of_utterance_mode == "FIXED": + args.end_of_utterance_mode = EndOfUtteranceMode.FIXED + else: + args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE + + return args if __name__ == "__main__": diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py index 97295a0a..d1e326da 100644 --- a/examples/voice/file_transcription.py +++ b/examples/voice/file_transcription.py @@ -1,4 +1,6 @@ +import argparse import asyncio +import os import sys import wave from pathlib import Path @@ -29,16 +31,17 @@ async def main() -> None: - Real-time audio playback during transcription - Speaker diarisation with voice activity detection - Comprehensive event handling and logging + - This example will **only** show transcription from the second speaker """ - # Parse command line arguments for audio file path - # Default to example.wav if no file specified - audio_file_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent / "../example2.wav" + # Parse command line arguments + args = parse_args() + + audio_file_path = Path(args.audio_file) # Validate that the audio file exists if not audio_file_path.exists(): print(f"Error: Audio file not found: {audio_file_path}") - print("Usage: python file_transcription.py [audio_file.wav]") - return + sys.exit(1) # Load and validate the audio file format # Only mono 16-bit WAV files are supported @@ -53,17 +56,31 @@ async def main() -> None: # User can select from available output devices audio_player = _setup_audio_player(sample_rate, channels, sample_width) + # Create speaker configuration if speaker options are provided + speaker_config = None + if args.focus_speakers or args.ignore_speakers: + focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.FOCUS + speaker_config = DiarizationSpeakerConfig( + focus_speakers=args.focus_speakers, + ignore_speakers=args.ignore_speakers, + focus_mode=focus_mode, + ) + else: + # Default behaviour: focus on second speaker and ignore first + speaker_config = DiarizationSpeakerConfig(focus_speakers=["S2"], focus_mode=DiarizationFocusMode.IGNORE) + # Configure Voice Agent with transcription settings config = VoiceAgentConfig( sample_rate=sample_rate, - end_of_utterance_silence_trigger=0.5, + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, + max_delay=args.max_delay, enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - speaker_config=DiarizationSpeakerConfig(focus_speakers=["S2"], focus_mode=DiarizationFocusMode.IGNORE), + end_of_utterance_mode=args.end_of_utterance_mode, + speaker_config=speaker_config, ) # Create Voice Agent client and start transcription - async with VoiceAgentClient(config=config) as client: + async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: # Register event handlers for transcription events _register_event_handlers(client, logger) @@ -151,6 +168,11 @@ def handle_speech_ended(message): """Handle speech end events.""" logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") + @client.on(AgentServerMessageType.END_OF_TURN) + def handle_end_of_turn(message): + """Handle end of turn events.""" + logger.log(CustomLevels.SPEAKER, f"🤖 End of turn: {message}") + @client.on(AgentServerMessageType.TTFB_METRICS) def handle_metrics(message): """Handle time-to-first-byte metrics.""" @@ -183,5 +205,72 @@ async def _stream_audio_file( await asyncio.sleep(chunk_duration) +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Transcribe an audio file with real-time playback and speaker diarisation using Speechmatics Voice API", + epilog="Example: python file_transcription.py audio.wav --focus-speakers S1 --max-delay 1.0", + ) + parser.add_argument( + "audio_file", + nargs="?", + default="../example2.wav", + help="Path to the input audio file (WAV format, mono 16-bit). Defaults to '../example2.wav'", + ) + parser.add_argument( + "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), + help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", + ) + parser.add_argument("--url", help="Speechmatics server URL (optional)") + + # Speaker configuration arguments + parser.add_argument( + "--focus-speakers", + nargs="*", + help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", + ) + parser.add_argument( + "--ignore-speakers", + nargs="*", + help="Specific speakers to ignore (e.g., S1 S2)", + ) + parser.add_argument( + "--ignore-mode", + action="store_true", + help="Use ignore mode instead of focus mode for --focus-speakers", + ) + + # Voice Agent configuration arguments + parser.add_argument( + "--max-delay", + type=float, + default=0.7, + help="Maximum delay for transcription results in seconds (default: 0.7)", + ) + parser.add_argument( + "--end-of-utterance-silence-trigger", + type=float, + default=0.5, + help="Silence duration to trigger end of utterance in seconds (default: 0.5)", + ) + parser.add_argument( + "--end-of-utterance-mode", + choices=["FIXED", "ADAPTIVE"], + default="ADAPTIVE", + help="End of utterance detection mode (default: ADAPTIVE)", + ) + + args = parser.parse_args() + + # Convert string to EndOfUtteranceMode enum + if args.end_of_utterance_mode == "FIXED": + args.end_of_utterance_mode = EndOfUtteranceMode.FIXED + else: + args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE + + return args + + if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index 95e281ac..d8293f18 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -1,4 +1,6 @@ +import argparse import asyncio +import os from colorama import init as colorama_init from utils import CustomLevels @@ -7,6 +9,7 @@ from speechmatics.rt import Microphone from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationFocusMode from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient @@ -27,27 +30,43 @@ async def main() -> None: - Performance metrics (TTFB) - Works with any PyAudio-compatible input device """ + # Parse command line arguments + args = parse_args() # Audio configuration for real-time processing - sample_rate = 16000 - chunk_size = 320 + sample_rate = args.sample_rate + chunk_size = args.chunk_size # Setup microphone with user device selection mic = _setup_microphone(sample_rate, chunk_size) if not mic: return + # Create speaker configuration if speaker options are provided + speaker_config = None + if args.focus_speakers or args.ignore_speakers: + focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.FOCUS + speaker_config = DiarizationSpeakerConfig( + focus_speakers=args.focus_speakers, + ignore_speakers=args.ignore_speakers, + focus_mode=focus_mode, + ) + else: + # Default behaviour: focus on first speaker + speaker_config = DiarizationSpeakerConfig(focus_speakers=["S1"]) + # Configure Voice Agent with microphone-specific settings config = VoiceAgentConfig( sample_rate=sample_rate, - end_of_utterance_silence_trigger=0.5, + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, + max_delay=args.max_delay, enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - speaker_config=DiarizationSpeakerConfig(focus_speakers=["S1"]), + end_of_utterance_mode=args.end_of_utterance_mode, + speaker_config=speaker_config, ) # Create Voice Agent client and start transcription - async with VoiceAgentClient(config=config) as client: + async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: # Register event handlers for real-time transcription events _register_event_handlers(client, logger) @@ -127,5 +146,68 @@ async def _stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_si await client.send_audio(frame) +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Live microphone transcription with speaker diarisation using Speechmatics Voice API", + epilog="Example: python microphone_transcription.py --focus-speakers S1 S2 --max-delay 1.0", + ) + parser.add_argument( + "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), + help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", + ) + parser.add_argument("--url", help="Speechmatics server URL (optional)") + parser.add_argument("--sample-rate", type=int, default=16000, help="Audio sample rate in Hz (default: 16000)") + parser.add_argument("--chunk-size", type=int, default=320, help="Audio chunk size in bytes (default: 320)") + + # Speaker configuration arguments + parser.add_argument( + "--focus-speakers", + nargs="*", + help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", + ) + parser.add_argument( + "--ignore-speakers", + nargs="*", + help="Specific speakers to ignore (e.g., S1 S2)", + ) + parser.add_argument( + "--ignore-mode", + action="store_true", + help="Use ignore mode instead of focus mode for --focus-speakers", + ) + + # Voice Agent configuration arguments + parser.add_argument( + "--max-delay", + type=float, + default=0.7, + help="Maximum delay for transcription results in seconds (default: 0.7)", + ) + parser.add_argument( + "--end-of-utterance-silence-trigger", + type=float, + default=0.5, + help="Silence duration to trigger end of utterance in seconds (default: 0.5)", + ) + parser.add_argument( + "--end-of-utterance-mode", + choices=["FIXED", "ADAPTIVE"], + default="ADAPTIVE", + help="End of utterance detection mode (default: ADAPTIVE)", + ) + + args = parser.parse_args() + + # Convert string to EndOfUtteranceMode enum + if args.end_of_utterance_mode == "FIXED": + args.end_of_utterance_mode = EndOfUtteranceMode.FIXED + else: + args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE + + return args + + if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/voice/requirements-examples.txt b/examples/voice/requirements-examples.txt index 593081ce..a0e6d1bd 100644 --- a/examples/voice/requirements-examples.txt +++ b/examples/voice/requirements-examples.txt @@ -1,2 +1,3 @@ -pyaudio colorama +pyaudio +speechmatics-voice From 71e5e9db190f92944d34a4bf449f2261b642dc2c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 20:59:07 +0100 Subject: [PATCH 047/215] Reduce superfluous messages when no changes. --- sdk/voice/speechmatics/voice/_client.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 084e615b..87b5bc1c 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -604,13 +604,6 @@ async def _process_speech_fragments(self) -> None: # Trim the view self._current_view.trim(start_time=self._current_view.start_time, end_time=last_segment.end_time) - # Emit the segments - asyncio.create_task(self._emit_segments()) - - # Only do this for ADAPTIVE end of utterance - if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE: - """If ADAPTIVE, then calculate whether the turn has completed.""" - # Compare previous view to this view if self._previous_view: changes = FragmentUtils.compare_views(self._session, self._previous_view, self._current_view) @@ -620,6 +613,14 @@ async def _process_speech_fragments(self) -> None: # Update the previous view self._previous_view = self._current_view + # Emit the segments + if changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL): + asyncio.create_task(self._emit_segments()) + + # Only do this for ADAPTIVE end of utterance + if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE: + """If ADAPTIVE, then calculate whether the turn has completed.""" + # Calculate delay to end of utterance should_emit, emit_final_delay = self._calc_delay_to_end_of_utterance(self._current_view, changes) From 4a8bc58820e08de6d9c2757aa3e27355ade7684b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 16 Sep 2025 21:22:25 +0100 Subject: [PATCH 048/215] README with pipeline info --- sdk/voice/README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 17788b80..c6f23d6d 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -4,7 +4,48 @@ An SDK for working with the Speechmatics Real-Time API optimised for use in voice agents or transcription services. -This uses the Python Real-Time API to process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. +## Overview + +The Voice Agent SDK is designed to be a set of helper classes that use the Real-Time AsyncClient to connect to the Speechmatics Real-Time API and process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. + +```mermaid +graph TD + A[Client connects] --> B[Recognition started] + B --> C[Audio chunks sent] + C --> D[STT processing] + + D --> E[Partial transcripts rx] + D --> F[Final transcripts rx] + + E --> G[Process transcripts] + F --> G + + G --> H[Accumulate speech fragments] + H --> I{Changes detected?} + + I -->|No| J[Skip] + I -->|Yes| K[Create speaker segments] + + K --> L{Segment type?} + L -->|Final| M[Emit final segments] + L -->|Partial| N[Emit partial segments] + + M --> O[Trim fragments] + N --> P{End of utterance?} + O --> P + + P -->|FIXED mode| Q[Wait for STT signal] + P -->|ADAPTIVE mode| R[Calculate timing] + + Q --> S[Emit END_OF_TURN] + R --> S + + style A fill:#e1f5fe + style B fill:#e8f5e8 + style M fill:#fff3e0 + style N fill:#fff3e0 + style S fill:#fce4ec +``` ## Installation @@ -14,7 +55,7 @@ pip install speechmatics-voice ## Requirements -You must have a valid Speechmatics API key to use this SDK. You can get one from the [Speechmatics Console](https://console.speechmatics.com). +You must have a valid Speechmatics API key to use this SDK. You can get one from the [Speechmatics Portal](https://portal.speechmatics.com/). Store this as `SPEECHMATICS_API_KEY` environment variable in your `.env` file or use `export SPEECHMATICS_API_KEY="your_api_key_here"` in your terminal. From cea576263eea5f57a9dfb38ec9a0eb150f7051f4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 17 Sep 2025 22:47:07 +0100 Subject: [PATCH 049/215] updated docs and add preview attribute --- sdk/voice/README.md | 2 +- sdk/voice/speechmatics/voice/_models.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index c6f23d6d..148d44b4 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -256,7 +256,7 @@ Emitted when the recognition has started and contains the session ID and base ti ### `SPEAKER_STARTED` -Emitted when a speaker starts speaking. Contains the speaker ID and the VAD status. If there are multiple speakers, this will be emitted each time a speaker starts speaking. There will only be one active speaker at any given time. +Emitted when a speaker starts speaking. Contains the speaker ID and the VAD status. If there are multiple speakers, this will be emitted each time a speaker starts speaking. There will only be one active speaker at any given time. As speakers switch during a turn, separate `SPEAKER_ENDED` and `SPEAKER_STARTED` events will be emitted. ```json { diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index e1fa5d98..0f3f94d2 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -174,6 +174,9 @@ class VoiceAgentConfig: include_results: Include word data in the response. This is useful for debugging and understanding the STT engine's behavior. Defaults to False. + enable_preview_features: Enable preview features using a preview endpoint provided by + Speechmatics. Defaults to False. + sample_rate: Audio sample rate for streaming. Defaults to 16000. audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. """ @@ -202,6 +205,7 @@ class VoiceAgentConfig: # Advanced features include_results: bool = False + enable_preview_features: bool = False # Audio sample_rate: int = 16000 @@ -326,6 +330,7 @@ class AnnotationFlags(str, Enum): UPDATED_FINALS = "updated_finals" UPDATED_PARTIALS = "updated_partials" UPDATED_SPEAKERS = "updated_speakers" + FINALIZED = "finalized" # Content of segments ONLY_ACTIVE_SPEAKERS = "only_active_speakers" @@ -924,6 +929,8 @@ def compare_views( ) -> AnnotationResult: """Compare two SpeakerSegmentView objects and return the differences. + View 1 (new) is compared to view 2 (old). + Args: session: SessionInfo object. view1: The first SpeakerSegmentView object to compare. @@ -965,5 +972,9 @@ def compare_views( elif view1.segment_count > 0: result.add(AnnotationFlags.NEW) + # Finalized (last segment only has finals) + if view1.segment_count > 0 and view1.partial_count == 0: + result.add(AnnotationFlags.FINALIZED) + # Return the result return result From 33232688e3159d35e0d09d5b7c8e13681cd5ef1d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 17 Sep 2025 22:50:24 +0100 Subject: [PATCH 050/215] preview feature --- sdk/voice/speechmatics/voice/_client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 87b5bc1c..716e76c0 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -37,7 +37,6 @@ from ._models import VoiceAgentConfig DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] -PREVIEW_FEATURES = os.getenv("SPEECHMATICS_PREVIEW_FEATURES", "0").lower() in ["1", "true"] if DEBUG_MORE: @@ -748,12 +747,15 @@ def finalize(self, ttl: Optional[float] = None) -> None: """ async def emit() -> None: + # Yield until the delay has passed if ttl is not None and ttl > 0: await asyncio.sleep(ttl) - if PREVIEW_FEATURES: + + # Finalize the segments + if self._config.enable_preview_features: await self.send_message({"message": "Finalize"}) else: - await self._emit_segments(finalize=True) + await self._emit_segments(finalize=True, end_of_turn=True) asyncio.create_task(emit()) From 1c6b5b1827a6c93b0335c81011bb22ea1b2c8842 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 17 Sep 2025 22:51:55 +0100 Subject: [PATCH 051/215] change to extra debugging --- sdk/voice/speechmatics/voice/_client.py | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 716e76c0..5ef35d1b 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -234,6 +234,8 @@ def _register_event_handlers(self) -> None: # Recognition started event @self.once(ServerMessageType.RECOGNITION_STARTED) def _evt_on_recognition_started(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) self._is_ready_for_audio = True self._session = SessionInfo( config=self._config, @@ -245,22 +247,26 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) def _evt_on_partial_transcript(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) self._handle_transcript(message, is_final=False) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) def _evt_on_final_transcript(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) self._handle_transcript(message, is_final=True) # End of utterance event - if self._end_of_utterance_mode == EndOfUtteranceMode.FIXED: - - @self.on(ServerMessageType.END_OF_UTTERANCE) - def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - self.emit( - AgentServerMessageType.END_OF_TURN, - {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, - ) + @self.on(ServerMessageType.END_OF_UTTERANCE) + def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + self.emit( + AgentServerMessageType.END_OF_TURN, + {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, + ) async def connect(self) -> None: """Connect to the Speechmatics API. @@ -400,10 +406,6 @@ async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool is_final: Whether the data is final or partial. """ - # Debug - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - # Add the speech fragments fragments_available = await self._add_speech_fragments( message=message, From 946251c912341cf302401eed6a3c17a2124d3f26 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 19 Sep 2025 09:54:43 +0100 Subject: [PATCH 052/215] Updated comment and message type. --- sdk/voice/speechmatics/voice/_client.py | 5 +++-- sdk/voice/speechmatics/voice/_models.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 5ef35d1b..61bfeef5 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -22,6 +22,7 @@ from . import __version__ from ._logging import get_logger +from ._models import AgentClientMessageType from ._models import AgentServerMessageType from ._models import AnnotationFlags from ._models import AnnotationResult @@ -138,7 +139,7 @@ def __init__( self._metrics_emitter_interval: float = 10.0 self._metrics_emitter_task: Optional[asyncio.Task] = None - # Audi sampling info + # Audio sampling info self._audio_sample_rate: float = self._audio_format.sample_rate * 1.0 self._audio_sample_width: float = { AudioEncoding.PCM_F32LE: 4.0, @@ -755,7 +756,7 @@ async def emit() -> None: # Finalize the segments if self._config.enable_preview_features: - await self.send_message({"message": "Finalize"}) + await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) else: await self._emit_segments(finalize=True, end_of_turn=True) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 0f3f94d2..c1def647 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -238,7 +238,7 @@ class AgentClientMessageType(str, Enum): >>> await client.send_message(end_message) """ - FINALIZE_TURN = "FinalizeTurn" + FINALIZE_TURN = "Finalize" END_OF_STREAM = "EndOfStream" GET_SPEAKERS = "GetSpeakers" From 34bfea4e8083b13cd04373896ee3f998d9abfe2a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 19 Sep 2025 18:34:15 +0100 Subject: [PATCH 053/215] Move to a message queue to process in turn --- sdk/voice/speechmatics/voice/_client.py | 114 +++++++++++++++--------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 61bfeef5..e4e224a0 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -9,7 +9,9 @@ import os import re import time +from collections.abc import Awaitable from typing import Any +from typing import Callable from typing import Optional from urllib.parse import urlencode @@ -95,6 +97,10 @@ def __init__( language_pack_info=LanguagePackInfo.from_dict({}), ) + # STT message received queue + self._stt_message_queue: asyncio.Queue[Callable[[], Awaitable[None]]] = asyncio.Queue() + self._stt_queue_task: Optional[asyncio.Task] = None + # Timing info self._total_time: float = 0 self._total_bytes: int = 0 @@ -126,14 +132,6 @@ def __init__( # Timers for EndOfUtterance and EndOfTurn self._finalize_task: Optional[asyncio.Task] = None - self._eot_task: Optional[asyncio.Task] = None - - # Segment processor - self._processor_wait_time: float = 0.005 - self._processor_task: Optional[asyncio.Task] = None - - # Emitter task - self._finals_emitter_task: Optional[asyncio.Task] = None # Metrics emitter task self._metrics_emitter_interval: float = 10.0 @@ -250,24 +248,36 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: def _evt_on_partial_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) - self._handle_transcript(message, is_final=False) + + async def _handle() -> None: + await self._handle_transcript(message, is_final=False) + + self._stt_message_queue.put_nowait(_handle) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) def _evt_on_final_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) - self._handle_transcript(message, is_final=True) + + async def _handle() -> None: + await self._handle_transcript(message, is_final=True) + + self._stt_message_queue.put_nowait(_handle) # End of utterance event @self.on(ServerMessageType.END_OF_UTTERANCE) def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) - self.emit( - AgentServerMessageType.END_OF_TURN, - {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, - ) + + async def _handle() -> None: + self.emit( + AgentServerMessageType.END_OF_TURN, + {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, + ) + + self._stt_message_queue.put_nowait(_handle) async def connect(self) -> None: """Connect to the Speechmatics API. @@ -285,6 +295,9 @@ async def connect(self) -> None: ) return + # Start the processor task + self._stt_queue_task = asyncio.create_task(self._run_stt_queue()) + # Connect to API try: await self.start_session( @@ -318,6 +331,11 @@ async def disconnect(self) -> None: self._is_ready_for_audio = False self._stop_metrics_task() + # Stop the task + if self._stt_queue_task: + self._stt_queue_task.cancel() + self._stt_queue_task = None + def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """Update the diarization configuration. @@ -330,6 +348,25 @@ def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """ self._dz_config = config + async def _run_stt_queue(self) -> None: + """Run the STT message queue.""" + while True: + callback = await self._stt_message_queue.get() + + if asyncio.iscoroutine(callback): + await callback + elif asyncio.iscoroutinefunction(callback): + await callback() + elif callable(callback): + result = callback() + if asyncio.iscoroutine(result): + await result + + def _cancel_stt_queue(self) -> None: + """Cancel the STT message queue.""" + if self._stt_queue_task: + self._stt_queue_task.cancel() + async def send_audio(self, payload: bytes) -> None: """Send an audio frame through the WebSocket. @@ -389,19 +426,12 @@ def _stop_metrics_task(self) -> None: self._metrics_emitter_task.cancel() self._metrics_emitter_task = None - def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> None: - """Handle the partial and final transcript events. - - Args: - message: The new Partial or Final from the STT engine. - is_final: Whether the data is final or partial. - """ - # Handle async - asyncio.create_task(self._handle_transcript_async(message, is_final)) - - async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool) -> None: + async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> None: """Handle the partial and final transcript events (async). + As `AddTranscript` messages are _always_ followed by `AddPartialTranscript` messages, + we can skip processing. Also skop if there are no fragments in the buffer. + Args: message: The new Partial or Final from the STT engine. is_final: Whether the data is final or partial. @@ -413,22 +443,12 @@ async def _handle_transcript_async(self, message: dict[str, Any], is_final: bool is_final=is_final, ) - # Skip if unchanged - if not fragments_available: + # Skip if no fragments or `AddTranscript` + if not fragments_available or is_final: return - # Clear any existing timer - if self._processor_task is not None: - self._processor_task.cancel() - - # Send transcription frames after delay - async def process_after_delay(delay: float) -> None: - await asyncio.sleep(delay) - await self._process_speech_fragments() - self._processor_task = None - - # Send frames after delay - self._processor_task = asyncio.create_task(process_after_delay(self._processor_wait_time)) + # Process + await self._process_speech_fragments() def _calculate_ttfb(self, end_time: float) -> None: """Calculate the time to first text. @@ -615,9 +635,14 @@ async def _process_speech_fragments(self) -> None: # Update the previous view self._previous_view = self._current_view + # Catch no changes for ADAPTIVE mode + if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE and not changes.any( + AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL + ): + return + # Emit the segments - if changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL): - asyncio.create_task(self._emit_segments()) + await self._emit_segments() # Only do this for ADAPTIVE end of utterance if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE: @@ -754,13 +779,16 @@ async def emit() -> None: if ttl is not None and ttl > 0: await asyncio.sleep(ttl) - # Finalize the segments + # Finalize the segments using `Finalize` message to STT if self._config.enable_preview_features: await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) + + # Force finalizing of segments in the buffer else: await self._emit_segments(finalize=True, end_of_turn=True) - asyncio.create_task(emit()) + # Add to queue + self._stt_message_queue.put_nowait(emit) async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: """Emit segments to listeners. From e9ac289ad52fbcf91105920baefaa52f19bfe8ff Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 18:57:41 +0100 Subject: [PATCH 054/215] fix to EOU handling and finals being received; updated tests --- sdk/voice/speechmatics/voice/_client.py | 21 +- tests/voice/assets/chat1.jsonl | 501 +++++++++++++----------- tests/voice/assets/chat2.jsonl | 27 +- tests/voice/assets/chat3.jsonl | 225 ----------- tests/voice/test_04_models.py | 53 ++- tests/voice/test_05_thought.py | 92 +++++ 6 files changed, 434 insertions(+), 485 deletions(-) delete mode 100644 tests/voice/assets/chat3.jsonl create mode 100644 tests/voice/test_05_thought.py diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index e4e224a0..c4c6111a 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -272,10 +272,7 @@ def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: self._logger.debug(json.dumps(message)) async def _handle() -> None: - self.emit( - AgentServerMessageType.END_OF_TURN, - {"message": AgentServerMessageType.END_OF_TURN.value, "metadata": message.get("metadata", {})}, - ) + await self._emit_segments(finalize=True, end_of_turn=True) self._stt_message_queue.put_nowait(_handle) @@ -348,6 +345,10 @@ def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """ self._dz_config = config + def _start_stt_queue(self) -> None: + """Start the STT message queue.""" + self._stt_queue_task = asyncio.create_task(self._run_stt_queue()) + async def _run_stt_queue(self) -> None: """Run the STT message queue.""" while True: @@ -362,8 +363,8 @@ async def _run_stt_queue(self) -> None: if asyncio.iscoroutine(result): await result - def _cancel_stt_queue(self) -> None: - """Cancel the STT message queue.""" + def _stop_stt_queue(self) -> None: + """Stop the STT message queue.""" if self._stt_queue_task: self._stt_queue_task.cancel() @@ -580,6 +581,14 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = self._speech_fragments.extend(fragments) self._speech_fragments.sort(key=lambda x: x.idx) + # Remove fragment at head that is for previous + if ( + self._speech_fragments + and self._speech_fragments[0].is_punctuation + and self._speech_fragments[0].attaches_to == "previous" + ): + self._speech_fragments.pop(0) + # Debug the fragments if DEBUG_MORE: debug_payload = { diff --git a/tests/voice/assets/chat1.jsonl b/tests/voice/assets/chat1.jsonl index d81d3284..0e03667d 100644 --- a/tests/voice/assets/chat1.jsonl +++ b/tests/voice/assets/chat1.jsonl @@ -1,230 +1,271 @@ -{"ts": 1.8e-05, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} -{"ts": 7.8e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} -{"ts": 0.000114, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} -{"ts": 0.000134, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} -{"ts": 0.10114, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-15T19:47:28Z"}} -{"ts": 0.167755, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "a8779b0b-a238-43de-8211-c70f5fcbe191", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} -{"ts": 0.708136, "audio_ts": 0.54, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} -{"ts": 0.708319, "audio_ts": 0.54, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} -{"ts": 1.066432, "audio_ts": 0.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} -{"ts": 1.401315, "audio_ts": 1.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} -{"ts": 1.401995, "audio_ts": 1.24, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 1.407782, "audio_ts": 1.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "text": "Welcome", "annotation": ["has_partial"]}], "metadata": {"start_time": 0.36, "end_time": 0.92}}} -{"ts": 1.803549, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} -{"ts": 1.803719, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} -{"ts": 1.81367, "audio_ts": 1.64, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.28}}} -{"ts": 2.196341, "audio_ts": 2.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} -{"ts": 2.196536, "audio_ts": 2.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} -{"ts": 2.196743, "audio_ts": 2.03, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 2.202608, "audio_ts": 2.04, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} -{"ts": 2.484286, "audio_ts": 2.32, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} -{"ts": 2.492678, "audio_ts": 2.32, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}], "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} -{"ts": 2.799758, "audio_ts": 2.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} -{"ts": 2.800017, "audio_ts": 2.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} -{"ts": 2.800089, "audio_ts": 2.64, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} -{"ts": 2.80614, "audio_ts": 2.64, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:29.096+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.32, "end_time": 1.32, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}, "annotation": null}], "text": "Welcome to speech.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} -{"ts": 3.200744, "audio_ts": 3.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.44, "transcript": ""}}} -{"ts": 3.200932, "audio_ts": 3.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.8, "transcript": ""}}} -{"ts": 3.528368, "audio_ts": 3.36, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 2.8, "transcript": ""}}} -{"ts": 3.529249, "audio_ts": 3.36, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} -{"ts": 3.881017, "audio_ts": 3.72, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} -{"ts": 3.881107, "audio_ts": 3.72, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.52, "transcript": ""}}} -{"ts": 4.307043, "audio_ts": 4.14, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.44, "transcript": "We're"}}} -{"ts": 4.307265, "audio_ts": 4.14, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 4.312648, "audio_ts": 4.15, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 7, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "text": "We're", "annotation": ["has_partial"]}], "metadata": {"start_time": 1.32, "end_time": 3.72}}} -{"ts": 4.616071, "audio_ts": 4.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.44, "transcript": "We're "}}} -{"ts": 4.616139, "audio_ts": 4.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} -{"ts": 4.621818, "audio_ts": 4.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 9, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 10, "start_time": 4.08, "end_time": 4.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "text": "We're delighted that", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.16}}} -{"ts": 5.050354, "audio_ts": 4.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} -{"ts": 5.050534, "audio_ts": 4.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} -{"ts": 5.056403, "audio_ts": 4.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.44, "end_time": 4.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "text": "We're delighted that you've done", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.52}}} -{"ts": 5.356861, "audio_ts": 5.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} -{"ts": 5.357231, "audio_ts": 5.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} -{"ts": 5.365982, "audio_ts": 5.2, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 4.84}}} -{"ts": 5.767761, "audio_ts": 5.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} -{"ts": 5.768419, "audio_ts": 5.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} -{"ts": 5.774285, "audio_ts": 5.61, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.96, "end_time": 5.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.24}}} -{"ts": 6.071697, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.93, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} -{"ts": 6.077611, "audio_ts": 5.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 5.36, "end_time": 5.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.93, "result": {"final": false, "alternatives": [{"confidence": 0.93, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.6}}} -{"ts": 6.424714, "audio_ts": 6.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.56, "start_time": 4.96, "transcript": "try our "}}} -{"ts": 6.424806, "audio_ts": 6.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.56, "transcript": "speech to"}}} -{"ts": 6.430272, "audio_ts": 6.27, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.88, "end_time": 5.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 5.96}}} -{"ts": 6.817595, "audio_ts": 6.65, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.56, "transcript": "speech to "}}} -{"ts": 6.817904, "audio_ts": 6.65, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} -{"ts": 6.824999, "audio_ts": 6.66, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 6.04, "end_time": 6.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 6.32}}} -{"ts": 7.16476, "audio_ts": 7.0, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} -{"ts": 7.164862, "audio_ts": 7.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} -{"ts": 7.164948, "audio_ts": 7.0, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 7.170339, "audio_ts": 7.01, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 6.36}}} -{"ts": 7.484641, "audio_ts": 7.32, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} -{"ts": 7.484776, "audio_ts": 7.32, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 7.490157, "audio_ts": 7.33, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 7.0}}} -{"ts": 7.894987, "audio_ts": 7.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} -{"ts": 7.895187, "audio_ts": 7.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} -{"ts": 7.901366, "audio_ts": 7.74, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 7.0, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 33, "start_time": 7.08, "end_time": 7.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software. Hello", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 1.32, "end_time": 7.4}}} -{"ts": 8.294391, "audio_ts": 8.13, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} -{"ts": 8.294649, "audio_ts": 8.13, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 8.294704, "audio_ts": 8.13, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 8.3004, "audio_ts": 8.14, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:32.216+00:00", "language": "en", "fragments": [{"idx": 8, "start_time": 3.48, "end_time": 3.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.44, "end_time": 4.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.84, "end_time": 4.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.56, "end_time": 5.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 6.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 6.04, "end_time": 6.36, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.36, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 7.0, "end_time": 7.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "text": "We're delighted that you've decided to try our speech to text software.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 1.32, "end_time": 7.76}}} -{"ts": 8.301351, "audio_ts": 8.14, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 34, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 35, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 36, "start_time": 7.64, "end_time": 7.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "text": "Hello. This", "annotation": ["has_partial"]}], "metadata": {"start_time": 1.32, "end_time": 7.76}}} -{"ts": 8.64121, "audio_ts": 8.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} -{"ts": 8.641446, "audio_ts": 8.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} -{"ts": 8.648669, "audio_ts": 8.48, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 41, "start_time": 8.0, "end_time": 8.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "text": "Hello. This is a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.12}}} -{"ts": 8.963358, "audio_ts": 8.8, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} -{"ts": 8.964111, "audio_ts": 8.8, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} -{"ts": 8.970202, "audio_ts": 8.81, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 8.16, "end_time": 8.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "text": "Hello. This is a test", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.48}}} -{"ts": 9.331958, "audio_ts": 9.17, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} -{"ts": 9.332375, "audio_ts": 9.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} -{"ts": 9.339541, "audio_ts": 9.18, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 48, "start_time": 8.76, "end_time": 8.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 8.84}}} -{"ts": 9.684083, "audio_ts": 9.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} -{"ts": 9.684431, "audio_ts": 9.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} -{"ts": 9.691709, "audio_ts": 9.53, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 51, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 9.04, "end_time": 9.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, three", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 9.2}}} -{"ts": 10.021835, "audio_ts": 9.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} -{"ts": 10.022083, "audio_ts": 9.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} -{"ts": 10.028, "audio_ts": 9.86, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.52, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "text": "Hello. This is a test one, two, three.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.0, "end_time": 9.52}}} -{"ts": 10.434258, "audio_ts": 10.27, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} -{"ts": 10.439841, "audio_ts": 10.28, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.6, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, three one", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 9.88}}} -{"ts": 10.834333, "audio_ts": 10.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} -{"ts": 10.839834, "audio_ts": 10.68, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 10.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, 312", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 10.16}}} -{"ts": 11.129317, "audio_ts": 10.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} -{"ts": 11.136012, "audio_ts": 10.97, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 61, "start_time": 9.04, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "text": "Hello. This is a test one, two, 3123", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.0, "end_time": 10.64}}} -{"ts": 11.521279, "audio_ts": 11.36, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 11.0, "start_time": 9.0, "transcript": "three. One. Two three. "}}} -{"ts": 11.521641, "audio_ts": 11.36, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 11.08, "transcript": ""}}} -{"ts": 11.521703, "audio_ts": 11.36, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.08, "start_time": 11.08}}} -{"ts": 11.521897, "audio_ts": 11.36, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 11.527533, "audio_ts": 11.36, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:35.816+00:00", "language": "en", "fragments": [{"idx": 37, "start_time": 7.08, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 38, "start_time": 7.6, "end_time": 7.6, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 39, "start_time": 7.64, "end_time": 7.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.16, "end_time": 8.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.44, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 50, "start_time": 8.72, "end_time": 8.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 54, "start_time": 8.76, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 55, "start_time": 9.0, "end_time": 9.0, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 63, "start_time": 9.52, "end_time": 9.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.6, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "One", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.88, "end_time": 9.88, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.92, "end_time": 10.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.2, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, "annotation": null}, {"idx": 68, "start_time": 10.64, "end_time": 10.64, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "text": "Hello. This is a test one, two, three. One. Two three.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.0, "end_time": 10.64}}} -{"ts": 11.946521, "audio_ts": 11.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.08, "transcript": ""}}} -{"ts": 11.946623, "audio_ts": 11.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.44, "transcript": ""}}} -{"ts": 12.194132, "audio_ts": 12.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.44, "transcript": ""}}} -{"ts": 12.194317, "audio_ts": 12.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} -{"ts": 12.607959, "audio_ts": 12.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} -{"ts": 12.608189, "audio_ts": 12.44, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 12.613619, "audio_ts": 12.45, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.08, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "text": "To", "annotation": ["has_partial"]}], "metadata": {"start_time": 10.64, "end_time": 12.08}}} -{"ts": 12.993248, "audio_ts": 12.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} -{"ts": 12.993443, "audio_ts": 12.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} -{"ts": 12.993762, "audio_ts": 12.83, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 12.993917, "audio_ts": 12.83, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 12.999893, "audio_ts": 12.84, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.28, "end_time": 12.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "text": "To get going", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 12.44}}} -{"ts": 13.333012, "audio_ts": 13.17, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} -{"ts": 13.333234, "audio_ts": 13.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} -{"ts": 13.339096, "audio_ts": 13.17, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "text": "To get going.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 12.76}}} -{"ts": 13.710176, "audio_ts": 13.55, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} -{"ts": 13.7104, "audio_ts": 13.55, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} -{"ts": 13.716376, "audio_ts": 13.55, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "text": "To get going. Just", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.0}}} -{"ts": 14.064707, "audio_ts": 13.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} -{"ts": 14.064963, "audio_ts": 13.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} -{"ts": 14.07142, "audio_ts": 13.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 81, "start_time": 13.32, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 82, "start_time": 13.36, "end_time": 13.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "text": "To get going. Just create. An", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.52}}} -{"ts": 14.401504, "audio_ts": 14.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} -{"ts": 14.401707, "audio_ts": 14.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} -{"ts": 14.407369, "audio_ts": 14.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 13.88}}} -{"ts": 14.760117, "audio_ts": 14.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} -{"ts": 14.760877, "audio_ts": 14.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} -{"ts": 14.76663, "audio_ts": 14.6, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.24}}} -{"ts": 15.098741, "audio_ts": 14.93, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} -{"ts": 15.0989, "audio_ts": 14.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} -{"ts": 15.105236, "audio_ts": 14.94, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.44}}} -{"ts": 15.493903, "audio_ts": 15.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} -{"ts": 15.494082, "audio_ts": 15.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} -{"ts": 15.499854, "audio_ts": 15.34, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.84}}} -{"ts": 15.833795, "audio_ts": 15.67, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} -{"ts": 15.834041, "audio_ts": 15.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} -{"ts": 15.834382, "audio_ts": 15.67, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 15.839965, "audio_ts": 15.68, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 14.84}}} -{"ts": 16.16751, "audio_ts": 16.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} -{"ts": 16.168197, "audio_ts": 16.0, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 16.174499, "audio_ts": 16.01, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 15.48}}} -{"ts": 16.616209, "audio_ts": 16.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} -{"ts": 16.616404, "audio_ts": 16.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} -{"ts": 16.622398, "audio_ts": 16.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.88, "end_time": 15.96, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.96, "end_time": 16.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 16.04}}} -{"ts": 16.959778, "audio_ts": 16.8, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} -{"ts": 16.960062, "audio_ts": 16.8, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} -{"ts": 16.967375, "audio_ts": 16.8, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 10.64, "end_time": 16.2}}} -{"ts": 17.24184, "audio_ts": 17.08, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} -{"ts": 17.24217, "audio_ts": 17.08, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} -{"ts": 17.247901, "audio_ts": 17.08, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.2, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.75, "result": {"final": false, "alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.72, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our API.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 16.72}}} -{"ts": 17.627421, "audio_ts": 17.46, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 17.12, "start_time": 16.2, "transcript": "API. "}}} -{"ts": 17.627528, "audio_ts": 17.46, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 17.2, "transcript": ""}}} -{"ts": 17.627559, "audio_ts": 17.46, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 17.2, "start_time": 17.2}}} -{"ts": 17.627684, "audio_ts": 17.46, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 17.633566, "audio_ts": 17.47, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:40.616+00:00", "language": "en", "fragments": [{"idx": 70, "start_time": 11.88, "end_time": 12.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.04, "end_time": 12.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.28, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 77, "start_time": 12.76, "end_time": 12.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 79, "start_time": 12.8, "end_time": 13.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.0, "end_time": 13.32, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.36, "end_time": 13.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.48, "end_time": 13.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 13.88, "end_time": 14.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.24, "end_time": 14.44, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.44, "end_time": 14.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.76, "end_time": 14.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 14.84, "end_time": 15.48, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.48, "end_time": 15.88, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 15.88, "end_time": 16.0, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.0, "end_time": 16.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.2, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 107, "start_time": 16.72, "end_time": 16.72, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "text": "To get going. Just create an API key and submit a transcription request to our API.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 10.64, "end_time": 16.72}}} -{"ts": 17.955812, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 17.2, "transcript": ""}}} -{"ts": 18.36611, "audio_ts": 18.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 17.12, "transcript": "We "}}} -{"ts": 18.366187, "audio_ts": 18.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} -{"ts": 18.366359, "audio_ts": 18.18, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 18.372256, "audio_ts": 18.21, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.76, "end_time": 17.84, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "text": "We hope you", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 16.72, "end_time": 17.84}}} -{"ts": 18.774276, "audio_ts": 18.61, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} -{"ts": 18.774563, "audio_ts": 18.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} -{"ts": 18.780795, "audio_ts": 18.62, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "text": "We hope you'll be very", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 16.72, "end_time": 18.2}}} -{"ts": 19.067576, "audio_ts": 18.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} -{"ts": 19.067795, "audio_ts": 18.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} -{"ts": 19.074971, "audio_ts": 18.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.84, "result": {"final": false, "alternatives": [{"confidence": 0.84, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 18.56}}} -{"ts": 19.470369, "audio_ts": 19.31, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} -{"ts": 19.470632, "audio_ts": 19.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} -{"ts": 19.476703, "audio_ts": 19.31, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.68, "end_time": 18.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.76, "end_time": 18.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 18.92}}} -{"ts": 19.789004, "audio_ts": 19.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} -{"ts": 19.78923, "audio_ts": 19.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} -{"ts": 19.795785, "audio_ts": 19.63, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.28, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 19.28}}} -{"ts": 20.162161, "audio_ts": 20.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} -{"ts": 20.168371, "audio_ts": 20.0, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 16.72, "end_time": 19.52}}} -{"ts": 20.496429, "audio_ts": 20.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}], "metadata": {"end_time": 20.0, "start_time": 18.8, "transcript": "results. "}}} -{"ts": 20.496691, "audio_ts": 20.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 20.08, "transcript": ""}}} -{"ts": 20.497783, "audio_ts": 20.33, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 20.499154, "audio_ts": 20.33, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 20.08, "start_time": 20.08}}} -{"ts": 20.505416, "audio_ts": 20.34, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:46.136+00:00", "language": "en", "fragments": [{"idx": 108, "start_time": 17.4, "end_time": 17.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.6, "end_time": 17.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.76, "end_time": 17.92, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 17.92, "end_time": 18.04, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.04, "end_time": 18.2, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.2, "end_time": 18.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.56, "end_time": 18.68, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.68, "end_time": 18.8, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 18.8, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 127, "start_time": 19.52, "end_time": 19.52, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}, "annotation": null}], "text": "We hope you'll be very impressed by the results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 16.72, "end_time": 19.52}}} -{"ts": 20.905258, "audio_ts": 20.74, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.08, "transcript": ""}}} -{"ts": 20.90535, "audio_ts": 20.74, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.44, "transcript": ""}}} -{"ts": 21.28093, "audio_ts": 21.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.36, "transcript": "Results"}}} -{"ts": 21.281084, "audio_ts": 21.12, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 21.286308, "audio_ts": 21.12, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.096+00:00", "language": "en", "fragments": [{"idx": 128, "start_time": 20.36, "end_time": 20.72, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}, "annotation": null}], "text": "Results", "annotation": ["has_partial"]}], "metadata": {"start_time": 19.52, "end_time": 20.72}}} -{"ts": 21.640986, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.36, "transcript": "Results. "}}} -{"ts": 21.641212, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} -{"ts": 21.64134, "audio_ts": 21.48, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 21.646717, "audio_ts": 21.48, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.096+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.36, "end_time": 20.76, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Results", "speaker": "S2", "confidence": 0.99, "result": {"final": true, "alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.76, "end_time": 20.76, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "text": "Results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 19.52, "end_time": 20.76}}} -{"ts": 21.995041, "audio_ts": 21.83, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} -{"ts": 21.995203, "audio_ts": 21.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are"}}} -{"ts": 21.995434, "audio_ts": 21.83, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 22.001087, "audio_ts": 21.84, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}], "text": "Really are", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 21.24}}} -{"ts": 22.276317, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} -{"ts": 22.276653, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} -{"ts": 22.283126, "audio_ts": 22.12, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "text": "Really are pretty", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 21.56}}} -{"ts": 22.62824, "audio_ts": 22.46, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} -{"ts": 22.62843, "audio_ts": 22.46, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} -{"ts": 22.634417, "audio_ts": 22.47, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.56, "end_time": 22.16, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "text": "Really are pretty amazing", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.76, "end_time": 22.16}}} -{"ts": 23.011228, "audio_ts": 22.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.52, "start_time": 21.56, "transcript": "amazing. "}}} -{"ts": 23.011483, "audio_ts": 22.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.6, "transcript": ""}}} -{"ts": 23.011722, "audio_ts": 22.85, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 23.012259, "audio_ts": 22.85, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 22.6, "start_time": 22.6}}} -{"ts": 23.017739, "audio_ts": 22.85, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-15T19:47:49.536+00:00", "language": "en", "fragments": [{"idx": 131, "start_time": 20.8, "end_time": 21.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 133, "start_time": 21.12, "end_time": 21.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.56, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.56, "end_time": 22.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 22.24, "end_time": 22.24, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "text": "Really are pretty amazing.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 20.76, "end_time": 22.24}}} -{"ts": 23.354514, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.6, "transcript": ""}}} -{"ts": 23.354677, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.96, "transcript": ""}}} -{"ts": 23.830358, "audio_ts": 23.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, {"alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.88, "transcript": "Thank you"}}} -{"ts": 23.830667, "audio_ts": 23.67, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 23.836925, "audio_ts": 23.67, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 139, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 23.12, "end_time": 23.24, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.95, "result": {"final": false, "alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "text": "Thank you", "annotation": ["has_partial"]}], "metadata": {"start_time": 22.24, "end_time": 23.24}}} -{"ts": 24.071311, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.88, "transcript": "Thank "}}} -{"ts": 24.071411, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you"}}} -{"ts": 24.076835, "audio_ts": 23.91, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 141, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 142, "start_time": 23.12, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}], "text": "Thank you", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 22.24, "end_time": 23.4}}} -{"ts": 24.491069, "audio_ts": 24.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} -{"ts": 24.491367, "audio_ts": 24.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} -{"ts": 24.491418, "audio_ts": 24.33, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} -{"ts": 24.491545, "audio_ts": 24.33, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 24.496997, "audio_ts": 24.33, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-15T19:47:51.616+00:00", "language": "en", "fragments": [{"idx": 141, "start_time": 22.88, "end_time": 23.12, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 144, "start_time": 23.4, "end_time": 23.4, "language": "en", "direction": "ltr", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "text": "Thank you.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 22.24, "end_time": 23.4}}} -{"ts": 24.794224, "audio_ts": 24.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} -{"ts": 24.794376, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.4, "transcript": ""}}} -{"ts": 25.1636, "audio_ts": 25.0, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.4, "transcript": ""}}} -{"ts": 25.16374, "audio_ts": 25.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} -{"ts": 25.496782, "audio_ts": 25.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} -{"ts": 25.496917, "audio_ts": 25.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 25.12, "transcript": ""}}} -{"ts": 25.897417, "audio_ts": 25.73, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.12, "transcript": ""}}} -{"ts": 25.899313, "audio_ts": 25.73, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} -{"ts": 26.257029, "audio_ts": 26.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} -{"ts": 26.25724, "audio_ts": 26.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.84, "transcript": ""}}} -{"ts": 26.559039, "audio_ts": 26.39, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.84, "transcript": ""}}} -{"ts": 26.559141, "audio_ts": 26.39, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} -{"ts": 26.9706, "audio_ts": 26.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} -{"ts": 26.97071, "audio_ts": 26.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.56, "transcript": ""}}} -{"ts": 27.31968, "audio_ts": 27.16, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.56, "transcript": ""}}} -{"ts": 27.319783, "audio_ts": 27.16, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} -{"ts": 27.72764, "audio_ts": 27.56, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} -{"ts": 27.72774, "audio_ts": 27.56, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 27.28, "transcript": ""}}} -{"ts": 28.028009, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.28, "transcript": ""}}} -{"ts": 28.028179, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} +{"ts": 2.9e-05, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} +{"ts": 8.8e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "include_results": false, "enable_preview_features": false, "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} +{"ts": 0.000128, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} +{"ts": 0.000148, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} +{"ts": 0.091146, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-22T16:51:01Z"}} +{"ts": 0.132706, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "9d8a5d05-7a62-4266-8cfc-9ffd9cb3cdf1", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} +{"ts": 0.67894, "audio_ts": 0.55, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 0.67918, "audio_ts": 0.55, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.10585, "audio_ts": 0.98, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.435181, "audio_ts": 1.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} +{"ts": 1.436055, "audio_ts": 1.31, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 1.436582, "audio_ts": 1.31, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome", "annotation": ["has_partial"]}], "metadata": {"start_time": 0.36, "end_time": 0.92}}} +{"ts": 1.792961, "audio_ts": 1.66, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} +{"ts": 1.793181, "audio_ts": 1.66, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.0}}} +{"ts": 1.793294, "audio_ts": 1.66, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} +{"ts": 1.793391, "audio_ts": 1.66, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to speech", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.28}}} +{"ts": 2.196876, "audio_ts": 2.07, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} +{"ts": 2.197238, "audio_ts": 2.07, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.197393, "audio_ts": 2.07, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.19746, "audio_ts": 2.07, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 2.197555, "audio_ts": 2.07, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.495748, "audio_ts": 2.37, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.496584, "audio_ts": 2.37, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to speech", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.828358, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} +{"ts": 2.828587, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} +{"ts": 2.828685, "audio_ts": 2.7, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} +{"ts": 2.829411, "audio_ts": 2.7, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:01.873+00:00", "language": "en", "text": "Welcome to speech.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 0.36, "end_time": 1.32}}} +{"ts": 2.829598, "audio_ts": 2.7, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 1.32, "end_time": 1.32}}} +{"ts": 3.168606, "audio_ts": 3.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.44, "transcript": ""}}} +{"ts": 3.168855, "audio_ts": 3.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.541596, "audio_ts": 3.41, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 2.8, "transcript": ""}}} +{"ts": 3.542262, "audio_ts": 3.41, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.894022, "audio_ts": 3.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} +{"ts": 3.894126, "audio_ts": 3.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.52, "transcript": ""}}} +{"ts": 4.29732, "audio_ts": 4.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.44, "transcript": "We're"}}} +{"ts": 4.297519, "audio_ts": 4.17, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 4.297701, "audio_ts": 4.17, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're", "annotation": ["has_partial"]}], "metadata": {"start_time": 3.48, "end_time": 3.72}}} +{"ts": 4.683379, "audio_ts": 4.55, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.44, "transcript": "We're "}}} +{"ts": 4.683496, "audio_ts": 4.55, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} +{"ts": 4.683796, "audio_ts": 4.55, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 3.72}}} +{"ts": 4.684178, "audio_ts": 4.55, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.16}}} +{"ts": 5.017098, "audio_ts": 4.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} +{"ts": 5.017376, "audio_ts": 4.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} +{"ts": 5.018423, "audio_ts": 4.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.24}}} +{"ts": 5.018884, "audio_ts": 4.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've done", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.52}}} +{"ts": 5.331675, "audio_ts": 5.2, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} +{"ts": 5.332795, "audio_ts": 5.2, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} +{"ts": 5.335569, "audio_ts": 5.2, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.44}}} +{"ts": 5.335927, "audio_ts": 5.2, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.84}}} +{"ts": 5.727246, "audio_ts": 5.6, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} +{"ts": 5.727506, "audio_ts": 5.6, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} +{"ts": 5.728125, "audio_ts": 5.6, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 4.96}}} +{"ts": 5.728571, "audio_ts": 5.6, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 5.24}}} +{"ts": 6.124554, "audio_ts": 6.0, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.93, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} +{"ts": 6.125347, "audio_ts": 6.0, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 5.6}}} +{"ts": 6.438556, "audio_ts": 6.31, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.56, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.56, "start_time": 4.96, "transcript": "try our "}}} +{"ts": 6.438908, "audio_ts": 6.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.56, "transcript": "speech"}}} +{"ts": 6.439773, "audio_ts": 6.31, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 5.56}}} +{"ts": 6.440425, "audio_ts": 6.31, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 5.88}}} +{"ts": 6.765347, "audio_ts": 6.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.56, "transcript": "speech to "}}} +{"ts": 6.765656, "audio_ts": 6.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} +{"ts": 6.766616, "audio_ts": 6.64, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 6.0}}} +{"ts": 6.767199, "audio_ts": 6.64, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 6.32}}} +{"ts": 7.20699, "audio_ts": 7.08, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} +{"ts": 7.207173, "audio_ts": 7.08, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} +{"ts": 7.207509, "audio_ts": 7.08, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 6.36}}} +{"ts": 7.207602, "audio_ts": 7.08, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 7.207709, "audio_ts": 7.08, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to text", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 6.36}}} +{"ts": 7.455293, "audio_ts": 7.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} +{"ts": 7.455772, "audio_ts": 7.33, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 7.456316, "audio_ts": 7.33, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to text software", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 3.48, "end_time": 7.0}}} +{"ts": 7.900214, "audio_ts": 7.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} +{"ts": 7.900544, "audio_ts": 7.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} +{"ts": 7.901426, "audio_ts": 7.77, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:04.993+00:00", "language": "en", "text": "We're delighted that you've decided to try our speech to text software.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 3.48, "end_time": 7.0}}} +{"ts": 7.90212, "audio_ts": 7.77, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello", "annotation": ["has_partial"]}], "metadata": {"start_time": 7.08, "end_time": 7.4}}} +{"ts": 8.281933, "audio_ts": 8.15, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} +{"ts": 8.282326, "audio_ts": 8.15, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 8.282391, "audio_ts": 8.15, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 8.282652, "audio_ts": 8.15, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This", "annotation": ["has_partial"]}], "metadata": {"start_time": 7.08, "end_time": 7.76}}} +{"ts": 8.621385, "audio_ts": 8.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} +{"ts": 8.6219, "audio_ts": 8.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} +{"ts": 8.622674, "audio_ts": 8.49, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 7.84}}} +{"ts": 8.623622, "audio_ts": 8.49, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 8.12}}} +{"ts": 8.987266, "audio_ts": 8.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} +{"ts": 8.988643, "audio_ts": 8.86, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 8.16}}} +{"ts": 8.989081, "audio_ts": 8.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} +{"ts": 8.989517, "audio_ts": 8.86, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 8.48}}} +{"ts": 9.350349, "audio_ts": 9.22, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} +{"ts": 9.350627, "audio_ts": 9.22, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} +{"ts": 9.351462, "audio_ts": 9.22, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 8.44}}} +{"ts": 9.351991, "audio_ts": 9.22, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 8.84}}} +{"ts": 9.723448, "audio_ts": 9.59, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} +{"ts": 9.723706, "audio_ts": 9.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} +{"ts": 9.724594, "audio_ts": 9.59, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one,", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_punctuation"]}], "metadata": {"start_time": 7.08, "end_time": 8.72}}} +{"ts": 9.72497, "audio_ts": 9.59, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, three", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 9.2}}} +{"ts": 10.007078, "audio_ts": 9.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} +{"ts": 10.007402, "audio_ts": 9.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} +{"ts": 10.008074, "audio_ts": 9.88, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two,", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_punctuation"]}], "metadata": {"start_time": 7.08, "end_time": 9.0}}} +{"ts": 10.008464, "audio_ts": 9.88, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, three.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.08, "end_time": 9.52}}} +{"ts": 10.395931, "audio_ts": 10.27, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} +{"ts": 10.39653, "audio_ts": 10.27, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, three one", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 9.88}}} +{"ts": 10.74807, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} +{"ts": 10.748675, "audio_ts": 10.62, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, 312", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 10.16}}} +{"ts": 11.092076, "audio_ts": 10.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} +{"ts": 11.092738, "audio_ts": 10.96, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, 3123", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 7.08, "end_time": 10.64}}} +{"ts": 11.442641, "audio_ts": 11.31, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "One", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.88, "is_eos": true, "start_time": 9.88, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "Two", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 10.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 11.0, "start_time": 9.0, "transcript": "three. One. Two three. "}}} +{"ts": 11.444378, "audio_ts": 11.31, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:08.593+00:00", "language": "en", "text": "Hello. This is a test one, two, three. One. Two three.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 7.08, "end_time": 10.64}}} +{"ts": 11.444561, "audio_ts": 11.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.444646, "audio_ts": 11.31, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.08, "start_time": 11.08}}} +{"ts": 11.444827, "audio_ts": 11.31, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 11.444928, "audio_ts": 11.31, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 10.64, "end_time": 10.64}}} +{"ts": 11.853717, "audio_ts": 11.72, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.08, "transcript": ""}}} +{"ts": 11.85388, "audio_ts": 11.72, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.12025, "audio_ts": 11.99, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.44, "transcript": ""}}} +{"ts": 12.120426, "audio_ts": 11.99, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} +{"ts": 12.555445, "audio_ts": 12.43, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} +{"ts": 12.555683, "audio_ts": 12.43, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 12.556105, "audio_ts": 12.43, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To", "annotation": ["has_partial"]}], "metadata": {"start_time": 11.88, "end_time": 12.08}}} +{"ts": 12.920393, "audio_ts": 12.79, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} +{"ts": 12.922719, "audio_ts": 12.79, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 11.88, "end_time": 12.04}}} +{"ts": 12.923296, "audio_ts": 12.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} +{"ts": 12.924095, "audio_ts": 12.79, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 12.92418, "audio_ts": 12.79, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 12.924401, "audio_ts": 12.79, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To get going", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 11.88, "end_time": 12.44}}} +{"ts": 13.225297, "audio_ts": 13.1, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} +{"ts": 13.226234, "audio_ts": 13.1, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To get", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 11.88, "end_time": 12.28}}} +{"ts": 13.226538, "audio_ts": 13.1, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} +{"ts": 13.226926, "audio_ts": 13.1, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To get going.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 11.88, "end_time": 12.76}}} +{"ts": 13.653997, "audio_ts": 13.53, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} +{"ts": 13.654176, "audio_ts": 13.53, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} +{"ts": 13.654529, "audio_ts": 13.53, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:13.393+00:00", "language": "en", "text": "To get going.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 11.88, "end_time": 12.76}}} +{"ts": 13.654706, "audio_ts": 13.53, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just", "annotation": ["has_partial"]}], "metadata": {"start_time": 12.8, "end_time": 13.0}}} +{"ts": 14.010459, "audio_ts": 13.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} +{"ts": 14.010949, "audio_ts": 13.88, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 13.0}}} +{"ts": 14.011338, "audio_ts": 13.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} +{"ts": 14.011865, "audio_ts": 13.88, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create. An", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 13.52}}} +{"ts": 14.368019, "audio_ts": 14.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} +{"ts": 14.368301, "audio_ts": 14.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} +{"ts": 14.369016, "audio_ts": 14.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 13.48}}} +{"ts": 14.369555, "audio_ts": 14.24, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 13.88}}} +{"ts": 14.735598, "audio_ts": 14.61, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} +{"ts": 14.73573, "audio_ts": 14.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} +{"ts": 14.736022, "audio_ts": 14.61, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 13.88}}} +{"ts": 14.736213, "audio_ts": 14.61, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.24}}} +{"ts": 15.157364, "audio_ts": 15.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} +{"ts": 15.157645, "audio_ts": 15.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} +{"ts": 15.159178, "audio_ts": 15.03, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.24}}} +{"ts": 15.159648, "audio_ts": 15.03, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.44}}} +{"ts": 15.479561, "audio_ts": 15.35, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} +{"ts": 15.479801, "audio_ts": 15.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} +{"ts": 15.480428, "audio_ts": 15.35, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.44}}} +{"ts": 15.480797, "audio_ts": 15.35, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.84}}} +{"ts": 15.75834, "audio_ts": 15.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} +{"ts": 15.758403, "audio_ts": 15.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} +{"ts": 15.758553, "audio_ts": 15.63, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.84}}} +{"ts": 15.758605, "audio_ts": 15.63, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 15.758659, "audio_ts": 15.63, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 14.84}}} +{"ts": 16.154197, "audio_ts": 16.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} +{"ts": 16.154598, "audio_ts": 16.03, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 16.155114, "audio_ts": 16.03, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 15.48}}} +{"ts": 16.502616, "audio_ts": 16.37, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} +{"ts": 16.502891, "audio_ts": 16.37, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} +{"ts": 16.503957, "audio_ts": 16.37, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 15.48}}} +{"ts": 16.504535, "audio_ts": 16.37, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 16.04}}} +{"ts": 16.895782, "audio_ts": 16.77, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} +{"ts": 16.896121, "audio_ts": 16.77, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} +{"ts": 16.896995, "audio_ts": 16.77, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 16.0}}} +{"ts": 16.897653, "audio_ts": 16.77, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to our", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 16.2}}} +{"ts": 17.231649, "audio_ts": 17.1, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} +{"ts": 17.232464, "audio_ts": 17.1, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to our", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 12.8, "end_time": 16.2}}} +{"ts": 17.233052, "audio_ts": 17.1, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.75, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} +{"ts": 17.233623, "audio_ts": 17.1, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to our API.", "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 12.8, "end_time": 16.72}}} +{"ts": 17.558128, "audio_ts": 17.43, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 17.12, "start_time": 16.2, "transcript": "API. "}}} +{"ts": 17.558414, "audio_ts": 17.43, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 17.2, "transcript": ""}}} +{"ts": 17.55851, "audio_ts": 17.43, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 17.2, "start_time": 17.2}}} +{"ts": 17.559607, "audio_ts": 17.43, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:14.313+00:00", "language": "en", "text": "Just create an API key and submit a transcription request to our API.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 12.8, "end_time": 16.72}}} +{"ts": 17.559849, "audio_ts": 17.43, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 17.559943, "audio_ts": 17.43, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 16.72, "end_time": 16.72}}} +{"ts": 17.955868, "audio_ts": 17.83, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 17.2, "transcript": ""}}} +{"ts": 18.341727, "audio_ts": 18.21, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 17.12, "transcript": "We "}}} +{"ts": 18.342021, "audio_ts": 18.21, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} +{"ts": 18.342854, "audio_ts": 18.21, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 17.56}}} +{"ts": 18.343158, "audio_ts": 18.21, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 18.343466, "audio_ts": 18.21, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 17.4, "end_time": 17.84}}} +{"ts": 18.665109, "audio_ts": 18.54, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} +{"ts": 18.665393, "audio_ts": 18.54, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} +{"ts": 18.666072, "audio_ts": 18.54, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 17.92}}} +{"ts": 18.666542, "audio_ts": 18.54, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very", "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}], "metadata": {"start_time": 17.4, "end_time": 18.2}}} +{"ts": 19.016404, "audio_ts": 18.89, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} +{"ts": 19.016696, "audio_ts": 18.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} +{"ts": 19.017383, "audio_ts": 18.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very", "annotation": ["has_final", "starts_with_final", "ends_with_final", "fast_speaker"]}], "metadata": {"start_time": 17.4, "end_time": 18.2}}} +{"ts": 19.017847, "audio_ts": 18.89, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 18.56}}} +{"ts": 19.391891, "audio_ts": 19.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} +{"ts": 19.392182, "audio_ts": 19.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.86, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} +{"ts": 19.392969, "audio_ts": 19.26, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 18.56}}} +{"ts": 19.393431, "audio_ts": 19.26, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 18.92}}} +{"ts": 19.744803, "audio_ts": 19.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} +{"ts": 19.745101, "audio_ts": 19.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} +{"ts": 19.745824, "audio_ts": 19.62, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed by the", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 18.8}}} +{"ts": 19.746324, "audio_ts": 19.62, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 19.28}}} +{"ts": 20.081443, "audio_ts": 19.95, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} +{"ts": 20.082731, "audio_ts": 19.95, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed by the results", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 17.4, "end_time": 19.52}}} +{"ts": 20.460972, "audio_ts": 20.33, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 19.52, "is_eos": true, "start_time": 19.52, "type": "punctuation"}], "metadata": {"end_time": 20.0, "start_time": 18.8, "transcript": "results. "}}} +{"ts": 20.461283, "audio_ts": 20.33, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.46141, "audio_ts": 20.33, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 20.08, "start_time": 20.08}}} +{"ts": 20.46271, "audio_ts": 20.33, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:18.913+00:00", "language": "en", "text": "We hope you'll be very impressed by the results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 17.4, "end_time": 19.52}}} +{"ts": 20.462907, "audio_ts": 20.33, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 20.462993, "audio_ts": 20.33, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 19.52, "end_time": 19.52}}} +{"ts": 20.833766, "audio_ts": 20.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.08, "transcript": ""}}} +{"ts": 20.833995, "audio_ts": 20.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.44, "start_time": 20.44, "transcript": ""}}} +{"ts": 21.194138, "audio_ts": 21.07, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.36, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.36, "transcript": "Results"}}} +{"ts": 21.194512, "audio_ts": 21.07, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 21.194982, "audio_ts": 21.07, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:21.873+00:00", "language": "en", "text": "Results", "annotation": ["has_partial"]}], "metadata": {"start_time": 20.36, "end_time": 20.72}}} +{"ts": 21.634402, "audio_ts": 21.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.36, "transcript": "Results. "}}} +{"ts": 21.634775, "audio_ts": 21.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} +{"ts": 21.635731, "audio_ts": 21.51, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:21.873+00:00", "language": "en", "text": "Results.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 20.36, "end_time": 20.76}}} +{"ts": 21.635884, "audio_ts": 21.51, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 21.912866, "audio_ts": 21.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} +{"ts": 21.913124, "audio_ts": 21.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are"}}} +{"ts": 21.913787, "audio_ts": 21.78, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 21.12}}} +{"ts": 21.914069, "audio_ts": 21.78, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S2"}}} +{"ts": 21.914382, "audio_ts": 21.78, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 21.24}}} +{"ts": 22.272704, "audio_ts": 22.14, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} +{"ts": 22.272905, "audio_ts": 22.14, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} +{"ts": 22.273503, "audio_ts": 22.14, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 21.24}}} +{"ts": 22.273949, "audio_ts": 22.14, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are pretty", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 21.56}}} +{"ts": 22.592997, "audio_ts": 22.46, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} +{"ts": 22.593262, "audio_ts": 22.46, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} +{"ts": 22.594044, "audio_ts": 22.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are pretty", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 21.56}}} +{"ts": 22.594571, "audio_ts": 22.46, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are pretty amazing", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 20.8, "end_time": 22.16}}} +{"ts": 22.940643, "audio_ts": 22.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.52, "start_time": 21.56, "transcript": "amazing. "}}} +{"ts": 22.940937, "audio_ts": 22.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.6, "transcript": ""}}} +{"ts": 22.941234, "audio_ts": 22.81, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 22.6, "start_time": 22.6}}} +{"ts": 22.941971, "audio_ts": 22.81, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-22T16:51:22.313+00:00", "language": "en", "text": "Really are pretty amazing.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 20.8, "end_time": 22.24}}} +{"ts": 22.942169, "audio_ts": 22.81, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S2"}}} +{"ts": 22.94226, "audio_ts": 22.81, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 22.24, "end_time": 22.24}}} +{"ts": 23.365603, "audio_ts": 23.24, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.6, "transcript": ""}}} +{"ts": 23.365829, "audio_ts": 23.24, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.96, "transcript": ""}}} +{"ts": 23.734221, "audio_ts": 23.61, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}, {"alternatives": [{"confidence": 0.95, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.88, "transcript": "Thank you"}}} +{"ts": 23.734553, "audio_ts": 23.61, "payload": {"message": "SpeakerStarted", "status": {"is_active": true, "speaker_id": "S1"}}} +{"ts": 23.734919, "audio_ts": 23.61, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:24.393+00:00", "language": "en", "text": "Thank you", "annotation": ["has_partial"]}], "metadata": {"start_time": 22.88, "end_time": 23.24}}} +{"ts": 24.070199, "audio_ts": 23.94, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.88, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.88, "transcript": "Thank "}}} +{"ts": 24.07058, "audio_ts": 23.94, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you"}}} +{"ts": 24.071296, "audio_ts": 23.94, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:24.393+00:00", "language": "en", "text": "Thank", "annotation": ["has_final", "starts_with_final", "ends_with_final"]}], "metadata": {"start_time": 22.88, "end_time": 23.12}}} +{"ts": 24.071869, "audio_ts": 23.94, "payload": {"message": "AddPartialSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:24.393+00:00", "language": "en", "text": "Thank you", "annotation": ["has_partial", "has_final", "starts_with_final"]}], "metadata": {"start_time": 22.88, "end_time": 23.44}}} +{"ts": 24.433138, "audio_ts": 24.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.44, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.44, "is_eos": true, "start_time": 23.44, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} +{"ts": 24.434497, "audio_ts": 24.3, "payload": {"message": "AddSegment", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-22T16:51:24.393+00:00", "language": "en", "text": "Thank you.", "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}], "metadata": {"start_time": 22.88, "end_time": 23.44}}} +{"ts": 24.435224, "audio_ts": 24.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.435351, "audio_ts": 24.3, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} +{"ts": 24.435635, "audio_ts": 24.3, "payload": {"message": "SpeakerEnded", "status": {"is_active": false, "speaker_id": "S1"}}} +{"ts": 24.435728, "audio_ts": 24.3, "payload": {"message": "EndOfTurn", "metadata": {"start_time": 23.44, "end_time": 23.44}}} +{"ts": 24.768097, "audio_ts": 24.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} +{"ts": 24.768313, "audio_ts": 24.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.4, "transcript": ""}}} +{"ts": 25.137276, "audio_ts": 25.01, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.4, "transcript": ""}}} +{"ts": 25.137542, "audio_ts": 25.01, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.469681, "audio_ts": 25.34, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} +{"ts": 25.470109, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.904285, "audio_ts": 25.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.12, "transcript": ""}}} +{"ts": 25.904505, "audio_ts": 25.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.273945, "audio_ts": 26.14, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} +{"ts": 26.274097, "audio_ts": 26.14, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.538952, "audio_ts": 26.41, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.84, "transcript": ""}}} +{"ts": 26.539192, "audio_ts": 26.41, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.943639, "audio_ts": 26.81, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} +{"ts": 26.943883, "audio_ts": 26.81, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.291209, "audio_ts": 27.16, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.56, "transcript": ""}}} +{"ts": 27.291421, "audio_ts": 27.16, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.628956, "audio_ts": 27.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} +{"ts": 27.629113, "audio_ts": 27.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 27.28, "transcript": ""}}} +{"ts": 28.028535, "audio_ts": 27.9, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.28, "transcript": ""}}} +{"ts": 28.028766, "audio_ts": 27.9, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/assets/chat2.jsonl b/tests/voice/assets/chat2.jsonl index 99e0acf4..e5a114a2 100644 --- a/tests/voice/assets/chat2.jsonl +++ b/tests/voice/assets/chat2.jsonl @@ -1,13 +1,14 @@ -{"ts": 2.40306, "audio_ts": 0.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} -{"ts": 2.922847, "audio_ts": 1.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.0, "transcript": ""}}} -{"ts": 3.221564, "audio_ts": 1.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.0, "transcript": "Welcome"}}} -{"ts": 3.52808, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.0, "transcript": "Welcome to "}}} -{"ts": 3.529091, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 3.815655, "audio_ts": 1.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.64, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 4.263167, "audio_ts": 2.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 2.0, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 4.587797, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.96, "start_time": 1.0, "transcript": "Speechmatics "}}} -{"ts": 4.588038, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.921012, "audio_ts": 3.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.04, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.921238, "audio_ts": 3.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.04, "transcript": ""}}} -{"ts": 5.302842, "audio_ts": 3.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}], "metadata": {"end_time": 3.08, "start_time": 1.96, "transcript": ". "}}} -{"ts": 5.303833, "audio_ts": 3.42, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 3.16, "start_time": 3.16}}} +{"ts": 0.091146, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-22T16:51:01Z"}} +{"ts": 0.132706, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "9d8a5d05-7a62-4266-8cfc-9ffd9cb3cdf1", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} +{"ts": 0.67894, "audio_ts": 0.55, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} +{"ts": 0.67918, "audio_ts": 0.55, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.10585, "audio_ts": 0.98, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.28, "transcript": ""}}} +{"ts": 1.435181, "audio_ts": 1.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.2, "transcript": "Welcome"}}} +{"ts": 1.792961, "audio_ts": 1.66, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.2, "transcript": "Welcome to "}}} +{"ts": 1.793294, "audio_ts": 1.66, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "speech"}}} +{"ts": 2.196876, "audio_ts": 2.07, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.32, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.32, "start_time": 1.0, "transcript": "speech "}}} +{"ts": 2.197393, "audio_ts": 2.07, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 1.72, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.495748, "audio_ts": 2.37, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.08, "start_time": 1.4, "transcript": ""}}} +{"ts": 2.828358, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.32, "is_eos": true, "start_time": 1.32, "type": "punctuation"}], "metadata": {"end_time": 2.36, "start_time": 1.32, "transcript": ". "}}} +{"ts": 2.828587, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.44, "transcript": ""}}} +{"ts": 2.828685, "audio_ts": 2.7, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 2.44, "start_time": 2.44}}} diff --git a/tests/voice/assets/chat3.jsonl b/tests/voice/assets/chat3.jsonl deleted file mode 100644 index 4f632e6e..00000000 --- a/tests/voice/assets/chat3.jsonl +++ /dev/null @@ -1,225 +0,0 @@ -{"ts": 2e-06, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./examples/example2.wav"}} -{"ts": 6.7e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.5, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} -{"ts": 0.000108, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": [{"content": "Speechmatics", "sounds_like": ["speech matters", "speech magic"]}], "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.5}, "ctrl": null, "channel_diarization_labels": null}} -{"ts": 0.000132, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} -{"ts": 0.138386, "audio_ts": 0.0, "payload": {"message": "Info", "type": "concurrent_session_usage", "reason": "1 concurrent sessions active out of quota 150", "usage": 1, "quota": 150, "last_updated": "2025-09-12T15:51:53Z"}} -{"ts": 1.888186, "audio_ts": 0.0, "payload": {"message": "RecognitionStarted", "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "09aaa112-9aee-4e1b-b7eb-b7e6016f09bd", "language_pack_info": {"adapted": false, "itn": true, "language_description": "English", "word_delimiter": " ", "writing_direction": "left-to-right"}}} -{"ts": 2.40306, "audio_ts": 0.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.28, "start_time": 0.0, "transcript": ""}}} -{"ts": 2.922847, "audio_ts": 1.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 0.64, "start_time": 0.0, "transcript": ""}}} -{"ts": 3.221564, "audio_ts": 1.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}], "metadata": {"end_time": 0.92, "start_time": 0.0, "transcript": "Welcome"}}} -{"ts": 3.222249, "audio_ts": 1.34, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 3.228478, "audio_ts": 1.34, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 1, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 3.52808, "audio_ts": 1.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}], "metadata": {"end_time": 1.0, "start_time": 0.0, "transcript": "Welcome to "}}} -{"ts": 3.529091, "audio_ts": 1.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.28, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 3.535048, "audio_ts": 1.65, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 4, "start_time": 1.04, "end_time": 1.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 0.98, "result": {"final": false, "alternatives": [{"confidence": 0.98, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.28, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 3.815655, "audio_ts": 1.93, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.64, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 3.821763, "audio_ts": 1.94, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 5, "start_time": 1.04, "end_time": 1.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.64, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 4.263167, "audio_ts": 2.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 2.0, "start_time": 1.0, "transcript": "Speechmatics"}}} -{"ts": 4.269567, "audio_ts": 2.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 6, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 4.587797, "audio_ts": 2.7, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}], "metadata": {"end_time": 1.96, "start_time": 1.0, "transcript": "Speechmatics "}}} -{"ts": 4.588038, "audio_ts": 2.7, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.44, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.588346, "audio_ts": 2.7, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 4.594102, "audio_ts": 2.71, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} -{"ts": 4.921012, "audio_ts": 3.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.04, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.921238, "audio_ts": 3.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 2.8, "start_time": 2.04, "transcript": ""}}} -{"ts": 4.927861, "audio_ts": 3.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "slow_speaker"]}]}} -{"ts": 5.302842, "audio_ts": 3.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}], "metadata": {"end_time": 3.08, "start_time": 1.96, "transcript": ". "}}} -{"ts": 5.302946, "audio_ts": 3.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.16, "start_time": 3.16, "transcript": ""}}} -{"ts": 5.303833, "audio_ts": 3.42, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 3.16, "start_time": 3.16}}} -{"ts": 5.308695, "audio_ts": 3.42, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:55.756+00:00", "language": "en", "fragments": [{"idx": 2, "start_time": 0.36, "end_time": 0.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Welcome", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Welcome", "language": "en", "speaker": "S1"}], "end_time": 0.92, "start_time": 0.36, "type": "word"}, "annotation": null}, {"idx": 3, "start_time": 0.92, "end_time": 1.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 1.0, "start_time": 0.92, "type": "word"}, "annotation": null}, {"idx": 7, "start_time": 1.04, "end_time": 1.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Speechmatics", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Speechmatics", "language": "en", "speaker": "S1"}], "end_time": 1.96, "start_time": 1.04, "type": "word"}, "annotation": null}, {"idx": 8, "start_time": 1.96, "end_time": 1.96, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 1.96, "is_eos": true, "start_time": 1.96, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 5.64786, "audio_ts": 3.76, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 3.52, "start_time": 3.16, "transcript": ""}}} -{"ts": 6.053817, "audio_ts": 4.17, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.8, "start_time": 3.08, "transcript": "We're"}}} -{"ts": 6.054156, "audio_ts": 4.17, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 6.060356, "audio_ts": 4.17, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 9, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 6.382232, "audio_ts": 4.5, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}], "metadata": {"end_time": 3.72, "start_time": 3.08, "transcript": "We're "}}} -{"ts": 6.382493, "audio_ts": 4.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.16, "start_time": 3.72, "transcript": "delighted that"}}} -{"ts": 6.388955, "audio_ts": 4.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 11, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 12, "start_time": 4.08, "end_time": 4.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.16, "start_time": 4.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 6.753409, "audio_ts": 4.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}], "metadata": {"end_time": 4.24, "start_time": 3.72, "transcript": "delighted that "}}} -{"ts": 6.753704, "audio_ts": 4.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.52, "start_time": 4.24, "transcript": "you've done"}}} -{"ts": 6.759998, "audio_ts": 4.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 15, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 16, "start_time": 4.44, "end_time": 4.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "done", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "done", "language": "en", "speaker": "S1"}], "end_time": 4.52, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.064508, "audio_ts": 5.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}], "metadata": {"end_time": 4.44, "start_time": 4.24, "transcript": "you've "}}} -{"ts": 7.064667, "audio_ts": 5.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}], "metadata": {"end_time": 4.88, "start_time": 4.44, "transcript": "decided"}}} -{"ts": 7.071052, "audio_ts": 5.18, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 18, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.471408, "audio_ts": 5.58, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}], "metadata": {"end_time": 4.96, "start_time": 4.44, "transcript": "decided to "}}} -{"ts": 7.472128, "audio_ts": 5.59, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}], "metadata": {"end_time": 5.24, "start_time": 4.96, "transcript": "try"}}} -{"ts": 7.478006, "audio_ts": 5.59, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 21, "start_time": 4.96, "end_time": 5.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.24, "start_time": 4.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 7.792774, "audio_ts": 5.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our"}}} -{"ts": 7.79933, "audio_ts": 5.91, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 22, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 23, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.237617, "audio_ts": 6.35, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}], "metadata": {"end_time": 5.6, "start_time": 4.96, "transcript": "try our "}}} -{"ts": 8.237751, "audio_ts": 6.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 5.96, "start_time": 5.6, "transcript": "speech to"}}} -{"ts": 8.243272, "audio_ts": 6.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 26, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 27, "start_time": 5.88, "end_time": 5.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 0.7, "result": {"final": false, "alternatives": [{"confidence": 0.7, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 5.96, "start_time": 5.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.506788, "audio_ts": 6.62, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}], "metadata": {"end_time": 6.0, "start_time": 5.6, "transcript": "speech to "}}} -{"ts": 8.507043, "audio_ts": 6.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.32, "start_time": 6.0, "transcript": "text"}}} -{"ts": 8.512609, "audio_ts": 6.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 30, "start_time": 6.04, "end_time": 6.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.32, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 8.93321, "audio_ts": 7.05, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}], "metadata": {"end_time": 6.36, "start_time": 6.0, "transcript": "text "}}} -{"ts": 8.933469, "audio_ts": 7.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 6.76, "start_time": 6.44, "transcript": ""}}} -{"ts": 8.933784, "audio_ts": 7.05, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 8.940048, "audio_ts": 7.05, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 9.240444, "audio_ts": 7.35, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}], "metadata": {"end_time": 7.04, "start_time": 6.36, "transcript": "software"}}} -{"ts": 9.240744, "audio_ts": 7.35, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 9.246894, "audio_ts": 7.36, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 32, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 9.663073, "audio_ts": 7.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}], "metadata": {"end_time": 7.0, "start_time": 6.36, "transcript": "software. "}}} -{"ts": 9.663265, "audio_ts": 7.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}], "metadata": {"end_time": 7.4, "start_time": 7.0, "transcript": "Hello"}}} -{"ts": 9.669355, "audio_ts": 7.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}, {"idx": 35, "start_time": 7.08, "end_time": 7.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "Hello", "language": "en", "speaker": "S1"}], "end_time": 7.4, "start_time": 7.08, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 10.008257, "audio_ts": 8.12, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.76, "start_time": 7.0, "transcript": "Hello. This"}}} -{"ts": 10.008693, "audio_ts": 8.12, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 10.008839, "audio_ts": 8.12, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 10.014702, "audio_ts": 8.13, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:51:58.876+00:00", "language": "en", "fragments": [{"idx": 10, "start_time": 3.48, "end_time": 3.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We're", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We're", "language": "en", "speaker": "S1"}], "end_time": 3.72, "start_time": 3.48, "type": "word"}, "annotation": null}, {"idx": 13, "start_time": 3.72, "end_time": 4.08, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "delighted", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "delighted", "language": "en", "speaker": "S1"}], "end_time": 4.08, "start_time": 3.72, "type": "word"}, "annotation": null}, {"idx": 14, "start_time": 4.08, "end_time": 4.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "that", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "that", "language": "en", "speaker": "S1"}], "end_time": 4.24, "start_time": 4.08, "type": "word"}, "annotation": null}, {"idx": 17, "start_time": 4.24, "end_time": 4.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you've", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you've", "language": "en", "speaker": "S1"}], "end_time": 4.44, "start_time": 4.24, "type": "word"}, "annotation": null}, {"idx": 19, "start_time": 4.44, "end_time": 4.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "decided", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "decided", "language": "en", "speaker": "S1"}], "end_time": 4.84, "start_time": 4.44, "type": "word"}, "annotation": null}, {"idx": 20, "start_time": 4.84, "end_time": 4.96, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 4.96, "start_time": 4.84, "type": "word"}, "annotation": null}, {"idx": 24, "start_time": 4.96, "end_time": 5.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "try", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "try", "language": "en", "speaker": "S1"}], "end_time": 5.36, "start_time": 4.96, "type": "word"}, "annotation": null}, {"idx": 25, "start_time": 5.36, "end_time": 5.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 5.6, "start_time": 5.36, "type": "word"}, "annotation": null}, {"idx": 28, "start_time": 5.6, "end_time": 5.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "speech", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "speech", "language": "en", "speaker": "S1"}], "end_time": 5.88, "start_time": 5.6, "type": "word"}, "annotation": null}, {"idx": 29, "start_time": 5.88, "end_time": 6.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 6.0, "start_time": 5.88, "type": "word"}, "annotation": null}, {"idx": 31, "start_time": 6.04, "end_time": 6.36, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "text", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "text", "language": "en", "speaker": "S1"}], "end_time": 6.36, "start_time": 6.04, "type": "word"}, "annotation": null}, {"idx": 33, "start_time": 6.36, "end_time": 7.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "software", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "software", "language": "en", "speaker": "S1"}], "end_time": 7.0, "start_time": 6.36, "type": "word"}, "annotation": null}, {"idx": 34, "start_time": 7.0, "end_time": 7.0, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 7.0, "is_eos": true, "start_time": 7.0, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 10.016382, "audio_ts": 8.13, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 36, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 37, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 38, "start_time": 7.64, "end_time": 7.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.76, "start_time": 7.64, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "slow_speaker"]}]}} -{"ts": 10.329943, "audio_ts": 8.44, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}], "metadata": {"end_time": 7.84, "start_time": 7.0, "transcript": "Hello. This "}}} -{"ts": 10.330203, "audio_ts": 8.44, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.12, "start_time": 7.84, "transcript": "is a"}}} -{"ts": 10.335966, "audio_ts": 8.45, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 42, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 43, "start_time": 8.0, "end_time": 8.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.12, "start_time": 8.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 10.752302, "audio_ts": 8.87, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}], "metadata": {"end_time": 8.16, "start_time": 7.84, "transcript": "is a "}}} -{"ts": 10.753003, "audio_ts": 8.87, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.48, "start_time": 8.16, "transcript": "test"}}} -{"ts": 10.75884, "audio_ts": 8.87, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 46, "start_time": 8.16, "end_time": 8.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.48, "start_time": 8.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.065937, "audio_ts": 9.18, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}], "metadata": {"end_time": 8.44, "start_time": 8.16, "transcript": "test "}}} -{"ts": 11.066226, "audio_ts": 9.18, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}], "metadata": {"end_time": 8.84, "start_time": 8.44, "transcript": "one, two"}}} -{"ts": 11.072343, "audio_ts": 9.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 48, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 49, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 50, "start_time": 8.76, "end_time": 8.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 8.84, "start_time": 8.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.395428, "audio_ts": 9.51, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}], "metadata": {"end_time": 8.72, "start_time": 8.44, "transcript": "one, "}}} -{"ts": 11.396274, "audio_ts": 9.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, {"alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 9.2, "start_time": 8.72, "transcript": "two, three"}}} -{"ts": 11.402234, "audio_ts": 9.52, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 53, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 54, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 55, "start_time": 9.04, "end_time": 9.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 0.8, "result": {"final": false, "alternatives": [{"confidence": 0.8, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.2, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 11.771481, "audio_ts": 9.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}], "metadata": {"end_time": 9.0, "start_time": 8.72, "transcript": "two, "}}} -{"ts": 11.772237, "audio_ts": 9.89, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}], "metadata": {"end_time": 9.56, "start_time": 9.0, "transcript": "three."}}} -{"ts": 11.778482, "audio_ts": 9.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 58, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 59, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 12.185364, "audio_ts": 10.3, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}], "metadata": {"end_time": 9.92, "start_time": 9.0, "transcript": "three one"}}} -{"ts": 12.191717, "audio_ts": 10.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 60, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 61, "start_time": 9.6, "end_time": 9.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 9.88, "start_time": 9.6, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 12.510004, "audio_ts": 10.62, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.28, "start_time": 9.0, "transcript": "312"}}} -{"ts": 12.51643, "audio_ts": 10.63, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 62, "start_time": 9.04, "end_time": 10.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "312", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "312", "language": "en", "speaker": "S2"}], "end_time": 10.16, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 12.853021, "audio_ts": 10.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "3123"}}} -{"ts": 12.858984, "audio_ts": 10.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 63, "start_time": 9.04, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "3123", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "3123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 13.308448, "audio_ts": 11.42, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}], "metadata": {"end_time": 10.64, "start_time": 9.0, "transcript": "three. 123. "}}} -{"ts": 13.308567, "audio_ts": 11.42, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.08, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.308661, "audio_ts": 11.42, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 13.314061, "audio_ts": 11.43, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:02.476+00:00", "language": "en", "fragments": [{"idx": 39, "start_time": 7.08, "end_time": 7.6, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Hello", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Hello", "language": "en", "speaker": "S2"}], "end_time": 7.6, "start_time": 7.08, "type": "word"}, "annotation": null}, {"idx": 40, "start_time": 7.6, "end_time": 7.6, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 7.6, "is_eos": true, "start_time": 7.6, "type": "punctuation"}, "annotation": null}, {"idx": 41, "start_time": 7.64, "end_time": 7.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "This", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "This", "language": "en", "speaker": "S2"}], "end_time": 7.84, "start_time": 7.64, "type": "word"}, "annotation": null}, {"idx": 44, "start_time": 7.84, "end_time": 8.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "is", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "is", "language": "en", "speaker": "S2"}], "end_time": 8.0, "start_time": 7.84, "type": "word"}, "annotation": null}, {"idx": 45, "start_time": 8.0, "end_time": 8.16, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S2"}], "end_time": 8.16, "start_time": 8.0, "type": "word"}, "annotation": null}, {"idx": 47, "start_time": 8.16, "end_time": 8.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "test", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "test", "language": "en", "speaker": "S2"}], "end_time": 8.44, "start_time": 8.16, "type": "word"}, "annotation": null}, {"idx": 51, "start_time": 8.44, "end_time": 8.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "one", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "one", "language": "en", "speaker": "S2"}], "end_time": 8.72, "start_time": 8.44, "type": "word"}, "annotation": null}, {"idx": 52, "start_time": 8.72, "end_time": 8.72, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 8.72, "is_eos": false, "start_time": 8.72, "type": "punctuation"}, "annotation": null}, {"idx": 56, "start_time": 8.76, "end_time": 9.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "two", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "two", "language": "en", "speaker": "S2"}], "end_time": 9.0, "start_time": 8.76, "type": "word"}, "annotation": null}, {"idx": 57, "start_time": 9.0, "end_time": 9.0, "language": "en", "_type": "punctuation", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ",", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ",", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.0, "is_eos": false, "start_time": 9.0, "type": "punctuation"}, "annotation": null}, {"idx": 64, "start_time": 9.04, "end_time": 9.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "three", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "three", "language": "en", "speaker": "S2"}], "end_time": 9.52, "start_time": 9.04, "type": "word"}, "annotation": null}, {"idx": 65, "start_time": 9.52, "end_time": 9.52, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 9.52, "is_eos": true, "start_time": 9.52, "type": "punctuation"}, "annotation": null}, {"idx": 66, "start_time": 9.6, "end_time": 10.64, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "123", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "123", "language": "en", "speaker": "S2"}], "end_time": 10.64, "start_time": 9.6, "type": "word"}, "annotation": null}, {"idx": 67, "start_time": 10.64, "end_time": 10.64, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 10.64, "is_eos": true, "start_time": 10.64, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 13.52763, "audio_ts": 11.64, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 10.72, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.527825, "audio_ts": 11.64, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.44, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.922054, "audio_ts": 12.04, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 10.72, "transcript": ""}}} -{"ts": 13.922129, "audio_ts": 12.04, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 11.8, "start_time": 11.8, "transcript": ""}}} -{"ts": 13.922158, "audio_ts": 12.04, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 11.8, "start_time": 11.8}}} -{"ts": 14.385998, "audio_ts": 12.5, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.08, "start_time": 11.72, "transcript": "To"}}} -{"ts": 14.386353, "audio_ts": 12.5, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 14.391992, "audio_ts": 12.51, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 68, "start_time": 11.88, "end_time": 12.08, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S2"}], "end_time": 12.08, "start_time": 11.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 14.633662, "audio_ts": 12.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}], "metadata": {"end_time": 12.04, "start_time": 11.72, "transcript": "To "}}} -{"ts": 14.633813, "audio_ts": 12.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}], "metadata": {"end_time": 12.44, "start_time": 12.04, "transcript": "get going"}}} -{"ts": 14.634032, "audio_ts": 12.75, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 14.634153, "audio_ts": 12.75, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 14.64005, "audio_ts": 12.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 70, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 71, "start_time": 12.28, "end_time": 12.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.44, "start_time": 12.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 14.976035, "audio_ts": 13.09, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}], "metadata": {"end_time": 12.28, "start_time": 12.04, "transcript": "get "}}} -{"ts": 14.976252, "audio_ts": 13.09, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.8, "start_time": 12.28, "transcript": "going."}}} -{"ts": 14.981592, "audio_ts": 13.1, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 73, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 74, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 15.377371, "audio_ts": 13.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}], "metadata": {"end_time": 12.76, "start_time": 12.28, "transcript": "going. "}}} -{"ts": 15.378387, "audio_ts": 13.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.16, "start_time": 12.76, "transcript": "Just"}}} -{"ts": 15.385407, "audio_ts": 13.5, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 77, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 15.737837, "audio_ts": 13.85, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}], "metadata": {"end_time": 13.0, "start_time": 12.76, "transcript": "Just "}}} -{"ts": 15.73864, "audio_ts": 13.85, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, {"alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.52, "start_time": 13.0, "transcript": "create. An"}}} -{"ts": 15.744679, "audio_ts": 13.86, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 79, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 80, "start_time": 13.32, "end_time": 13.32, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 13.32, "is_eos": true, "start_time": 13.32, "type": "punctuation"}, "annotation": null}, {"idx": 81, "start_time": 13.36, "end_time": 13.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "An", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "An", "language": "en", "speaker": "S1"}], "end_time": 13.52, "start_time": 13.36, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 16.071516, "audio_ts": 14.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}], "metadata": {"end_time": 13.48, "start_time": 13.0, "transcript": "create an "}}} -{"ts": 16.071747, "audio_ts": 14.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API"}}} -{"ts": 16.077695, "audio_ts": 14.19, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 84, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 16.414864, "audio_ts": 14.53, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}], "metadata": {"end_time": 13.88, "start_time": 13.48, "transcript": "API "}}} -{"ts": 16.415066, "audio_ts": 14.53, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key"}}} -{"ts": 16.421228, "audio_ts": 14.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 86, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 16.843428, "audio_ts": 14.96, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}], "metadata": {"end_time": 14.24, "start_time": 13.88, "transcript": "key "}}} -{"ts": 16.84362, "audio_ts": 14.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.6, "start_time": 14.24, "transcript": "and"}}} -{"ts": 16.849775, "audio_ts": 14.96, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 88, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 17.287971, "audio_ts": 15.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}], "metadata": {"end_time": 14.44, "start_time": 14.24, "transcript": "and "}}} -{"ts": 17.288201, "audio_ts": 15.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.96, "start_time": 14.44, "transcript": "submit a"}}} -{"ts": 17.29366, "audio_ts": 15.41, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 90, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 91, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 17.520679, "audio_ts": 15.63, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}], "metadata": {"end_time": 14.84, "start_time": 14.44, "transcript": "submit a "}}} -{"ts": 17.520938, "audio_ts": 15.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 15.4, "start_time": 14.92, "transcript": ""}}} -{"ts": 17.521447, "audio_ts": 15.63, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 17.527366, "audio_ts": 15.64, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 17.902758, "audio_ts": 16.02, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.68, "start_time": 14.84, "transcript": "transcription"}}} -{"ts": 17.903008, "audio_ts": 16.02, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 17.909177, "audio_ts": 16.02, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 94, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 0.99, "result": {"final": false, "alternatives": [{"confidence": 0.99, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 18.262821, "audio_ts": 16.38, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}], "metadata": {"end_time": 15.48, "start_time": 14.84, "transcript": "transcription "}}} -{"ts": 18.263012, "audio_ts": 16.38, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}], "metadata": {"end_time": 16.04, "start_time": 15.48, "transcript": "request to our"}}} -{"ts": 18.268827, "audio_ts": 16.38, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 96, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 97, "start_time": 15.88, "end_time": 15.96, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 15.96, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 98, "start_time": 15.96, "end_time": 16.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.04, "start_time": 15.96, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 18.633779, "audio_ts": 16.75, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}], "metadata": {"end_time": 16.0, "start_time": 15.48, "transcript": "request to "}}} -{"ts": 18.634053, "audio_ts": 16.75, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.4, "start_time": 16.0, "transcript": "our"}}} -{"ts": 18.640547, "audio_ts": 16.75, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 101, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 18.943305, "audio_ts": 17.06, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}], "metadata": {"end_time": 16.2, "start_time": 16.0, "transcript": "our "}}} -{"ts": 18.943422, "audio_ts": 17.06, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.76, "start_time": 16.2, "transcript": "API."}}} -{"ts": 18.94966, "audio_ts": 17.06, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 103, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 0.74, "result": {"final": false, "alternatives": [{"confidence": 0.74, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 104, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 19.3389, "audio_ts": 17.45, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}], "metadata": {"end_time": 16.72, "start_time": 16.2, "transcript": "API. "}}} -{"ts": 19.33907, "audio_ts": 17.45, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.2, "start_time": 16.8, "transcript": ""}}} -{"ts": 19.339197, "audio_ts": 17.45, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 19.344855, "audio_ts": 17.46, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:07.276+00:00", "language": "en", "fragments": [{"idx": 69, "start_time": 11.88, "end_time": 12.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "To", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "To", "language": "en", "speaker": "S1"}], "end_time": 12.04, "start_time": 11.88, "type": "word"}, "annotation": null}, {"idx": 72, "start_time": 12.04, "end_time": 12.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "get", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "get", "language": "en", "speaker": "S1"}], "end_time": 12.28, "start_time": 12.04, "type": "word"}, "annotation": null}, {"idx": 75, "start_time": 12.28, "end_time": 12.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "going", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "going", "language": "en", "speaker": "S1"}], "end_time": 12.76, "start_time": 12.28, "type": "word"}, "annotation": null}, {"idx": 76, "start_time": 12.76, "end_time": 12.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 12.76, "is_eos": true, "start_time": 12.76, "type": "punctuation"}, "annotation": null}, {"idx": 78, "start_time": 12.8, "end_time": 13.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Just", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Just", "language": "en", "speaker": "S1"}], "end_time": 13.0, "start_time": 12.8, "type": "word"}, "annotation": null}, {"idx": 82, "start_time": 13.0, "end_time": 13.32, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "create", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "create", "language": "en", "speaker": "S1"}], "end_time": 13.32, "start_time": 13.0, "type": "word"}, "annotation": null}, {"idx": 83, "start_time": 13.36, "end_time": 13.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "an", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "an", "language": "en", "speaker": "S1"}], "end_time": 13.48, "start_time": 13.36, "type": "word"}, "annotation": null}, {"idx": 85, "start_time": 13.48, "end_time": 13.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 13.88, "start_time": 13.48, "type": "word"}, "annotation": null}, {"idx": 87, "start_time": 13.88, "end_time": 14.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "key", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "key", "language": "en", "speaker": "S1"}], "end_time": 14.24, "start_time": 13.88, "type": "word"}, "annotation": null}, {"idx": 89, "start_time": 14.24, "end_time": 14.44, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "and", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "and", "language": "en", "speaker": "S1"}], "end_time": 14.44, "start_time": 14.24, "type": "word"}, "annotation": null}, {"idx": 92, "start_time": 14.44, "end_time": 14.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "submit", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "submit", "language": "en", "speaker": "S1"}], "end_time": 14.76, "start_time": 14.44, "type": "word"}, "annotation": null}, {"idx": 93, "start_time": 14.76, "end_time": 14.84, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "a", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "a", "language": "en", "speaker": "S1"}], "end_time": 14.84, "start_time": 14.76, "type": "word"}, "annotation": null}, {"idx": 95, "start_time": 14.84, "end_time": 15.48, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "transcription", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "transcription", "language": "en", "speaker": "S1"}], "end_time": 15.48, "start_time": 14.84, "type": "word"}, "annotation": null}, {"idx": 99, "start_time": 15.48, "end_time": 15.88, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "request", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "request", "language": "en", "speaker": "S1"}], "end_time": 15.88, "start_time": 15.48, "type": "word"}, "annotation": null}, {"idx": 100, "start_time": 15.88, "end_time": 16.0, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "to", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "to", "language": "en", "speaker": "S1"}], "end_time": 16.0, "start_time": 15.88, "type": "word"}, "annotation": null}, {"idx": 102, "start_time": 16.0, "end_time": 16.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "our", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "our", "language": "en", "speaker": "S1"}], "end_time": 16.2, "start_time": 16.0, "type": "word"}, "annotation": null}, {"idx": 105, "start_time": 16.2, "end_time": 16.72, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "API", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "API", "language": "en", "speaker": "S1"}], "end_time": 16.72, "start_time": 16.2, "type": "word"}, "annotation": null}, {"idx": 106, "start_time": 16.72, "end_time": 16.72, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 16.72, "is_eos": true, "start_time": 16.72, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 19.675487, "audio_ts": 17.79, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 17.56, "start_time": 16.8, "transcript": ""}}} -{"ts": 20.032894, "audio_ts": 18.15, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}], "metadata": {"end_time": 17.56, "start_time": 16.72, "transcript": "We "}}} -{"ts": 20.033185, "audio_ts": 18.15, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.84, "start_time": 17.56, "transcript": "hope you"}}} -{"ts": 20.03351, "audio_ts": 18.15, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 20.039813, "audio_ts": 18.15, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 108, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 109, "start_time": 17.76, "end_time": 17.84, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 17.84, "start_time": 17.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} -{"ts": 20.406231, "audio_ts": 18.52, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}], "metadata": {"end_time": 17.92, "start_time": 17.56, "transcript": "hope you'll "}}} -{"ts": 20.406479, "audio_ts": 18.52, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very"}}} -{"ts": 20.412281, "audio_ts": 18.53, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 112, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 113, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "fast_speaker"]}]}} -{"ts": 20.765333, "audio_ts": 18.88, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}], "metadata": {"end_time": 18.2, "start_time": 17.92, "transcript": "be very "}}} -{"ts": 20.765568, "audio_ts": 18.88, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed"}}} -{"ts": 20.771675, "audio_ts": 18.89, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 116, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 0.83, "result": {"final": false, "alternatives": [{"confidence": 0.83, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.133457, "audio_ts": 19.25, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}], "metadata": {"end_time": 18.56, "start_time": 18.2, "transcript": "impressed "}}} -{"ts": 21.133646, "audio_ts": 19.25, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, {"alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}], "metadata": {"end_time": 18.92, "start_time": 18.56, "transcript": "by the results"}}} -{"ts": 21.139587, "audio_ts": 19.25, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 118, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 119, "start_time": 18.68, "end_time": 18.76, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.76, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 120, "start_time": 18.76, "end_time": 18.92, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 0.85, "result": {"final": false, "alternatives": [{"confidence": 0.85, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 18.92, "start_time": 18.76, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.461483, "audio_ts": 19.57, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}], "metadata": {"end_time": 18.8, "start_time": 18.56, "transcript": "by the "}}} -{"ts": 21.461757, "audio_ts": 19.57, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.28, "start_time": 18.8, "transcript": "results"}}} -{"ts": 21.468508, "audio_ts": 19.58, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 123, "start_time": 18.8, "end_time": 19.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.28, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 21.849236, "audio_ts": 19.96, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.64, "start_time": 18.8, "transcript": "results"}}} -{"ts": 21.855117, "audio_ts": 19.97, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 124, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 22.189932, "audio_ts": 20.3, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}], "metadata": {"end_time": 19.52, "start_time": 18.8, "transcript": "results "}}} -{"ts": 22.191317, "audio_ts": 20.31, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 20.08, "start_time": 19.6, "transcript": ""}}} -{"ts": 22.191722, "audio_ts": 20.31, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 22.197864, "audio_ts": 20.31, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final"]}]}} -{"ts": 22.553322, "audio_ts": 20.67, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.36, "start_time": 19.52, "transcript": ". The"}}} -{"ts": 22.553734, "audio_ts": 20.67, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S3"}}} -{"ts": 22.560245, "audio_ts": 20.67, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 126, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}, {"speaker_id": "S3", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 127, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S3", "confidence": 0.97, "result": {"final": false, "alternatives": [{"confidence": 0.97, "content": "The", "language": "en", "speaker": "S3"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 22.917949, "audio_ts": 21.03, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, {"alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}], "metadata": {"end_time": 20.28, "start_time": 19.52, "transcript": ". The "}}} -{"ts": 22.918164, "audio_ts": 21.03, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}], "metadata": {"end_time": 20.72, "start_time": 20.28, "transcript": "results"}}} -{"ts": 22.918499, "audio_ts": 21.03, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S3"}}} -{"ts": 22.918602, "audio_ts": 21.03, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 22.924306, "audio_ts": 21.04, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:12.796+00:00", "language": "en", "fragments": [{"idx": 107, "start_time": 17.4, "end_time": 17.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "We", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "We", "language": "en", "speaker": "S1"}], "end_time": 17.56, "start_time": 17.4, "type": "word"}, "annotation": null}, {"idx": 110, "start_time": 17.6, "end_time": 17.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "hope", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "hope", "language": "en", "speaker": "S1"}], "end_time": 17.76, "start_time": 17.6, "type": "word"}, "annotation": null}, {"idx": 111, "start_time": 17.76, "end_time": 17.92, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you'll", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you'll", "language": "en", "speaker": "S1"}], "end_time": 17.92, "start_time": 17.76, "type": "word"}, "annotation": null}, {"idx": 114, "start_time": 17.92, "end_time": 18.04, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "be", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "be", "language": "en", "speaker": "S1"}], "end_time": 18.04, "start_time": 17.92, "type": "word"}, "annotation": null}, {"idx": 115, "start_time": 18.04, "end_time": 18.2, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "very", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "very", "language": "en", "speaker": "S1"}], "end_time": 18.2, "start_time": 18.04, "type": "word"}, "annotation": null}, {"idx": 117, "start_time": 18.2, "end_time": 18.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "impressed", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "impressed", "language": "en", "speaker": "S1"}], "end_time": 18.56, "start_time": 18.2, "type": "word"}, "annotation": null}, {"idx": 121, "start_time": 18.56, "end_time": 18.68, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "by", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "by", "language": "en", "speaker": "S1"}], "end_time": 18.68, "start_time": 18.56, "type": "word"}, "annotation": null}, {"idx": 122, "start_time": 18.68, "end_time": 18.8, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "the", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "the", "language": "en", "speaker": "S1"}], "end_time": 18.8, "start_time": 18.68, "type": "word"}, "annotation": null}, {"idx": 125, "start_time": 18.8, "end_time": 19.52, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S1"}], "end_time": 19.52, "start_time": 18.8, "type": "word"}, "annotation": null}, {"idx": 128, "start_time": 20.16, "end_time": 20.16, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 20.16, "is_eos": true, "start_time": 20.16, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 22.924952, "audio_ts": 21.04, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 130, "start_time": 20.28, "end_time": 20.72, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 0.73, "result": {"final": false, "alternatives": [{"confidence": 0.73, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.72, "start_time": 20.28, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 23.370478, "audio_ts": 21.48, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}], "metadata": {"end_time": 20.76, "start_time": 20.28, "transcript": "results? "}}} -{"ts": 23.370714, "audio_ts": 21.48, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 21.16, "start_time": 20.84, "transcript": ""}}} -{"ts": 23.370926, "audio_ts": 21.48, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 23.376688, "audio_ts": 21.49, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:15.556+00:00", "language": "en", "fragments": [{"idx": 129, "start_time": 20.16, "end_time": 20.28, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "The", "speaker": "S2", "confidence": 0.73, "result": {"final": true, "alternatives": [{"confidence": 0.73, "content": "The", "language": "en", "speaker": "S2"}], "end_time": 20.28, "start_time": 20.16, "type": "word"}, "annotation": null}, {"idx": 131, "start_time": 20.28, "end_time": 20.76, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "results", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "results", "language": "en", "speaker": "S2"}], "end_time": 20.76, "start_time": 20.28, "type": "word"}, "annotation": null}, {"idx": 132, "start_time": 20.76, "end_time": 20.76, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": "?", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "?", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 20.76, "is_eos": true, "start_time": 20.76, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 23.662326, "audio_ts": 21.78, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}], "metadata": {"end_time": 21.12, "start_time": 20.76, "transcript": "Really "}}} -{"ts": 23.662507, "audio_ts": 21.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.44, "start_time": 21.12, "transcript": "are pretty"}}} -{"ts": 23.662712, "audio_ts": 21.78, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S2"}}} -{"ts": 23.668783, "audio_ts": 21.78, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 134, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 135, "start_time": 21.24, "end_time": 21.44, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.44, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 24.000056, "audio_ts": 22.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}], "metadata": {"end_time": 21.24, "start_time": 21.12, "transcript": "are "}}} -{"ts": 24.000313, "audio_ts": 22.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.8, "start_time": 21.24, "transcript": "pretty"}}} -{"ts": 24.006407, "audio_ts": 22.12, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 137, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final"]}]}} -{"ts": 24.373133, "audio_ts": 22.49, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}], "metadata": {"end_time": 21.56, "start_time": 21.24, "transcript": "pretty "}}} -{"ts": 24.374785, "audio_ts": 22.49, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}], "metadata": {"end_time": 22.16, "start_time": 21.56, "transcript": "amazing"}}} -{"ts": 24.381632, "audio_ts": 22.49, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 139, "start_time": 21.56, "end_time": 22.16, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.16, "start_time": 21.56, "type": "word"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "slow_speaker"]}]}} -{"ts": 24.707232, "audio_ts": 22.82, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}], "metadata": {"end_time": 22.24, "start_time": 21.56, "transcript": "amazing. "}}} -{"ts": 24.707448, "audio_ts": 22.82, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.6, "start_time": 22.32, "transcript": ""}}} -{"ts": 24.707509, "audio_ts": 22.82, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S2"}}} -{"ts": 24.712719, "audio_ts": 22.83, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S2", "is_active": true, "timestamp": "2025-09-12T15:52:16.196+00:00", "language": "en", "fragments": [{"idx": 133, "start_time": 20.8, "end_time": 21.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Really", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Really", "language": "en", "speaker": "S2"}], "end_time": 21.12, "start_time": 20.8, "type": "word"}, "annotation": null}, {"idx": 136, "start_time": 21.12, "end_time": 21.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "are", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "are", "language": "en", "speaker": "S2"}], "end_time": 21.24, "start_time": 21.12, "type": "word"}, "annotation": null}, {"idx": 138, "start_time": 21.24, "end_time": 21.56, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "pretty", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "pretty", "language": "en", "speaker": "S2"}], "end_time": 21.56, "start_time": 21.24, "type": "word"}, "annotation": null}, {"idx": 140, "start_time": 21.56, "end_time": 22.24, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "amazing", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "amazing", "language": "en", "speaker": "S2"}], "end_time": 22.24, "start_time": 21.56, "type": "word"}, "annotation": null}, {"idx": 141, "start_time": 22.24, "end_time": 22.24, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S2", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S2"}], "attaches_to": "previous", "end_time": 22.24, "is_eos": true, "start_time": 22.24, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation", "slow_speaker"]}]}} -{"ts": 25.077868, "audio_ts": 23.19, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.32, "start_time": 22.32, "transcript": ""}}} -{"ts": 25.077952, "audio_ts": 23.19, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 22.96, "start_time": 22.32, "transcript": ""}}} -{"ts": 25.565104, "audio_ts": 23.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, {"alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}], "metadata": {"end_time": 23.24, "start_time": 22.24, "transcript": "Thank you"}}} -{"ts": 25.56526, "audio_ts": 23.68, "payload": {"message": "SpeakingStarted", "status": {"is_active": true, "speaker_id": "S1"}}} -{"ts": 25.57096, "audio_ts": 23.68, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 142, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 143, "start_time": 23.12, "end_time": 23.24, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 0.96, "result": {"final": false, "alternatives": [{"confidence": 0.96, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.24, "start_time": 23.12, "type": "word"}, "annotation": null}], "annotation": ["has_partial"]}]}} -{"ts": 25.800109, "audio_ts": 23.91, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}], "metadata": {"end_time": 23.12, "start_time": 22.24, "transcript": "Thank "}}} -{"ts": 25.800404, "audio_ts": 23.91, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.6, "start_time": 23.12, "transcript": "you."}}} -{"ts": 25.806547, "audio_ts": 23.92, "payload": {"message": "AddInterimSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 145, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": false, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 146, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": false, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": false, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_partial", "has_final", "starts_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 26.145423, "audio_ts": 24.26, "payload": {"message": "AddTranscript", "format": "2.9", "results": [{"alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, {"alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}], "metadata": {"end_time": 23.96, "start_time": 23.12, "transcript": "you. "}}} -{"ts": 26.145641, "audio_ts": 24.26, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.04, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.145718, "audio_ts": 24.26, "payload": {"message": "EndOfUtterance", "format": "2.9", "metadata": {"end_time": 24.04, "start_time": 24.04}}} -{"ts": 26.145902, "audio_ts": 24.26, "payload": {"message": "SpeakingEnded", "status": {"is_active": false, "speaker_id": "S1"}}} -{"ts": 26.151383, "audio_ts": 24.27, "payload": {"message": "AddSegments", "segments": [{"speaker_id": "S1", "is_active": true, "timestamp": "2025-09-12T15:52:18.236+00:00", "language": "en", "fragments": [{"idx": 144, "start_time": 22.84, "end_time": 23.12, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "Thank", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "Thank", "language": "en", "speaker": "S1"}], "end_time": 23.12, "start_time": 22.84, "type": "word"}, "annotation": null}, {"idx": 147, "start_time": 23.12, "end_time": 23.4, "language": "en", "_type": "word", "is_eos": false, "is_final": true, "is_disfluency": false, "is_punctuation": false, "attaches_to": "", "content": "you", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": "you", "language": "en", "speaker": "S1"}], "end_time": 23.4, "start_time": 23.12, "type": "word"}, "annotation": null}, {"idx": 148, "start_time": 23.4, "end_time": 23.4, "language": "en", "_type": "punctuation", "is_eos": true, "is_final": true, "is_disfluency": false, "is_punctuation": true, "attaches_to": "previous", "content": ".", "speaker": "S1", "confidence": 1.0, "result": {"final": true, "alternatives": [{"confidence": 1.0, "content": ".", "language": "en", "speaker": "S1"}], "attaches_to": "previous", "end_time": 23.4, "is_eos": true, "start_time": 23.4, "type": "punctuation"}, "annotation": null}], "annotation": ["has_final", "starts_with_final", "ends_with_final", "ends_with_eos", "ends_with_punctuation"]}]}} -{"ts": 26.514257, "audio_ts": 24.63, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.4, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.859512, "audio_ts": 24.97, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.04, "transcript": ""}}} -{"ts": 26.859743, "audio_ts": 24.97, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 24.76, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.224564, "audio_ts": 25.34, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.12, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.562289, "audio_ts": 25.68, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 24.76, "transcript": ""}}} -{"ts": 27.56251, "audio_ts": 25.68, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.48, "start_time": 25.48, "transcript": ""}}} -{"ts": 27.935434, "audio_ts": 26.05, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 25.84, "start_time": 25.48, "transcript": ""}}} -{"ts": 28.289689, "audio_ts": 26.4, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 25.48, "transcript": ""}}} -{"ts": 28.289875, "audio_ts": 26.4, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.2, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.669148, "audio_ts": 26.78, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.56, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.997916, "audio_ts": 27.11, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.2, "transcript": ""}}} -{"ts": 28.99816, "audio_ts": 27.11, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 26.92, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.392005, "audio_ts": 27.51, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.28, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.747696, "audio_ts": 27.86, "payload": {"message": "AddTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 26.92, "transcript": ""}}} -{"ts": 29.747844, "audio_ts": 27.86, "payload": {"message": "AddPartialTranscript", "format": "2.9", "results": [], "metadata": {"end_time": 27.64, "start_time": 27.64, "transcript": ""}}} diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 3de3195a..6f2699a3 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -78,7 +78,9 @@ async def test_speech_fragments(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) # Start time start_time = datetime.datetime.now() @@ -87,6 +89,9 @@ async def test_speech_fragments(): client = await get_client(api_key="NONE", connect=False) assert client is not None + # Start the queue + client._start_stt_queue() + # Event to wait event_rx: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None @@ -125,14 +130,14 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) # Inject first partial - await send_message(0, count=3, use_ttl=False) + await send_message(0, count=6, use_ttl=False) # Wait for first segment try: await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None except asyncio.TimeoutError: - pytest.fail("ADD_INTERIM_SEGMENTS event was not received within 5 seconds") + pytest.fail("ADD_PARTIAL_SEGMENT event was not received within 5 seconds") # Check the right message was received assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT @@ -150,14 +155,14 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) # Send a more partials and finals - await send_message(3, count=10, use_ttl=False) + await send_message(5, count=8, use_ttl=False) # Wait for final segment try: await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None except asyncio.TimeoutError: - pytest.fail("ADD_SEGMENTS event was not received within 5 seconds") + pytest.fail("ADD_SEGMENT event was not received within 5 seconds") # Check the right message was received assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT @@ -170,6 +175,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert seg0["text"] == "Welcome to Speechmatics." assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics." + # Stop the queue + client._stop_stt_queue() + @pytest.mark.asyncio async def test_end_of_utterance_fixed(): @@ -181,7 +189,9 @@ async def test_end_of_utterance_fixed(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) # Start time start_time = datetime.datetime.now() @@ -190,6 +200,9 @@ async def test_end_of_utterance_fixed(): client = await get_client(api_key="NONE", connect=False) assert client is not None + # Start the queue + client._start_stt_queue() + # Event to wait event_rx: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None @@ -221,7 +234,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.END_OF_TURN, message_rx) # Inject conversation - await send_message(0, count=13, use_ttl=False) + await send_message(0, count=14, use_ttl=False) # Wait for EndOfTurn try: @@ -244,7 +257,9 @@ async def test_external_vad(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) # Start time start_time = datetime.datetime.now() @@ -262,6 +277,8 @@ async def test_external_vad(): ) assert client is not None + # Start the queue + client._start_stt_queue() # Event to wait event_rx: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None @@ -290,7 +307,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.emit(message["payload"]["message"], message["payload"]) # Inject conversation - await send_message(0, count=11, use_ttl=False) + await send_message(0, count=12, use_ttl=False) # Momentary pause await asyncio.sleep(0.5) @@ -298,6 +315,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Add listener for first interim segment client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) + # Pause for a moment + await asyncio.sleep(0.5) + # Send finalize client.finalize() @@ -306,7 +326,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.wait_for(event_rx.wait(), timeout=4) assert last_message is not None except asyncio.TimeoutError: - pytest.fail("ADD_SEGMENTS event was not received within 4 seconds") + pytest.fail("ADD_SEGMENT event was not received within 4 seconds") # Check the right message was received assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT @@ -319,6 +339,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert seg0["text"] == "Welcome to Speechmatics" assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics" + # Stop the queue + client._stop_stt_queue() + @pytest.mark.asyncio async def test_end_of_utterance_adaptive_vad(): @@ -330,7 +353,9 @@ async def test_end_of_utterance_adaptive_vad(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) # Start time start_time = datetime.datetime.now() @@ -348,6 +373,9 @@ async def test_end_of_utterance_adaptive_vad(): ) assert client is not None + # Start the queue + client._start_stt_queue() + # Event to wait eot_received: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None @@ -417,3 +445,6 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): expected_max_interval = adaptive_timeout * 0.5 * 1.1 assert receive_interval >= expected_min_interval assert receive_interval <= expected_max_interval + + # Stop the queue + client._stop_stt_queue() diff --git a/tests/voice/test_05_thought.py b/tests/voice/test_05_thought.py new file mode 100644 index 00000000..9d7be856 --- /dev/null +++ b/tests/voice/test_05_thought.py @@ -0,0 +1,92 @@ +import asyncio +import datetime +import os +from typing import Any +from typing import Optional + +import pytest +from _utils import ConversationLog +from _utils import get_client + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig + + +@pytest.mark.asyncio +async def test_end_of_thought(): + """Use inference for end of thought. + + - send the utterance + - predict end of thought + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + + # Start time + start_time = datetime.datetime.now() + + # Adaptive timeout + adaptive_timeout = 0.5 + + # Create a client + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + ), + ) + assert client is not None + + # Event to wait + had_speaking_ended: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) + client.once(AgentServerMessageType.SPEAKER_ENDED, lambda message: had_speaking_ended.set()) + + # Inject conversation + await send_message(0, count=12, use_ttl=False) + + # Wait for EndOfUtterance + try: + await asyncio.wait_for(had_speaking_ended.wait(), timeout=10.0) + except asyncio.TimeoutError: + pytest.fail("SPEAKING_ENDED event was not received within 10 seconds") + + # Check the last message was expected + assert last_message is not None + assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT + + # Segment + segment = last_message.get("segments", [])[0] + assert segment is not None + + # Debug + print(segment["text"]) From 42313d999542404f157fcad4e7dbac66efd2407b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 19:00:13 +0100 Subject: [PATCH 055/215] updated test --- tests/voice/test_04_models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 6f2699a3..bc0d44d1 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -349,6 +349,7 @@ async def test_end_of_utterance_adaptive_vad(): - send converstaion messages (realtime) - wait for `EndOfUtterance` message from SDK (adaptive) + - check the interval to receive the EndOfUtterance message is within 25% of expected """ # Test conversation @@ -440,9 +441,9 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Check the right message was received assert last_message.get("message") == AgentServerMessageType.END_OF_TURN - # Check the interval was within +/- 10% of the adaptive trigger of 0.5 the timeout (see client code) - expected_min_interval = adaptive_timeout * 0.5 * 0.9 - expected_max_interval = adaptive_timeout * 0.5 * 1.1 + # Check the interval was within +/- 25% of the adaptive trigger of 0.5 the timeout (see client code) + expected_min_interval = adaptive_timeout * 0.5 * 0.75 + expected_max_interval = adaptive_timeout * 0.5 * 1.25 assert receive_interval >= expected_min_interval assert receive_interval <= expected_max_interval From 4122d515f6344801468f40d8ce40f4147e1e7e27 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 19:15:36 +0100 Subject: [PATCH 056/215] updated tests --- tests/voice/test_04_models.py | 1 + tests/voice/test_05_thought.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index bc0d44d1..dad9a9fa 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -279,6 +279,7 @@ async def test_external_vad(): # Start the queue client._start_stt_queue() + # Event to wait event_rx: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None diff --git a/tests/voice/test_05_thought.py b/tests/voice/test_05_thought.py index 9d7be856..e83cf783 100644 --- a/tests/voice/test_05_thought.py +++ b/tests/voice/test_05_thought.py @@ -23,7 +23,9 @@ async def test_end_of_thought(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation(["AddPartialTranscript", "AddTranscript", "EndOfUtterance"]) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) # Start time start_time = datetime.datetime.now() @@ -36,11 +38,16 @@ async def test_end_of_thought(): api_key="NONE", connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + end_of_utterance_silence_trigger=adaptive_timeout, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + enable_diarization=False, ), ) assert client is not None + # Start the queue + client._start_stt_queue() + # Event to wait had_speaking_ended: asyncio.Event = asyncio.Event() last_message: Optional[dict[str, Any]] = None @@ -78,7 +85,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): try: await asyncio.wait_for(had_speaking_ended.wait(), timeout=10.0) except asyncio.TimeoutError: - pytest.fail("SPEAKING_ENDED event was not received within 10 seconds") + pytest.fail("SPEAKER_ENDED event was not received within 10 seconds") # Check the last message was expected assert last_message is not None @@ -90,3 +97,6 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Debug print(segment["text"]) + + # Stop the queue + client._stop_stt_queue() From a0ce465e9393f2e510fb135e2fa245c58eb9a057 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 20:16:13 +0100 Subject: [PATCH 057/215] fix metadata --- sdk/voice/speechmatics/voice/_client.py | 40 ++++++++++++++----------- sdk/voice/speechmatics/voice/_models.py | 8 ++--- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c4c6111a..4b549035 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -354,14 +354,18 @@ async def _run_stt_queue(self) -> None: while True: callback = await self._stt_message_queue.get() - if asyncio.iscoroutine(callback): - await callback - elif asyncio.iscoroutinefunction(callback): - await callback() - elif callable(callback): - result = callback() - if asyncio.iscoroutine(result): - await result + try: + if asyncio.iscoroutine(callback): + await callback + elif asyncio.iscoroutinefunction(callback): + await callback() + elif callable(callback): + result = callback() + if asyncio.iscoroutine(result): + await result + + except Exception: + self._logger.warning("Exception in STT message queue", exc_info=True) def _stop_stt_queue(self) -> None: """Stop the STT message queue.""" @@ -811,17 +815,12 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False end_of_turn: Whether to emit an end of turn event. """ - # Metadata - if self._current_view: - metadata = {"start_time": self._current_view.start_time, "end_time": self._current_view.end_time} - elif self._previous_view: - metadata = {"start_time": self._previous_view.start_time, "end_time": self._previous_view.end_time} - else: - metadata = None - # Lock the speech fragments if self._current_view and self._current_view.segment_count > 0: async with self._speech_fragments_lock: + # Metadata + metadata = {"start_time": self._current_view.start_time, "end_time": self._current_view.end_time} + # Force finalize if finalize: final_segments = self._current_view.segments @@ -866,7 +865,11 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._update_current_view() # Emit end of turn - if end_of_turn: + if end_of_turn and self._previous_view: + # Metadata (for LAST view) + metadata = {"start_time": self._previous_view.start_time, "end_time": self._previous_view.end_time} + + # Emit self.emit( AgentServerMessageType.END_OF_TURN, { @@ -875,6 +878,9 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False }, ) + # Reset the previous view + self._previous_view = None + def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index c1def647..087f5988 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -533,12 +533,12 @@ class SpeakerSegment: @property def start_time(self) -> float: """Return the start time of the segment.""" - return self.fragments[0].start_time + return self.fragments[0].start_time if self.fragments else 0.0 @property def end_time(self) -> float: """Return the end time of the segment.""" - return self.fragments[-1].end_time + return self.fragments[-1].end_time if self.fragments else 0.0 def to_dict(self, include_results: bool = False) -> dict[str, Any]: """Return a dictionary representation of the object.""" @@ -593,11 +593,11 @@ def __init__( @property def start_time(self) -> float: - return self.fragments[0].start_time + return self.fragments[0].start_time if self.fragments else 0.0 @property def end_time(self) -> float: - return self.fragments[-1].end_time + return self.fragments[-1].end_time if self.fragments else 0.0 @property def final_count(self) -> int: From 682326f89e7e11ac11859f28b9e67022930f5cf1 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 20:46:29 +0100 Subject: [PATCH 058/215] Version bump. --- sdk/voice/README.md | 1 + sdk/voice/speechmatics/voice/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 148d44b4..b8198c25 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -176,6 +176,7 @@ The `VoiceAgentClient` can be configured with a number of options to control the #### Advanced Features - **`include_results`** (`bool`): Include word data in the response. This is useful for debugging and understanding the STT engine's behaviour. Defaults to `False`. +- **`enable_preview_features`** (`bool`): Enable preview features. Defaults to `False`. #### Audio Configuration diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 14ea54dc..502f5bb9 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.6" +__version__ = "0.1.7" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 846ff632c0cf140d39c026c934497ea2ed638cf4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 22 Sep 2025 20:46:55 +0100 Subject: [PATCH 059/215] Version 0.1.6 --- sdk/voice/speechmatics/voice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 502f5bb9..14ea54dc 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.7" +__version__ = "0.1.6" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 54f0de5b313d66b4a995631a9c76688ab086e31e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 15:11:33 +0100 Subject: [PATCH 060/215] test other language transcription --- tests/voice/_utils.py | 18 +++ tests/voice/assets/languages/de_de_000675.wav | 3 + tests/voice/assets/languages/fr_fr_000378.wav | 3 + tests/voice/test_06_languages.py | 139 ++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 tests/voice/assets/languages/de_de_000675.wav create mode 100644 tests/voice/assets/languages/fr_fr_000378.wav create mode 100644 tests/voice/test_06_languages.py diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index ceca7950..50be5fa0 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -1,7 +1,9 @@ import asyncio import json import os +import re import time +import unicodedata from typing import Any from typing import Callable from typing import Optional @@ -109,6 +111,22 @@ async def send_audio_file( next_time += delay +def normalize(text: str) -> str: + """Normalise text.""" + + # Lowercase + text = text.lower() + + # Remove punctuation (Unicode category "P") + text = "".join(ch for ch in text if unicodedata.category(ch)[0] != "P") + + # Collapse whitespace + text = re.sub(r"\s+", " ", text).strip() + + # Return cleaned text + return text + + class ConversationLog: """Load a JSONL past conversation.""" diff --git a/tests/voice/assets/languages/de_de_000675.wav b/tests/voice/assets/languages/de_de_000675.wav new file mode 100644 index 00000000..57920545 --- /dev/null +++ b/tests/voice/assets/languages/de_de_000675.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe2b28fa4bf8123fa5494cb4cfdbed535899048c892fe14033424f443bdd7330 +size 355244 diff --git a/tests/voice/assets/languages/fr_fr_000378.wav b/tests/voice/assets/languages/fr_fr_000378.wav new file mode 100644 index 00000000..b620f877 --- /dev/null +++ b/tests/voice/assets/languages/fr_fr_000378.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27e79762c5577bfd21fb874bd78b4c8c8659b1413ae892c6ee72ed08c5b4f20f +size 313004 diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_06_languages.py new file mode 100644 index 00000000..f1e57825 --- /dev/null +++ b/tests/voice/test_06_languages.py @@ -0,0 +1,139 @@ +import asyncio +import datetime +import json +import os +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Optional + +import pytest +from _utils import get_client +from _utils import normalize +from _utils import send_audio_file + +from speechmatics.voice import AdditionalVocabEntry +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._helpers import to_serializable + +api_key = os.getenv("SPEECHMATICS_API_KEY") + + +@dataclass +class AudioSample: + language: str + path: str + transcript: str + vocab: list[str] = field(default_factory=list) + + +SAMPLES: list[AudioSample] = [ + AudioSample( + language="fr", + path="./assets/languages/fr_fr_000378.wav", + transcript="la partie extérieure que nous voyons lorsque nous regardons le soleil s’appelle la photosphère ce qui signifie « boule de lumière »", + ), + AudioSample( + language="de", + path="./assets/languages/de_de_000675.wav", + transcript="Die Einreise in das südliche Afrika mit dem Auto ist eine erstaunliche Möglichkeit, die ganze Schönheit der Region zu sehen und an Orte abseits der normalen Touristenrouten zu gelangen.", + ), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sample", SAMPLES, ids=lambda s: f"{s.language}:{s.path}") +async def test_transcribe_languages(sample: AudioSample): + """Test transcription. + + This test will: + - log messages + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Client + client = await get_client( + api_key=api_key, + connect=False, + config=VoiceAgentConfig( + max_delay=1.2, + end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, + language=sample.language, + additional_vocab=[AdditionalVocabEntry(content=vocab) for vocab in sample.vocab], + ), + ) + + # Create an event to track when the callback is called + messages: list[str] = [] + bytes_sent: int = 0 + last_message: Optional[dict[str, Any]] = None + + # Start time + start_time = datetime.datetime.now() + + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + + # Callback for each message + def log_message(message): + nonlocal last_message + last_message = message + ts = (datetime.datetime.now() - start_time).total_seconds() + audio_ts = bytes_sent / 16000 / 2 + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + messages.append(log) + + # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + + # Load the audio file + audio_file = sample.path + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) + + # Send finalize after a short delay + await asyncio.sleep(2.0) + client.finalize() + + # Wait for 5 seconds + await asyncio.sleep(1.5) + + # Extract the last message + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT + + # Check the segment + segments = last_message.get("segments", []) + assert len(segments) == 1 + seg0 = segments[0] + + # Check language + assert seg0.get("language") == sample.language + + # Check contents + str_original = normalize(sample.transcript) + str_transcribed = normalize(seg0.get("text", "UNKNOWN")) + if str_original != str_transcribed: + print("\n".join(messages)) + print(f"Original: [{str_original}]") + print(f"Transcribed: [{str_transcribed}]") + raise AssertionError("Transcription does not match original") + + # Close session + await client.disconnect() + assert not client._is_connected From 5706951f5ce255746e65708f6f8f9c7d65aef24b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 15:55:07 +0100 Subject: [PATCH 061/215] Added Mandarin to tests and employ CER (optional) --- tests/voice/_utils.py | 37 ++++++++++++++++ .../assets/languages/cmn_hans_cn_000328.wav | 3 ++ tests/voice/assets/languages/he_il_000432.wav | 3 ++ tests/voice/test_06_languages.py | 43 ++++++++++++++++--- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 tests/voice/assets/languages/cmn_hans_cn_000328.wav create mode 100644 tests/voice/assets/languages/he_il_000432.wav diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index 50be5fa0..c564ea00 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -127,6 +127,43 @@ def normalize(text: str) -> str: return text +def cer(ref: str, hyp: str) -> float: + """ + Compute Character Error Rate (CER) between reference and hypothesis. + + CER = (S + D + I) / N + where + S = substitutions + D = deletions + I = insertions + N = number of characters in reference + """ + + # Initialise DP matrix + n, m = len(ref), len(hyp) + dp = [[0] * (m + 1) for _ in range(n + 1)] + + # Base cases + for i in range(n + 1): + dp[i][0] = i + for j in range(m + 1): + dp[0][j] = j + + # Fill DP matrix + for i in range(1, n + 1): + for j in range(1, m + 1): + cost = 0 if ref[i - 1] == hyp[j - 1] else 1 + dp[i][j] = min( + dp[i - 1][j] + 1, # deletion + dp[i][j - 1] + 1, # insertion + dp[i - 1][j - 1] + cost, # substitution + ) + + # Return CER + distance = dp[n][m] + return distance / n if n > 0 else float("inf") + + class ConversationLog: """Load a JSONL past conversation.""" diff --git a/tests/voice/assets/languages/cmn_hans_cn_000328.wav b/tests/voice/assets/languages/cmn_hans_cn_000328.wav new file mode 100644 index 00000000..e0cb525e --- /dev/null +++ b/tests/voice/assets/languages/cmn_hans_cn_000328.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f04d0429ccbdf4b69f6d5bb4893b91fc04a65809d863224218b3ef8c955064a1 +size 378284 diff --git a/tests/voice/assets/languages/he_il_000432.wav b/tests/voice/assets/languages/he_il_000432.wav new file mode 100644 index 00000000..16af9d2f --- /dev/null +++ b/tests/voice/assets/languages/he_il_000432.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f64da0b3ffa3a979e97a0b354a884f6a77029f8ab3546d95537a37cf460059 +size 268844 diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_06_languages.py index f1e57825..7c9ef7ce 100644 --- a/tests/voice/test_06_languages.py +++ b/tests/voice/test_06_languages.py @@ -8,6 +8,7 @@ from typing import Optional import pytest +from _utils import cer from _utils import get_client from _utils import normalize from _utils import send_audio_file @@ -26,6 +27,7 @@ class AudioSample: language: str path: str transcript: str + use_cer: bool = False vocab: list[str] = field(default_factory=list) @@ -33,12 +35,29 @@ class AudioSample: AudioSample( language="fr", path="./assets/languages/fr_fr_000378.wav", - transcript="la partie extérieure que nous voyons lorsque nous regardons le soleil s’appelle la photosphère ce qui signifie « boule de lumière »", + transcript=( + "la partie extérieure que nous voyons lorsque nous regardons le soleil " + "s’appelle la photosphère ce qui signifie « boule de lumière »" + ), ), AudioSample( language="de", path="./assets/languages/de_de_000675.wav", - transcript="Die Einreise in das südliche Afrika mit dem Auto ist eine erstaunliche Möglichkeit, die ganze Schönheit der Region zu sehen und an Orte abseits der normalen Touristenrouten zu gelangen.", + transcript=( + "Die Einreise in das südliche Afrika mit dem Auto ist eine erstaunliche Möglichkeit, " + "die ganze Schönheit der Region zu sehen und an Orte abseits der normalen Touristenrouten zu gelangen." + ), + ), + AudioSample( + language="he", + path="./assets/languages/he_il_000432.wav", + transcript="טורקיה מוקפת ים משלושה כיוונים: הים האגאי ממערב, הים השחור מצפון והים התיכון מדרום.", + ), + AudioSample( + language="cmn", + path="./assets/languages/cmn_hans_cn_000328.wav", + transcript="博贝克出生于克罗地亚首都萨格勒布,在为贝尔格莱德游击队足球俱乐部效力时成名", + use_cer=True, ), ] @@ -46,10 +65,12 @@ class AudioSample: @pytest.mark.asyncio @pytest.mark.parametrize("sample", SAMPLES, ids=lambda s: f"{s.language}:{s.path}") async def test_transcribe_languages(sample: AudioSample): - """Test transcription. + """Test foreign language transcription. This test will: - - log messages + - use samples from the FLEURS dataset + - use different languages + - compare the normalized transcriptions with the reference transcription """ # API key @@ -125,13 +146,23 @@ def log_message(message): # Check language assert seg0.get("language") == sample.language - # Check contents + # Get normalized versions of the transcription and reference str_original = normalize(sample.transcript) str_transcribed = normalize(seg0.get("text", "UNKNOWN")) - if str_original != str_transcribed: + str_cer = cer(str_original, str_transcribed) + + # Assert the CER + if sample.use_cer: + ok = str_cer < 0.05 # < 5% CER acceptable + else: + ok = str_original == str_transcribed # Exact match required + + # Compare transcriptions + if not ok: print("\n".join(messages)) print(f"Original: [{str_original}]") print(f"Transcribed: [{str_transcribed}]") + print(f"CER: {str_cer}") raise AssertionError("Transcription does not match original") # Close session From e0ca82ee4b8b26fdcbb7b6cd665f88ed22b958a2 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 16:26:07 +0100 Subject: [PATCH 062/215] Add Japanese test (with custom CER value). --- tests/voice/assets/languages/ja_jp_000595.wav | 3 +++ tests/voice/test_06_languages.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tests/voice/assets/languages/ja_jp_000595.wav diff --git a/tests/voice/assets/languages/ja_jp_000595.wav b/tests/voice/assets/languages/ja_jp_000595.wav new file mode 100644 index 00000000..e40a0e53 --- /dev/null +++ b/tests/voice/assets/languages/ja_jp_000595.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:939884e5d7c23ce68bb310ac3df01073554a9da28470ed976519dc0b241a20db +size 351404 diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_06_languages.py index 7c9ef7ce..1e75b1a8 100644 --- a/tests/voice/test_06_languages.py +++ b/tests/voice/test_06_languages.py @@ -28,6 +28,7 @@ class AudioSample: path: str transcript: str use_cer: bool = False + cer_pass: float = 0.05 vocab: list[str] = field(default_factory=list) @@ -56,9 +57,16 @@ class AudioSample: AudioSample( language="cmn", path="./assets/languages/cmn_hans_cn_000328.wav", - transcript="博贝克出生于克罗地亚首都萨格勒布,在为贝尔格莱德游击队足球俱乐部效力时成名", + transcript="博贝克出生于克罗地亚首都萨格勒布,在为贝尔格莱德游击队足球俱乐部效力时成名。", use_cer=True, ), + AudioSample( + language="ja", + path="./assets/languages/ja_jp_000595.wav", + transcript="動物は地球上のいたるところに生息しています。地面を掘ったり、海を泳ぎ回ったり、空を飛んだりしています。", + use_cer=True, + cer_pass=0.07, + ), ] @@ -153,7 +161,7 @@ def log_message(message): # Assert the CER if sample.use_cer: - ok = str_cer < 0.05 # < 5% CER acceptable + ok = str_cer < sample.cer_pass # < 5% CER acceptable (default) else: ok = str_original == str_transcribed # Exact match required From 084634b8fafbfb27606e2f75a919ab1ba5d76bb4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 16:49:56 +0100 Subject: [PATCH 063/215] Update for "previous" and "next" attachment. --- sdk/voice/speechmatics/voice/_models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 087f5988..4220c95d 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -715,12 +715,16 @@ def format_segment_text( else: fragments = segment.fragments - # Assemble the text (LTR) + # Assemble the text + previous_frag: Optional[SpeechFragment] = None for frag in fragments: - if content == "" or frag.attaches_to == "previous": + if not previous_frag: + content = frag.content + elif frag.attaches_to == "previous" or previous_frag.attaches_to == "next": content += frag.content else: content += session.language_pack_info.word_delimiter + frag.content + previous_frag = frag # Return the formatted text return format.format( From 33892a1881dde1d6970a2b3e9734d65188e0b0b6 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 17:23:02 +0100 Subject: [PATCH 064/215] Add Spanish and Thai to tests (for RTL language). --- .../voice/assets/languages/es_419_000896.wav | 3 ++ tests/voice/assets/languages/th_th_000208.wav | 3 ++ tests/voice/test_06_languages.py | 32 +++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/voice/assets/languages/es_419_000896.wav create mode 100644 tests/voice/assets/languages/th_th_000208.wav diff --git a/tests/voice/assets/languages/es_419_000896.wav b/tests/voice/assets/languages/es_419_000896.wav new file mode 100644 index 00000000..9618a4ed --- /dev/null +++ b/tests/voice/assets/languages/es_419_000896.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56eab3b040e2a9de740d0e70ea51ebd37637cb30b20709c9b8e454ef3d326782 +size 460844 diff --git a/tests/voice/assets/languages/th_th_000208.wav b/tests/voice/assets/languages/th_th_000208.wav new file mode 100644 index 00000000..096a0cd7 --- /dev/null +++ b/tests/voice/assets/languages/th_th_000208.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ca5846de9f877921737ad8a6f01f7c39e8df8f9ef67ca22596b39b045d1f1a3 +size 276524 diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_06_languages.py index 1e75b1a8..a41d4bd1 100644 --- a/tests/voice/test_06_languages.py +++ b/tests/voice/test_06_languages.py @@ -49,6 +49,15 @@ class AudioSample: "die ganze Schönheit der Region zu sehen und an Orte abseits der normalen Touristenrouten zu gelangen." ), ), + AudioSample( + language="es", + path="./assets/languages/es_419_000896.wav", + transcript=( + "Es esencial que cuente, cuando menos, con calzado con suelas apropiadas. " + "Los zapatos de verano por lo general resbalan mucho en el hielo y la nieve, " + "incluso hay botas de invierno que no son adecuadas." + ), + ), AudioSample( language="he", path="./assets/languages/he_il_000432.wav", @@ -67,6 +76,13 @@ class AudioSample: use_cer=True, cer_pass=0.07, ), + AudioSample( + language="th", + path="./assets/languages/th_th_000208.wav", + transcript="ข้สภาพอากาศเลวร้ายที่เป็นสาเหตุของการยกเลิกการลงจอดทำให้การค้นหายากลำบาก", + use_cer=True, + cer_pass=0.03, + ), ] @@ -103,6 +119,9 @@ async def test_transcribe_languages(sample: AudioSample): bytes_sent: int = 0 last_message: Optional[dict[str, Any]] = None + # Segments + segments: list[dict[str, Any]] = [] + # Start time start_time = datetime.datetime.now() @@ -120,9 +139,14 @@ def log_message(message): log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) messages.append(log) + # Log a segment + def log_segment(message): + segments.extend(message["segments"]) + # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_segment) # Load the audio file audio_file = sample.path @@ -147,16 +171,18 @@ def log_message(message): assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT # Check the segment - segments = last_message.get("segments", []) - assert len(segments) == 1 + assert len(segments) >= 1 seg0 = segments[0] # Check language assert seg0.get("language") == sample.language + # Concatenate text from segments + transcribed = " ".join([seg["text"] for seg in segments]) + # Get normalized versions of the transcription and reference str_original = normalize(sample.transcript) - str_transcribed = normalize(seg0.get("text", "UNKNOWN")) + str_transcribed = normalize(transcribed) str_cer = cer(str_original, str_transcribed) # Assert the CER From 438e81c84a0df2039777d846c78b210987080393 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 19:22:46 +0100 Subject: [PATCH 065/215] version 0.1.7 --- sdk/voice/speechmatics/voice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 14ea54dc..502f5bb9 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.6" +__version__ = "0.1.7" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat From 94208839176f68feefc629221aea2d9b2d191622 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 24 Sep 2025 23:41:33 +0100 Subject: [PATCH 066/215] Filter unchanged segments and trigger on finals. --- sdk/voice/speechmatics/voice/__init__.py | 2 +- sdk/voice/speechmatics/voice/_client.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 502f5bb9..76dab33d 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.7" +__version__ = "0.1.8" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 4b549035..751d8f99 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -648,10 +648,8 @@ async def _process_speech_fragments(self) -> None: # Update the previous view self._previous_view = self._current_view - # Catch no changes for ADAPTIVE mode - if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE and not changes.any( - AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL - ): + # Catch no changes + if not changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL): return # Emit the segments @@ -664,6 +662,12 @@ async def _process_speech_fragments(self) -> None: # Calculate delay to end of utterance should_emit, emit_final_delay = self._calc_delay_to_end_of_utterance(self._current_view, changes) + # Debug + if DEBUG_MORE: + self._logger.debug( + f"changes: {changes}, should_emit: {should_emit}, emit_final_delay: {emit_final_delay}" + ) + # Emit finalized segments async def emit(delay: float | None) -> None: if delay and delay > 0: @@ -829,9 +833,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Split between finals and interim segments else: final_segments = [ - s - for s in self._current_view.segments - if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + s for s in self._current_view.segments if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL) ] interim_segments = [s for s in self._current_view.segments if s not in final_segments] @@ -884,7 +886,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. - This will emit `SPEECH_STARTED` and `SPEECH_ENDED` events to the client and is + This will emit `SPEAKER_STARTED` and `SPEAKER_ENDED` events to the client and is based on valid transcription for active speakers. Ignored or speakers not in focus will not be considered an active participant. From 74114692637c7abe945d128e55e74a3f50d8a3e8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 13:10:35 +0100 Subject: [PATCH 067/215] Updated tests. --- sdk/voice/speechmatics/voice/__init__.py | 2 +- tests/voice/test_03_conversation.py | 1 + tests/voice/test_05_thought.py | 3 ++- tests/voice/test_06_languages.py | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 76dab33d..7db74869 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.8" +__version__ = "0.1.9" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 9f4c3e63..af791f62 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -14,6 +14,7 @@ api_key = os.getenv("SPEECHMATICS_API_KEY") +@pytest.mark.skip(reason="Only use manually!") @pytest.mark.asyncio async def test_log_messages(): """Test transcription. diff --git a/tests/voice/test_05_thought.py b/tests/voice/test_05_thought.py index e83cf783..15cdcf7e 100644 --- a/tests/voice/test_05_thought.py +++ b/tests/voice/test_05_thought.py @@ -13,6 +13,7 @@ from speechmatics.voice import VoiceAgentConfig +@pytest.mark.skip(reason="Not fully implemented!") @pytest.mark.asyncio async def test_end_of_thought(): """Use inference for end of thought. @@ -96,7 +97,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert segment is not None # Debug - print(segment["text"]) + # print(segment["text"]) # Stop the queue client._stop_stt_queue() diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_06_languages.py index a41d4bd1..8d0e0151 100644 --- a/tests/voice/test_06_languages.py +++ b/tests/voice/test_06_languages.py @@ -160,11 +160,9 @@ def log_segment(message): # Individual payloads await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) - # Send finalize after a short delay - await asyncio.sleep(2.0) + # Send finalize + await asyncio.sleep(1.5) client.finalize() - - # Wait for 5 seconds await asyncio.sleep(1.5) # Extract the last message From 0f6ee675fd555bed151b8afed047e8f27eceacba Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 21:19:19 +0100 Subject: [PATCH 068/215] Fix version for build system. --- .pre-commit-config.yaml | 1 + sdk/voice/speechmatics/voice/__init__.py | 2 +- sdk/voice/speechmatics/voice/_client.py | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e98d5415..b5277c6a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,6 +50,7 @@ repos: - id: bandit files: ^sdk/.*/speechmatics/ args: [-r, -f, json, -ll] + additional_dependencies: ["pbr"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 7db74869..c3627de8 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -11,7 +11,7 @@ transcriptions with flags to indicate changes between messages, etc. """ -__version__ = "0.1.9" +__version__ = "0.0.0" from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 751d8f99..a4275104 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -231,7 +231,7 @@ def _register_event_handlers(self) -> None: """ # Recognition started event - @self.once(ServerMessageType.RECOGNITION_STARTED) + @self.once(ServerMessageType.RECOGNITION_STARTED) # type: ignore[misc] def _evt_on_recognition_started(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) @@ -244,7 +244,7 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: ) # Partial transcript event - @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) + @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) # type: ignore[misc] def _evt_on_partial_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) @@ -255,7 +255,7 @@ async def _handle() -> None: self._stt_message_queue.put_nowait(_handle) # Final transcript event - @self.on(ServerMessageType.ADD_TRANSCRIPT) + @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] def _evt_on_final_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) @@ -266,7 +266,7 @@ async def _handle() -> None: self._stt_message_queue.put_nowait(_handle) # End of utterance event - @self.on(ServerMessageType.END_OF_UTTERANCE) + @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) From 93ac69bc7108dbad3e7b22b8fe79f87fb56fcfe8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 21:28:40 +0100 Subject: [PATCH 069/215] Updated bug issues. --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +++++++++----- .github/ISSUE_TEMPLATE/question.md | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4482bf27..d81d76f4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,18 +1,21 @@ --- name: Bug report about: Create a report to help us improve -title: '[BUG] ' -labels: 'bug' -assignees: '' +title: "[BUG] " +labels: "bug" +assignees: "" --- **Which SDK are you using?** + - [ ] speechmatics-rt (Real-Time SDK) - [ ] speechmatics-batch (Batch SDK) -- [ ] Both +- [ ] speechmatics-voice (Voice SDK) +- [ ] All **Package Information** -- **Package Name**: (e.g., speechmatics-rt, speechmatics-batch) + +- **Package Name**: (e.g., speechmatics-rt, speechmatics-batch, speechmatics-voice) - **Package Version**: (e.g., 1.0.0) - **Python Version**: (e.g., 3.9, 3.10, 3.11, 3.12, 3.13) - **Operating System**: (e.g., Windows 10, macOS 12, Ubuntu 20.04) @@ -22,6 +25,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. 2. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index b3e00c56..99c47f34 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,21 +1,24 @@ --- name: Question about: Ask a question or request support -title: '[QUESTION] ' -labels: 'question' -assignees: '' +title: "[QUESTION] " +labels: "question" +assignees: "" --- **Which SDK is your question about?** + - [ ] speechmatics-rt (Real-Time SDK) - [ ] speechmatics-batch (Batch SDK) -- [ ] Both SDKs +- [ ] speechmatics-voice (Voice SDK) +- [ ] All - [ ] General usage question - [ ] Setup/Installation - [ ] Not sure **Package Information** (if applicable) -- **Package Name**: (e.g., speechmatics-rt, speechmatics-batch) + +- **Package Name**: (e.g., speechmatics-rt, speechmatics-batch, speechmatics-voice) - **Package Version**: (e.g., 1.0.0) - **Python Version**: (e.g., 3.9, 3.10, 3.11, 3.12, 3.13) - **Operating System**: (e.g., Windows 10, macOS 12, Ubuntu 20.04) @@ -35,4 +38,5 @@ What result or behavior are you hoping to achieve? **Related issues** Link any related issues or discussions: + - Related to # From b44a2d88d8f1edcb2d0fbe256fbca87ab9303e14 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 21:42:24 +0100 Subject: [PATCH 070/215] Updated example.wav reference --- examples/batch/transcribe_file_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/batch/transcribe_file_async.py b/examples/batch/transcribe_file_async.py index 0d601e20..7217e7f0 100644 --- a/examples/batch/transcribe_file_async.py +++ b/examples/batch/transcribe_file_async.py @@ -11,7 +11,7 @@ from speechmatics.batch import Transcript from speechmatics.batch import TranscriptionConfig -audio_file = os.getenv("AUDIO_FILE_PATH", os.path.join(os.path.dirname(__file__), "../example.wav")) +audio_file = os.getenv("AUDIO_FILE_PATH", os.path.join(os.path.dirname(__file__), "../example1.wav")) async def main() -> None: From 7438dca5eb6c4b34e6078b5a5dd3656a70a923ee Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 23:09:58 +0100 Subject: [PATCH 071/215] Migrate to Pydantic models for future WebSocket interface. --- examples/voice/file_to_jsonl.py | 9 +- sdk/voice/pyproject.toml | 2 +- sdk/voice/speechmatics/voice/_client.py | 14 +- sdk/voice/speechmatics/voice/_helpers.py | 40 -- sdk/voice/speechmatics/voice/_models.py | 150 ++--- tests/voice/test_03_conversation.py | 9 +- tests/voice/test_04_models.py | 528 ++++++------------ tests/voice/test_05_utterance.py | 398 +++++++++++++ ...{test_05_thought.py => test_06_thought.py} | 0 ...t_06_languages.py => test_07_languages.py} | 3 +- 10 files changed, 640 insertions(+), 513 deletions(-) delete mode 100644 sdk/voice/speechmatics/voice/_helpers.py create mode 100644 tests/voice/test_05_utterance.py rename tests/voice/{test_05_thought.py => test_06_thought.py} (100%) rename tests/voice/{test_06_languages.py => test_07_languages.py} (98%) diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py index ccf3a831..a1d5ede3 100644 --- a/examples/voice/file_to_jsonl.py +++ b/examples/voice/file_to_jsonl.py @@ -17,7 +17,6 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._helpers import to_serializable async def main() -> None: @@ -73,7 +72,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message.model_dump()}) messages.append(log) # Output to file or stdout @@ -85,9 +84,9 @@ def log_message(message): # Log script info log_message({"message": "AudioFile", "path": args.input_file}) - log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) - log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) - log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index 629e27ee..9d000517 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" -dependencies = ["speechmatics-rt"] +dependencies = ["speechmatics-rt", "pydantic"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a4275104..34e8dae7 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -94,7 +94,7 @@ def __init__( config=self._config, session_id="NOT_SET", base_time=datetime.datetime.now(datetime.timezone.utc), - language_pack_info=LanguagePackInfo.from_dict({}), + language_pack_info=LanguagePackInfo.model_validate({}), ) # STT message received queue @@ -240,7 +240,7 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: config=self._config, session_id=message.get("id", "UNKNOWN"), base_time=datetime.datetime.now(datetime.timezone.utc), - language_pack_info=LanguagePackInfo.from_dict(message.get("language_pack_info", {})), + language_pack_info=LanguagePackInfo.model_validate(message.get("language_pack_info", {})), ) # Partial transcript event @@ -843,7 +843,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.ADD_SEGMENT, { "message": AgentServerMessageType.ADD_SEGMENT.value, - "segments": [s.to_dict(self._config.include_results) for s in final_segments], + "segments": [s.model_dump(self._config.include_results) for s in final_segments], "metadata": metadata, }, ) @@ -858,7 +858,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.ADD_PARTIAL_SEGMENT, { "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, - "segments": [s.to_dict(self._config.include_results) for s in interim_segments], + "segments": [s.model_dump(self._config.include_results) for s in interim_segments], "metadata": metadata, }, ) @@ -931,14 +931,14 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: AgentServerMessageType.SPEAKER_ENDED, { "message": AgentServerMessageType.SPEAKER_ENDED.value, - "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False).to_dict(), + "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False).model_dump(), }, ) self.emit( AgentServerMessageType.SPEAKER_STARTED, { "message": AgentServerMessageType.SPEAKER_STARTED.value, - "status": SpeakerVADStatus(speaker_id=speaker, is_active=True).to_dict(), + "status": SpeakerVADStatus(speaker_id=speaker, is_active=True).model_dump(), }, ) @@ -960,7 +960,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: msg, { "message": msg.value, - "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking).to_dict(), + "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking).model_dump(), }, ) diff --git a/sdk/voice/speechmatics/voice/_helpers.py b/sdk/voice/speechmatics/voice/_helpers.py deleted file mode 100644 index 305eb27e..00000000 --- a/sdk/voice/speechmatics/voice/_helpers.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (c) 2025, Speechmatics / Cantab Research Ltd -# - -from __future__ import annotations - -from dataclasses import asdict -from dataclasses import is_dataclass -from typing import Any -from typing import cast - - -def to_serializable(obj: Any): - """Convert an object into a JSON-serializable form. - - Args: - obj: The object to convert. - - Returns: - The JSON-serializable form of the object. - """ - - # Dataclasses → dict - if is_dataclass(obj) and not isinstance(obj, type): - return {k: to_serializable(v) for k, v in asdict(cast(Any, obj)).items()} - - # dicts → dict with serializable values - if isinstance(obj, dict): - return {k: to_serializable(v) for k, v in obj.items()} - - # lists/tuples → list with serializable items - if isinstance(obj, (list, tuple)): - return [to_serializable(v) for v in obj] - - # basic types → leave as is - if isinstance(obj, (str, int, float, bool)) or obj is None: - return obj - - # fallback → string - return str(obj) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 4220c95d..b7d25a03 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -5,12 +5,13 @@ from __future__ import annotations import datetime -from dataclasses import dataclass -from dataclasses import field from enum import Enum from typing import Any from typing import Optional +from pydantic import BaseModel +from pydantic import Field + from speechmatics.rt import AudioEncoding from speechmatics.rt import OperatingPoint @@ -30,8 +31,7 @@ class DiarizationFocusMode(str, Enum): IGNORE = "ignore" -@dataclass -class AdditionalVocabEntry: +class AdditionalVocabEntry(BaseModel): """Additional vocabulary entry. Parameters: @@ -40,11 +40,10 @@ class AdditionalVocabEntry: """ content: str - sounds_like: list[str] = field(default_factory=list) + sounds_like: list[str] = Field(default_factory=list) -@dataclass -class DiarizationKnownSpeaker: +class DiarizationKnownSpeaker(BaseModel): """Known speakers for speaker diarization. Parameters: @@ -56,8 +55,7 @@ class DiarizationKnownSpeaker: speaker_identifiers: list[str] -@dataclass -class DiarizationSpeakerConfig: +class DiarizationSpeakerConfig(BaseModel): """Speaker Diarization Config. List of speakers to focus on, ignore and how to deal with speakers that are not @@ -86,13 +84,12 @@ class DiarizationSpeakerConfig: Defaults to `DiarizationFocusMode.RETAIN`. """ - focus_speakers: list[str] = field(default_factory=list) - ignore_speakers: list[str] = field(default_factory=list) + focus_speakers: list[str] = Field(default_factory=list) + ignore_speakers: list[str] = Field(default_factory=list) focus_mode: DiarizationFocusMode = DiarizationFocusMode.RETAIN -@dataclass -class VoiceAgentConfig: +class VoiceAgentConfig(BaseModel): """Voice Agent configuration. A framework-independent configuration object for the Speechmatics Voice Agent. This uses @@ -192,7 +189,7 @@ class VoiceAgentConfig: end_of_utterance_silence_trigger: float = 0.2 end_of_utterance_max_delay: float = 10.0 end_of_utterance_mode: EndOfUtteranceMode = EndOfUtteranceMode.FIXED - additional_vocab: list[AdditionalVocabEntry] = field(default_factory=list) + additional_vocab: list[AdditionalVocabEntry] = Field(default_factory=list) punctuation_overrides: Optional[dict] = None # Diarization @@ -200,8 +197,8 @@ class VoiceAgentConfig: speaker_sensitivity: float = 0.5 max_speakers: Optional[int] = None prefer_current_speaker: bool = False - speaker_config: DiarizationSpeakerConfig = field(default_factory=DiarizationSpeakerConfig) - known_speakers: list[DiarizationKnownSpeaker] = field(default_factory=list) + speaker_config: DiarizationSpeakerConfig = Field(default_factory=DiarizationSpeakerConfig) + known_speakers: list[DiarizationKnownSpeaker] = Field(default_factory=list) # Advanced features include_results: bool = False @@ -359,8 +356,7 @@ class AnnotationFlags(str, Enum): END_OF_UTTERANCE = "end_of_utterance" -@dataclass -class LanguagePackInfo: +class LanguagePackInfo(BaseModel): """Information about the language pack used in a session. Attributes: @@ -371,35 +367,14 @@ class LanguagePackInfo: writing_direction (str): The writing direction ('ltr' or 'rtl'). """ - adapted: bool - itn: bool - language_description: str - word_delimiter: str - writing_direction: str - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> LanguagePackInfo: - """Create LanguagePackInfo from dictionary.""" - return cls( - adapted=data.get("adapted", False), - itn=data.get("itn", True), - language_description=data.get("language_description", "English"), - word_delimiter=data.get("word_delimiter", " "), - writing_direction="rtl" if data.get("writing_direction", "left-to-right") == "right-to-left" else "ltr", - ) - - def to_dict(self) -> dict[str, Any]: - return { - "adapted": self.adapted, - "itn": self.itn, - "language_description": self.language_description, - "word_delimiter": self.word_delimiter, - "writing_direction": self.writing_direction, - } + adapted: bool = False + itn: bool = True + language_description: str = "English" + word_delimiter: str = " " + writing_direction: str = "ltr" -@dataclass -class SessionInfo: +class SessionInfo(BaseModel): """Information about the session. Attributes: @@ -414,14 +389,6 @@ class SessionInfo: base_time: datetime.datetime language_pack_info: LanguagePackInfo - def to_dict(self) -> dict[str, Any]: - return { - "config": self.config, - "session_id": self.session_id, - "base_time": self.base_time, - "language_pack_info": self.language_pack_info.to_dict(), - } - class AnnotationResult(list): """Processing result.""" @@ -459,13 +426,8 @@ def __eq__(self, other: object) -> bool: return set(self) == set(other) return False - # def __repr__(self) -> str: - # """Return a representation of the object.""" - # return repr([f.value for f in self]) - -@dataclass -class SpeechFragment: +class SpeechFragment(BaseModel): """Fragment of a speech event. As the transcript is processed (partials and finals), a list of SpeechFragments @@ -507,9 +469,10 @@ class SpeechFragment: result: Optional[Any] = None annotation: Optional[AnnotationResult] = None + model_config = {"arbitrary_types_allowed": True} + -@dataclass -class SpeakerSegment: +class SpeakerSegment(BaseModel): """SpeechFragment items grouped by speaker_id and whether the speaker is active. Parameters: @@ -526,9 +489,11 @@ class SpeakerSegment: is_active: bool = False timestamp: Optional[str] = None language: Optional[str] = None - fragments: list[SpeechFragment] = field(default_factory=list) + fragments: list[SpeechFragment] = Field(default_factory=list) text: str | None = None - annotation: AnnotationResult = field(default_factory=AnnotationResult) + annotation: AnnotationResult = Field(default_factory=AnnotationResult) + + model_config = {"arbitrary_types_allowed": True} @property def start_time(self) -> float: @@ -540,26 +505,22 @@ def end_time(self) -> float: """Return the end time of the segment.""" return self.fragments[-1].end_time if self.fragments else 0.0 - def to_dict(self, include_results: bool = False) -> dict[str, Any]: - """Return a dictionary representation of the object.""" - return { - "speaker_id": self.speaker_id, - "is_active": self.is_active, - "timestamp": self.timestamp, - "language": self.language, - "text": self.text, - "annotation": self.annotation, - **({"results": [f.result for f in self.fragments]} if include_results else {}), - } - - def __str__(self) -> str: - """Return a string representation of the object.""" - meta = self.to_dict(include_results=True) - return f"SpeakerSegment({', '.join(f'{k}={v}' for k, v in meta.items())})" - - -@dataclass -class SpeakerSegmentView: + def model_dump(self, include_results: bool = False, **kwargs: Any) -> dict[str, Any]: + """Override model_dump to control fragments/results inclusion.""" + + # Always exclude fragments from the base dump + kwargs["exclude"] = {"fragments"} + data: dict[str, Any] = super().model_dump(**kwargs) + + # Add results if requested + if include_results: + data["results"] = [f.result for f in self.fragments] + + # Return the dump + return data + + +class SpeakerSegmentView(BaseModel): """View for speaker fragments. Parameters: @@ -570,7 +531,8 @@ class SpeakerSegmentView: session: SessionInfo fragments: list[SpeechFragment] - segments: list[SpeakerSegment] + segments: list[SpeakerSegment] = Field(default_factory=list) + focus_speakers: Optional[list[str]] = None def __init__( self, @@ -578,19 +540,18 @@ def __init__( fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate_segments: bool = True, - ): - self.session = session - self.fragments = fragments - self.focus_speakers = focus_speakers - + **data: Any, + ) -> None: # Process fragments into a list of segments - self.segments = FragmentUtils.segment_list_from_fragments( + segments = FragmentUtils.segment_list_from_fragments( session=session, fragments=fragments, focus_speakers=focus_speakers, annotate_segments=annotate_segments, ) + super().__init__(session=session, fragments=fragments, segments=segments, focus_speakers=focus_speakers, **data) + @property def start_time(self) -> float: return self.fragments[0].start_time if self.fragments else 0.0 @@ -666,8 +627,7 @@ def trim(self, start_time: float, end_time: float, annotate_segments: bool = Tru ) -@dataclass -class SpeakerVADStatus: +class SpeakerVADStatus(BaseModel): """Emitted when a speaker starts or ends speaking. The speaker id is taken from the last word in the segment when @@ -681,12 +641,6 @@ class SpeakerVADStatus: is_active: bool speaker_id: Optional[str] = None - def to_dict(self) -> dict[str, Any]: - return { - "is_active": self.is_active, - "speaker_id": self.speaker_id, - } - class FragmentUtils: """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index af791f62..a34095dc 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -9,7 +9,6 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._helpers import to_serializable api_key = os.getenv("SPEECHMATICS_API_KEY") @@ -56,7 +55,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) messages.append(log) print(log) @@ -82,9 +81,9 @@ def log_message(message): print() print("---") log_message({"message": "AudioFile", "path": audio_file}) - log_message({"message": "VoiceAgentConfig", **to_serializable(client._config)}) - log_message({"message": "TranscriptionConfig", **to_serializable(client._transcription_config)}) - log_message({"message": "AudioFormat", **to_serializable(client._audio_format)}) + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) # Connect await client.connect() diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index dad9a9fa..228a2c6e 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -1,19 +1,52 @@ -import asyncio -import datetime import json -import os -from typing import Any -from typing import Optional import pytest -from _utils import ConversationLog -from _utils import get_client -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import AdditionalVocabEntry from speechmatics.voice._models import AnnotationFlags from speechmatics.voice._models import AnnotationResult +from speechmatics.voice._models import DiarizationFocusMode +from speechmatics.voice._models import DiarizationKnownSpeaker +from speechmatics.voice._models import DiarizationSpeakerConfig +from speechmatics.voice._models import SpeakerSegment +from speechmatics.voice._models import SpeechFragment + + +@pytest.mark.asyncio +async def test_voice_agent_config(): + """Test VoiceAgentConfig Pydantic serialisation and deserialisation.""" + # Create instance with custom values + config = VoiceAgentConfig( + language="en", + max_delay=1.5, + enable_diarization=True, + speaker_sensitivity=0.7, + additional_vocab=[AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"])], + known_speakers=[DiarizationKnownSpeaker(label="John", speaker_identifiers=["78673523465237xx"])], + ) + + # Test JSON serialisation + json_data = config.model_dump() + assert json_data["language"] == "en" + assert json_data["max_delay"] == 1.5 + assert json_data["enable_diarization"] is True + assert json_data["speaker_sensitivity"] == 0.7 + assert len(json_data["additional_vocab"]) == 1 + assert json_data["additional_vocab"][0]["content"] == "Speechmatics" + assert len(json_data["known_speakers"]) == 1 + assert json_data["known_speakers"][0]["label"] == "John" + + # Test JSON deserialisation + config_from_json = VoiceAgentConfig.model_validate(json_data) + assert config_from_json.language == config.language + assert config_from_json.max_delay == config.max_delay + assert config_from_json.enable_diarization == config.enable_diarization + assert config_from_json.speaker_sensitivity == config.speaker_sensitivity + assert len(config_from_json.additional_vocab) == 1 + assert config_from_json.additional_vocab[0].content == "Speechmatics" + assert len(config_from_json.known_speakers) == 1 + assert config_from_json.known_speakers[0].label == "John" @pytest.mark.asyncio @@ -68,385 +101,170 @@ async def test_annotation_result(): @pytest.mark.asyncio -async def test_speech_fragments(): - """Test SpeechFragment. +async def test_additional_vocab_entry(): + """Test AdditionalVocabEntry serialisation and deserialisation. - - create fragment(s) - - check output from processing conversation + - create instance - serialize to JSON + - deserialize from JSON """ - # Test conversation - log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] - ) - - # Start time - start_time = datetime.datetime.now() - - # Create a client - client = await get_client(api_key="NONE", connect=False) - assert client is not None - - # Start the queue - client._start_stt_queue() - - # Event to wait - event_rx: asyncio.Event = asyncio.Event() - last_message: Optional[dict[str, Any]] = None - - # Reset message - def message_reset(): - nonlocal last_message - last_message = None - event_rx.clear() - - # Message receiver - def message_rx(message: dict[str, Any]): - nonlocal last_message - last_message = message - event_rx.set() - - # Send a message from the conversation - async def send_message(idx: int, count: int = 1, use_ttl: bool = True): - for i in range(count): - # Get the message from the chat - message = chat[idx + i] - - # Wait for TTL to expire - if use_ttl: - ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() - if ttl.total_seconds() > 0: - await asyncio.sleep(ttl.total_seconds()) - else: - await asyncio.sleep(0.05) - - # Emit the message - client.emit(message["payload"]["message"], message["payload"]) - - # Add listener for first interim segment - message_reset() - client.once(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) - - # Inject first partial - await send_message(0, count=6, use_ttl=False) - - # Wait for first segment - try: - await asyncio.wait_for(event_rx.wait(), timeout=5.0) - assert last_message is not None - except asyncio.TimeoutError: - pytest.fail("ADD_PARTIAL_SEGMENT event was not received within 5 seconds") - - # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT - - # Check the segment - segments = last_message.get("segments", []) - assert len(segments) == 1 - seg0 = segments[0] - assert seg0["speaker_id"] == "S1" - assert seg0["text"] == "Welcome" - assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome" - - # Add listener for final segment - message_reset() - client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) - - # Send a more partials and finals - await send_message(5, count=8, use_ttl=False) - - # Wait for final segment - try: - await asyncio.wait_for(event_rx.wait(), timeout=5.0) - assert last_message is not None - except asyncio.TimeoutError: - pytest.fail("ADD_SEGMENT event was not received within 5 seconds") - - # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT - - # Check the segment - segments = last_message.get("segments", []) - assert len(segments) == 1 - seg0 = segments[0] - assert seg0["speaker_id"] == "S1" - assert seg0["text"] == "Welcome to Speechmatics." - assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics." - - # Stop the queue - client._stop_stt_queue() - - -@pytest.mark.asyncio -async def test_end_of_utterance_fixed(): - """Test EndOfUtterance from STT engine. - - - send converstaion messages (fast) - - wait for `EndOfUtterance` message - """ - - # Test conversation - log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] - ) - - # Start time - start_time = datetime.datetime.now() + # Create instance + entry = AdditionalVocabEntry(content="hello", sounds_like=["helo", "hallo"]) - # Create a client - client = await get_client(api_key="NONE", connect=False) - assert client is not None + # Test JSON serialisation + json_data = entry.model_dump() + assert json_data["content"] == "hello" + assert json_data["sounds_like"] == ["helo", "hallo"] - # Start the queue - client._start_stt_queue() + # Test JSON deserialisation + entry_from_json = AdditionalVocabEntry.model_validate(json_data) + assert entry_from_json.content == entry.content + assert entry_from_json.sounds_like == entry.sounds_like - # Event to wait - event_rx: asyncio.Event = asyncio.Event() - last_message: Optional[dict[str, Any]] = None + # Test with defaults + entry_minimal = AdditionalVocabEntry(content="test") + json_minimal = entry_minimal.model_dump() + assert json_minimal["sounds_like"] == [] - # Message receiver - def message_rx(message: dict[str, Any]): - nonlocal last_message - last_message = message - event_rx.set() - # Send a message from the conversation - async def send_message(idx: int, count: int = 1, use_ttl: bool = True): - for i in range(count): - # Get the message from the chat - message = chat[idx + i] - - # Wait for TTL to expire - if use_ttl: - ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() - if ttl.total_seconds() > 0: - await asyncio.sleep(ttl.total_seconds()) - else: - await asyncio.sleep(0.005) - - # Emit the message - client.emit(message["payload"]["message"], message["payload"]) +@pytest.mark.asyncio +async def test_diarization_known_speaker(): + """Test DiarizationKnownSpeaker serialisation and deserialisation. - # Add listener for first interim segment - client.once(AgentServerMessageType.END_OF_TURN, message_rx) + - create instance + - serialize to JSON + - deserialize from JSON + """ - # Inject conversation - await send_message(0, count=14, use_ttl=False) + # Create instance + speaker = DiarizationKnownSpeaker(label="Speaker1", speaker_identifiers=["id1", "id2", "id3"]) - # Wait for EndOfTurn - try: - await asyncio.wait_for(event_rx.wait(), timeout=5.0) - assert last_message is not None - except asyncio.TimeoutError: - pytest.fail("END_OF_TURN event was not received within 5 seconds") + # Test JSON serialisation + json_data = speaker.model_dump() + assert json_data["label"] == "Speaker1" + assert json_data["speaker_identifiers"] == ["id1", "id2", "id3"] - # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.END_OF_TURN + # Test JSON deserialisation + speaker_from_json = DiarizationKnownSpeaker.model_validate(json_data) + assert speaker_from_json.label == speaker.label + assert speaker_from_json.speaker_identifiers == speaker.speaker_identifiers @pytest.mark.asyncio -async def test_external_vad(): - """Test EndOfUtterance from STT engine. +async def test_diarization_speaker_config(): + """Test DiarizationSpeakerConfig serialisation and deserialisation. - - send converstaion messages (realtime) - - finalizes based on external VAD (e.g. Pipecat's `UserStoppedSpeakingFrame` frame) + - create instance with custom values + - serialize to JSON + - deserialize from JSON """ - # Test conversation - log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] - ) - - # Start time - start_time = datetime.datetime.now() - - # Adaptive timeout - adaptive_timeout = 1.0 - - # Create a client - client = await get_client( - api_key="NONE", - connect=False, - config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL - ), + # Create instance with custom values + config = DiarizationSpeakerConfig( + focus_speakers=["S1", "S2"], + ignore_speakers=["__ASSISTANT__", "__SYSTEM__"], + focus_mode=DiarizationFocusMode.IGNORE, ) - assert client is not None - - # Start the queue - client._start_stt_queue() - - # Event to wait - event_rx: asyncio.Event = asyncio.Event() - last_message: Optional[dict[str, Any]] = None - - # Message receiver - def message_rx(message: dict[str, Any]): - nonlocal last_message - last_message = message - event_rx.set() - - # Send a message from the conversation - async def send_message(idx: int, count: int = 1, use_ttl: bool = True): - for i in range(count): - # Get the message from the chat - message = chat[idx + i] - - # Wait for TTL to expire - if use_ttl: - ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() - if ttl.total_seconds() > 0: - await asyncio.sleep(ttl.total_seconds()) - else: - await asyncio.sleep(0.005) - # Emit the message - client.emit(message["payload"]["message"], message["payload"]) + # Test JSON serialisation + json_data = config.model_dump() + assert json_data["focus_speakers"] == ["S1", "S2"] + assert json_data["ignore_speakers"] == ["__ASSISTANT__", "__SYSTEM__"] + assert json_data["focus_mode"] == DiarizationFocusMode.IGNORE - # Inject conversation - await send_message(0, count=12, use_ttl=False) + # Test JSON deserialisation + config_from_json = DiarizationSpeakerConfig.model_validate(json_data) + assert config_from_json.focus_speakers == config.focus_speakers + assert config_from_json.ignore_speakers == config.ignore_speakers + assert config_from_json.focus_mode == config.focus_mode - # Momentary pause - await asyncio.sleep(0.5) + # Test with defaults + config_default = DiarizationSpeakerConfig() + json_default = config_default.model_dump() + assert json_default["focus_speakers"] == [] + assert json_default["ignore_speakers"] == [] + assert json_default["focus_mode"] == DiarizationFocusMode.RETAIN - # Add listener for first interim segment - client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) - # Pause for a moment - await asyncio.sleep(0.5) - - # Send finalize - client.finalize() - - # Wait for AddSegments - try: - await asyncio.wait_for(event_rx.wait(), timeout=4) - assert last_message is not None - except asyncio.TimeoutError: - pytest.fail("ADD_SEGMENT event was not received within 4 seconds") +@pytest.mark.asyncio +async def test_speech_fragment(): + """Test SpeechFragment serialisation and deserialisation. - # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT + - create instance with annotation + - serialize to JSON + - deserialize from JSON + """ - # Check the segment - segments = last_message.get("segments", []) - assert len(segments) == 1 - seg0 = segments[0] - assert seg0["speaker_id"] == "S1" - assert seg0["text"] == "Welcome to Speechmatics" - assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics" + # Create instance with annotation + annotation = AnnotationResult.from_flags(AnnotationFlags.HAS_FINAL, AnnotationFlags.ENDS_WITH_EOS) + + # Create fragment + fragment = SpeechFragment( + idx=1, + start_time=0.5, + end_time=1.2, + language="en", + content="Hello", + speaker="S1", + is_final=True, + confidence=0.95, + annotation=annotation, + ) - # Stop the queue - client._stop_stt_queue() + # Test JSON serialisation + json_data = fragment.model_dump() + assert json_data["idx"] == 1 + assert json_data["start_time"] == 0.5 + assert json_data["end_time"] == 1.2 + assert json_data["content"] == "Hello" + assert json_data["speaker"] == "S1" + assert json_data["is_final"] is True + assert json_data["confidence"] == 0.95 + assert isinstance(json_data["annotation"], list) @pytest.mark.asyncio -async def test_end_of_utterance_adaptive_vad(): - """Test EndOfUtterance from STT engine. +async def test_speaker_segment(): + """Test SpeakerSegment serialisation and deserialisation. - - send converstaion messages (realtime) - - wait for `EndOfUtterance` message from SDK (adaptive) - - check the interval to receive the EndOfUtterance message is within 25% of expected + - create instance with annotation + - serialize to JSON + - deserialize from JSON """ - # Test conversation - log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + # Create fragments + fragment1 = SpeechFragment(idx=1, start_time=0.5, end_time=1.0, content="Hello", speaker="S1") + fragment2 = SpeechFragment(idx=2, start_time=1.0, end_time=1.5, content="world", speaker="S1") + + # Create annotation + annotation = AnnotationResult.from_flags(AnnotationFlags.HAS_FINAL, AnnotationFlags.MULTIPLE_SPEAKERS) + + # Create instance + segment = SpeakerSegment( + speaker_id="S1", + is_active=True, + timestamp="2025-01-01T12:00:00.500", + language="en", + fragments=[fragment1, fragment2], + text="Hello world", + annotation=annotation, ) - # Start time - start_time = datetime.datetime.now() - - # Adaptive timeout - adaptive_timeout = 0.5 - - # Create a client - client = await get_client( - api_key="NONE", - connect=False, - config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE - ), - ) - assert client is not None - - # Start the queue - client._start_stt_queue() - - # Event to wait - eot_received: asyncio.Event = asyncio.Event() - last_message: Optional[dict[str, Any]] = None - - # Time for last final (used to calculate the interval) - last_final_time: Optional[float] = None - receive_interval: Optional[float] = None - - # Transcript receiver - def transcript_rx(message: dict[str, Any]): - if not eot_received.is_set(): - nonlocal last_final_time - last_final_time = datetime.datetime.now() - - # End of turn receiver - def eot_rx(message: dict[str, Any]): - nonlocal last_message - nonlocal receive_interval - last_message = message - receive_interval = (datetime.datetime.now() - last_final_time).total_seconds() - eot_received.set() - - # Send a message from the conversation - async def send_message(idx: int, count: int = 1, use_ttl: bool = True): - for i in range(count): - # Get the message from the chat - message = chat[idx + i] - - # Wait for TTL to expire - if use_ttl: - ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() - if ttl.total_seconds() > 0: - await asyncio.sleep(ttl.total_seconds()) - else: - await asyncio.sleep(0.005) - - # Emit the message - client.emit(message["payload"]["message"], message["payload"]) - - # Add listener for partials (as these will trigger the adaptive timer)) - client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, transcript_rx) - - # Add listener for end of turn - client.once(AgentServerMessageType.END_OF_TURN, eot_rx) - - # Inject conversation up to the penultimate final from the STT - await send_message(0, count=12, use_ttl=True) - - # Check we have had a final - assert last_final_time is not None - - # Timing info - timeout = adaptive_timeout * 1.5 - - # Wait for EndOfUtterance - try: - await asyncio.wait_for(eot_received.wait(), timeout=timeout) - assert last_message is not None - except asyncio.TimeoutError: - pytest.fail(f"END_OF_TURN event was not received within {timeout} seconds") - - # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.END_OF_TURN - - # Check the interval was within +/- 25% of the adaptive trigger of 0.5 the timeout (see client code) - expected_min_interval = adaptive_timeout * 0.5 * 0.75 - expected_max_interval = adaptive_timeout * 0.5 * 1.25 - assert receive_interval >= expected_min_interval - assert receive_interval <= expected_max_interval - - # Stop the queue - client._stop_stt_queue() + # Test model_dump() default behavior (should exclude fragments by default) + json_data = segment.model_dump() + assert json_data["speaker_id"] == "S1" + assert json_data["is_active"] is True + assert json_data["timestamp"] == "2025-01-01T12:00:00.500" + assert json_data["text"] == "Hello world" + assert "fragments" not in json_data + assert "results" not in json_data + assert isinstance(json_data["annotation"], list) + + # Test model_dump with include_results=True + dict_data_results = segment.model_dump(include_results=True) + assert dict_data_results["speaker_id"] == "S1" + assert dict_data_results["text"] == "Hello world" + assert "results" in dict_data_results + assert "fragments" not in dict_data_results + assert len(dict_data_results["results"]) == 2 diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py new file mode 100644 index 00000000..bc602d5a --- /dev/null +++ b/tests/voice/test_05_utterance.py @@ -0,0 +1,398 @@ +import asyncio +import datetime +import os +from typing import Any +from typing import Optional + +import pytest +from _utils import ConversationLog +from _utils import get_client + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig + + +@pytest.mark.asyncio +async def test_speech_fragments(): + """Test SpeechFragment. + + - create fragment(s) + - check output from processing conversation + - serialize to JSON + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) + + # Start time + start_time = datetime.datetime.now() + + # Create a client + client = await get_client(api_key="NONE", connect=False) + assert client is not None + + # Start the queue + client._start_stt_queue() + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Reset message + def message_reset(): + nonlocal last_message + last_message = None + event_rx.clear() + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.05) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + message_reset() + client.once(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) + + # Inject first partial + await send_message(0, count=6, use_ttl=False) + + # Wait for first segment + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_PARTIAL_SEGMENT event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT + + # Check the segment + segments = last_message.get("segments", []) + assert len(segments) == 1 + seg0 = segments[0] + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome" + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome" + + # Add listener for final segment + message_reset() + client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) + + # Send a more partials and finals + await send_message(5, count=8, use_ttl=False) + + # Wait for final segment + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_SEGMENT event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT + + # Check the segment + segments = last_message.get("segments", []) + assert len(segments) == 1 + seg0 = segments[0] + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome to Speechmatics." + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics." + + # Stop the queue + client._stop_stt_queue() + + +@pytest.mark.asyncio +async def test_end_of_utterance_fixed(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (fast) + - wait for `EndOfUtterance` message + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) + + # Start time + start_time = datetime.datetime.now() + + # Create a client + client = await get_client(api_key="NONE", connect=False) + assert client is not None + + # Start the queue + client._start_stt_queue() + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for first interim segment + client.once(AgentServerMessageType.END_OF_TURN, message_rx) + + # Inject conversation + await send_message(0, count=14, use_ttl=False) + + # Wait for EndOfTurn + try: + await asyncio.wait_for(event_rx.wait(), timeout=5.0) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("END_OF_TURN event was not received within 5 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.END_OF_TURN + + +@pytest.mark.asyncio +async def test_external_vad(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (realtime) + - finalizes based on external VAD (e.g. Pipecat's `UserStoppedSpeakingFrame` frame) + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) + + # Start time + start_time = datetime.datetime.now() + + # Adaptive timeout + adaptive_timeout = 1.0 + + # Create a client + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL + ), + ) + assert client is not None + + # Start the queue + client._start_stt_queue() + + # Event to wait + event_rx: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Message receiver + def message_rx(message: dict[str, Any]): + nonlocal last_message + last_message = message + event_rx.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Inject conversation + await send_message(0, count=12, use_ttl=False) + + # Momentary pause + await asyncio.sleep(0.5) + + # Add listener for first interim segment + client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) + + # Pause for a moment + await asyncio.sleep(0.5) + + # Send finalize + client.finalize() + + # Wait for AddSegments + try: + await asyncio.wait_for(event_rx.wait(), timeout=4) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail("ADD_SEGMENT event was not received within 4 seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT + + # Check the segment + segments = last_message.get("segments", []) + assert len(segments) == 1 + seg0 = segments[0] + assert seg0["speaker_id"] == "S1" + assert seg0["text"] == "Welcome to Speechmatics" + assert f"{seg0['speaker_id']}: {seg0['text']}" == "S1: Welcome to Speechmatics" + + # Stop the queue + client._stop_stt_queue() + + +@pytest.mark.asyncio +async def test_end_of_utterance_adaptive_vad(): + """Test EndOfUtterance from STT engine. + + - send converstaion messages (realtime) + - wait for `EndOfUtterance` message from SDK (adaptive) + - check the interval to receive the EndOfUtterance message is within 25% of expected + """ + + # Test conversation + log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) + chat = log.get_conversation( + ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] + ) + + # Start time + start_time = datetime.datetime.now() + + # Adaptive timeout + adaptive_timeout = 0.5 + + # Create a client + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + ), + ) + assert client is not None + + # Start the queue + client._start_stt_queue() + + # Event to wait + eot_received: asyncio.Event = asyncio.Event() + last_message: Optional[dict[str, Any]] = None + + # Time for last final (used to calculate the interval) + last_final_time: Optional[float] = None + receive_interval: Optional[float] = None + + # Transcript receiver + def transcript_rx(message: dict[str, Any]): + if not eot_received.is_set(): + nonlocal last_final_time + last_final_time = datetime.datetime.now() + + # End of turn receiver + def eot_rx(message: dict[str, Any]): + nonlocal last_message + nonlocal receive_interval + last_message = message + receive_interval = (datetime.datetime.now() - last_final_time).total_seconds() + eot_received.set() + + # Send a message from the conversation + async def send_message(idx: int, count: int = 1, use_ttl: bool = True): + for i in range(count): + # Get the message from the chat + message = chat[idx + i] + + # Wait for TTL to expire + if use_ttl: + ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() + if ttl.total_seconds() > 0: + await asyncio.sleep(ttl.total_seconds()) + else: + await asyncio.sleep(0.005) + + # Emit the message + client.emit(message["payload"]["message"], message["payload"]) + + # Add listener for partials (as these will trigger the adaptive timer)) + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, transcript_rx) + + # Add listener for end of turn + client.once(AgentServerMessageType.END_OF_TURN, eot_rx) + + # Inject conversation up to the penultimate final from the STT + await send_message(0, count=12, use_ttl=True) + + # Check we have had a final + assert last_final_time is not None + + # Timing info + timeout = adaptive_timeout * 1.5 + + # Wait for EndOfUtterance + try: + await asyncio.wait_for(eot_received.wait(), timeout=timeout) + assert last_message is not None + except asyncio.TimeoutError: + pytest.fail(f"END_OF_TURN event was not received within {timeout} seconds") + + # Check the right message was received + assert last_message.get("message") == AgentServerMessageType.END_OF_TURN + + # Check the interval was within +/- 25% of the adaptive trigger of 0.5 the timeout (see client code) + expected_min_interval = adaptive_timeout * 0.5 * 0.75 + expected_max_interval = adaptive_timeout * 0.5 * 1.25 + assert receive_interval >= expected_min_interval + assert receive_interval <= expected_max_interval + + # Stop the queue + client._stop_stt_queue() diff --git a/tests/voice/test_05_thought.py b/tests/voice/test_06_thought.py similarity index 100% rename from tests/voice/test_05_thought.py rename to tests/voice/test_06_thought.py diff --git a/tests/voice/test_06_languages.py b/tests/voice/test_07_languages.py similarity index 98% rename from tests/voice/test_06_languages.py rename to tests/voice/test_07_languages.py index 8d0e0151..02773fe8 100644 --- a/tests/voice/test_06_languages.py +++ b/tests/voice/test_07_languages.py @@ -17,7 +17,6 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._helpers import to_serializable api_key = os.getenv("SPEECHMATICS_API_KEY") @@ -136,7 +135,7 @@ def log_message(message): last_message = message ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": to_serializable(message)}) + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message.model_dump()}) messages.append(log) # Log a segment From 496968d30a4b5ae9e6ac485c38127cfa2e9afbb7 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 25 Sep 2025 23:17:27 +0100 Subject: [PATCH 072/215] Fix to Optional[] for older Python --- .gitignore | 1 + sdk/voice/speechmatics/voice/_models.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f3074d56..96d57095 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ +.benchmarks/ # Translations *.mo diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index b7d25a03..d827e638 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -490,7 +490,7 @@ class SpeakerSegment(BaseModel): timestamp: Optional[str] = None language: Optional[str] = None fragments: list[SpeechFragment] = Field(default_factory=list) - text: str | None = None + text: Optional[str] = None annotation: AnnotationResult = Field(default_factory=AnnotationResult) model_config = {"arbitrary_types_allowed": True} From e4ce8fcd1afec4d835c87a2a35db1cb752240c17 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 11:57:14 +0100 Subject: [PATCH 073/215] updated makefile --- Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eff2b794..58657772 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ build-voice: install-build cd sdk/voice && python -m build # Cleaning targets -clean-all: clean-rt clean-batch clean-flow clean-voice +clean-all: clean-rt clean-batch clean-flow clean-voice clean-test clean-examples clean-rt: rm -rf sdk/rt/dist sdk/rt/build sdk/rt/*.egg-info @@ -170,3 +170,11 @@ clean-flow: clean-voice: rm -rf sdk/voice/dist sdk/voice/build sdk/voice/*.egg-info find sdk/voice -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + +clean-test: + find tests -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + rm -rf .pytest_cache + rm -rf .mypy_cache + +clean-examples: + find examples -name __pycache__ -exec rm -rf {} + 2>/dev/null || true From 5afea075e3624b672eeeb14d64a89b0ea2f7ee41 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 11:57:38 +0100 Subject: [PATCH 074/215] update test audio files --- .../assets/{audio_01.wav => audio_01_16kHz.wav} | 0 tests/voice/assets/audio_02_8kHz.wav | 3 +++ tests/voice/assets/chat1.jsonl | 2 +- tests/voice/test_02_transcriber.py | 12 ++++++------ tests/voice/test_03_conversation.py | 4 ++-- tests/voice/test_07_languages.py | 1 + 6 files changed, 13 insertions(+), 9 deletions(-) rename tests/voice/assets/{audio_01.wav => audio_01_16kHz.wav} (100%) create mode 100644 tests/voice/assets/audio_02_8kHz.wav diff --git a/tests/voice/assets/audio_01.wav b/tests/voice/assets/audio_01_16kHz.wav similarity index 100% rename from tests/voice/assets/audio_01.wav rename to tests/voice/assets/audio_01_16kHz.wav diff --git a/tests/voice/assets/audio_02_8kHz.wav b/tests/voice/assets/audio_02_8kHz.wav new file mode 100644 index 00000000..45ae225b --- /dev/null +++ b/tests/voice/assets/audio_02_8kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e93958ef0a3bf812e176a4003d942cdeceb1340583a79d120fa54c430293bb9 +size 579016 diff --git a/tests/voice/assets/chat1.jsonl b/tests/voice/assets/chat1.jsonl index 0e03667d..e017e5ac 100644 --- a/tests/voice/assets/chat1.jsonl +++ b/tests/voice/assets/chat1.jsonl @@ -1,4 +1,4 @@ -{"ts": 2.9e-05, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01.wav"}} +{"ts": 2.9e-05, "audio_ts": 0.0, "payload": {"message": "AudioFile", "path": "./assets/audio_01_16kHz.wav"}} {"ts": 8.8e-05, "audio_ts": 0.0, "payload": {"message": "VoiceAgentConfig", "operating_point": "enhanced", "domain": null, "language": "en", "output_locale": null, "max_delay": 0.7, "end_of_utterance_silence_trigger": 0.2, "end_of_utterance_max_delay": 10.0, "end_of_utterance_mode": "fixed", "additional_vocab": [], "punctuation_overrides": null, "enable_diarization": true, "speaker_sensitivity": 0.5, "max_speakers": null, "prefer_current_speaker": false, "speaker_config": {"focus_speakers": [], "ignore_speakers": [], "focus_mode": "retain"}, "known_speakers": [], "include_results": false, "enable_preview_features": false, "sample_rate": 16000, "audio_encoding": "pcm_s16le"}} {"ts": 0.000128, "audio_ts": 0.0, "payload": {"message": "TranscriptionConfig", "language": "en", "operating_point": "enhanced", "output_locale": null, "diarization": "speaker", "additional_vocab": null, "punctuation_overrides": null, "domain": null, "enable_entities": null, "audio_filtering_config": null, "transcript_filtering_config": null, "max_delay": 0.7, "max_delay_mode": null, "enable_partials": true, "speaker_diarization_config": {"speaker_sensitivity": 0.5, "prefer_current_speaker": false}, "streaming_mode": null, "conversation_config": {"end_of_utterance_silence_trigger": 0.2}, "ctrl": null, "channel_diarization_labels": null}} {"ts": 0.000148, "audio_ts": 0.0, "payload": {"message": "AudioFormat", "encoding": "pcm_s16le", "sample_rate": 16000, "chunk_size": 320}} diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index 77ba133c..c40fddd0 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -53,8 +53,8 @@ def on_partial_received(message): # Add listener for PARTIALS client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, on_partial_received) - # Load the audio file `./assets/audio_01.wav` - await send_audio_file(client, "./assets/audio_01.wav", event_received) + # Load the audio file `./assets/audio_01_16kHz.wav` + await send_audio_file(client, "./assets/audio_01_16kHz.wav", event_received) # Wait for the callback with timeout try: @@ -101,8 +101,8 @@ def on_final_received(message): # Add listener for PARTIALS client.on(AgentServerMessageType.ADD_TRANSCRIPT, on_final_received) - # Load the audio file `./assets/audio_01.wav` - await send_audio_file(client, "./assets/audio_01.wav", event_received) + # Load the audio file `./assets/audio_01_16kHz.wav` + await send_audio_file(client, "./assets/audio_01_16kHz.wav", event_received) # Wait for the callback with timeout try: @@ -161,8 +161,8 @@ def on_segment_received(message): # Add listener for PARTIALS client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, on_segment_received) - # Load the audio file `./assets/audio_01.wav` - await send_audio_file(client=client, audio_file="./assets/audio_01.wav", terminate_event=event_received) + # Load the audio file `./assets/audio_01_16kHz.wav` + await send_audio_file(client=client, audio_file="./assets/audio_01_16kHz.wav", terminate_event=event_received) # Wait for the callback with timeout try: diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index a34095dc..3df06fad 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -73,8 +73,8 @@ def log_message(message): client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) - # Load the audio file `./assets/audio_01.wav` - audio_file = "./assets/audio_01.wav" + # Load the audio file `./assets/audio_01_16kHz.wav` + audio_file = "./assets/audio_01_16kHz.wav" # HEADER print() diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 02773fe8..e7ab5b65 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -112,6 +112,7 @@ async def test_transcribe_languages(sample: AudioSample): additional_vocab=[AdditionalVocabEntry(content=vocab) for vocab in sample.vocab], ), ) + assert client is not None # Create an event to track when the callback is called messages: list[str] = [] From 9ac2222325998f77933272330c74d419fa5846ec Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 11:57:47 +0100 Subject: [PATCH 075/215] test diarization controls --- tests/voice/test_08_multiple_speakers.py | 187 +++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 tests/voice/test_08_multiple_speakers.py diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py new file mode 100644 index 00000000..b9db3fbe --- /dev/null +++ b/tests/voice/test_08_multiple_speakers.py @@ -0,0 +1,187 @@ +import datetime +import json +import os +import re +from dataclasses import field + +import pytest +from _utils import get_client +from _utils import send_audio_file +from pydantic import BaseModel + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import DiarizationSpeakerConfig +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import SpeakerSegment + +api_key = os.getenv("SPEECHMATICS_API_KEY") +show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] + + +class SpeakerTest(BaseModel): + id: str + path: str + sample_rate: int = 16000 + sample_size: int = 2 + segment_regex: list[str] = field(default_factory=list) + config: VoiceAgentConfig | None = None + speaker_config: DiarizationSpeakerConfig | None = None + + +SAMPLES: list[SpeakerTest] = [ + SpeakerTest( + id="multiple_speakers", + path="./assets/audio_02_8kHz.wav", + sample_rate=8000, + sample_size=1, + segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill", "Rickmansworth"], + ), + SpeakerTest( + id="focus_s2", + path="./assets/audio_02_8kHz.wav", + sample_rate=8000, + sample_size=1, + segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill"], + speaker_config=DiarizationSpeakerConfig( + focus_speakers=["S2"], + ), + ), + SpeakerTest( + id="only_s2", + path="./assets/audio_02_8kHz.wav", + sample_rate=8000, + sample_size=1, + segment_regex=["Buckingham", "Notting Hill"], + speaker_config=DiarizationSpeakerConfig( + focus_speakers=["S2"], + focus_mode=DiarizationFocusMode.IGNORE, + ), + ), + SpeakerTest( + id="ignore_s2", + path="./assets/audio_02_8kHz.wav", + sample_rate=8000, + sample_size=1, + segment_regex=["^Welcome", "please repeat or clarify", "Rickmansworth"], + speaker_config=DiarizationSpeakerConfig( + ignore_speakers=["S2"], + ), + ), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sample", SAMPLES, ids=lambda s: f"{s.id}:{s.path}") +async def test_multiple_speakers(sample: SpeakerTest): + """Test transcription. + + This test will: + - log messages + - transcribe audio with diarization config + - validate the segments received + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Config + config = sample.config or VoiceAgentConfig( + end_of_utterance_silence_trigger=1.0, + max_delay=2.0, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + ) + + # Diarization options + if sample.speaker_config: + config.speaker_config = sample.speaker_config + + # Standard features + config.enable_diarization = True + config.sample_rate = sample.sample_rate + + # Client + client = await get_client( + api_key=api_key, + connect=False, + config=config, + ) + + # Create an event to track when the callback is called + messages: list[str] = [] + bytes_sent: int = 0 + final_segments: list[dict] = [] + + # Start time + start_time = datetime.datetime.now() + + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + audio_ts = bytes_sent / sample.sample_rate / sample.sample_size + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + messages.append(log) + if show_log: + print(log) + + def log_final_segment(message): + segments: list[SpeakerSegment] = message["segments"] + final_segments.extend(segments) + + # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.once(AgentServerMessageType.INFO, log_message) + client.on(AgentServerMessageType.WARNING, log_message) + client.on(AgentServerMessageType.ERROR, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + + # Log ADD_SEGMENT + client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) + + # Load the audio file `./assets/audio_01_16kHz.wav` + audio_file = "./assets/audio_02_8kHz.wav" + + # HEADER + if show_log: + print() + print() + print("---") + log_message({"message": "Sample", **sample.model_dump()}) + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) + + # FOOTER + if show_log: + print("---") + print() + print() + + # Check final segments against regex + for idx, _test in enumerate(sample.segment_regex): + assert re.search(_test, final_segments[idx].get("text")) + + # Close session + await client.disconnect() + assert not client._is_connected From 670c71376e8abadc324cb8bbcf885a8c53434d1e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 12:01:33 +0100 Subject: [PATCH 076/215] path fix --- tests/voice/test_08_multiple_speakers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index b9db3fbe..b055dc1b 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -150,9 +150,6 @@ def log_final_segment(message): # Log ADD_SEGMENT client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) - # Load the audio file `./assets/audio_01_16kHz.wav` - audio_file = "./assets/audio_02_8kHz.wav" - # HEADER if show_log: print() @@ -170,7 +167,7 @@ def log_final_segment(message): assert client._is_connected # Individual payloads - await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) + await send_audio_file(client, sample.path, progress_callback=log_bytes_sent) # FOOTER if show_log: From 1b1fa38f1353a3f5880e753f66bb64fefe66ff03 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 12:06:19 +0100 Subject: [PATCH 077/215] use Optional in tests --- tests/voice/test_08_multiple_speakers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index b055dc1b..f19a95a9 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -3,6 +3,7 @@ import os import re from dataclasses import field +from typing import Optional import pytest from _utils import get_client @@ -26,8 +27,8 @@ class SpeakerTest(BaseModel): sample_rate: int = 16000 sample_size: int = 2 segment_regex: list[str] = field(default_factory=list) - config: VoiceAgentConfig | None = None - speaker_config: DiarizationSpeakerConfig | None = None + config: Optional[VoiceAgentConfig] = None + speaker_config: Optional[DiarizationSpeakerConfig] = None SAMPLES: list[SpeakerTest] = [ From 6675a741bd56f4a29edc70f1425dd6c7f58fe59d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 15:08:15 +0100 Subject: [PATCH 078/215] Support testing speaker id --- tests/voice/README.md | 25 +++ tests/voice/test_03_conversation.py | 27 +-- tests/voice/test_08_multiple_speakers.py | 10 + tests/voice/test_09_speaker_id.py | 262 +++++++++++++++++++++++ 4 files changed, 312 insertions(+), 12 deletions(-) create mode 100644 tests/voice/README.md create mode 100644 tests/voice/test_09_speaker_id.py diff --git a/tests/voice/README.md b/tests/voice/README.md new file mode 100644 index 00000000..55bc7040 --- /dev/null +++ b/tests/voice/README.md @@ -0,0 +1,25 @@ +# Voice SDK Tests + +You will need a `SPEECHMATICS_API_KEY` to run most of the tests, as they will use live transcription. + +To run tests: + +```bash +# Install dependencies +make install-dev + +# Run tests without an API key (those needing live transcription will be skipped) +make test-voice + +# Run all tests +SPEECHMATICS_API_KEY=your_api_key make voice-tests + +# Run a specific test +SPEECHMATICS_API_KEY=your_api_key pytest -v -s tests/voice/test_03_conversation.py + +# Run a specific sub-test +SPEECHMATICS_API_KEY=your_api_key pytest -v -s tests/voice/test_03_conversation.py::test_log_messages + +# Run a specific test with logging +SPEECHMATICS_API_KEY=your_api_key SPEECHMATICS_SHOW_LOG=1 pytest -v -s tests/voice/test_03_conversation.py +``` diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 3df06fad..6cbd5421 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -11,9 +11,9 @@ from speechmatics.voice import VoiceAgentConfig api_key = os.getenv("SPEECHMATICS_API_KEY") +show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] -@pytest.mark.skip(reason="Only use manually!") @pytest.mark.asyncio async def test_log_messages(): """Test transcription. @@ -57,7 +57,8 @@ def log_message(message): audio_ts = bytes_sent / 16000 / 2 log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) messages.append(log) - print(log) + if show_log: + print(log) # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) @@ -77,13 +78,14 @@ def log_message(message): audio_file = "./assets/audio_01_16kHz.wav" # HEADER - print() - print() - print("---") - log_message({"message": "AudioFile", "path": audio_file}) - log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) - log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) - log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) + if show_log: + print() + print() + print("---") + log_message({"message": "AudioFile", "path": audio_file}) + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) # Connect await client.connect() @@ -95,9 +97,10 @@ def log_message(message): await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) # FOOTER - print("---") - print() - print() + if show_log: + print("---") + print() + print() # Close session await client.disconnect() diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index f19a95a9..e2ea9fb7 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -29,6 +29,7 @@ class SpeakerTest(BaseModel): segment_regex: list[str] = field(default_factory=list) config: Optional[VoiceAgentConfig] = None speaker_config: Optional[DiarizationSpeakerConfig] = None + speakers_present: list[str] = field(default_factory=list) SAMPLES: list[SpeakerTest] = [ @@ -38,6 +39,7 @@ class SpeakerTest(BaseModel): sample_rate=8000, sample_size=1, segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill", "Rickmansworth"], + speakers_present=["S1", "S2"], ), SpeakerTest( id="focus_s2", @@ -48,6 +50,7 @@ class SpeakerTest(BaseModel): speaker_config=DiarizationSpeakerConfig( focus_speakers=["S2"], ), + speakers_present=["S1", "S2"], ), SpeakerTest( id="only_s2", @@ -59,6 +62,7 @@ class SpeakerTest(BaseModel): focus_speakers=["S2"], focus_mode=DiarizationFocusMode.IGNORE, ), + speakers_present=["S2"], ), SpeakerTest( id="ignore_s2", @@ -69,6 +73,7 @@ class SpeakerTest(BaseModel): speaker_config=DiarizationSpeakerConfig( ignore_speakers=["S2"], ), + speakers_present=["S1"], ), ] @@ -133,6 +138,7 @@ def log_message(message): if show_log: print(log) + # Log final segments def log_final_segment(message): segments: list[SpeakerSegment] = message["segments"] final_segments.extend(segments) @@ -180,6 +186,10 @@ def log_final_segment(message): for idx, _test in enumerate(sample.segment_regex): assert re.search(_test, final_segments[idx].get("text")) + # Check only speakers present + speakers = [segment.get("speaker_id") for segment in final_segments] + assert set(speakers) == set(sample.speakers_present) + # Close session await client.disconnect() assert not client._is_connected diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py new file mode 100644 index 00000000..900d9f22 --- /dev/null +++ b/tests/voice/test_09_speaker_id.py @@ -0,0 +1,262 @@ +import asyncio +import datetime +import json +import os + +import pytest +from _utils import get_client +from _utils import send_audio_file + +from speechmatics.voice import AgentClientMessageType +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationKnownSpeaker +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import SpeakerSegment + +api_key = os.getenv("SPEECHMATICS_API_KEY") +show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] + +speaker_ids: list[DiarizationKnownSpeaker] = [] + + +@pytest.mark.asyncio +async def test_extract_speaker_ids(): + """Test speaker id extraction. + + This test will: + - transcribe audio with diarization config + - get speaker ids for the two speakers + - uses legacy format until out of preview! + - this MUST use a preview endpoint + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Client + client = await get_client( + api_key=api_key, + url="wss://preview.rt.speechmatics.com/v1", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=1.0, + max_delay=2.0, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + enable_diarization=True, + sample_rate=8000, + ), + ) + + # Create an event to track when the callback is called + messages: list[str] = [] + bytes_sent: int = 0 + + # Flag + speakers_event_received = asyncio.Event() + + # Start time + start_time = datetime.datetime.now() + + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + audio_ts = bytes_sent / 8000 + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + messages.append(log) + if show_log: + print(log) + + # Log speakers result + def save_speakers_result(message): + speakers_event_received.set() + for label, speaker_identifiers in message.get("results", {}).items(): + speaker_ids.append( + DiarizationKnownSpeaker( + label=label, + speaker_identifiers=speaker_identifiers, + ) + ) + + # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + + # Log SPEAKERS_RESULT + client.once(AgentServerMessageType.SPEAKERS_RESULT, save_speakers_result) + + # HEADER + if show_log: + print() + print() + print("---") + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, "./assets/audio_02_8kHz.wav", progress_callback=log_bytes_sent) + + # Request the speakers result + await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS}) + + # Wait for the callback with timeout + try: + await asyncio.wait_for(speakers_event_received.wait(), timeout=5.0) + except asyncio.TimeoutError: + pytest.fail("SPEAKERS_RESULT event was not received within 5 seconds of audio finish") + + # FOOTER + if show_log: + print("---") + print() + print() + + # Check speaker IDs + assert speaker_ids + assert len(speaker_ids) == 2 + + # Close session + await client.disconnect() + assert not client._is_connected + + +@pytest.mark.asyncio +async def test_known_speakers(): + """Test using known speakers. + + This test will: + - use known speakers + - check names for speakers + - this MUST use a preview endpoint + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Copy known speakers + known_speakers = speaker_ids.copy() + known_speakers[0].label = "Assistant" + known_speakers[1].label = "John Doe" + + # Client + client = await get_client( + api_key=api_key, + url="wss://preview.rt.speechmatics.com/v1", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=1.0, + max_delay=2.0, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + enable_diarization=True, + sample_rate=8000, + known_speakers=known_speakers, + ), + ) + + # Create an event to track when the callback is called + final_segments: list[dict] = [] + + # Log final segments + def log_final_segment(message): + segments: list[SpeakerSegment] = message["segments"] + final_segments.extend(segments) + + # Add listeners + client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, "./assets/audio_02_8kHz.wav") + + # Check only speakers present + speakers = [segment.get("speaker_id") for segment in final_segments] + assert set(speakers) == set({"Assistant", "John Doe"}) + + # Close session + await client.disconnect() + assert not client._is_connected + + +@pytest.mark.asyncio +async def test_ignoring_assistant(): + """Test ignoring the assistant. + + This test will: + - use known speakers + - set assistant to `__ASSISTANT__` + - this MUST use a preview endpoint + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Copy known speakers + known_speakers = speaker_ids.copy() + known_speakers[0].label = "__ASSISTANT__" + known_speakers[1].label = "John Doe" + + # Client + client = await get_client( + api_key=api_key, + url="wss://preview.rt.speechmatics.com/v1", + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=1.0, + max_delay=2.0, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + enable_diarization=True, + sample_rate=8000, + known_speakers=known_speakers, + ), + ) + + # Create an event to track when the callback is called + final_segments: list[dict] = [] + + # Log final segments + def log_final_segment(message): + segments: list[SpeakerSegment] = message["segments"] + final_segments.extend(segments) + + # Add listeners + client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, "./assets/audio_02_8kHz.wav") + + # Check only speakers present + speakers = [segment.get("speaker_id") for segment in final_segments] + assert set(speakers) == set({"John Doe"}) + + # Close session + await client.disconnect() + assert not client._is_connected From b827f52554e4c8509ac0ce61251157b1393a79b3 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 15:12:49 +0100 Subject: [PATCH 079/215] updated speaker id test --- tests/voice/test_09_speaker_id.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 900d9f22..276e50ed 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -193,6 +193,9 @@ def log_final_segment(message): speakers = [segment.get("speaker_id") for segment in final_segments] assert set(speakers) == set({"Assistant", "John Doe"}) + # Should be 5 segments + assert len(final_segments) == 5 + # Close session await client.disconnect() assert not client._is_connected @@ -257,6 +260,13 @@ def log_final_segment(message): speakers = [segment.get("speaker_id") for segment in final_segments] assert set(speakers) == set({"John Doe"}) + # Should be only 2 segments + assert len(final_segments) == 2 + + # No segment should contain `Rickmansworth` + for segment in final_segments: + assert "Rickmansworth" not in segment.get("text", "") + # Close session await client.disconnect() assert not client._is_connected From d7a2c13a595a80505999d8b0c0a7bf8fac697da0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 15:18:28 +0100 Subject: [PATCH 080/215] updated comments --- tests/voice/test_09_speaker_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 276e50ed..0f495d08 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -169,7 +169,7 @@ async def test_known_speakers(): ), ) - # Create an event to track when the callback is called + # Finalised segments final_segments: list[dict] = [] # Log final segments @@ -236,7 +236,7 @@ async def test_ignoring_assistant(): ), ) - # Create an event to track when the callback is called + # Finalised segments final_segments: list[dict] = [] # Log final segments From 5e4e39b039adfbdac83e3758886fa8d1d4b460d0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 15:43:37 +0100 Subject: [PATCH 081/215] updated speaker id test --- tests/voice/test_09_speaker_id.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 0f495d08..51a14da1 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -2,6 +2,7 @@ import datetime import json import os +from typing import Optional import pytest from _utils import get_client @@ -15,6 +16,7 @@ from speechmatics.voice._models import SpeakerSegment api_key = os.getenv("SPEECHMATICS_API_KEY") +url: Optional[str] = "wss://preview.rt.speechmatics.com/v1" show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] speaker_ids: list[DiarizationKnownSpeaker] = [] @@ -39,7 +41,7 @@ async def test_extract_speaker_ids(): # Client client = await get_client( api_key=api_key, - url="wss://preview.rt.speechmatics.com/v1", + url=url, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, @@ -76,7 +78,6 @@ def log_message(message): # Log speakers result def save_speakers_result(message): - speakers_event_received.set() for label, speaker_identifiers in message.get("results", {}).items(): speaker_ids.append( DiarizationKnownSpeaker( @@ -84,6 +85,7 @@ def save_speakers_result(message): speaker_identifiers=speaker_identifiers, ) ) + speakers_event_received.set() # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) @@ -157,7 +159,7 @@ async def test_known_speakers(): # Client client = await get_client( api_key=api_key, - url="wss://preview.rt.speechmatics.com/v1", + url=url, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, @@ -224,7 +226,7 @@ async def test_ignoring_assistant(): # Client client = await get_client( api_key=api_key, - url="wss://preview.rt.speechmatics.com/v1", + url=url, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, From 9c5ddb6e7b7ab2a5fc3bdc06c57431988e6b8aad Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 18:44:23 +0100 Subject: [PATCH 082/215] close session with END_OF_STREAM --- sdk/voice/speechmatics/voice/_client.py | 16 +++++++++++----- sdk/voice/speechmatics/voice/_models.py | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 34e8dae7..7f8ef093 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -18,6 +18,7 @@ from speechmatics.rt import AsyncClient from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat +from speechmatics.rt import ClientMessageType from speechmatics.rt import ConversationConfig from speechmatics.rt import ServerMessageType from speechmatics.rt import TranscriptionConfig @@ -28,12 +29,12 @@ from ._models import AgentServerMessageType from ._models import AnnotationFlags from ._models import AnnotationResult +from ._models import ClientSessionInfo from ._models import DiarizationFocusMode from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode from ._models import FragmentUtils from ._models import LanguagePackInfo -from ._models import SessionInfo from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment @@ -90,7 +91,7 @@ def __init__( self._is_ready_for_audio: bool = False # Session info (updated on session created) - self._session: SessionInfo = SessionInfo( + self._client_session: ClientSessionInfo = ClientSessionInfo( config=self._config, session_id="NOT_SET", base_time=datetime.datetime.now(datetime.timezone.utc), @@ -236,7 +237,7 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) self._is_ready_for_audio = True - self._session = SessionInfo( + self._client_session = ClientSessionInfo( config=self._config, session_id=message.get("id", "UNKNOWN"), base_time=datetime.datetime.now(datetime.timezone.utc), @@ -314,6 +315,11 @@ async def disconnect(self) -> None: if not self._is_connected: return + # Send end of transcript to release resources + await self.send_message( + {"message": ClientMessageType.END_OF_STREAM, "last_seq_no": self._session.sequence_number} + ) + # Disconnect from API try: await asyncio.wait_for(self.close(), timeout=5.0) @@ -641,7 +647,7 @@ async def _process_speech_fragments(self) -> None: # Compare previous view to this view if self._previous_view: - changes = FragmentUtils.compare_views(self._session, self._previous_view, self._current_view) + changes = FragmentUtils.compare_views(self._client_session, self._previous_view, self._current_view) else: changes = AnnotationResult.from_flags(AnnotationFlags.NEW) @@ -683,7 +689,7 @@ async def emit(delay: float | None) -> None: def _update_current_view(self) -> None: """Load the current view of the speech fragments.""" self._current_view = SpeakerSegmentView( - session=self._session, + session=self._client_session, fragments=self._speech_fragments.copy(), focus_speakers=self._dz_config.focus_speakers, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index d827e638..4115d2eb 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -374,7 +374,7 @@ class LanguagePackInfo(BaseModel): writing_direction: str = "ltr" -class SessionInfo(BaseModel): +class ClientSessionInfo(BaseModel): """Information about the session. Attributes: @@ -524,19 +524,19 @@ class SpeakerSegmentView(BaseModel): """View for speaker fragments. Parameters: - session: SessionInfo object. + session: ClientSessionInfo object. fragments: List of fragments. focus_speakers: List of speakers to focus on or None. """ - session: SessionInfo + session: ClientSessionInfo fragments: list[SpeechFragment] segments: list[SpeakerSegment] = Field(default_factory=list) focus_speakers: Optional[list[str]] = None def __init__( self, - session: SessionInfo, + session: ClientSessionInfo, fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate_segments: bool = True, @@ -647,12 +647,12 @@ class FragmentUtils: @staticmethod def format_segment_text( - session: SessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False + session: ClientSessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False ) -> str: """Format a segment's text based on the language pack info. Args: - session: SessionInfo object. + session: ClientSessionInfo object. segment: SpeakerSegment object. words_only: Whether to include only word fragments. @@ -692,7 +692,7 @@ def format_segment_text( @staticmethod def segment_list_from_fragments( - session: SessionInfo, + session: ClientSessionInfo, fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate_segments: bool = True, @@ -700,7 +700,7 @@ def segment_list_from_fragments( """Create SpeakerSegment objects from a list of SpeechFragment objects. Args: - session: SessionInfo object. + session: ClientSessionInfo object. fragments: List of SpeechFragment objects. focus_speakers: List of speakers to focus on or None. annotate_segments: Whether to annotate segments. @@ -739,7 +739,7 @@ def segment_list_from_fragments( @staticmethod def segment_from_fragments( - session: SessionInfo, + session: ClientSessionInfo, fragments: list[SpeechFragment], focus_speakers: Optional[list[str]] = None, annotate: bool = True, @@ -754,7 +754,7 @@ def segment_from_fragments( be removed. Args: - session: SessionInfo object. + session: ClientSessionInfo object. fragments: List of SpeechFragment objects. focus_speakers: List of speakers to focus on. annotate: Whether to annotate the segment. @@ -883,14 +883,14 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: @staticmethod def compare_views( - session: SessionInfo, view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] + session: ClientSessionInfo, view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] ) -> AnnotationResult: """Compare two SpeakerSegmentView objects and return the differences. View 1 (new) is compared to view 2 (old). Args: - session: SessionInfo object. + session: ClientSessionInfo object. view1: The first SpeakerSegmentView object to compare. view2: The second SpeakerSegmentView object to compare to or None. From adfb04374cbf797e377ebd9ff03b99401afdfa86 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 18:44:50 +0100 Subject: [PATCH 083/215] updated audio sender and silence utility --- tests/voice/_utils.py | 55 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index c564ea00..fe0311eb 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -63,6 +63,8 @@ async def send_audio_file( audio_file: str, terminate_event: Optional[asyncio.Event] = None, chunk_size: int = 320, + sample_rate: int = 16000, + sample_size: int = 2, progress_callback: Optional[Callable[[int], None]] = None, ) -> None: """Send audio data to the API server.""" @@ -77,8 +79,12 @@ async def send_audio_file( file = os.path.join(os.path.dirname(__file__), audio_file) assert os.path.exists(file) + # Make sure progress callback is callable + if progress_callback: + assert callable(progress_callback) + # Delay is based off 16kHz int16 and chunk size - delay = chunk_size / 16000 / 2 + delay = chunk_size / sample_rate / sample_size # Load the file async with aiofiles.open(file, "rb") as wav_file: @@ -111,6 +117,53 @@ async def send_audio_file( next_time += delay +async def send_silence( + client: VoiceAgentClient, + duration: float, + terminate_event: Optional[asyncio.Event] = None, + chunk_size: int = 320, + sample_rate: int = 16000, + sample_size: int = 2, + progress_callback: Optional[Callable[[int], None]] = None, +): + """Send silence to the client (creates a chunk of silence and sends it to the client)""" + + # Make sure client is connected + assert client._is_connected + + # Make sure duration is positive + assert duration > 0 + + # Make sure chunk size is positive + assert chunk_size > 0 + + # Make sure progress callback is callable + if progress_callback: + assert callable(progress_callback) + + # Send silence + silence = b"\x00" * chunk_size + + # Timing + delay = chunk_size / sample_rate / sample_size + next_time = time.perf_counter() + delay + + # Keep sending + while not terminate_event.is_set() if terminate_event else True: + # Send audio to client + await client.send_audio(silence) + + # Do any callbacks + if progress_callback: + progress_callback(len(silence)) + + # Precision delay + sleep_time = next_time - time.perf_counter() + if sleep_time > 0: + await asyncio.sleep(sleep_time) + next_time += delay + + def normalize(text: str) -> str: """Normalise text.""" From 2cb1a7f9dbcf5680c66a9049c587d8793b7bce6e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 18:45:07 +0100 Subject: [PATCH 084/215] updated tests for sending audio --- tests/voice/assets/audio_03_16kHz.wav | 3 + tests/voice/test_01_client.py | 6 +- tests/voice/test_02_transcriber.py | 2 +- tests/voice/test_03_conversation.py | 10 +- tests/voice/test_07_languages.py | 2 +- tests/voice/test_08_multiple_speakers.py | 22 ++-- tests/voice/test_09_speaker_id.py | 32 +++--- tests/voice/test_10_finalize.py | 127 +++++++++++++++++++++++ 8 files changed, 172 insertions(+), 32 deletions(-) create mode 100644 tests/voice/assets/audio_03_16kHz.wav create mode 100644 tests/voice/test_10_finalize.py diff --git a/tests/voice/assets/audio_03_16kHz.wav b/tests/voice/assets/audio_03_16kHz.wav new file mode 100644 index 00000000..0c9b1793 --- /dev/null +++ b/tests/voice/assets/audio_03_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eadc1c0609e13027d0bb7bfe6bf7868c123cbd17421898e6023b1889f103cf17 +size 58460 diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py index bd0838db..2cb45d0a 100644 --- a/tests/voice/test_01_client.py +++ b/tests/voice/test_01_client.py @@ -36,6 +36,6 @@ async def test_client(): assert not client._is_connected # Check session info - assert client._session.session_id != "NOT_SET" - assert client._session.language_pack_info is not None - assert client._session.language_pack_info.language_description == "English" + assert client._client_session.session_id != "NOT_SET" + assert client._client_session.language_pack_info is not None + assert client._client_session.language_pack_info.language_description == "English" diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index c40fddd0..3fe49b00 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -9,7 +9,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import VoiceAgentConfig -api_key = os.getenv("SPEECHMATICS_API_KEY") +API_KEY = os.getenv("SPEECHMATICS_API_KEY") @pytest.mark.asyncio diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 6cbd5421..d46e2583 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -10,8 +10,8 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig -api_key = os.getenv("SPEECHMATICS_API_KEY") -show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] +API_KEY = os.getenv("SPEECHMATICS_API_KEY") +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] @pytest.mark.asyncio @@ -57,7 +57,7 @@ def log_message(message): audio_ts = bytes_sent / 16000 / 2 log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) messages.append(log) - if show_log: + if SHOW_LOG: print(log) # Add listeners @@ -78,7 +78,7 @@ def log_message(message): audio_file = "./assets/audio_01_16kHz.wav" # HEADER - if show_log: + if SHOW_LOG: print() print() print("---") @@ -97,7 +97,7 @@ def log_message(message): await send_audio_file(client, audio_file, progress_callback=log_bytes_sent) # FOOTER - if show_log: + if SHOW_LOG: print("---") print() print() diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index e7ab5b65..2326b90d 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -18,7 +18,7 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig -api_key = os.getenv("SPEECHMATICS_API_KEY") +API_KEY = os.getenv("SPEECHMATICS_API_KEY") @dataclass diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index e2ea9fb7..acaf8118 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -17,8 +17,8 @@ from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment -api_key = os.getenv("SPEECHMATICS_API_KEY") -show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] +API_KEY = os.getenv("SPEECHMATICS_API_KEY") +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] class SpeakerTest(BaseModel): @@ -37,7 +37,6 @@ class SpeakerTest(BaseModel): id="multiple_speakers", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - sample_size=1, segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill", "Rickmansworth"], speakers_present=["S1", "S2"], ), @@ -45,7 +44,6 @@ class SpeakerTest(BaseModel): id="focus_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - sample_size=1, segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill"], speaker_config=DiarizationSpeakerConfig( focus_speakers=["S2"], @@ -56,7 +54,6 @@ class SpeakerTest(BaseModel): id="only_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - sample_size=1, segment_regex=["Buckingham", "Notting Hill"], speaker_config=DiarizationSpeakerConfig( focus_speakers=["S2"], @@ -68,7 +65,6 @@ class SpeakerTest(BaseModel): id="ignore_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - sample_size=1, segment_regex=["^Welcome", "please repeat or clarify", "Rickmansworth"], speaker_config=DiarizationSpeakerConfig( ignore_speakers=["S2"], @@ -135,7 +131,7 @@ def log_message(message): audio_ts = bytes_sent / sample.sample_rate / sample.sample_size log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) messages.append(log) - if show_log: + if SHOW_LOG: print(log) # Log final segments @@ -158,7 +154,7 @@ def log_final_segment(message): client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) # HEADER - if show_log: + if SHOW_LOG: print() print() print("---") @@ -174,10 +170,16 @@ def log_final_segment(message): assert client._is_connected # Individual payloads - await send_audio_file(client, sample.path, progress_callback=log_bytes_sent) + await send_audio_file( + client, + sample.path, + sample_rate=sample.sample_rate, + sample_size=sample.sample_size, + progress_callback=log_bytes_sent, + ) # FOOTER - if show_log: + if SHOW_LOG: print("---") print() print() diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 51a14da1..6c0bcac5 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -15,9 +15,9 @@ from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment -api_key = os.getenv("SPEECHMATICS_API_KEY") -url: Optional[str] = "wss://preview.rt.speechmatics.com/v1" -show_log = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] +API_KEY = os.getenv("SPEECHMATICS_API_KEY") +URL: Optional[str] = "wss://preview.rt.speechmatics.com/v2" +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] speaker_ids: list[DiarizationKnownSpeaker] = [] @@ -41,7 +41,7 @@ async def test_extract_speaker_ids(): # Client client = await get_client( api_key=api_key, - url=url, + url=URL, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, @@ -73,7 +73,7 @@ def log_message(message): audio_ts = bytes_sent / 8000 log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) messages.append(log) - if show_log: + if SHOW_LOG: print(log) # Log speakers result @@ -95,7 +95,7 @@ def save_speakers_result(message): client.once(AgentServerMessageType.SPEAKERS_RESULT, save_speakers_result) # HEADER - if show_log: + if SHOW_LOG: print() print() print("---") @@ -110,7 +110,7 @@ def save_speakers_result(message): assert client._is_connected # Individual payloads - await send_audio_file(client, "./assets/audio_02_8kHz.wav", progress_callback=log_bytes_sent) + await send_audio_file(client, "./assets/audio_02_8kHz.wav", sample_rate=8000, progress_callback=log_bytes_sent) # Request the speakers result await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS}) @@ -122,7 +122,7 @@ def save_speakers_result(message): pytest.fail("SPEAKERS_RESULT event was not received within 5 seconds of audio finish") # FOOTER - if show_log: + if SHOW_LOG: print("---") print() print() @@ -159,7 +159,7 @@ async def test_known_speakers(): # Client client = await get_client( api_key=api_key, - url=url, + url=URL, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, @@ -189,7 +189,11 @@ def log_final_segment(message): assert client._is_connected # Individual payloads - await send_audio_file(client, "./assets/audio_02_8kHz.wav") + await send_audio_file( + client, + "./assets/audio_02_8kHz.wav", + sample_rate=8000, + ) # Check only speakers present speakers = [segment.get("speaker_id") for segment in final_segments] @@ -226,7 +230,7 @@ async def test_ignoring_assistant(): # Client client = await get_client( api_key=api_key, - url=url, + url=URL, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, @@ -256,7 +260,11 @@ def log_final_segment(message): assert client._is_connected # Individual payloads - await send_audio_file(client, "./assets/audio_02_8kHz.wav") + await send_audio_file( + client, + "./assets/audio_02_8kHz.wav", + sample_rate=8000, + ) # Check only speakers present speakers = [segment.get("speaker_id") for segment in final_segments] diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py new file mode 100644 index 00000000..9a323b08 --- /dev/null +++ b/tests/voice/test_10_finalize.py @@ -0,0 +1,127 @@ +import asyncio +import datetime +import json +import os +from typing import Optional + +import pytest +from _utils import get_client +from _utils import send_audio_file +from _utils import send_silence + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig + +API_KEY = os.getenv("SPEECHMATICS_API_KEY") +URL: Optional[str] = "wss://jamesw.lab.speechmatics.io/v2" +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] +AUDIO_FILE = "./assets/audio_03_16kHz.wav" + + +@pytest.mark.asyncio +async def test_finalize(): + """Test finalization. + + This test will: + - play a short audio clip + - finalize the segment + - this MUST use a preview / dev endpoint + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Client + client = await get_client( + api_key=api_key, + url=URL, + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=0.7, + max_delay=1.2, + end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, + enable_preview_features=True, + ), + ) + + # Create an event to track when the callback is called + messages: list[str] = [] + bytes_sent: int = 0 + + # Flag + eot_received = asyncio.Event() + + # Start time + start_time = datetime.datetime.now() + + # Bytes logger + def log_bytes_sent(bytes): + nonlocal bytes_sent + bytes_sent += bytes + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + audio_ts = bytes_sent / 16000 / 2 + log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + messages.append(log) + if SHOW_LOG: + print(log) + + # EOT received + def eot_received_callback(message): + eot_received.set() + + # Add listeners + client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + + # End of Turn + client.once(AgentServerMessageType.END_OF_TURN, eot_received_callback) + + # HEADER + if SHOW_LOG: + print() + print() + print("---") + log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) + log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) + log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, AUDIO_FILE, chunk_size=160, progress_callback=log_bytes_sent) + + # Send silence in a thread + asyncio.create_task(send_silence(client, 10.0, chunk_size=160, terminate_event=eot_received)) + + # Request the speakers result + finalize_trigger_time = datetime.datetime.now() + client.finalize() + + # Wait for the callback with timeout + try: + await asyncio.wait_for(eot_received.wait(), timeout=5.0) + finalize_latency = (datetime.datetime.now() - finalize_trigger_time).total_seconds() * 1000 + except asyncio.TimeoutError: + pytest.fail("END_OF_TURN event was not received within 5 seconds of audio finish") + + # FOOTER + if SHOW_LOG: + print(f"--- latency {finalize_latency:.2f} ms") + print() + print() + + # Close session + await client.disconnect() + assert not client._is_connected From 751d01bd997e46776a3cf85efbb074b387c19b21 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 26 Sep 2025 19:05:33 +0100 Subject: [PATCH 085/215] updated test info --- tests/voice/_utils.py | 8 +++++- tests/voice/test_03_conversation.py | 2 +- tests/voice/test_07_languages.py | 2 +- tests/voice/test_08_multiple_speakers.py | 2 +- tests/voice/test_09_speaker_id.py | 17 ++++++++++--- tests/voice/test_10_finalize.py | 32 ++++++++++++++++++++---- 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index fe0311eb..7c97a664 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -148,8 +148,11 @@ async def send_silence( delay = chunk_size / sample_rate / sample_size next_time = time.perf_counter() + delay + # Iterations required + iterations = int(duration / delay) + # Keep sending - while not terminate_event.is_set() if terminate_event else True: + while (not terminate_event.is_set() if terminate_event else True) and iterations > 0: # Send audio to client await client.send_audio(silence) @@ -163,6 +166,9 @@ async def send_silence( await asyncio.sleep(sleep_time) next_time += delay + # Reduce iterations + iterations -= 1 + def normalize(text: str) -> str: """Normalise text.""" diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index d46e2583..eba8be0f 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -55,7 +55,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message}) messages.append(log) if SHOW_LOG: print(log) diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 2326b90d..184603de 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -136,7 +136,7 @@ def log_message(message): last_message = message ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message.model_dump()}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message.model_dump()}) messages.append(log) # Log a segment diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index acaf8118..7bdbd579 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -129,7 +129,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / sample.sample_rate / sample.sample_size - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message}) messages.append(log) if SHOW_LOG: print(log) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 6c0bcac5..7a188bb0 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -71,7 +71,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 8000 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message}) messages.append(log) if SHOW_LOG: print(log) @@ -104,7 +104,10 @@ def save_speakers_result(message): log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) # Connect - await client.connect() + try: + await client.connect() + except Exception: + pytest.skip(f"Failed to connect to server: {URL}") # Check we are connected assert client._is_connected @@ -183,7 +186,10 @@ def log_final_segment(message): client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) # Connect - await client.connect() + try: + await client.connect() + except Exception: + pytest.skip(f"Failed to connect to server: {URL}") # Check we are connected assert client._is_connected @@ -254,7 +260,10 @@ def log_final_segment(message): client.on(AgentServerMessageType.ADD_SEGMENT, log_final_segment) # Connect - await client.connect() + try: + await client.connect() + except Exception: + pytest.skip(f"Failed to connect to server: {URL}") # Check we are connected assert client._is_connected diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 9a323b08..e77c9e19 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -66,7 +66,7 @@ def log_bytes_sent(bytes): def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message}) messages.append(log) if SHOW_LOG: print(log) @@ -94,16 +94,31 @@ def eot_received_callback(message): log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) # Connect - await client.connect() + try: + await client.connect() + except Exception: + pytest.skip(f"Failed to connect to server: {URL}") # Check we are connected assert client._is_connected - # Individual payloads - await send_audio_file(client, AUDIO_FILE, chunk_size=160, progress_callback=log_bytes_sent) + # Set chunk size + chunk_size = 160 + + # Send words twice with silence in-between + await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) + await send_silence(client, 1.0, chunk_size=chunk_size, progress_callback=log_bytes_sent) + await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) # Send silence in a thread - asyncio.create_task(send_silence(client, 10.0, chunk_size=160, terminate_event=eot_received)) + asyncio.create_task( + send_silence( + client, 10.0, chunk_size=chunk_size, progress_callback=log_bytes_sent, terminate_event=eot_received + ) + ) + + # Wait for 0.25 seconds + await asyncio.sleep(0.25) # Request the speakers result finalize_trigger_time = datetime.datetime.now() @@ -122,6 +137,13 @@ def eot_received_callback(message): print() print() + # Debug result + print(f"{finalize_latency:.2f}ms") + + # Make sure latency is within bounds + assert finalize_latency > 50 + assert finalize_latency < 500 + # Close session await client.disconnect() assert not client._is_connected From 73ab3a12c3eb45ae49eece87db85eaecb35ba095 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 3 Oct 2025 18:10:03 +0100 Subject: [PATCH 086/215] Support for AudioBuffer --- sdk/voice/speechmatics/voice/_audio.py | 172 +++++++++++++++ sdk/voice/speechmatics/voice/_client.py | 41 +++- sdk/voice/speechmatics/voice/_models.py | 8 + tests/voice/.gitignore | 1 + tests/voice/test_11_audio_buffer.py | 277 ++++++++++++++++++++++++ 5 files changed, 489 insertions(+), 10 deletions(-) create mode 100644 sdk/voice/speechmatics/voice/_audio.py create mode 100644 tests/voice/.gitignore create mode 100644 tests/voice/test_11_audio_buffer.py diff --git a/sdk/voice/speechmatics/voice/_audio.py b/sdk/voice/speechmatics/voice/_audio.py new file mode 100644 index 00000000..c200dd4b --- /dev/null +++ b/sdk/voice/speechmatics/voice/_audio.py @@ -0,0 +1,172 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +import asyncio + + +class AudioBuffer: + """Rolling audio buffer. + + A rolling audio buffer that has a set sample_rate, sample_size, + frame_size and total_seconds. As the buffer fills, the oldest + data is removed and the start_time is updated. + + The function get_slice(start_time, end_time) will return a snapshot + ot the data between the start_time and end_time. If the start_time is + before the start of the buffer, then the start_time will be set to the + start of the buffer. If the end_time is after the end of the buffer, + then the end_time will be set to the end of the buffer. + + Timing is based on the number of bytes added to the buffer. + + The buffer is thread-safe and can be used from multiple threads, using + asyncio locks to ensure thread safety. + + Parameters: + sample_rate: The sample rate of the audio. + frame_size: The frame size of the audio. + total_seconds: The total number of seconds to keep in the buffer. + """ + + def __init__(self, sample_rate: int, frame_size: int, sample_width: int = 2, total_seconds: float = 20.0): + # Store audio format info + self._sample_rate: int = sample_rate + self._sample_width: int = sample_width + self._frame_size: int = frame_size + self._frame_bytes: int = frame_size * sample_width + + # Queue + self._frames: list[bytes] = [] + self._max_frames: int = int(total_seconds * (sample_rate / frame_size)) + self._lock = asyncio.Lock() + + # Under / overflow + self._buffer: bytes = b"" + + # Timing info + self._total_frames: int = 0 + + def _get_time_from_frame(self, frame_index: int) -> float: + """Get the time from a frame index.""" + return frame_index / (self._sample_rate / self._frame_size) + + def _get_frame_from_time(self, time: float) -> int: + """Get the frame index from a time. + + Uses int() with a small epsilon to handle floating-point precision issues + while maintaining consistent truncation behaviour. + """ + return int(time * (self._sample_rate / self._frame_size) + 1e-9) + + async def put_bytes(self, data: bytes) -> None: + """Add data to the buffer. + + Arbitrary length of bytes to save to buffer. Accumulates until there is + a frame size worth of data, then puts a frame into the buffer. + + Arguments: + data: The data frame to add to the buffer. + """ + + # If the right length and buffer zero + if len(data) // self._sample_width == self._frame_size and len(self._buffer) == 0: + return await self.put_frame(data) + + # Add to the buffer + self._buffer += data + + # While the buffer is greater than or equal to the frame size + while len(self._buffer) >= self._frame_bytes: + # Get the frame + frame = self._buffer[: self._frame_bytes] + + # Remove the frame from the buffer + self._buffer = self._buffer[self._frame_bytes :] + + # Put the frame into the queue + await self.put_frame(frame) + + async def put_frame(self, data: bytes) -> None: + """Add data to the buffer. + + New data added to the end of the buffer. The oldest data is removed + to maintain the total number of seconds in the buffer. + + Arguments: + data: The data frame to add to the buffer. + """ + + # Add data to the buffer + async with self._lock: + self._frames.append(data) + self._total_frames += 1 + if len(self._frames) > self._max_frames: + self._frames = self._frames[-self._max_frames :] + + async def get_frames(self, start_time: float, end_time: float) -> bytes: + """Get a slice of the buffer. + + Get a slice of the buffer between the start_time and end_time. + If the start_time is before the start of the buffer, then the + start_time will be set to the start of the buffer. If the end_time + is after the end of the buffer, then the end_time will be set to + the end of the buffer. + + Arguments: + start_time: The start time of the slice. + end_time: The end time of the slice. + + Returns: + The slice of the buffer between the start_time and end_time. + """ + + # Get the slice of the buffer + async with self._lock: + # Get the start and end frame indices (absolute frame numbers) + start_index = self._get_frame_from_time(start_time) + end_index = self._get_frame_from_time(end_time) + + # Calculate the range of frames currently in the buffer + buffer_start_frame = self._total_frames - len(self._frames) + buffer_end_frame = self._total_frames + + # Check if the requested range is entirely outside the buffer + if end_index <= buffer_start_frame or start_index >= buffer_end_frame: + return b"" + + # Clamp the requested range to what's available in the buffer + clamped_start = max(start_index, buffer_start_frame) + clamped_end = min(end_index, buffer_end_frame) + + # Convert absolute frame indices to buffer indices + actual_start_index = clamped_start - buffer_start_frame + actual_end_index = clamped_end - buffer_start_frame + + # Get what frames are available + frames = self._frames[actual_start_index:actual_end_index] + + # Return the joined frames + return b"".join(frames) + + async def reset(self) -> None: + """Reset the buffer.""" + async with self._lock: + self._frames = [] + + @property + def total_frames(self) -> int: + """Get the total number of frames added to the buffer.""" + return self._total_frames + + @property + def total_time(self) -> float: + """Get the total time added to the buffer.""" + return self._get_time_from_frame(self._total_frames) + + @property + def size(self) -> int: + """Get the size of the buffer.""" + return len(self._frames) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 7f8ef093..fff51a1d 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -24,6 +24,7 @@ from speechmatics.rt import TranscriptionConfig from . import __version__ +from ._audio import AudioBuffer from ._logging import get_logger from ._models import AgentClientMessageType from ._models import AgentServerMessageType @@ -139,11 +140,17 @@ def __init__( self._metrics_emitter_task: Optional[asyncio.Task] = None # Audio sampling info - self._audio_sample_rate: float = self._audio_format.sample_rate * 1.0 - self._audio_sample_width: float = { - AudioEncoding.PCM_F32LE: 4.0, - AudioEncoding.PCM_S16LE: 2.0, - }.get(self._audio_format.encoding, 1.0) + self._audio_sample_rate: int = self._audio_format.sample_rate + self._audio_sample_width: int = { + AudioEncoding.PCM_F32LE: 4, + AudioEncoding.PCM_S16LE: 2, + }.get(self._audio_format.encoding, 1) + + # Audio buffer + if self._config.enable_audio_buffer: + self._audio_buffer: AudioBuffer = AudioBuffer( + sample_rate=self._audio_format.sample_rate, frame_size=self._audio_format.chunk_size, total_seconds=20.0 + ) # Register handlers self._register_event_handlers() @@ -395,6 +402,10 @@ async def send_audio(self, payload: bytes) -> None: # Send to the AsyncClient await super().send_audio(payload) + # Add to audio buffer (use put_bytes to handle variable chunk sizes) + if self._config.enable_audio_buffer: + await self._audio_buffer.put_bytes(payload) + # Calculate the time (in seconds) for the payload if self._audio_format is not None: self._total_bytes += len(payload) @@ -797,6 +808,9 @@ def finalize(self, ttl: Optional[float] = None) -> None: ttl: Optional delay before finalizing partial segments. """ + if DEBUG_MORE: + self._logger.debug("external finalise() called") + async def emit() -> None: # Yield until the delay has passed if ttl is not None and ttl > 0: @@ -828,9 +842,6 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Lock the speech fragments if self._current_view and self._current_view.segment_count > 0: async with self._speech_fragments_lock: - # Metadata - metadata = {"start_time": self._current_view.start_time, "end_time": self._current_view.end_time} - # Force finalize if finalize: final_segments = self._current_view.segments @@ -845,12 +856,17 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Emit finals first if final_segments: + # Metadata for final segments uses actual start/end times of the segments being emitted + final_metadata = { + "start_time": final_segments[0].start_time, + "end_time": final_segments[-1].end_time, + } self.emit( AgentServerMessageType.ADD_SEGMENT, { "message": AgentServerMessageType.ADD_SEGMENT.value, "segments": [s.model_dump(self._config.include_results) for s in final_segments], - "metadata": metadata, + "metadata": final_metadata, }, ) self._trim_before_time = final_segments[-1].end_time @@ -860,12 +876,17 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Emit interim segments if interim_segments: + # Metadata for partial segments uses actual start/end times of the segments being emitted + partial_metadata = { + "start_time": interim_segments[0].start_time, + "end_time": interim_segments[-1].end_time, + } self.emit( AgentServerMessageType.ADD_PARTIAL_SEGMENT, { "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, "segments": [s.model_dump(self._config.include_results) for s in interim_segments], - "metadata": metadata, + "metadata": partial_metadata, }, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 4115d2eb..e582dc29 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -174,6 +174,9 @@ class VoiceAgentConfig(BaseModel): enable_preview_features: Enable preview features using a preview endpoint provided by Speechmatics. Defaults to False. + enable_audio_buffer: Enable audio buffer to extract slices of recent audio for post-processing + by end of thought models. Defaults to False. + sample_rate: Audio sample rate for streaming. Defaults to 16000. audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. """ @@ -203,6 +206,7 @@ class VoiceAgentConfig(BaseModel): # Advanced features include_results: bool = False enable_preview_features: bool = False + enable_audio_buffer: bool = False # Audio sample_rate: int = 16000 @@ -512,6 +516,10 @@ def model_dump(self, include_results: bool = False, **kwargs: Any) -> dict[str, kwargs["exclude"] = {"fragments"} data: dict[str, Any] = super().model_dump(**kwargs) + # Add timing information + data["start_time"] = self.start_time + data["end_time"] = self.end_time + # Add results if requested if include_results: data["results"] = [f.result for f in self.fragments] diff --git a/tests/voice/.gitignore b/tests/voice/.gitignore new file mode 100644 index 00000000..d36977dc --- /dev/null +++ b/tests/voice/.gitignore @@ -0,0 +1 @@ +.tmp diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py new file mode 100644 index 00000000..30d0cafd --- /dev/null +++ b/tests/voice/test_11_audio_buffer.py @@ -0,0 +1,277 @@ +import asyncio +import os +import random +import shutil +import wave + +import aiofiles +import pytest +from _utils import get_client +from _utils import send_audio_file +from _utils import send_silence + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._audio import AudioBuffer + + +@pytest.mark.asyncio +async def test_clean_tmp(): + """Clear tmp directory""" + + # Output directory + tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp") + + # Clean tmp + if os.path.exists(tmp_dir): + shutil.rmtree(tmp_dir, ignore_errors=True) + + # Re-create + os.makedirs(tmp_dir, exist_ok=True) + assert os.path.exists(tmp_dir) + + +@pytest.mark.asyncio +async def test_buffer(): + """Test AudioBuffer""" + + # Audio info + sample_rate = 16000 + sample_width = 2 + frame_size = 160 + frame_bytes = frame_size * sample_width + + # Create buffer + buffer = AudioBuffer(sample_rate=sample_rate, frame_size=frame_size, sample_width=sample_width, total_seconds=10.0) + + # Check zeros + assert buffer.total_frames == 0 + assert buffer.total_time == 0.0 + assert buffer.size == 0 + + # Add in 20 seconds of data + for _ in range(int(20.0 * sample_rate / frame_size)): + await buffer.put_frame(b"\x00" * frame_bytes) + + # Check values + assert buffer.total_frames == int(20.0 * sample_rate / frame_size) + assert buffer.total_time == 20.0 + assert buffer.size == int(10.0 * sample_rate / frame_size) + + # Check frame >< time conversion + assert buffer._get_frame_from_time(buffer._get_time_from_frame(1234)) == 1234 + + # Get data from more than 10 seconds ago + data = await buffer.get_frames(2.5, 7.5) + assert len(data) == 0 + + # Get a 5 second slice from 12.5 seconds in + data = await buffer.get_frames(12.5, 17.5) + assert len(data) == int(5.0 * sample_rate / frame_size) * frame_bytes + + +@pytest.mark.asyncio +async def test_buffer_bytes(): + """Test AudioBuffer with byte payloads""" + + # Audio info + sample_rate = 16000 + sample_width = 2 + frame_size = 160 + frame_bytes = frame_size * sample_width + + # Create buffer + buffer = AudioBuffer(sample_rate=sample_rate, frame_size=frame_size, sample_width=sample_width, total_seconds=10.0) + + # Check zeros + assert buffer.total_frames == 0 + assert buffer.total_time == 0.0 + assert buffer.size == 0 + + # 20 seconds of frames + twenty_second_frame_count = int(20.0 * sample_rate / frame_size) + + # Fill with random payloads of data + while buffer.total_frames < twenty_second_frame_count - 1: + await buffer.put_bytes(b"\x00" * random.randint(1, frame_bytes)) + + # Add one last frame of zeros + await buffer.put_frame(b"\xff" * frame_bytes) + + # Check values + assert buffer.total_frames == int(20.0 * sample_rate / frame_size) + assert buffer.total_time == 20.0 + assert buffer.size == int(10.0 * sample_rate / frame_size) + + # Check frame >< time conversion + assert buffer._get_frame_from_time(buffer._get_time_from_frame(1234)) == 1234 + + # Get data from more than 10 seconds ago + data = await buffer.get_frames(2.5, 7.5) + assert len(data) == 0 + + # Get a 5 second slice from 12.5 seconds in + data = await buffer.get_frames(12.5, 17.5) + assert len(data) == int(5.0 * sample_rate / frame_size) * frame_bytes + + # Get most recent frame + start_time = buffer.total_time - (frame_size / sample_rate) + end_time = buffer.total_time + + # Extract data + data = await buffer.get_frames(start_time, end_time) + + # Test + assert len(data) == int((end_time - start_time) * sample_rate / frame_size) * frame_bytes + + +@pytest.mark.asyncio +async def test_load_audio_file(): + """Test loading audio file into buffer""" + + # File + file = "audio_01_16kHz" + + # Check file exists + input_file = os.path.join(os.path.dirname(__file__), f"./assets/{file}.wav") + assert os.path.exists(input_file) + + # Output file + output_file = os.path.join(os.path.dirname(__file__), f"./.tmp/{file}_slice.wav") + output_folder = os.path.dirname(output_file) + os.makedirs(output_folder, exist_ok=True) + assert os.path.exists(output_folder) + + # Audio info + sample_rate = 16000 + sample_width = 2 + frame_size = 160 + frame_bytes = frame_size * sample_width + + # Create buffer + buffer = AudioBuffer(sample_rate=sample_rate, frame_size=frame_size, sample_width=sample_width, total_seconds=35.0) + + # Load the file + async with aiofiles.open(input_file, "rb") as wav_file: + await wav_file.seek(44) + while True: + chunk = await wav_file.read(frame_bytes) + if not chunk: + break + await buffer.put_frame(chunk) + + # Check size + assert buffer.total_frames == 2817 + assert buffer.total_time == 28.17 + assert buffer.size == 2817 + + # Slice + slice_start = 3.52 + slice_end = 6.96 + + # Get a 5 second slice + data = await buffer.get_frames(slice_start, slice_end) + + # Lengths - calculate expected using same logic as buffer + start_frame = buffer._get_frame_from_time(slice_start) + end_frame = buffer._get_frame_from_time(slice_end) + + # Check length + assert len(data) == (end_frame - start_frame) * frame_bytes + + # Write bytes to a temporary WAV file + with wave.open(output_file, "wb") as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(sample_rate) + wav_file.writeframes(data) + + +@pytest.mark.asyncio +async def test_transcribe_and_slice(): + """Load, transcribe and slice an audio file""" + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Input file + file = "audio_01_16kHz" + + # Check file exists + input_file = os.path.join(os.path.dirname(__file__), f"./assets/{file}.wav") + assert os.path.exists(input_file) + + # Output directory + output_folder = os.path.join(os.path.dirname(__file__), "./.tmp") + os.makedirs(output_folder, exist_ok=True) + assert os.path.exists(output_folder) + + # Exceptions + exceptions: Exception = [] + + # Save a slice + async def save_slice(start_time: float, end_time: float, prefix: str = "slice") -> None: + try: + output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}.wav") + data = await client._audio_buffer.get_frames(start_time, end_time) + with wave.open(output_file, "wb") as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(client._audio_buffer._sample_rate) + wav_file.writeframes(data) + except Exception as e: + exceptions.append(e) + + # Client + client = await get_client( + api_key=api_key, + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=0.2, + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + enable_diarization=True, + enable_audio_buffer=True, + ), + ) + + # Check audio buffer is enabled + assert client._audio_buffer + + # Bytes logger + def final_segment(message): + try: + segments = message.get("segments", []) + assert segments + + for segment in segments: + start_time = segment["start_time"] + end_time = segment["end_time"] + speaker_id = segment["speaker_id"] + asyncio.create_task(save_slice(start_time, end_time, speaker_id)) + + except Exception as e: + exceptions.append(e) + + # Add listeners + client.on(AgentServerMessageType.ADD_SEGMENT, final_segment) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Send audio + await send_audio_file(client, input_file) + await send_silence(client, 2.0) + + # Close session + await client.disconnect() + assert not client._is_connected + + # Check exceptions + assert not exceptions From ce3e220292ca5703dc635a17701acd3d76ef4c4d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 3 Oct 2025 18:26:40 +0100 Subject: [PATCH 087/215] Updated tests. --- tests/voice/assets/audio_01_16kHz.wav | 4 ++-- tests/voice/test_11_audio_buffer.py | 31 +++++++++++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/voice/assets/audio_01_16kHz.wav b/tests/voice/assets/audio_01_16kHz.wav index 67209392..4bc7b283 100644 --- a/tests/voice/assets/audio_01_16kHz.wav +++ b/tests/voice/assets/audio_01_16kHz.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c744f7e3f3d45fc1d64000670da99c223bcdec348cbf014385eaf98b0913016f -size 901352 +oid sha256:93dbce77fcc7457c87e0559dd7501a2f24861239466f90664decdb6d465818e2 +size 902240 diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index 30d0cafd..439ffecd 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -1,8 +1,10 @@ import asyncio +import json import os import random import shutil import wave +from typing import Optional import aiofiles import pytest @@ -10,6 +12,7 @@ from _utils import send_audio_file from _utils import send_silence +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig @@ -161,11 +164,6 @@ async def test_load_audio_file(): break await buffer.put_frame(chunk) - # Check size - assert buffer.total_frames == 2817 - assert buffer.total_time == 28.17 - assert buffer.size == 2817 - # Slice slice_start = 3.52 slice_end = 6.96 @@ -213,15 +211,20 @@ async def test_transcribe_and_slice(): exceptions: Exception = [] # Save a slice - async def save_slice(start_time: float, end_time: float, prefix: str = "slice") -> None: + async def save_slice( + start_time: float, end_time: float, prefix: str = "slice", json_data: Optional[str] = None + ) -> None: try: - output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}.wav") + output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}") data = await client._audio_buffer.get_frames(start_time, end_time) - with wave.open(output_file, "wb") as wav_file: + with wave.open(f"{output_file}.wav", "wb") as wav_file: wav_file.setnchannels(1) wav_file.setsampwidth(2) wav_file.setframerate(client._audio_buffer._sample_rate) wav_file.writeframes(data) + if json_data: + with open(f"{output_file}.json", "w") as json_file: + json_file.write(json_data) except Exception as e: exceptions.append(e) @@ -235,6 +238,9 @@ async def save_slice(start_time: float, end_time: float, prefix: str = "slice") end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, enable_audio_buffer=True, + dditional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ], ), ) @@ -251,7 +257,14 @@ def final_segment(message): start_time = segment["start_time"] end_time = segment["end_time"] speaker_id = segment["speaker_id"] - asyncio.create_task(save_slice(start_time, end_time, speaker_id)) + asyncio.create_task( + save_slice( + start_time=start_time, + end_time=end_time, + prefix=speaker_id, + json_data=json.dumps(segment, indent=2), + ) + ) except Exception as e: exceptions.append(e) From 4ad3e94568946e571a557c6cfad9237f426c97e4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 5 Oct 2025 16:47:27 +0100 Subject: [PATCH 088/215] Skip audio tests on GitHub. --- tests/voice/test_11_audio_buffer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index 439ffecd..f09ed7a3 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -133,6 +133,11 @@ async def test_buffer_bytes(): async def test_load_audio_file(): """Test loading audio file into buffer""" + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + # File file = "audio_01_16kHz" From 2c8dd685b58e1f1b417a3cd06db19c5fd0ebb323 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 5 Oct 2025 17:53:06 +0100 Subject: [PATCH 089/215] Fade out audio from the buffer (as required). --- sdk/voice/pyproject.toml | 2 +- sdk/voice/speechmatics/voice/_audio.py | 54 ++++++++- tests/voice/assets/audio_04_16kHz.wav | 3 + tests/voice/test_03_conversation.py | 4 + tests/voice/test_11_audio_buffer.py | 2 +- tests/voice/test_12_smart_turn.py | 155 +++++++++++++++++++++++++ 6 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 tests/voice/assets/audio_04_16kHz.wav create mode 100644 tests/voice/test_12_smart_turn.py diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index 9d000517..cd086ab4 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" -dependencies = ["speechmatics-rt", "pydantic"] +dependencies = ["speechmatics-rt", "pydantic>=2.10.6,<3", "numpy>=1.26.4,<3"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/sdk/voice/speechmatics/voice/_audio.py b/sdk/voice/speechmatics/voice/_audio.py index c200dd4b..63b79d48 100644 --- a/sdk/voice/speechmatics/voice/_audio.py +++ b/sdk/voice/speechmatics/voice/_audio.py @@ -6,6 +6,8 @@ import asyncio +import numpy as np + class AudioBuffer: """Rolling audio buffer. @@ -106,7 +108,7 @@ async def put_frame(self, data: bytes) -> None: if len(self._frames) > self._max_frames: self._frames = self._frames[-self._max_frames :] - async def get_frames(self, start_time: float, end_time: float) -> bytes: + async def get_frames(self, start_time: float, end_time: float, fade_out: float = 0) -> bytes: """Get a slice of the buffer. Get a slice of the buffer between the start_time and end_time. @@ -115,9 +117,13 @@ async def get_frames(self, start_time: float, end_time: float) -> bytes: is after the end of the buffer, then the end_time will be set to the end of the buffer. + If a fade out time is specified, then the end of the slice will be + faded out by the specified amount of seconds. + Arguments: start_time: The start time of the slice. end_time: The end time of the slice. + fade_out: The fade out time in seconds. Returns: The slice of the buffer between the start_time and end_time. @@ -148,8 +154,52 @@ async def get_frames(self, start_time: float, end_time: float) -> bytes: # Get what frames are available frames = self._frames[actual_start_index:actual_end_index] + # Bytes + data = b"".join(frames) + + # Fade out + if fade_out > 0: + data = self._fade_out_audio(data, fade_out=fade_out) + # Return the joined frames - return b"".join(frames) + return data + + def _fade_out_audio(self, data: bytes, fade_out: float = 0.01) -> bytes: + """ + Apply a fade-out over the final `fade_out` seconds of PCM audio data. + + Args: + data: Raw PCM audio data as bytes. + fade_out: Duration of fade-out in seconds (e.g., 0.01 = 10 ms). + + Returns: + Bytes with fade-out applied. + """ + # Choose dtype + if self._sample_width == 1: + dtype = np.int8 + elif self._sample_width == 2: + dtype = np.int16 + else: + raise ValueError(f"Unsupported sample_width {self._sample_width}: must be 1 or 2") + + # Convert bytes to NumPy array + samples = np.frombuffer(data, dtype=dtype) + + # Number of samples to fade + fade_samples = int(self._sample_rate * fade_out) + if fade_samples <= 0 or fade_samples > len(samples): + return data + + # Linear fade envelope + envelope = np.linspace(1.0, 0.0, fade_samples, endpoint=True) + + # Apply fade + faded = samples.astype(np.float32) + faded[-fade_samples:] *= envelope + + # Convert back to original dtype and bytes + return bytes(faded.astype(dtype).tobytes()) async def reset(self) -> None: """Reset the buffer.""" diff --git a/tests/voice/assets/audio_04_16kHz.wav b/tests/voice/assets/audio_04_16kHz.wav new file mode 100644 index 00000000..163edcb9 --- /dev/null +++ b/tests/voice/assets/audio_04_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9057a6051415206c0bd9c35c0cfc6cb3bedd1809f4ba7575a82dad29fb995d91 +size 283142 diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index eba8be0f..74430cdc 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -6,6 +6,7 @@ from _utils import get_client from _utils import send_audio_file +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig @@ -36,6 +37,9 @@ async def test_log_messages(): max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ], ), ) diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index f09ed7a3..829f0d6c 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -243,7 +243,7 @@ async def save_slice( end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, enable_audio_buffer=True, - dditional_vocab=[ + additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], ), diff --git a/tests/voice/test_12_smart_turn.py b/tests/voice/test_12_smart_turn.py new file mode 100644 index 00000000..b271aa8d --- /dev/null +++ b/tests/voice/test_12_smart_turn.py @@ -0,0 +1,155 @@ +import asyncio +import json +import os +import shutil +import wave +from typing import Optional + +import pytest +from _utils import get_client +from _utils import send_audio_file +from _utils import send_silence + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig + + +@pytest.mark.asyncio +async def test_clean_tmp(): + """Clear tmp directory""" + + # Output directory + tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp") + + # Clean tmp + if os.path.exists(tmp_dir): + shutil.rmtree(tmp_dir, ignore_errors=True) + + # Re-create + os.makedirs(tmp_dir, exist_ok=True) + assert os.path.exists(tmp_dir) + + +@pytest.mark.asyncio +async def test_transcribe_and_slice(): + """Load, transcribe and slice an audio file""" + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Input file + file = "audio_04_16kHz" + + # Check file exists + input_file = os.path.join(os.path.dirname(__file__), f"./assets/{file}.wav") + assert os.path.exists(input_file) + + # Output directory + output_folder = os.path.join(os.path.dirname(__file__), "./.tmp") + os.makedirs(output_folder, exist_ok=True) + assert os.path.exists(output_folder) + + # Exceptions + exceptions: Exception = [] + + # Save a slice + async def save_slice( + start_time: float, end_time: float, prefix: str = "slice", json_data: Optional[str] = None + ) -> None: + try: + output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}") + data = await client._audio_buffer.get_frames(start_time, end_time, fade_out=0.01) + with wave.open(f"{output_file}.wav", "wb") as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(client._audio_buffer._sample_rate) + wav_file.writeframes(data) + if json_data: + with open(f"{output_file}.json", "w") as json_file: + json_file.write(json_data) + except Exception as e: + exceptions.append(e) + + # Client + client = await get_client( + api_key=api_key, + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=1.0, + max_delay=2.0, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + enable_diarization=True, + enable_audio_buffer=True, + ), + ) + + # Check audio buffer is enabled + assert client._audio_buffer + + # Final segment + def final_segment(message): + try: + segments = message.get("segments", []) + assert segments + + for segment in segments: + start_time = segment["start_time"] + end_time = segment["end_time"] + speaker_id = segment["speaker_id"] + asyncio.create_task( + save_slice( + start_time=start_time, + end_time=end_time, + prefix=f"final_{speaker_id}", + json_data=json.dumps(segment, indent=2), + ) + ) + + except Exception as e: + exceptions.append(e) + + # Bytes logger + def partial_segment(message): + try: + segments = message.get("segments", []) + assert segments + + for segment in segments: + start_time = segment["start_time"] + end_time = segment["end_time"] + speaker_id = segment["speaker_id"] + asyncio.create_task( + save_slice( + start_time=start_time, + end_time=end_time, + prefix=f"partial_{speaker_id}", + json_data=json.dumps(segment, indent=2), + ) + ) + + except Exception as e: + exceptions.append(e) + + # Add listeners + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, partial_segment) + client.on(AgentServerMessageType.ADD_SEGMENT, final_segment) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Send audio + await send_audio_file(client, input_file) + await send_silence(client, 2.0) + + # Close session + await client.disconnect() + assert not client._is_connected + + # Check exceptions + assert not exceptions From bd4b451fcace5a4c5c5017e1f033f17953329c58 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 5 Oct 2025 19:11:55 +0100 Subject: [PATCH 090/215] Updated audio buffer config options. --- sdk/voice/speechmatics/voice/_client.py | 8 +++++--- sdk/voice/speechmatics/voice/_models.py | 6 +++--- tests/voice/assets/audio_04_16kHz.wav | 4 ++-- tests/voice/test_11_audio_buffer.py | 4 ++-- tests/voice/test_12_smart_turn.py | 8 ++++---- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index fff51a1d..8b950595 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -147,9 +147,11 @@ def __init__( }.get(self._audio_format.encoding, 1) # Audio buffer - if self._config.enable_audio_buffer: + if self._config.audio_buffer_length > 0: self._audio_buffer: AudioBuffer = AudioBuffer( - sample_rate=self._audio_format.sample_rate, frame_size=self._audio_format.chunk_size, total_seconds=20.0 + sample_rate=self._audio_format.sample_rate, + frame_size=self._audio_format.chunk_size, + total_seconds=self._config.audio_buffer_length, ) # Register handlers @@ -403,7 +405,7 @@ async def send_audio(self, payload: bytes) -> None: await super().send_audio(payload) # Add to audio buffer (use put_bytes to handle variable chunk sizes) - if self._config.enable_audio_buffer: + if self._config.audio_buffer_length > 0: await self._audio_buffer.put_bytes(payload) # Calculate the time (in seconds) for the payload diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index e582dc29..8cf3f7d7 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -174,8 +174,8 @@ class VoiceAgentConfig(BaseModel): enable_preview_features: Enable preview features using a preview endpoint provided by Speechmatics. Defaults to False. - enable_audio_buffer: Enable audio buffer to extract slices of recent audio for post-processing - by end of thought models. Defaults to False. + audio_buffer_length: Length of audio buffer to extract slices of recent audio for post-processing + by end of thought models. Defaults to 0.0 seconds. sample_rate: Audio sample rate for streaming. Defaults to 16000. audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. @@ -206,7 +206,7 @@ class VoiceAgentConfig(BaseModel): # Advanced features include_results: bool = False enable_preview_features: bool = False - enable_audio_buffer: bool = False + audio_buffer_length: float = 0.0 # Audio sample_rate: int = 16000 diff --git a/tests/voice/assets/audio_04_16kHz.wav b/tests/voice/assets/audio_04_16kHz.wav index 163edcb9..e208d440 100644 --- a/tests/voice/assets/audio_04_16kHz.wav +++ b/tests/voice/assets/audio_04_16kHz.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9057a6051415206c0bd9c35c0cfc6cb3bedd1809f4ba7575a82dad29fb995d91 -size 283142 +oid sha256:3727bb0e0d9aef2b60c512ff5c49b8b04eaecee142db495fddf2d604c75b337f +size 324014 diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index 829f0d6c..c16b839c 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -238,11 +238,11 @@ async def save_slice( api_key=api_key, connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.2, + end_of_utterance_silence_trigger=0.35, max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, - enable_audio_buffer=True, + audio_buffer_length=20.0, additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], diff --git a/tests/voice/test_12_smart_turn.py b/tests/voice/test_12_smart_turn.py index b271aa8d..d2ef6eec 100644 --- a/tests/voice/test_12_smart_turn.py +++ b/tests/voice/test_12_smart_turn.py @@ -80,9 +80,9 @@ async def save_slice( config=VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, max_delay=2.0, - end_of_utterance_mode=EndOfUtteranceMode.FIXED, + end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, enable_diarization=True, - enable_audio_buffer=True, + audio_buffer_length=8.0, ), ) @@ -111,8 +111,9 @@ def final_segment(message): except Exception as e: exceptions.append(e) - # Bytes logger + # Partial segment def partial_segment(message): + print(message) try: segments = message.get("segments", []) assert segments @@ -145,7 +146,6 @@ def partial_segment(message): # Send audio await send_audio_file(client, input_file) - await send_silence(client, 2.0) # Close session await client.disconnect() From 1927bd3aa66ca5cea0645e249bd2e50ab43436de Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 5 Oct 2025 19:45:16 +0100 Subject: [PATCH 091/215] smart turn work --- tests/voice/test_12_smart_turn.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_12_smart_turn.py b/tests/voice/test_12_smart_turn.py index d2ef6eec..64c6ff86 100644 --- a/tests/voice/test_12_smart_turn.py +++ b/tests/voice/test_12_smart_turn.py @@ -8,7 +8,6 @@ import pytest from _utils import get_client from _utils import send_audio_file -from _utils import send_silence from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode @@ -78,9 +77,9 @@ async def save_slice( api_key=api_key, connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=1.0, + end_of_utterance_silence_trigger=0.7, max_delay=2.0, - end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, enable_diarization=True, audio_buffer_length=8.0, ), @@ -89,8 +88,13 @@ async def save_slice( # Check audio buffer is enabled assert client._audio_buffer + # Log raw messages + def log_raw(message): + print(message) + # Final segment def final_segment(message): + print(message) try: segments = message.get("segments", []) assert segments @@ -135,6 +139,8 @@ def partial_segment(message): exceptions.append(e) # Add listeners + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_raw) + client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_raw) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, partial_segment) client.on(AgentServerMessageType.ADD_SEGMENT, final_segment) From ca83e77420f424524e3b6c587c6f609db50a587e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 7 Oct 2025 10:31:52 +0100 Subject: [PATCH 092/215] Updated audio handling and smart turn work. --- sdk/voice/speechmatics/voice/_audio.py | 1 + sdk/voice/speechmatics/voice/_client.py | 25 +++++++++++- sdk/voice/speechmatics/voice/_models.py | 53 +++++++++++++++++++------ tests/voice/test_12_smart_turn.py | 8 ++-- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_audio.py b/sdk/voice/speechmatics/voice/_audio.py index 63b79d48..8aae3bfd 100644 --- a/sdk/voice/speechmatics/voice/_audio.py +++ b/sdk/voice/speechmatics/voice/_audio.py @@ -176,6 +176,7 @@ def _fade_out_audio(self, data: bytes, fade_out: float = 0.01) -> bytes: Bytes with fade-out applied. """ # Choose dtype + dtype: type[np.signedinteger] if self._sample_width == 1: dtype = np.int8 elif self._sample_width == 2: diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 8b950595..91cedd36 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -39,6 +39,7 @@ from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment +from ._models import TranscriptionUpdatePreset from ._models import VoiceAgentConfig DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] @@ -99,6 +100,26 @@ def __init__( language_pack_info=LanguagePackInfo.model_validate({}), ) + # Change filter to emit segments + self._change_filter: list[AnnotationFlags] = [AnnotationFlags.NEW] + # Full text has changed + if self._config.transcription_update_preset == TranscriptionUpdatePreset.COMPLETE: + self._change_filter.append(AnnotationFlags.UPDATED_FULL) + # Full text and timing have changed + elif self._config.transcription_update_preset == TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING: + self._change_filter.append(AnnotationFlags.UPDATED_FULL) + self._change_filter.append(AnnotationFlags.UPDATED_WORD_TIMINGS) + # Word content only has changed + elif self._config.transcription_update_preset == TranscriptionUpdatePreset.WORDS: + self._change_filter.append(AnnotationFlags.UPDATED_STRIPPED) + # Word content and timing have changed + elif self._config.transcription_update_preset == TranscriptionUpdatePreset.WORDS_PLUS_TIMING: + self._change_filter.append(AnnotationFlags.UPDATED_STRIPPED) + self._change_filter.append(AnnotationFlags.UPDATED_WORD_TIMINGS) + # Timing only has changed + elif self._config.transcription_update_preset == TranscriptionUpdatePreset.TIMING: + self._change_filter.append(AnnotationFlags.UPDATED_WORD_TIMINGS) + # STT message received queue self._stt_message_queue: asyncio.Queue[Callable[[], Awaitable[None]]] = asyncio.Queue() self._stt_queue_task: Optional[asyncio.Task] = None @@ -668,7 +689,7 @@ async def _process_speech_fragments(self) -> None: self._previous_view = self._current_view # Catch no changes - if not changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL): + if not changes.any(*self._change_filter): return # Emit the segments @@ -738,7 +759,7 @@ def _calc_delay_to_end_of_utterance( last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None # If this is NEW or UPDATED_FULL_LCASE - if view_changes.any(AnnotationFlags.NEW, AnnotationFlags.UPDATED_FULL_LCASE): + if view_changes.any(*self._change_filter): """Process the annotation flags to determine how long before sending a final segment.""" # Fallback when using FIXED diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 8cf3f7d7..d4194aa1 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -24,6 +24,16 @@ class EndOfUtteranceMode(str, Enum): ADAPTIVE = "adaptive" +class TranscriptionUpdatePreset(str, Enum): + """Filter options for when to emit changes to transcription.""" + + COMPLETE = "complete" + COMPLETE_PLUS_TIMING = "complete_plus_timing" + WORDS = "words" + WORDS_PLUS_TIMING = "words_plus_timing" + TIMING = "timing" + + class DiarizationFocusMode(str, Enum): """Speaker focus mode for diarization.""" @@ -98,28 +108,28 @@ class VoiceAgentConfig(BaseModel): Parameters: operating_point: Operating point for transcription accuracy vs. latency tradeoff. It is - recommended to use OperatingPoint.ENHANCED for most use cases. Defaults to - OperatingPoint.ENHANCED. + recommended to use `OperatingPoint.ENHANCED` for most use cases. Defaults to + `OperatingPoint.ENHANCED`. - domain: Domain for Speechmatics API. Defaults to None. + domain: Domain for Speechmatics API. Defaults to `None`. language: Language code for transcription. Defaults to `en`. - output_locale: Output locale for transcription, e.g. `en-GB`. Defaults to None. + output_locale: Output locale for transcription, e.g. `en-GB`. Defaults to `None`. max_delay: Maximum delay in seconds for transcription. This forces the STT engine to speed up the processing of transcribed words and reduces the interval between partial - and final results. Lower values can have an impact on accuracy. Defaults to 0.7. + and final results. Lower values can have an impact on accuracy. Defaults to `0.7`. end_of_utterance_silence_trigger: Maximum delay in seconds for end of utterance trigger. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be lower than max_delay. - Defaults to 0.2. + Defaults to `0.2`. end_of_utterance_max_delay: Maximum delay in seconds for end of utterance delay. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be greater than end_of_utterance_silence_trigger. - Defaults to 10.0. + Defaults to `10.0`. end_of_utterance_mode: End of utterance delay mode. When ADAPTIVE is used, the delay can be adjusted on the content of what the most recent speaker has said, such as @@ -136,21 +146,21 @@ class VoiceAgentConfig(BaseModel): punctuation_overrides: Punctuation overrides. This allows you to override the punctuation in the STT engine. This is useful for languages that use different punctuation than English. See documentation for more information. - Defaults to None. + Defaults to `None`. enable_diarization: Enable speaker diarization. When enabled, the STT engine will determine and attribute words to unique speakers. The speaker_sensitivity parameter can be used to adjust the sensitivity of diarization. - Defaults to False. + Defaults to `False`. speaker_sensitivity: Diarization sensitivity. A higher value increases the sensitivity of diarization and helps when two or more speakers have similar voices. - Defaults to 0.5. + Defaults to `0.5`. max_speakers: Maximum number of speakers to detect. This forces the STT engine to cluster words into a fixed number of speakers. It should not be used to limit the number of speakers, unless it is clear that there will only be a known number of speakers. - Defaults to None. + Defaults to `None`. prefer_current_speaker: Prefer current speaker ID. When set to true, groups of words close together are given extra weight to be identified as the same speaker. @@ -174,11 +184,18 @@ class VoiceAgentConfig(BaseModel): enable_preview_features: Enable preview features using a preview endpoint provided by Speechmatics. Defaults to False. + transcription_update_preset: Emit segments when the text content or word timings change. + Options are: `COMPLETE` (emit on changes to text content), `COMPLETE_PLUS_TIMING` + (emit on changes to text content and word timings), `WORDS` (emit on changes to word + content, without punctuation), `WORDS_PLUS_TIMING` (emit on changes to word content + and word timings), and `TIMING` (emit on changes to word timings, not recommended). + Defaults to `TranscriptionUpdatePreset.COMPLETE`. + audio_buffer_length: Length of audio buffer to extract slices of recent audio for post-processing by end of thought models. Defaults to 0.0 seconds. - sample_rate: Audio sample rate for streaming. Defaults to 16000. - audio_encoding: Audio encoding format. Defaults to AudioEncoding.PCM_S16LE. + sample_rate: Audio sample rate for streaming. Defaults to `16000`. + audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. """ # Service configuration @@ -206,6 +223,7 @@ class VoiceAgentConfig(BaseModel): # Advanced features include_results: bool = False enable_preview_features: bool = False + transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE audio_buffer_length: float = 0.0 # Audio @@ -331,6 +349,7 @@ class AnnotationFlags(str, Enum): UPDATED_FINALS = "updated_finals" UPDATED_PARTIALS = "updated_partials" UPDATED_SPEAKERS = "updated_speakers" + UPDATED_WORD_TIMINGS = "updated_word_timings" FINALIZED = "finalized" # Content of segments @@ -695,6 +714,8 @@ def format_segment_text( "text": content, "ts": segment.timestamp, "lang": segment.language, + "start_time": fragments[0].start_time, + "end_time": fragments[-1].end_time, } ) @@ -926,6 +947,12 @@ def compare_views( if view1_stripped_str.lower() != view2_stripped_str.lower(): result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) + # Word timings + view1_timings_str: str = view1.format_view_text(format="|{start_time}-{end_time}|", words_only=True) + view2_timings_str: str = view2.format_view_text(format="|{start_time}-{end_time}|", words_only=True) + if view1_timings_str != view2_timings_str: + result.add(AnnotationFlags.UPDATED_WORD_TIMINGS) + # Partials, finals and speakers if view1.final_count != view2.final_count: result.add(AnnotationFlags.UPDATED_FINALS) diff --git a/tests/voice/test_12_smart_turn.py b/tests/voice/test_12_smart_turn.py index 64c6ff86..8bf20c1f 100644 --- a/tests/voice/test_12_smart_turn.py +++ b/tests/voice/test_12_smart_turn.py @@ -12,6 +12,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import TranscriptionUpdatePreset @pytest.mark.asyncio @@ -77,11 +78,12 @@ async def save_slice( api_key=api_key, connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.7, + end_of_utterance_silence_trigger=0.5, max_delay=2.0, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, enable_diarization=True, audio_buffer_length=8.0, + transcription_update_preset=TranscriptionUpdatePreset.COMPLETE, ), ) @@ -139,8 +141,8 @@ def partial_segment(message): exceptions.append(e) # Add listeners - client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_raw) - client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_raw) + # client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_raw) + # client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_raw) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, partial_segment) client.on(AgentServerMessageType.ADD_SEGMENT, final_segment) From 31f599a410e56e8ac64cc397527f3249f0bf4040 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 7 Oct 2025 10:33:10 +0100 Subject: [PATCH 093/215] Remove smart turn test. --- tests/voice/test_12_smart_turn.py | 163 ------------------------------ 1 file changed, 163 deletions(-) delete mode 100644 tests/voice/test_12_smart_turn.py diff --git a/tests/voice/test_12_smart_turn.py b/tests/voice/test_12_smart_turn.py deleted file mode 100644 index 8bf20c1f..00000000 --- a/tests/voice/test_12_smart_turn.py +++ /dev/null @@ -1,163 +0,0 @@ -import asyncio -import json -import os -import shutil -import wave -from typing import Optional - -import pytest -from _utils import get_client -from _utils import send_audio_file - -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._models import TranscriptionUpdatePreset - - -@pytest.mark.asyncio -async def test_clean_tmp(): - """Clear tmp directory""" - - # Output directory - tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp") - - # Clean tmp - if os.path.exists(tmp_dir): - shutil.rmtree(tmp_dir, ignore_errors=True) - - # Re-create - os.makedirs(tmp_dir, exist_ok=True) - assert os.path.exists(tmp_dir) - - -@pytest.mark.asyncio -async def test_transcribe_and_slice(): - """Load, transcribe and slice an audio file""" - - # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: - pytest.skip("Valid API key required for test") - - # Input file - file = "audio_04_16kHz" - - # Check file exists - input_file = os.path.join(os.path.dirname(__file__), f"./assets/{file}.wav") - assert os.path.exists(input_file) - - # Output directory - output_folder = os.path.join(os.path.dirname(__file__), "./.tmp") - os.makedirs(output_folder, exist_ok=True) - assert os.path.exists(output_folder) - - # Exceptions - exceptions: Exception = [] - - # Save a slice - async def save_slice( - start_time: float, end_time: float, prefix: str = "slice", json_data: Optional[str] = None - ) -> None: - try: - output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}") - data = await client._audio_buffer.get_frames(start_time, end_time, fade_out=0.01) - with wave.open(f"{output_file}.wav", "wb") as wav_file: - wav_file.setnchannels(1) - wav_file.setsampwidth(2) - wav_file.setframerate(client._audio_buffer._sample_rate) - wav_file.writeframes(data) - if json_data: - with open(f"{output_file}.json", "w") as json_file: - json_file.write(json_data) - except Exception as e: - exceptions.append(e) - - # Client - client = await get_client( - api_key=api_key, - connect=False, - config=VoiceAgentConfig( - end_of_utterance_silence_trigger=0.5, - max_delay=2.0, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - enable_diarization=True, - audio_buffer_length=8.0, - transcription_update_preset=TranscriptionUpdatePreset.COMPLETE, - ), - ) - - # Check audio buffer is enabled - assert client._audio_buffer - - # Log raw messages - def log_raw(message): - print(message) - - # Final segment - def final_segment(message): - print(message) - try: - segments = message.get("segments", []) - assert segments - - for segment in segments: - start_time = segment["start_time"] - end_time = segment["end_time"] - speaker_id = segment["speaker_id"] - asyncio.create_task( - save_slice( - start_time=start_time, - end_time=end_time, - prefix=f"final_{speaker_id}", - json_data=json.dumps(segment, indent=2), - ) - ) - - except Exception as e: - exceptions.append(e) - - # Partial segment - def partial_segment(message): - print(message) - try: - segments = message.get("segments", []) - assert segments - - for segment in segments: - start_time = segment["start_time"] - end_time = segment["end_time"] - speaker_id = segment["speaker_id"] - asyncio.create_task( - save_slice( - start_time=start_time, - end_time=end_time, - prefix=f"partial_{speaker_id}", - json_data=json.dumps(segment, indent=2), - ) - ) - - except Exception as e: - exceptions.append(e) - - # Add listeners - # client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_raw) - # client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_raw) - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, partial_segment) - client.on(AgentServerMessageType.ADD_SEGMENT, final_segment) - - # Connect - await client.connect() - - # Check we are connected - assert client._is_connected - - # Send audio - await send_audio_file(client, input_file) - - # Close session - await client.disconnect() - assert not client._is_connected - - # Check exceptions - assert not exceptions From 7e06a57d253fe804060ffebfa466b2e1fb7df553 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 7 Oct 2025 12:22:36 +0100 Subject: [PATCH 094/215] Fix for asyncio error on message queue at end of session. --- sdk/voice/speechmatics/voice/_client.py | 34 ++++++-- tests/voice/test_06_stt_config.py | 9 ++ tests/voice/test_06_thought.py | 103 ----------------------- tests/voice/test_08_multiple_speakers.py | 9 +- 4 files changed, 42 insertions(+), 113 deletions(-) create mode 100644 tests/voice/test_06_stt_config.py delete mode 100644 tests/voice/test_06_thought.py diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 91cedd36..3fd7e4e7 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -364,9 +364,22 @@ async def disconnect(self) -> None: self._is_ready_for_audio = False self._stop_metrics_task() - # Stop the task + # Stop the finalize task + if self._finalize_task: + self._finalize_task.cancel() + try: + await self._finalize_task + except asyncio.CancelledError: + pass + self._finalize_task = None + + # Stop the STT queue task if self._stt_queue_task: self._stt_queue_task.cancel() + try: + await self._stt_queue_task + except asyncio.CancelledError: + pass self._stt_queue_task = None def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: @@ -388,9 +401,9 @@ def _start_stt_queue(self) -> None: async def _run_stt_queue(self) -> None: """Run the STT message queue.""" while True: - callback = await self._stt_message_queue.get() - try: + callback = await self._stt_message_queue.get() + if asyncio.iscoroutine(callback): await callback elif asyncio.iscoroutinefunction(callback): @@ -400,6 +413,12 @@ async def _run_stt_queue(self) -> None: if asyncio.iscoroutine(result): await result + except asyncio.CancelledError: + self._logger.debug("STT queue task cancelled") + return + except RuntimeError: + self._logger.debug("STT queue event loop closed") + return except Exception: self._logger.warning("Exception in STT message queue", exc_info=True) @@ -710,9 +729,12 @@ async def _process_speech_fragments(self) -> None: # Emit finalized segments async def emit(delay: float | None) -> None: - if delay and delay > 0: - await asyncio.sleep(delay) - asyncio.create_task(self._emit_segments(finalize=True, end_of_turn=True)) + try: + if delay and delay > 0: + await asyncio.sleep(delay) + await self._emit_segments(finalize=True, end_of_turn=True) + except asyncio.CancelledError: + pass # Finalize after delay if should_emit: diff --git a/tests/voice/test_06_stt_config.py b/tests/voice/test_06_stt_config.py new file mode 100644 index 00000000..124c0825 --- /dev/null +++ b/tests/voice/test_06_stt_config.py @@ -0,0 +1,9 @@ + +import pytest + + +@pytest.mark.asyncio +async def test_no_partials(): + """Tests for STT config (no partials).""" + + pass diff --git a/tests/voice/test_06_thought.py b/tests/voice/test_06_thought.py deleted file mode 100644 index 15cdcf7e..00000000 --- a/tests/voice/test_06_thought.py +++ /dev/null @@ -1,103 +0,0 @@ -import asyncio -import datetime -import os -from typing import Any -from typing import Optional - -import pytest -from _utils import ConversationLog -from _utils import get_client - -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentConfig - - -@pytest.mark.skip(reason="Not fully implemented!") -@pytest.mark.asyncio -async def test_end_of_thought(): - """Use inference for end of thought. - - - send the utterance - - predict end of thought - """ - - # Test conversation - log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] - ) - - # Start time - start_time = datetime.datetime.now() - - # Adaptive timeout - adaptive_timeout = 0.5 - - # Create a client - client = await get_client( - api_key="NONE", - connect=False, - config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - enable_diarization=False, - ), - ) - assert client is not None - - # Start the queue - client._start_stt_queue() - - # Event to wait - had_speaking_ended: asyncio.Event = asyncio.Event() - last_message: Optional[dict[str, Any]] = None - - # Message receiver - def message_rx(message: dict[str, Any]): - nonlocal last_message - last_message = message - - # Send a message from the conversation - async def send_message(idx: int, count: int = 1, use_ttl: bool = True): - for i in range(count): - # Get the message from the chat - message = chat[idx + i] - - # Wait for TTL to expire - if use_ttl: - ttl = (start_time + datetime.timedelta(seconds=message["ts"])) - datetime.datetime.now() - if ttl.total_seconds() > 0: - await asyncio.sleep(ttl.total_seconds()) - else: - await asyncio.sleep(0.005) - - # Emit the message - client.emit(message["payload"]["message"], message["payload"]) - - # Add listener for first interim segment - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, message_rx) - client.once(AgentServerMessageType.SPEAKER_ENDED, lambda message: had_speaking_ended.set()) - - # Inject conversation - await send_message(0, count=12, use_ttl=False) - - # Wait for EndOfUtterance - try: - await asyncio.wait_for(had_speaking_ended.wait(), timeout=10.0) - except asyncio.TimeoutError: - pytest.fail("SPEAKER_ENDED event was not received within 10 seconds") - - # Check the last message was expected - assert last_message is not None - assert last_message.get("message") == AgentServerMessageType.ADD_PARTIAL_SEGMENT - - # Segment - segment = last_message.get("segments", [])[0] - assert segment is not None - - # Debug - # print(segment["text"]) - - # Stop the queue - client._stop_stt_queue() diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index 7bdbd579..b28f1993 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -37,14 +37,14 @@ class SpeakerTest(BaseModel): id="multiple_speakers", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill", "Rickmansworth"], + segment_regex=["^Welcome", "Buckingham", "clarify", "Notting Hill", "Rickmansworth"], speakers_present=["S1", "S2"], ), SpeakerTest( id="focus_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "Buckingham", "please repeat or clarify", "Notting Hill"], + segment_regex=["^Welcome", "Buckingham", "clarify", "Notting Hill"], speaker_config=DiarizationSpeakerConfig( focus_speakers=["S2"], ), @@ -65,7 +65,7 @@ class SpeakerTest(BaseModel): id="ignore_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "please repeat or clarify", "Rickmansworth"], + segment_regex=["^Welcome", "clarify", "Rickmansworth"], speaker_config=DiarizationSpeakerConfig( ignore_speakers=["S2"], ), @@ -186,7 +186,8 @@ def log_final_segment(message): # Check final segments against regex for idx, _test in enumerate(sample.segment_regex): - assert re.search(_test, final_segments[idx].get("text")) + print(f"`{_test}` -> `{final_segments[idx].get('text')}`") + assert re.search(_test, final_segments[idx].get("text"), flags=re.IGNORECASE | re.MULTILINE) # Check only speakers present speakers = [segment.get("speaker_id") for segment in final_segments] From 36746f7446d0d56f1323b552b3273493612c5126 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 7 Oct 2025 17:03:00 +0100 Subject: [PATCH 095/215] Update for new Speaker ID format. --- sdk/voice/speechmatics/voice/_client.py | 4 +++- tests/voice/test_06_stt_config.py | 1 - tests/voice/test_09_speaker_id.py | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 3fd7e4e7..cb329613 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -228,7 +228,9 @@ def _process_config( if config.prefer_current_speaker is not None: dz_cfg["prefer_current_speaker"] = config.prefer_current_speaker if config.known_speakers: - dz_cfg["speakers"] = {s.label: s.speaker_identifiers for s in config.known_speakers} + dz_cfg["speakers"] = [ + {"label": s.label, "speaker_identifiers": s.speaker_identifiers} for s in config.known_speakers + ] if config.max_speakers is not None: dz_cfg["max_speakers"] = config.max_speakers if dz_cfg: diff --git a/tests/voice/test_06_stt_config.py b/tests/voice/test_06_stt_config.py index 124c0825..cc7b3cac 100644 --- a/tests/voice/test_06_stt_config.py +++ b/tests/voice/test_06_stt_config.py @@ -1,4 +1,3 @@ - import pytest diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 7a188bb0..37363846 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -78,13 +78,20 @@ def log_message(message): # Log speakers result def save_speakers_result(message): - for label, speaker_identifiers in message.get("results", {}).items(): + for speaker in message.get("speakers", []): + label: str = speaker.get("label") + speaker_identifiers: list[str] = speaker.get("speaker_identifiers", []) + + if not label or not speaker_identifiers: + continue + speaker_ids.append( DiarizationKnownSpeaker( label=label, speaker_identifiers=speaker_identifiers, ) ) + speakers_event_received.set() # Add listeners From dd05b2a1987004ee2e22284fe7cd4416f6db7f89 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 9 Oct 2025 11:44:31 +0100 Subject: [PATCH 096/215] try and use env variable for tests --- tests/voice/test_09_speaker_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 37363846..867c0341 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -16,7 +16,7 @@ from speechmatics.voice._models import SpeakerSegment API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = "wss://preview.rt.speechmatics.com/v2" +URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] speaker_ids: list[DiarizationKnownSpeaker] = [] From ef45b65e966b61280266c1a51b2cfcfd56e7f449 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 9 Oct 2025 15:54:51 +0100 Subject: [PATCH 097/215] fix to sdout in examples --- examples/voice/microphone_transcription.py | 4 ++-- examples/voice/utils.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py index d8293f18..583a0e90 100644 --- a/examples/voice/microphone_transcription.py +++ b/examples/voice/microphone_transcription.py @@ -22,11 +22,11 @@ async def main() -> None: """ - Live microphone transcription with speaker diarisation. + Live microphone transcription with speaker diarization. This example demonstrates: - Real-time microphone input with device selection - - Speaker diarisation and voice activity detection + - Speaker diarization and voice activity detection - Performance metrics (TTFB) - Works with any PyAudio-compatible input device """ diff --git a/examples/voice/utils.py b/examples/voice/utils.py index 9cc1f3d1..342a2bcc 100644 --- a/examples/voice/utils.py +++ b/examples/voice/utils.py @@ -1,4 +1,5 @@ import logging +import sys import pyaudio @@ -189,7 +190,7 @@ class CustomTextFormatter(logging.Formatter): CustomLevels.FINAL: "\033[33m", # Yellow CustomLevels.SPEAKER: "\033[36m", # Cyan } - RESET = "\033[0m" + RESET = "\033[0m\r" def format(self, record): colour = self.COLOURS.get(record.levelno, self.RESET) @@ -199,7 +200,7 @@ def format(self, record): def get_logger(name: str) -> logging.Logger: """Setup coloured logger for transcription events.""" - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.INFO, stream=sys.stdout) logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) From d850e3e6ccb245a40d3658bd8855c21d8be92211 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 12:16:49 +0100 Subject: [PATCH 098/215] close the session and emit final messages --- sdk/voice/speechmatics/voice/_client.py | 30 +++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index cb329613..c5bbff44 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -347,25 +347,15 @@ async def disconnect(self) -> None: if not self._is_connected: return + # Stop audio and metrics tasks + self._is_ready_for_audio = False + self._stop_metrics_task() + # Send end of transcript to release resources await self.send_message( {"message": ClientMessageType.END_OF_STREAM, "last_seq_no": self._session.sequence_number} ) - # Disconnect from API - try: - await asyncio.wait_for(self.close(), timeout=5.0) - except asyncio.TimeoutError: - self._logger.warning(f"{self} Timeout while closing Speechmatics client connection") - raise - except Exception as e: - self._logger.error(f"{self} Error closing Speechmatics client: {e}") - raise - finally: - self._is_connected = False - self._is_ready_for_audio = False - self._stop_metrics_task() - # Stop the finalize task if self._finalize_task: self._finalize_task.cancel() @@ -384,6 +374,18 @@ async def disconnect(self) -> None: pass self._stt_queue_task = None + # Wait for the EndOfTranscript event to be received + try: + await asyncio.wait_for(self._session_done_evt.wait(), timeout=2.0) + except asyncio.TimeoutError: + self._logger.warning(f"{self} Timeout while closing Speechmatics client connection") + raise + except Exception as e: + self._logger.error(f"{self} Error closing Speechmatics client: {e}") + raise + finally: + self._is_connected = False + def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """Update the diarization configuration. From 51181ef24fcd457e0506d919fc1bdef4936ab65f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 12:17:03 +0100 Subject: [PATCH 099/215] speaker id example --- examples/voice/speaker_id/README.md | 96 +++++++++++ examples/voice/speaker_id/main.py | 253 ++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 examples/voice/speaker_id/README.md create mode 100644 examples/voice/speaker_id/main.py diff --git a/examples/voice/speaker_id/README.md b/examples/voice/speaker_id/README.md new file mode 100644 index 00000000..f25203bd --- /dev/null +++ b/examples/voice/speaker_id/README.md @@ -0,0 +1,96 @@ +# Realtime speaker identification + +Speechmatics STT engine is able to provide labels and identifiers for speakers in a session which can be used in subsequent sessions to train the engine on known speakers. Speaker identifiers are encrypted and specific to your Speechmatics API key and are not transferable to other API keys. Multiple identifiers can be used to train the engine on multiple speakers. + +For more information on speaker ID, see the [Speechmatics documentation](https://docs.speechmatics.com//speech-to-text/realtime/realtime-speaker-identification). + +### Enrolling one or more speakers + +Speaker enrolment can be requested at any point during a conversation. It is recommended that individual speakers have spoken at least 20 words so that the engine can settle and identify them using characteristics from their voice. The example `enrol.py` requests the speaker identifiers to be emitted at the end of the session. + +To request the speaker identifiers during a session, you call the `send_message` function. This will then return a `SPEAKERS_RESULT` event. + +```python +# listen for speakers result +client.on(AgentServerMessageType.SPEAKERS_RESULT, handler) + +# request speaker ID information at the end of the session +await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) + +# request speaker ID information up to this point +await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": False}) +``` + +### Using known speakers + +Speaker identifiers can be used in subsequent sessions to train the engine on known speakers. + +```python +# create a set of known speakers +known_speakers: list[DiarizationKnownSpeaker] = [ + DiarizationKnownSpeaker( + label="John", + speaker_identifiers=["XX...XX"] + ), + DiarizationKnownSpeaker( + label="Jane", + speaker_identifiers=["YY...YY"] + ) +] + +# create a config with known speakers +VoiceAgentConfig( + known_speakers=known_speakers, +) +``` + +## Requirements + +You will need to have a Speechmatics API key, available from our [portal](https://portal.speechmatics.com/). Use the API key either as an environment variable (`SPEECHMATICS_API_KEY`) or as command line parameter (`--api-key`). + +Install the dependencies for the examples, as shown in [example README](../README.md). + +## Enrolling example + +To enrol a speaker, run the following example with the `--enrol` argument. + +```shell +# run the example with API key +python ./examples/voice/speaker_id/main.py \ + --api-key \ + --enrol +``` + +Once you have finished speaking, **press any key** and this will show the speaker information in the console. + +```json +[{ "label": "S1", "speaker_identifiers": ["XX...XX"] }] +``` + +## Known speaker example + +To use one or more known speakers, use the following example with the `--speakers` argument. It is recommended to NOT use the labels `S1`, `S2`, etc. as these are reserved for the engine to use. + +The SDK is also configured that any transcription from a speaker with the label that is in the format `__XXX__` will be ignored. + +```shell +# run the example with API key +python ./examples/voice/speaker_id/main.py \ + --api-key \ + --speakers +``` + +Where `` is a JSON payload string of the form: + +```json +[ + { "label": "John", "speaker_identifiers": ["XX...XX"] }, + { "label": "Jane", "speaker_identifiers": ["YY...YY"] } +] +``` + +When you speak, you will now see the enrolled speaker information in the console. + +``` +2025-10-10 12:00:00.000 FINAL 💬 Partial: ['@John: Hello!'] +``` diff --git a/examples/voice/speaker_id/main.py b/examples/voice/speaker_id/main.py new file mode 100644 index 00000000..c0222048 --- /dev/null +++ b/examples/voice/speaker_id/main.py @@ -0,0 +1,253 @@ +import argparse +import asyncio +import json +import os +import sys +import termios +import tty +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from colorama import init as colorama_init +from utils import CustomLevels +from utils import get_logger +from utils import select_audio_device + +from speechmatics.rt import Microphone +from speechmatics.voice import AgentClientMessageType +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationKnownSpeaker +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + +colorama_init(autoreset=True) + +logger = get_logger(__name__) + + +async def main() -> None: + """ + Live microphone transcription with speaker diarization and speaker ID extraction. + + This example demonstrates: + - Real-time microphone input with device selection + - Speaker diarization and voice activity detection + - Works with any PyAudio-compatible input device + - At the end of the session the speaker ID is shown in the console + """ + # Parse command line arguments + args = parse_args() + + # Audio configuration for real-time processing + sample_rate = args.sample_rate + chunk_size = args.chunk_size + + # Setup microphone with user device selection + mic = _setup_microphone(sample_rate, chunk_size) + if not mic: + return + + # Known speakers + known_speakers: list[DiarizationKnownSpeaker] = ( + [DiarizationKnownSpeaker(**s) for s in args.speakers] if args.speakers else [] + ) + + # Configure Voice Agent with microphone-specific settings + config = VoiceAgentConfig( + sample_rate=sample_rate, + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, + max_delay=args.max_delay, + enable_diarization=True, + end_of_utterance_mode=args.end_of_utterance_mode, + known_speakers=known_speakers, + ) + + # Create Voice Agent client and start transcription + async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: + # Register event handlers for real-time transcription events + _register_event_handlers(client, logger) + + try: + # User instructions + print("Microphone ready - speak now... (Press any key to stop)\n") + + # Connect to the Voice Agent service + await client.connect() + + # Request speaker IDs at the end of the session + if args.enrol: + await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) + + # Start streaming microphone audio in a task + streaming_task = asyncio.create_task(_stream_microphone(mic, client, chunk_size)) + keypress_task = asyncio.create_task(_wait_for_keypress()) + + # Wait for either task to complete (keypress or error in streaming) + done, pending = await asyncio.wait([streaming_task, keypress_task], return_when=asyncio.FIRST_COMPLETED) + + # End the session + await client.disconnect() + + # Cancel any remaining tasks + for task in pending: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + except asyncio.CancelledError: + print("\nTranscription cancelled") + except Exception as e: + print(f"Error: {e}") + + +def _register_event_handlers(client: VoiceAgentClient, logger) -> None: + """Register event handlers for real-time transcription events.""" + + def _format_segment(segment) -> str: + """Format speaker segment for display.""" + template = "@{speaker_id}: {text}" if segment["is_active"] else "@{speaker_id} (background): {text}" + return template.format(**segment) + + @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) + def handle_partial_segments(message): + """Handle partial transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + delay = message.get("delay_to_finalize") + if delay is not None: + logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)") + else: + logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") + + @client.on(AgentServerMessageType.ADD_SEGMENT) + def handle_final_segments(message): + """Handle final transcription segments.""" + segments = [_format_segment(s) for s in message["segments"]] + logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") + + @client.on(AgentServerMessageType.SPEAKER_STARTED) + def handle_speech_started(message): + """Handle speech start events.""" + logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") + + @client.on(AgentServerMessageType.SPEAKER_ENDED) + def handle_speech_ended(message): + """Handle speech end events.""" + logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") + + @client.on(AgentServerMessageType.SPEAKERS_RESULT) + def handle_metrics(message): + """Show the speakers result.""" + if message["speakers"]: + logger.info(f"😀 Speakers: {json.dumps(message['speakers'])}") + + +def _setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: + """Setup microphone with device selection.""" + selected_device = select_audio_device() + + mic = Microphone( + sample_rate=sample_rate, + chunk_size=chunk_size, + device_index=selected_device, + ) + + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return None + return mic + + +async def _stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: + """Stream microphone audio to client.""" + try: + while True: + frame = await mic.read(chunk_size) + await client.send_audio(frame) + except asyncio.CancelledError: + logger.debug("Microphone streaming task cancelled") + raise + + +async def _wait_for_keypress() -> None: + """Wait for any key press in a non-blocking way.""" + loop = asyncio.get_event_loop() + + def _read_key(): + """Read a single key press (Unix/Mac only).""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + return ch + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + # Run the blocking key read in a thread pool + await loop.run_in_executor(None, _read_key) + + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Live microphone transcription with speaker diarization using Speechmatics Voice API", + ) + parser.add_argument( + "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), + help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", + ) + parser.add_argument("--url", help="Speechmatics server URL (optional)") + parser.add_argument("--sample-rate", type=int, default=16000, help="Audio sample rate in Hz (default: 16000)") + parser.add_argument("--chunk-size", type=int, default=320, help="Audio chunk size in bytes (default: 320)") + + # Voice Agent configuration arguments + parser.add_argument( + "--max-delay", + type=float, + default=0.7, + help="Maximum delay for transcription results in seconds (default: 0.7)", + ) + parser.add_argument( + "--end-of-utterance-silence-trigger", + type=float, + default=0.5, + help="Silence duration to trigger end of utterance in seconds (default: 0.5)", + ) + parser.add_argument( + "--end-of-utterance-mode", + choices=["FIXED", "ADAPTIVE"], + default="ADAPTIVE", + help="End of utterance detection mode (default: ADAPTIVE)", + ) + + # Enrolment + parser.add_argument( + "--enrol", + action="store_true", + help="Enroll a speaker (default: False)", + ) + parser.add_argument( + "--speakers", + type=json.loads, + help="Known speakers (default: None)", + ) + + # Process the arguments + args = parser.parse_args() + + # Convert string to EndOfUtteranceMode enum + if args.end_of_utterance_mode == "FIXED": + args.end_of_utterance_mode = EndOfUtteranceMode.FIXED + else: + args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE + + return args + + +if __name__ == "__main__": + asyncio.run(main()) From 9f5a076d23eacc3fcb439cfb97251b4b8571bf60 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 17:53:50 +0100 Subject: [PATCH 100/215] change `_type` to `type_` (pedantic Pydantic!) --- sdk/voice/speechmatics/voice/_client.py | 8 ++++---- sdk/voice/speechmatics/voice/_models.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c5bbff44..47f05737 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -529,7 +529,7 @@ def _calculate_ttfb(self, end_time: float) -> None: end_time: The end time of the payload from the STT engine. """ # Skip if no fragments are words - if len(self._speech_fragments) == 0 or all(f._type != "word" for f in self._speech_fragments): + if len(self._speech_fragments) == 0 or all(f.type_ != "word" for f in self._speech_fragments): return # Get start of the first fragment @@ -597,7 +597,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = end_time=result.get("end_time", 0), language=alt.get("language", "en"), direction=alt.get("direction", "ltr"), - _type=result.get("type", "word"), + type_=result.get("type", "word"), is_eos=result.get("is_eos", False), is_disfluency="disfluency" in alt.get("tags", []), is_punctuation=result.get("type", "") == "punctuation", @@ -977,10 +977,10 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: partial_words = [ frag for frag in fragments - if frag.speaker in self._dz_config.focus_speakers and frag._type == "word" and not frag.is_final + if frag.speaker in self._dz_config.focus_speakers and frag.type_ == "word" and not frag.is_final ] else: - partial_words = [frag for frag in fragments if frag._type == "word" and not frag.is_final] + partial_words = [frag for frag in fragments if frag.type_ == "word" and not frag.is_final] # Evaluate if any valid partial words exist has_valid_partial = len(partial_words) > 0 diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index d4194aa1..aa163a77 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -462,7 +462,7 @@ class SpeechFragment(BaseModel): end_time: End time of the fragment in seconds (from session start). language: Language of the fragment. Defaults to `en`. direction: Direction of the fragment. Defaults to `ltr`. - _type: Type of the fragment. Defaults to `word`. + type_: Type of the fragment. Defaults to `word`. is_eos: Whether the fragment is the end of a sentence. Defaults to `False`. is_final: Whether the fragment is the final fragment. Defaults to `False`. is_disfluency: Whether the fragment is a disfluency. Defaults to `False`. @@ -480,7 +480,7 @@ class SpeechFragment(BaseModel): end_time: float language: str = "en" direction: str = "ltr" - _type: str = "word" + type_: str = "word" is_eos: bool = False is_final: bool = False is_disfluency: bool = False @@ -692,7 +692,7 @@ def format_segment_text( # Select fragments to include if words_only: - fragments = [frag for frag in segment.fragments if frag._type == "word"] + fragments = [frag for frag in segment.fragments if frag.type_ == "word"] else: fragments = segment.fragments @@ -850,7 +850,7 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: penultimate_fragment: Optional[SpeechFragment] = segment.fragments[-2] if segment_length > 1 else None # Count of words - words = [frag for frag in segment.fragments if frag._type == "word"] + words = [frag for frag in segment.fragments if frag.type_ == "word"] word_count = len(words) if word_count == 0: result.add(AnnotationFlags.NO_TEXT) From 62180fb7199f268eb6bae7ce4a08774a224f3f6d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 17:54:11 +0100 Subject: [PATCH 101/215] updates to timer for ADAPTIVE --- sdk/voice/speechmatics/voice/_client.py | 48 ++++++++++++------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 47f05737..98bac1a8 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -141,6 +141,7 @@ def __init__( self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() self._current_view: Optional[SpeakerSegmentView] = None self._previous_view: Optional[SpeakerSegmentView] = None + self._turn_start_time: Optional[float] = None # Speaking states self._is_speaking: bool = False @@ -684,7 +685,10 @@ async def _process_speech_fragments(self) -> None: so the next comparison is based on the remaining + new fragments. """ + # Lock the speech fragments async with self._speech_fragments_lock: + """Creates a new view of the fragments and compares against the last view.""" + # Create a view of the current segments self._update_current_view() @@ -740,10 +744,12 @@ async def emit(delay: float | None) -> None: except asyncio.CancelledError: pass + # Cancel any existing finalize task + if self._finalize_task: + self._finalize_task.cancel() + # Finalize after delay if should_emit: - if self._finalize_task: - self._finalize_task.cancel() self._finalize_task = asyncio.create_task(emit(emit_final_delay)) def _update_current_view(self) -> None: @@ -788,12 +794,8 @@ def _calc_delay_to_end_of_utterance( if view_changes.any(*self._change_filter): """Process the annotation flags to determine how long before sending a final segment.""" - # Fallback when using FIXED - if self._end_of_utterance_mode == EndOfUtteranceMode.FIXED: - emit_final_delay = self._end_of_utterance_delay * 5.0 - - # Timer for when ADAPTIVE - elif self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE and last_active_segment: + # If the last segment is for an active speaker + if last_active_segment: """Check the contents of the last segment." Check for: @@ -817,13 +819,11 @@ def _calc_delay_to_end_of_utterance( if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): multiplier *= 1.5 - # Has a disfluency - if last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - multiplier *= 1.5 - - # Ends with a disfluency + # Disfluencies if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): multiplier *= 4.0 + elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): + multiplier *= 1.5 # Ends with an end of sentence if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): @@ -832,8 +832,6 @@ def _calc_delay_to_end_of_utterance( # Calculate the delay emit_final_delay = delay * multiplier - # TODO - Other checks / end of turn detection - # Constrain to `end_of_utterance_max_delay` if emit_final_delay is not None: emit_final_delay = min(emit_final_delay, self._config.end_of_utterance_max_delay) @@ -857,19 +855,12 @@ def finalize(self, ttl: Optional[float] = None) -> None: ttl: Optional delay before finalizing partial segments. """ - if DEBUG_MORE: - self._logger.debug("external finalise() called") - + # Emit the finalize or use EndOfTurn on demand preview async def emit() -> None: - # Yield until the delay has passed if ttl is not None and ttl > 0: await asyncio.sleep(ttl) - - # Finalize the segments using `Finalize` message to STT if self._config.enable_preview_features: await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) - - # Force finalizing of segments in the buffer else: await self._emit_segments(finalize=True, end_of_turn=True) @@ -899,7 +890,9 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Split between finals and interim segments else: final_segments = [ - s for s in self._current_view.segments if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL) + s + for s in self._current_view.segments + if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) ] interim_segments = [s for s in self._current_view.segments if s not in final_segments] @@ -942,10 +935,14 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Update the current view self._update_current_view() + # Reset the turn start time + if not self._turn_start_time: + self._turn_start_time = self._current_view.start_time + # Emit end of turn if end_of_turn and self._previous_view: # Metadata (for LAST view) - metadata = {"start_time": self._previous_view.start_time, "end_time": self._previous_view.end_time} + metadata = {"start_time": self._turn_start_time, "end_time": self._previous_view.end_time} # Emit self.emit( @@ -958,6 +955,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Reset the previous view self._previous_view = None + self._turn_start_time = None def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. From 2ff55586845326ae5c9f3d305120c4df3341760d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 17:54:51 +0100 Subject: [PATCH 102/215] updated gitignore --- .gitignore | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 96d57095..b0c8376f 100644 --- a/.gitignore +++ b/.gitignore @@ -56,22 +56,6 @@ cover/ *.mo *.pot -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - # PyBuilder .pybuilder/ target/ @@ -172,3 +156,7 @@ cython_debug/ # PyPI configuration file .pypirc + +# Temporary files / directories +.tmp/ +tmp/ From bb969825550714ddc8ccbe3e2a1359fd2d53510e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 10 Oct 2025 18:11:19 +0100 Subject: [PATCH 103/215] updated test with GeoRouter vocab --- tests/voice/test_08_multiple_speakers.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index b28f1993..777213ca 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -10,6 +10,7 @@ from _utils import send_audio_file from pydantic import BaseModel +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationFocusMode from speechmatics.voice import DiarizationSpeakerConfig @@ -37,14 +38,14 @@ class SpeakerTest(BaseModel): id="multiple_speakers", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "Buckingham", "clarify", "Notting Hill", "Rickmansworth"], + segment_regex=["^Welcome to GeoRouter", "Buckingham", "clarify", "Notting Hill", "Rickmansworth"], speakers_present=["S1", "S2"], ), SpeakerTest( id="focus_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "Buckingham", "clarify", "Notting Hill"], + segment_regex=["^Welcome to GeoRouter", "Buckingham", "clarify", "Notting Hill"], speaker_config=DiarizationSpeakerConfig( focus_speakers=["S2"], ), @@ -65,7 +66,7 @@ class SpeakerTest(BaseModel): id="ignore_s2", path="./assets/audio_02_8kHz.wav", sample_rate=8000, - segment_regex=["^Welcome", "clarify", "Rickmansworth"], + segment_regex=["^Welcome to GeoRouter", "clarify", "Rickmansworth"], speaker_config=DiarizationSpeakerConfig( ignore_speakers=["S2"], ), @@ -95,6 +96,9 @@ async def test_multiple_speakers(sample: SpeakerTest): end_of_utterance_silence_trigger=1.0, max_delay=2.0, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + additional_vocab=[ + AdditionalVocabEntry(content="GeoRouter"), + ], ) # Diarization options From ec7348c093c554b1f98c995a41da821d798d5158 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:30:42 +0100 Subject: [PATCH 104/215] smart turn prediction --- sdk/voice/pyproject.toml | 10 +- sdk/voice/speechmatics/voice/_turn.py | 264 ++++++++++++++++++ tests/voice/_utils.py | 16 ++ .../assets/smart_turn/01_false_16kHz.wav | 3 + .../assets/smart_turn/02_false_16kHz.wav | 3 + .../voice/assets/smart_turn/03_true_16kHz.wav | 3 + tests/voice/test_12_smart_turn_with_files.py | 82 ++++++ 7 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 sdk/voice/speechmatics/voice/_turn.py create mode 100644 tests/voice/assets/smart_turn/01_false_16kHz.wav create mode 100644 tests/voice/assets/smart_turn/02_false_16kHz.wav create mode 100644 tests/voice/assets/smart_turn/03_true_16kHz.wav create mode 100644 tests/voice/test_12_smart_turn_with_files.py diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index cd086ab4..799ed74d 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -10,7 +10,13 @@ readme = "README.md" authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" -dependencies = ["speechmatics-rt", "pydantic>=2.10.6,<3", "numpy>=1.26.4,<3"] +dependencies = [ + "speechmatics-rt", + "pydantic>=2.10.6,<3", + "numpy>=1.26.4,<3", + "onnxruntime>=1.23.1,<2", + "transformers>=4.57.0,<5" +] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -63,5 +69,5 @@ version = { attr = "speechmatics.voice.__version__" } where = ["."] [[tool.mypy.overrides]] -module = ["speechmatics.rt.*"] +module = ["speechmatics.rt.*", "onnxruntime.*"] ignore_missing_imports = true diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py new file mode 100644 index 00000000..b5ed292b --- /dev/null +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -0,0 +1,264 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +import datetime +import logging +import os +import urllib.request +from typing import Optional +from urllib.parse import urlparse + +import numpy as np +import onnxruntime as ort +from pydantic import BaseModel +from transformers import WhisperFeatureExtractor + +logger = logging.getLogger(__name__) + +# Disable transformer messages +os.environ["TRANSFORMERS_VERBOSITY"] = "error" + +# Base model from HuggingFace +ONNX_HF_URL = "https://huggingface.co/pipecat-ai/smart-turn-v3/resolve/main/smart-turn-v3.0.onnx" +ONNX_MODEL_PATH = os.getenv("SMART_TURN_MODEL_PATH", ".models/smart-turn-v3.0.onnx") + + +class SmartTurnPredictionResult(BaseModel): + """Prediction result from the smart turn detector. + + Attributes: + prediction: True for complete, False for incomplete + probability: Probability of completion (sigmoid output) + processing_time: Time taken to process the audio (in seconds) + error: Error message if an error occurred + """ + + prediction: bool = False + probability: float = 0.0 + processing_time: Optional[float] = None + error: Optional[str] = None + + +class SmartTurnDetector: + """Smart Turn Detector. + + Uses Pipecat's opensource acoustic model for determining if an audio sample + is predicted to be complete or incomplete. + + Further informtaion at https://github.com/pipecat-ai/smart-turn + """ + + def __init__(self, auto_init: bool = True, threshold: float = 0.8): + """Create the new SmartTurnDetector.""" + + # Has initialized + self._is_initialized: bool = False + + # Threshold + self._threshold: float = threshold + + # If auto_init is True, setup the detector + if auto_init: + self.setup() + + def setup(self) -> None: + """Setup the detector.""" + + try: + # Check / download the model + self.download_model() + + # Build the session + self.session = self.build_session(ONNX_MODEL_PATH) + + # Load the feature extractor + self.feature_extractor = WhisperFeatureExtractor(chunk_length=8) + + # Set initialized + self._is_initialized = True + + except Exception as e: + logger.error(f"Failed to setup SmartTurnDetector: {e}") + + def build_session(self, onnx_path: str) -> ort.InferenceSession: + """Build the ONNX session and load resources. + + Args: + onnx_path: Path to the ONNX model + + Returns: + ort.InferenceSession: ONNX session + """ + + # Build the session + so = ort.SessionOptions() + so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL + so.inter_op_num_threads = 1 + so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL + + # Return the new session + return ort.InferenceSession(onnx_path, sess_options=so) + + async def predict( + self, audio_array: bytes, language: str, sample_rate: int = 16000, sample_width: int = 2 + ) -> SmartTurnPredictionResult: + """Predict whether an audio segment is complete (turn ended) or incomplete. + + Args: + audio_array: Numpy array containing audio samples at 16kHz. The function + will convert the audio into float32 and truncate to 8 seconds (keeping the end) + or pad to 8 seconds. + language: Language of the audio + sample_rate: Sample rate of the audio + sample_width: Sample width of the audio + + Returns: + SmartTurnPredictionResult: Prediction result + """ + + # Check if initialized + if not self._is_initialized: + return SmartTurnPredictionResult(error="SmartTurnDetector is not initialized") + + # Check a valid language + if not self.valid_language(language): + logger.warning(f"Invalid language: {language}.") + return SmartTurnPredictionResult(error=f"Invalid language: {language}") + + # Record start time + start_time = datetime.datetime.now() + + # Convert into numpy array + dtype = np.int16 if sample_width == 2 else np.int8 + int16_array: np.ndarray = np.frombuffer(audio_array, dtype=dtype).astype(np.int16) + + # Process audio using Whisper's feature extractor + inputs = self.feature_extractor( + int16_array, + sampling_rate=sample_rate, + return_tensors="np", + padding="max_length", + max_length=8 * sample_rate, + truncation=True, + do_normalize=True, + ) + + # Extract features and ensure correct shape for ONNX + input_features = inputs.input_features.squeeze(0).astype(np.float32) + input_features = np.expand_dims(input_features, axis=0) + + # Run ONNX inference + outputs = self.session.run(None, {"input_features": input_features}) + + # Extract probability (ONNX model returns sigmoid probabilities) + probability = outputs[0][0].item() + + # Make prediction (True for Complete, False for Incomplete) + prediction = probability >= self._threshold + + # Record end time + end_time = datetime.datetime.now() + + # Return the result + return SmartTurnPredictionResult( + prediction=prediction, + probability=probability, + processing_time=float((end_time - start_time).total_seconds()), + ) + + @staticmethod + def truncate_audio_to_last_n_seconds( + audio_array: np.ndarray, n_seconds: float = 8.0, sample_rate: int = 16000 + ) -> np.ndarray: + """Truncate audio to last n seconds or pad with zeros to meet n seconds. + + Args: + audio_array: Numpy array containing audio samples at 16kHz + n_seconds: Number of seconds to truncate to + sample_rate: Sample rate of the audio + + Returns: + Numpy array containing audio samples at 16kHz truncated to last n seconds + or padded with zeros to meet n seconds + """ + + # Calculate the max samples we should have + max_samples = int(n_seconds * sample_rate) + + # Truncate if longer + if len(audio_array) > max_samples: + return audio_array[-max_samples:] + + # Pad if shorter + elif len(audio_array) < max_samples: + padding = max_samples - len(audio_array) + return np.pad(audio_array, (padding, 0), mode="constant", constant_values=0) + + # Otherwise return the array + return audio_array + + @staticmethod + def download_model() -> None: + """Download the ONNX model. + + This will check if the model has been downloaded and is available in the + location specified by the SMART_TURN_MODEL_PATH environment variable. + + If not, it will download the model from HuggingFace. + """ + + # Check if model file exists + if SmartTurnDetector.model_exists(): + return + + # Check the URL for valid schemes + parsed_url = urlparse(ONNX_HF_URL) + if parsed_url.scheme not in ("http", "https"): + logger.error(f"Invalid URL scheme: {parsed_url.scheme}") + return + + # Report to the user + logger.warning("Smart Turn model not found. Downloading from HuggingFace...") + + # Create the directory + os.makedirs(os.path.dirname(ONNX_MODEL_PATH), exist_ok=True) + + # Download + urllib.request.urlretrieve(ONNX_HF_URL, ONNX_MODEL_PATH) # nosec B310 + + @staticmethod + def model_exists() -> bool: + """Check the model has been downloaded.""" + return os.path.exists(ONNX_MODEL_PATH) + + @staticmethod + def valid_language(language: str) -> bool: + """Check if the language is valid.""" + return language in [ + "ar", + "bn", + "zh", + "da", + "nl", + "de", + "en", + "fi", + "fr", + "hi", + "id", + "it", + "ja", + "ko", + "mr", + "no", + "pl", + "pt", + "ru", + "es", + "tr", + "uk", + "vi", + ] diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index 7c97a664..a1d4019e 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -4,6 +4,7 @@ import re import time import unicodedata +import wave from typing import Any from typing import Callable from typing import Optional @@ -117,6 +118,21 @@ async def send_audio_file( next_time += delay +async def load_audio_file(audio_file: str) -> bytes: + """Load an audio file.""" + + # Make sure file ends with .wav + assert audio_file.lower().endswith(".wav") + + # Check file exists + file = os.path.join(os.path.dirname(__file__), audio_file) + assert os.path.exists(file) + + # Load the file + with wave.open(file, "rb") as wav_file: + return wav_file.readframes(wav_file.getnframes()) + + async def send_silence( client: VoiceAgentClient, duration: float, diff --git a/tests/voice/assets/smart_turn/01_false_16kHz.wav b/tests/voice/assets/smart_turn/01_false_16kHz.wav new file mode 100644 index 00000000..2df00a8a --- /dev/null +++ b/tests/voice/assets/smart_turn/01_false_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff2f716f2b806a7f4a6e10860a5547885d69b26dd000468d221f57fa5b1aa086 +size 82018 diff --git a/tests/voice/assets/smart_turn/02_false_16kHz.wav b/tests/voice/assets/smart_turn/02_false_16kHz.wav new file mode 100644 index 00000000..e9cfe7f8 --- /dev/null +++ b/tests/voice/assets/smart_turn/02_false_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:442f8ae1d0a1f714df4610d8765106ec602f5c77a26169650b33e7b73293cbb8 +size 150280 diff --git a/tests/voice/assets/smart_turn/03_true_16kHz.wav b/tests/voice/assets/smart_turn/03_true_16kHz.wav new file mode 100644 index 00000000..243b359c --- /dev/null +++ b/tests/voice/assets/smart_turn/03_true_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e3721e260f59673de77eb795d683a591f0df23919afb9072f32eebabee9ecc4 +size 236004 diff --git a/tests/voice/test_12_smart_turn_with_files.py b/tests/voice/test_12_smart_turn_with_files.py new file mode 100644 index 00000000..20ca83f6 --- /dev/null +++ b/tests/voice/test_12_smart_turn_with_files.py @@ -0,0 +1,82 @@ +import pytest +from _utils import load_audio_file +from pydantic import BaseModel + +from speechmatics.voice._turn import SmartTurnDetector +from speechmatics.voice._turn import SmartTurnPredictionResult + +# Detector +detector = SmartTurnDetector(auto_init=False, threshold=0.75) + + +class PredictionTest(BaseModel): + id: str + path: str + language: str + expected: SmartTurnPredictionResult + + +SAMPLES: list[PredictionTest] = [ + PredictionTest( + id="01", + path="./assets/smart_turn/01_false_16KHz.wav", + language="en", + expected=SmartTurnPredictionResult( + prediction=False, + probability=0.095, + ), + ), + PredictionTest( + id="02", + path="./assets/smart_turn/02_false_16KHz.wav", + language="en", + expected=SmartTurnPredictionResult( + prediction=False, + probability=0.011, + ), + ), + PredictionTest( + id="03", + path="./assets/smart_turn/03_true_16KHz.wav", + language="en", + expected=SmartTurnPredictionResult( + prediction=True, + probability=0.892, + ), + ), +] + + +@pytest.mark.asyncio +async def test_onnx_model(): + """Download ONNX model""" + + # Initialize + detector.setup() + + # Check exists + assert detector.model_exists() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sample", SAMPLES, ids=lambda s: f"{s.id}:{s.path}") +async def test_prediction(sample: PredictionTest): + """Test prediction""" + + # Load an audio snippet + bytes_array = await load_audio_file(sample.path) + + # Run an inference + result = await detector.predict(bytes_array, language=sample.language, sample_rate=16000, sample_width=2) + + # Processing time < 50ms + assert result.processing_time < 0.05 + + # Check result + assert result.prediction == sample.expected.prediction + + # Prediction within 5% of expected + assert ( + result.probability >= sample.expected.probability - 0.05 + and result.probability <= sample.expected.probability + 0.05 + ) From dcd13cbc5e4ff8b08c9b967a2df7509861d651b4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:32:11 +0100 Subject: [PATCH 105/215] exclude cached models from git --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index b0c8376f..6ad6fbc1 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,8 @@ cython_debug/ # Temporary files / directories .tmp/ tmp/ + +# Model caches +.models/ +.cache/ +*.onnx From 70af0bcd359934882ae738f44665ef229f398e06 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:34:29 +0100 Subject: [PATCH 106/215] catch model not downloaded --- sdk/voice/speechmatics/voice/_turn.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index b5ed292b..78b5cc79 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -71,6 +71,11 @@ def setup(self) -> None: # Check / download the model self.download_model() + # Check the model downloaded + if not self.model_exists(): + logger.warning("Smart Turn model not found. Please download the model first.") + return + # Build the session self.session = self.build_session(ONNX_MODEL_PATH) From 9c91f799bc5deff382653b88a6bbf349d2e75ebb Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:35:43 +0100 Subject: [PATCH 107/215] SMART_TURN_HF_URL env variable to override the default model --- sdk/voice/speechmatics/voice/_turn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index 78b5cc79..c3d1054b 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -22,7 +22,9 @@ os.environ["TRANSFORMERS_VERBOSITY"] = "error" # Base model from HuggingFace -ONNX_HF_URL = "https://huggingface.co/pipecat-ai/smart-turn-v3/resolve/main/smart-turn-v3.0.onnx" +ONNX_HF_URL = os.getenv( + "SMART_TURN_HF_URL", "https://huggingface.co/pipecat-ai/smart-turn-v3/resolve/main/smart-turn-v3.0.onnx" +) ONNX_MODEL_PATH = os.getenv("SMART_TURN_MODEL_PATH", ".models/smart-turn-v3.0.onnx") From f67c8238860ea5dd0047cfad2903681be48397db Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:44:52 +0100 Subject: [PATCH 108/215] lower onnxruntime requirements for python 3.9 --- sdk/voice/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index 799ed74d..287132cb 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "speechmatics-rt", "pydantic>=2.10.6,<3", "numpy>=1.26.4,<3", - "onnxruntime>=1.23.1,<2", + "onnxruntime>=1.19.0,<2", "transformers>=4.57.0,<5" ] classifiers = [ From 9754d7cc861e0ac09142cc26e1649fa8cf31f73c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:51:57 +0100 Subject: [PATCH 109/215] try and skip CI tests (known to fail on GitHub) --- tests/voice/test_09_speaker_id.py | 3 +++ tests/voice/test_10_finalize.py | 1 + tests/voice/test_11_audio_buffer.py | 1 + tests/voice/test_12_smart_turn_with_files.py | 5 +++++ 4 files changed, 10 insertions(+) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 867c0341..a163a3e7 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -15,6 +15,9 @@ from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + API_KEY = os.getenv("SPEECHMATICS_API_KEY") URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index e77c9e19..8b5b0504 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -19,6 +19,7 @@ AUDIO_FILE = "./assets/audio_03_16kHz.wav" +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping in CI") @pytest.mark.asyncio async def test_finalize(): """Test finalization. diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index c16b839c..d16e644e 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -129,6 +129,7 @@ async def test_buffer_bytes(): assert len(data) == int((end_time - start_time) * sample_rate / frame_size) * frame_bytes +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping in CI") @pytest.mark.asyncio async def test_load_audio_file(): """Test loading audio file into buffer""" diff --git a/tests/voice/test_12_smart_turn_with_files.py b/tests/voice/test_12_smart_turn_with_files.py index 20ca83f6..e708683b 100644 --- a/tests/voice/test_12_smart_turn_with_files.py +++ b/tests/voice/test_12_smart_turn_with_files.py @@ -1,3 +1,5 @@ +import os + import pytest from _utils import load_audio_file from pydantic import BaseModel @@ -5,6 +7,9 @@ from speechmatics.voice._turn import SmartTurnDetector from speechmatics.voice._turn import SmartTurnPredictionResult +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + # Detector detector = SmartTurnDetector(auto_init=False, threshold=0.75) From b9050423f80a24cfe9a54a0c76c17277d85df38d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:56:33 +0100 Subject: [PATCH 110/215] updated comments in tests --- tests/voice/test_02_transcriber.py | 4 ++++ tests/voice/test_03_conversation.py | 4 ++++ tests/voice/test_07_languages.py | 4 ++++ tests/voice/test_08_multiple_speakers.py | 4 ++++ tests/voice/test_09_speaker_id.py | 2 ++ tests/voice/test_10_finalize.py | 5 ++++- 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index 3fe49b00..a1992dd8 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -9,6 +9,10 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import VoiceAgentConfig +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 74430cdc..246dc520 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -11,6 +11,10 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 184603de..abb4cf3b 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -18,6 +18,10 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index 777213ca..ffa79521 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -18,6 +18,10 @@ from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index a163a3e7..b75bd200 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -18,10 +18,12 @@ # Skip for CI testing pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] +# List of know speakers during tests speaker_ids: list[DiarizationKnownSpeaker] = [] diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 8b5b0504..ce092eaa 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -13,13 +13,16 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + +# Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") URL: Optional[str] = "wss://jamesw.lab.speechmatics.io/v2" SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] AUDIO_FILE = "./assets/audio_03_16kHz.wav" -@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping in CI") @pytest.mark.asyncio async def test_finalize(): """Test finalization. From c81be68e2f8f6bbbd926d266fe2d815636cc1655 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 11 Oct 2025 16:59:00 +0100 Subject: [PATCH 111/215] updated warnings --- tests/voice/test_02_transcriber.py | 2 +- tests/voice/test_03_conversation.py | 2 +- tests/voice/test_07_languages.py | 2 +- tests/voice/test_08_multiple_speakers.py | 2 +- tests/voice/test_09_speaker_id.py | 2 +- tests/voice/test_10_finalize.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index a1992dd8..ea1f5d42 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -10,7 +10,7 @@ from speechmatics.voice import VoiceAgentConfig # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping transcriber tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 246dc520..1cd59df8 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -12,7 +12,7 @@ from speechmatics.voice import VoiceAgentConfig # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping transcription tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index abb4cf3b..efb5c53c 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -19,7 +19,7 @@ from speechmatics.voice import VoiceAgentConfig # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping language tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index ffa79521..aa7b2ab2 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -19,7 +19,7 @@ from speechmatics.voice._models import SpeakerSegment # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping diarization tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index b75bd200..723f5a11 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -16,7 +16,7 @@ from speechmatics.voice._models import SpeakerSegment # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping speaker id tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index ce092eaa..2433c97e 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -14,7 +14,7 @@ from speechmatics.voice import VoiceAgentConfig # Skip for CI testing -pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping finalization tests in CI") # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") From 5a3ed4e0ee8ccf730f2f873daa62090035e86b1d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 00:27:48 +0100 Subject: [PATCH 112/215] add `time` to speaker started / ended event --- sdk/voice/speechmatics/voice/_client.py | 26 +++++++++++++++++++++---- sdk/voice/speechmatics/voice/_models.py | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 98bac1a8..a6266092 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -146,7 +146,7 @@ def __init__( # Speaking states self._is_speaking: bool = False self._current_speaker: Optional[str] = None - self._last_vad_time: float = 0 + self._last_fragment_end_time: float = 0 # Diarization / speaker focus self._end_of_utterance_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode @@ -635,6 +635,9 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Add the fragment fragments.append(fragment) + # Track the last fragment end time + self._last_fragment_end_time = max(self._last_fragment_end_time, fragment.end_time) + # Evaluate for VAD (only done on partials) if not is_final: self._vad_evaluation(fragments) @@ -991,6 +994,12 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: speaker = partial_words[-1].speaker if has_valid_partial else self._current_speaker speaker_changed = speaker != current_speaker and current_speaker is not None + # Start time (taken from the first partial word) + speaker_start_time = partial_words[0].start_time if has_valid_partial else None + + # End time (taken from the last partial final) + speaker_end_time = self._last_fragment_end_time + # If diarization is enabled, indicate speaker switching if self._dz_enabled and speaker is not None: """When enabled, we send a speech events if the speaker has changed. @@ -1005,14 +1014,18 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: AgentServerMessageType.SPEAKER_ENDED, { "message": AgentServerMessageType.SPEAKER_ENDED.value, - "status": SpeakerVADStatus(speaker_id=current_speaker, is_active=False).model_dump(), + "status": SpeakerVADStatus( + speaker_id=current_speaker, is_active=False, time=speaker_end_time + ).model_dump(), }, ) self.emit( AgentServerMessageType.SPEAKER_STARTED, { "message": AgentServerMessageType.SPEAKER_STARTED.value, - "status": SpeakerVADStatus(speaker_id=speaker, is_active=True).model_dump(), + "status": SpeakerVADStatus( + speaker_id=speaker, is_active=True, time=speaker_end_time + ).model_dump(), }, ) @@ -1026,6 +1039,9 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Set the speaking state self._is_speaking = not self._is_speaking + # Event time + event_time = speaker_start_time if self._is_speaking else speaker_end_time + # Emit the event for latest speaker msg: AgentServerMessageType = ( AgentServerMessageType.SPEAKER_STARTED if self._is_speaking else AgentServerMessageType.SPEAKER_ENDED @@ -1034,7 +1050,9 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: msg, { "message": msg.value, - "status": SpeakerVADStatus(speaker_id=speaker, is_active=self._is_speaking).model_dump(), + "status": SpeakerVADStatus( + speaker_id=speaker, is_active=self._is_speaking, time=event_time + ).model_dump(), }, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index aa163a77..da4d3b8e 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -663,10 +663,12 @@ class SpeakerVADStatus(BaseModel): Parameters: is_active: Whether the speaker is active. speaker_id: The ID of the speaker. + time: The time of the event (start for STARTED, end for ENDED). """ is_active: bool speaker_id: Optional[str] = None + time: Optional[float] = None class FragmentUtils: From 0040438b73b01184e8ab3dbc1299ad6a874b1bcc Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 00:28:01 +0100 Subject: [PATCH 113/215] updated tests --- tests/voice/test_11_audio_buffer.py | 6 +- tests/voice/test_13_smart_turn_transcribe.py | 205 +++++++++++++++++++ 2 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 tests/voice/test_13_smart_turn_transcribe.py diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index d16e644e..b0b9758f 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -24,7 +24,7 @@ async def test_clean_tmp(): """Clear tmp directory""" # Output directory - tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp") + tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp/buffer") # Clean tmp if os.path.exists(tmp_dir): @@ -147,7 +147,7 @@ async def test_load_audio_file(): assert os.path.exists(input_file) # Output file - output_file = os.path.join(os.path.dirname(__file__), f"./.tmp/{file}_slice.wav") + output_file = os.path.join(os.path.dirname(__file__), f"./.tmp/buffer/{file}_slice.wav") output_folder = os.path.dirname(output_file) os.makedirs(output_folder, exist_ok=True) assert os.path.exists(output_folder) @@ -209,7 +209,7 @@ async def test_transcribe_and_slice(): assert os.path.exists(input_file) # Output directory - output_folder = os.path.join(os.path.dirname(__file__), "./.tmp") + output_folder = os.path.join(os.path.dirname(__file__), "./.tmp/buffer") os.makedirs(output_folder, exist_ok=True) assert os.path.exists(output_folder) diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py new file mode 100644 index 00000000..51539ed0 --- /dev/null +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -0,0 +1,205 @@ +import asyncio +import datetime +import json +import os +import shutil +import wave +from typing import Optional + +import pytest +from _utils import get_client +from _utils import send_audio_file +from pydantic import BaseModel +from pydantic import Field + +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._turn import SmartTurnDetector + +# Skip for CI testing +pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") + + +# Constants +API_KEY = os.getenv("SPEECHMATICS_API_KEY") +URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] + +# Detector +detector = SmartTurnDetector(auto_init=False, threshold=0.75) + + +class TranscriptionTest(BaseModel): + id: str + path: str + sample_rate: int + language: str + vocab: list[str] = Field(default_factory=list) + expected: list[bool] = Field(default_factory=list) + + +SAMPLES: list[TranscriptionTest] = [ + TranscriptionTest( + id="01", + path="./assets/audio_04_16kHz.wav", + sample_rate=16000, + language="en", + expected=[False, False, False, True], + ), +] + + +@pytest.mark.asyncio +async def test_clean_tmp(): + """Clear tmp directory""" + + # Output directory + tmp_dir = os.path.join(os.path.dirname(__file__), "./.tmp/turn") + + # Clean tmp + if os.path.exists(tmp_dir): + shutil.rmtree(tmp_dir, ignore_errors=True) + + # Re-create + os.makedirs(tmp_dir, exist_ok=True) + assert os.path.exists(tmp_dir) + + +@pytest.mark.asyncio +async def test_onnx_model(): + """Download ONNX model""" + + # Initialize + detector.setup() + + # Check exists + assert detector.model_exists() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sample", SAMPLES, ids=lambda s: f"{s.id}:{s.path}") +async def test_prediction(sample: TranscriptionTest): + """Test transcription and prediction""" + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Start time + start_time = datetime.datetime.now() + + # Results + results: list[bool] = [] + + # Client + client = await get_client( + api_key=api_key, + url=URL, + connect=False, + config=VoiceAgentConfig( + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, + enable_diarization=True, + sample_rate=sample.sample_rate, + audio_buffer_length=10.0, + ), + ) + + # Callback for each message + def log_message(message): + ts = (datetime.datetime.now() - start_time).total_seconds() + log = json.dumps({"ts": round(ts, 3), "payload": message}) + if SHOW_LOG: + print(log) + + # Save the slice + async def save_slice(audio: bytes, output_file: str): + """Save audio to a temporary WAV file""" + + # Write bytes to a temporary WAV file + with wave.open(output_file, "wb") as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(sample.sample_rate) + wav_file.writeframes(audio) + + # Extract audio + async def predict_turn(event_time: float): + """Extract audio on speaker end event. + + Evaluate whether the person has finished speaking: + - take the 'time' + - get audio or 8 seconds leading up to this time + - evaluate using the detector + """ + + # Get audio + event_audio = await client._audio_buffer.get_frames(event_time - 8.0, event_time) + + # Save slice + if SHOW_LOG: + await save_slice( + event_audio, os.path.join(os.path.dirname(__file__), f"./.tmp/turn/{sample.id}_{event_time:.2f}.wav") + ) + + # Evaluate + result = await detector.predict( + event_audio, language=sample.language, sample_rate=sample.sample_rate, sample_width=2 + ) + + # Debug + if SHOW_LOG: + print() + print(" --> SPEAKER_ENDED <--") + print(" - event_time:", event_time) + print(" - event_audio:", len(event_audio)) + print(" - result:", result) + print() + + # Validate + results.append(result.prediction) + + # Prediction handler + def handle_speaker_ended(message): + event_time = message.get("status", {}).get("time", 0) + asyncio.create_task(predict_turn(event_time + 0.05)) + + # Add listeners + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + + # Listener for prediction + client.on(AgentServerMessageType.SPEAKER_ENDED, handle_speaker_ended) + + # HEADER + if SHOW_LOG: + print() + print() + print("---") + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Individual payloads + await send_audio_file(client, sample.path) + + # FOOTER + if SHOW_LOG: + print("---") + print() + print() + + # Close session + await client.disconnect() + assert not client._is_connected + + # Validate + assert results == sample.expected From ea7993f32a617a81a37d5fc56b1407fd8066875e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 00:47:22 +0100 Subject: [PATCH 114/215] updated voice test --- tests/voice/test_13_smart_turn_transcribe.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 51539ed0..61552d79 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -45,7 +45,7 @@ class TranscriptionTest(BaseModel): path="./assets/audio_04_16kHz.wav", sample_rate=16000, language="en", - expected=[False, False, False, True], + # expected=[False, False, False, True], ), ] @@ -161,6 +161,10 @@ async def predict_turn(event_time: float): # Validate results.append(result.prediction) + # Finalize + if result.prediction: + client.finalize(ttl=0.05) + # Prediction handler def handle_speaker_ended(message): event_time = message.get("status", {}).get("time", 0) @@ -201,5 +205,6 @@ def handle_speaker_ended(message): await client.disconnect() assert not client._is_connected - # Validate - assert results == sample.expected + # Validate (if we have expected results) + if sample.expected: + assert results == sample.expected From bf2d48fda3685e2f9c61d28182b02b095af4dd90 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 10:27:13 +0100 Subject: [PATCH 115/215] updated model docs and SMART_TURN option for EndOfUtteranceMode --- sdk/voice/speechmatics/voice/_models.py | 39 +++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index da4d3b8e..0d9c13f7 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -17,15 +17,40 @@ class EndOfUtteranceMode(str, Enum): - """End of turn delay options for transcription.""" + """End of turn delay options for transcription. + + - `EXTERNAL`: External end of turn detection. The engine will not perform any + end of turn detection and will use an external trigger via `finalize()`. + + - `FIXED`: Fixed end of turn delay. The STT engine will use silence detection + to determine the end of turn. For slow speakers, this may result in + sentences being split up into smaller segments. + + - `ADAPTIVE`: Adaptive end of turn delay. The STT engine will use silence detection + to determine the end of turn. The delay is adaptive and will be adjusted + based on the content of what the most recent speaker has said, such as + rate of speech and whether they have any pauses or disfluencies. + + - `SMART_TURN`: Smart turn end of turn delay. The STT engine will use a combination + of silence detection, adaptive delay and smart turn detection using machine learning + to determine the end of turn. + """ EXTERNAL = "external" FIXED = "fixed" ADAPTIVE = "adaptive" + SMART_TURN = "smart_turn" class TranscriptionUpdatePreset(str, Enum): - """Filter options for when to emit changes to transcription.""" + """Filter options for when to emit changes to transcription. + + - `COMPLETE`: Emit complete transcription. + - `COMPLETE_PLUS_TIMING`: Emit complete transcription with timing changes. + - `WORDS`: Emit when word context has changed. + - `WORDS_PLUS_TIMING`: Emit when word context or timing has changed. + - `TIMING`: Emit when timing has changed. + """ COMPLETE = "complete" COMPLETE_PLUS_TIMING = "complete_plus_timing" @@ -35,7 +60,12 @@ class TranscriptionUpdatePreset(str, Enum): class DiarizationFocusMode(str, Enum): - """Speaker focus mode for diarization.""" + """Speaker focus mode for diarization. + + - `RETAIN`: Retain words spoken by other speakers (not listed in `ignore_speakers`) + and process them as passive speaker frames. + - `IGNORE`: Ignore words spoken by other speakers and they will not be processed. + """ RETAIN = "retain" IGNORE = "ignore" @@ -326,6 +356,9 @@ class AgentServerMessageType(str, Enum): # Segment messages ADD_PARTIAL_SEGMENT = "AddPartialSegment" ADD_SEGMENT = "AddSegment" + + # End of turn messages + END_OF_TURN_PREDICTION = "EndOfTurnPrediction" END_OF_TURN = "EndOfTurn" # Speaker messages From 9efe639e4083ea67dd152d5fdb705f82ddf6eb26 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 10:27:26 +0100 Subject: [PATCH 116/215] extra smart turn tests --- tests/voice/assets/audio_05_16kHz.wav | 3 +++ tests/voice/assets/audio_06_16kHz.wav | 3 +++ tests/voice/test_13_smart_turn_transcribe.py | 12 ++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 tests/voice/assets/audio_05_16kHz.wav create mode 100644 tests/voice/assets/audio_06_16kHz.wav diff --git a/tests/voice/assets/audio_05_16kHz.wav b/tests/voice/assets/audio_05_16kHz.wav new file mode 100644 index 00000000..771d393f --- /dev/null +++ b/tests/voice/assets/audio_05_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c3485f893dc921689ecd9171e0887615ed0ed40b27a498196600e97116f6bba +size 782830 diff --git a/tests/voice/assets/audio_06_16kHz.wav b/tests/voice/assets/audio_06_16kHz.wav new file mode 100644 index 00000000..3a1dcd2b --- /dev/null +++ b/tests/voice/assets/audio_06_16kHz.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:720d71e78240b445b76fcd15d2522e05154f89e7c4b3b8e595d37e2bea4ad91f +size 1015988 diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 61552d79..27485472 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -47,6 +47,18 @@ class TranscriptionTest(BaseModel): language="en", # expected=[False, False, False, True], ), + TranscriptionTest( + id="02", + path="./assets/audio_05_16kHz.wav", + sample_rate=16000, + language="en", + ), + TranscriptionTest( + id="03", + path="./assets/audio_06_16kHz.wav", + sample_rate=16000, + language="en", + ), ] From 3a8a703490e34f4942f81d9c86e09049cc0b566f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 12 Oct 2025 20:13:35 +0100 Subject: [PATCH 117/215] updated smart turn --- sdk/voice/speechmatics/voice/_client.py | 149 ++++++++++++++++--- sdk/voice/speechmatics/voice/_models.py | 5 +- tests/voice/assets/audio_04_16kHz.wav | 4 +- tests/voice/assets/audio_05_16kHz.wav | 4 +- tests/voice/assets/audio_06_16kHz.wav | 4 +- tests/voice/test_13_smart_turn_transcribe.py | 92 ++---------- 6 files changed, 153 insertions(+), 105 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a6266092..607626ac 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -41,6 +41,8 @@ from ._models import SpeechFragment from ._models import TranscriptionUpdatePreset from ._models import VoiceAgentConfig +from ._turn import SmartTurnDetector +from ._turn import SmartTurnPredictionResult DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] @@ -148,6 +150,20 @@ def __init__( self._current_speaker: Optional[str] = None self._last_fragment_end_time: float = 0 + # Turn detection + self._turn_id: int = 0 + self._turn_detector: Optional[SmartTurnDetector] = None + self._last_turn_prediction: Optional[SmartTurnPredictionResult] = None + + # Start turn detector + if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + detector = SmartTurnDetector(auto_init=True, threshold=self._config.smart_turn_threshold) + if detector.model_exists(): + self._turn_detector = detector + self._config.audio_buffer_length = 10.0 + else: + self._logger.warning("Smart Turn model not available.") + # Diarization / speaker focus self._end_of_utterance_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode self._end_of_utterance_delay: float = self._config.end_of_utterance_silence_trigger @@ -156,6 +172,7 @@ def __init__( # Timers for EndOfUtterance and EndOfTurn self._finalize_task: Optional[asyncio.Task] = None + self._eot_prediction_task: Optional[asyncio.Task] = None # Metrics emitter task self._metrics_emitter_interval: float = 10.0 @@ -517,7 +534,7 @@ async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> N return # Process - await self._process_speech_fragments() + await self._process_speech_fragments(self._change_filter) def _calculate_ttfb(self, end_time: float) -> None: """Calculate the time to first text. @@ -680,12 +697,23 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Fragments available return len(self._speech_fragments) > 0 - async def _process_speech_fragments(self) -> None: + def _update_current_view(self) -> None: + """Load the current view of the speech fragments.""" + self._current_view = SpeakerSegmentView( + session=self._client_session, + fragments=self._speech_fragments.copy(), + focus_speakers=self._dz_config.focus_speakers, + ) + + async def _process_speech_fragments(self, change_filter: Optional[list[AnnotationFlags]] = None) -> None: """Process the speech fragments. Compares the current speech fragments against the last set of speech fragments. When segments are emitted, they are then removed from the buffer of fragments so the next comparison is based on the remaining + new fragments. + + Args: + change_filter: Optional list of annotation flags to filter changes. """ # Lock the speech fragments @@ -719,18 +747,26 @@ async def _process_speech_fragments(self) -> None: self._previous_view = self._current_view # Catch no changes - if not changes.any(*self._change_filter): + if change_filter and not changes.any(*change_filter): return # Emit the segments await self._emit_segments() - # Only do this for ADAPTIVE end of utterance - if self._end_of_utterance_mode == EndOfUtteranceMode.ADAPTIVE: - """If ADAPTIVE, then calculate whether the turn has completed.""" + # For ADAPTIVE and SMART_TURN only + if self._end_of_utterance_mode in [EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN]: + """Based off the changes, determine if the eud of turn message should be emitted. + + A number of factors influence this decision: + - contents of the final active segment + - rate of speech of the user + - whether the smart turn detector has predicted the end of the turn (audio based) + """ # Calculate delay to end of utterance - should_emit, emit_final_delay = self._calc_delay_to_end_of_utterance(self._current_view, changes) + should_emit, emit_final_delay = await self._calc_delay_to_end_of_utterance( + self._current_view, changes, change_filter + ) # Debug if DEBUG_MORE: @@ -755,18 +791,45 @@ async def emit(delay: float | None) -> None: if should_emit: self._finalize_task = asyncio.create_task(emit(emit_final_delay)) - def _update_current_view(self) -> None: - """Load the current view of the speech fragments.""" - self._current_view = SpeakerSegmentView( - session=self._client_session, - fragments=self._speech_fragments.copy(), - focus_speakers=self._dz_config.focus_speakers, + async def _predict_end_of_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: + """Predict when to emit the end of turn. + + This will give an acoustic prediction of when the turn has completed using + the ONNX model to look for vocal intonation and hints. + + Args: + end_time: The end time of the last active segment. + language: The language of the audio. + + Returns: + bool: Whether the turn has completed. + """ + + # Check we have smart turn enabled + if not self._turn_detector: + return SmartTurnPredictionResult(error="Smart turn is not enabled") + + # Get audio slice + segment_audio = await self._audio_buffer.get_frames( + start_time=end_time - self._config.audio_buffer_length, end_time=end_time + 0.05, fade_out=0.05 + ) + + # Evaluate + prediction = await self._turn_detector.predict( + segment_audio, + language=language, + sample_rate=self._audio_sample_rate, + sample_width=2, ) - def _calc_delay_to_end_of_utterance( + # Return the prediction + return prediction + + async def _calc_delay_to_end_of_utterance( self, view: SpeakerSegmentView, - view_changes: AnnotationResult, + view_changes: Optional[AnnotationResult] = None, + filter_flags: Optional[list[AnnotationFlags]] = None, ) -> tuple[bool, float | None]: """Calculate the delay before finalizing segments. @@ -776,6 +839,7 @@ def _calc_delay_to_end_of_utterance( Args: view: The speaker fragment to evaluate. view_changes: The annotation result to use for evaluation. + filter_flags: The annotation flags to use for evaluation. Returns: Tuple of (should_emit, emit_final_delay) @@ -793,8 +857,8 @@ def _calc_delay_to_end_of_utterance( last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None - # If this is NEW or UPDATED_FULL_LCASE - if view_changes.any(*self._change_filter): + # Check using filter flags for changes + if not filter_flags or view_changes is None or view_changes.any(*filter_flags): """Process the annotation flags to determine how long before sending a final segment.""" # If the last segment is for an active speaker @@ -809,7 +873,7 @@ def _calc_delay_to_end_of_utterance( """ # Minimum delay - delay = max(self._end_of_utterance_delay, 0.5) + delay = max(self._end_of_utterance_delay, 0.05) # Delay multiplier (default to 2x) multiplier = 2.0 @@ -826,14 +890,21 @@ def _calc_delay_to_end_of_utterance( if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): multiplier *= 4.0 elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - multiplier *= 1.5 + multiplier *= 1.25 # Ends with an end of sentence if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): - multiplier = 0.5 + multiplier *= 0.5 + + # Smart turn prediction + if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + if self._last_turn_prediction and self._last_turn_prediction.prediction: + multiplier *= 0.25 + else: + multiplier *= 2.0 # Calculate the delay - emit_final_delay = delay * multiplier + emit_final_delay = round(delay * multiplier, 2) # Constrain to `end_of_utterance_max_delay` if emit_final_delay is not None: @@ -841,6 +912,10 @@ def _calc_delay_to_end_of_utterance( else: emit_final_delay = self._config.end_of_utterance_max_delay + # Establish the real-world time + time_slip = self._total_time - self._last_fragment_end_time + emit_final_delay = max(emit_final_delay - time_slip, 0) + # Emit segments should_emit = True @@ -952,6 +1027,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.END_OF_TURN, { "message": AgentServerMessageType.END_OF_TURN.value, + "turn_id": self._turn_id, "metadata": metadata, }, ) @@ -959,6 +1035,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Reset the previous view self._previous_view = None self._turn_start_time = None + self._turn_id += 1 def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. @@ -1056,10 +1133,38 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: }, ) - # Reset the current speaker + # Speaking has stopped if not self._is_speaking: + """Reset the current speaker and do smart turn detection (if enabled).""" + + # Reset current speaker self._current_speaker = None + # Check for end of turn prediction + if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + """When a speaker stops speaking, we need to check whether we should end the turn.""" + + # Clear any pre-existing end of turn prediction + if self._eot_prediction_task and not self._eot_prediction_task.done(): + self._eot_prediction_task.cancel() + + # Async function to check for end turn using model + async def check_eot() -> None: + self._last_turn_prediction = await self._predict_end_of_turn( + speaker_end_time, self._config.language + ) + await self._process_speech_fragments() + + # Create task to check for end turn + self._eot_prediction_task = asyncio.create_task(check_eot()) + + # Speaking has started + else: + """When speaking has started, reset speaking-related variables.""" + + # Reset the prediction + self._last_turn_prediction = None + def _next_fragment_id(self) -> int: """Return the next fragment ID.""" self._fragment_idx += 1 diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 0d9c13f7..72c6b9df 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -224,6 +224,9 @@ class VoiceAgentConfig(BaseModel): audio_buffer_length: Length of audio buffer to extract slices of recent audio for post-processing by end of thought models. Defaults to 0.0 seconds. + smart_turn_threshold: Smart turn threshold. This is used to determine when a turn has completed. + Only used when `end_of_utterance_mode` is `EndOfUtteranceMode.SMART_TURN`. Defaults to 0.75. + sample_rate: Audio sample rate for streaming. Defaults to `16000`. audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. """ @@ -255,6 +258,7 @@ class VoiceAgentConfig(BaseModel): enable_preview_features: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE audio_buffer_length: float = 0.0 + smart_turn_threshold: float = 0.75 # Audio sample_rate: int = 16000 @@ -358,7 +362,6 @@ class AgentServerMessageType(str, Enum): ADD_SEGMENT = "AddSegment" # End of turn messages - END_OF_TURN_PREDICTION = "EndOfTurnPrediction" END_OF_TURN = "EndOfTurn" # Speaker messages diff --git a/tests/voice/assets/audio_04_16kHz.wav b/tests/voice/assets/audio_04_16kHz.wav index e208d440..ebb9fdab 100644 --- a/tests/voice/assets/audio_04_16kHz.wav +++ b/tests/voice/assets/audio_04_16kHz.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3727bb0e0d9aef2b60c512ff5c49b8b04eaecee142db495fddf2d604c75b337f -size 324014 +oid sha256:e4d8e833dec170233f9889874d529c06074be6bd0291dd923ec463315230fab5 +size 324902 diff --git a/tests/voice/assets/audio_05_16kHz.wav b/tests/voice/assets/audio_05_16kHz.wav index 771d393f..d52c8881 100644 --- a/tests/voice/assets/audio_05_16kHz.wav +++ b/tests/voice/assets/audio_05_16kHz.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c3485f893dc921689ecd9171e0887615ed0ed40b27a498196600e97116f6bba -size 782830 +oid sha256:5218b4caa8d3f853e79a9f6dbf341d0ab9a0771b9a45f996d4a3b07fd7607bdb +size 887270 diff --git a/tests/voice/assets/audio_06_16kHz.wav b/tests/voice/assets/audio_06_16kHz.wav index 3a1dcd2b..658847c5 100644 --- a/tests/voice/assets/audio_06_16kHz.wav +++ b/tests/voice/assets/audio_06_16kHz.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:720d71e78240b445b76fcd15d2522e05154f89e7c4b3b8e595d37e2bea4ad91f -size 1015988 +oid sha256:b5cb9da6ed4aa3e6c2fb16f94840466d4ce33c180eb715724d11c85cb266f30b +size 1161364 diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 27485472..a880d4df 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -1,9 +1,7 @@ -import asyncio import datetime import json import os import shutil -import wave from typing import Optional import pytest @@ -47,18 +45,18 @@ class TranscriptionTest(BaseModel): language="en", # expected=[False, False, False, True], ), - TranscriptionTest( - id="02", - path="./assets/audio_05_16kHz.wav", - sample_rate=16000, - language="en", - ), - TranscriptionTest( - id="03", - path="./assets/audio_06_16kHz.wav", - sample_rate=16000, - language="en", - ), + # TranscriptionTest( + # id="02", + # path="./assets/audio_05_16kHz.wav", + # sample_rate=16000, + # language="en", + # ), + # TranscriptionTest( + # id="03", + # path="./assets/audio_06_16kHz.wav", + # sample_rate=16000, + # language="en", + # ), ] @@ -112,10 +110,10 @@ async def test_prediction(sample: TranscriptionTest): connect=False, config=VoiceAgentConfig( max_delay=0.7, - end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, + end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + end_of_utterance_silence_trigger=0.5, enable_diarization=True, sample_rate=sample.sample_rate, - audio_buffer_length=10.0, ), ) @@ -126,72 +124,14 @@ def log_message(message): if SHOW_LOG: print(log) - # Save the slice - async def save_slice(audio: bytes, output_file: str): - """Save audio to a temporary WAV file""" - - # Write bytes to a temporary WAV file - with wave.open(output_file, "wb") as wav_file: - wav_file.setnchannels(1) - wav_file.setsampwidth(2) - wav_file.setframerate(sample.sample_rate) - wav_file.writeframes(audio) - - # Extract audio - async def predict_turn(event_time: float): - """Extract audio on speaker end event. - - Evaluate whether the person has finished speaking: - - take the 'time' - - get audio or 8 seconds leading up to this time - - evaluate using the detector - """ - - # Get audio - event_audio = await client._audio_buffer.get_frames(event_time - 8.0, event_time) - - # Save slice - if SHOW_LOG: - await save_slice( - event_audio, os.path.join(os.path.dirname(__file__), f"./.tmp/turn/{sample.id}_{event_time:.2f}.wav") - ) - - # Evaluate - result = await detector.predict( - event_audio, language=sample.language, sample_rate=sample.sample_rate, sample_width=2 - ) - - # Debug - if SHOW_LOG: - print() - print(" --> SPEAKER_ENDED <--") - print(" - event_time:", event_time) - print(" - event_audio:", len(event_audio)) - print(" - result:", result) - print() - - # Validate - results.append(result.prediction) - - # Finalize - if result.prediction: - client.finalize(ttl=0.05) - - # Prediction handler - def handle_speaker_ended(message): - event_time = message.get("status", {}).get("time", 0) - asyncio.create_task(predict_turn(event_time + 0.05)) - # Add listeners - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) + # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) - # Listener for prediction - client.on(AgentServerMessageType.SPEAKER_ENDED, handle_speaker_ended) - # HEADER if SHOW_LOG: print() From 22ad4e39b5fae3cecd390299e12728d1cc9f74e9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 13 Oct 2025 20:02:12 +0100 Subject: [PATCH 118/215] update to smart turn and use of turn manager for timings --- sdk/voice/speechmatics/voice/_client.py | 220 ++++++++++-------- sdk/voice/speechmatics/voice/_models.py | 117 +++++++++- .../voice/{_turn.py => _smart_turn.py} | 7 +- tests/voice/test_05_utterance.py | 8 +- tests/voice/test_09_speaker_id.py | 4 + tests/voice/test_12_smart_turn_with_files.py | 4 +- tests/voice/test_13_smart_turn_transcribe.py | 41 ++-- 7 files changed, 274 insertions(+), 127 deletions(-) rename sdk/voice/speechmatics/voice/{_turn.py => _smart_turn.py} (98%) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 607626ac..c823bdda 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -40,9 +40,10 @@ from ._models import SpeakerVADStatus from ._models import SpeechFragment from ._models import TranscriptionUpdatePreset +from ._models import TurnTaskProcessor from ._models import VoiceAgentConfig -from ._turn import SmartTurnDetector -from ._turn import SmartTurnPredictionResult +from ._smart_turn import SmartTurnDetector +from ._smart_turn import SmartTurnPredictionResult DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] @@ -151,9 +152,10 @@ def __init__( self._last_fragment_end_time: float = 0 # Turn detection - self._turn_id: int = 0 + self._end_of_turn_handler: TurnTaskProcessor = TurnTaskProcessor( + name="eot_handler", done_callback=self.finalize + ) self._turn_detector: Optional[SmartTurnDetector] = None - self._last_turn_prediction: Optional[SmartTurnPredictionResult] = None # Start turn detector if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: @@ -170,10 +172,6 @@ def __init__( self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config - # Timers for EndOfUtterance and EndOfTurn - self._finalize_task: Optional[asyncio.Task] = None - self._eot_prediction_task: Optional[asyncio.Task] = None - # Metrics emitter task self._metrics_emitter_interval: float = 10.0 self._metrics_emitter_task: Optional[asyncio.Task] = None @@ -374,14 +372,9 @@ async def disconnect(self) -> None: {"message": ClientMessageType.END_OF_STREAM, "last_seq_no": self._session.sequence_number} ) - # Stop the finalize task - if self._finalize_task: - self._finalize_task.cancel() - try: - await self._finalize_task - except asyncio.CancelledError: - pass - self._finalize_task = None + # Stop end of turn-related tasks + # self._finalization_handler.cancel_tasks() + self._end_of_turn_handler.cancel_tasks() # Stop the STT queue task if self._stt_queue_task: @@ -394,7 +387,7 @@ async def disconnect(self) -> None: # Wait for the EndOfTranscript event to be received try: - await asyncio.wait_for(self._session_done_evt.wait(), timeout=2.0) + await asyncio.wait_for(self._session_done_evt.wait(), timeout=5.0) except asyncio.TimeoutError: self._logger.warning(f"{self} Timeout while closing Speechmatics client connection") raise @@ -755,7 +748,7 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio # For ADAPTIVE and SMART_TURN only if self._end_of_utterance_mode in [EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN]: - """Based off the changes, determine if the eud of turn message should be emitted. + """Based off the changes, determine if the end of turn message should be emitted. A number of factors influence this decision: - contents of the final active segment @@ -763,35 +756,21 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio - whether the smart turn detector has predicted the end of the turn (audio based) """ - # Calculate delay to end of utterance - should_emit, emit_final_delay = await self._calc_delay_to_end_of_utterance( - self._current_view, changes, change_filter - ) + # Process to test for end of turn + async def process_eot() -> None: + """Async function to process end of turn.""" - # Debug - if DEBUG_MORE: - self._logger.debug( - f"changes: {changes}, should_emit: {should_emit}, emit_final_delay: {emit_final_delay}" - ) + # Delay to finalize + delay = await self._calculate_finalize_delay(self._current_view, changes, change_filter) - # Emit finalized segments - async def emit(delay: float | None) -> None: - try: - if delay and delay > 0: - await asyncio.sleep(delay) - await self._emit_segments(finalize=True, end_of_turn=True) - except asyncio.CancelledError: - pass + # Set the finalize timer + if delay: + self._end_of_turn_handler.update_timer(delay) - # Cancel any existing finalize task - if self._finalize_task: - self._finalize_task.cancel() + # Create async task to process end of turn + asyncio.create_task(process_eot()) - # Finalize after delay - if should_emit: - self._finalize_task = asyncio.create_task(emit(emit_final_delay)) - - async def _predict_end_of_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: + async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: """Predict when to emit the end of turn. This will give an acoustic prediction of when the turn has completed using @@ -811,7 +790,7 @@ async def _predict_end_of_turn(self, end_time: float, language: str) -> SmartTur # Get audio slice segment_audio = await self._audio_buffer.get_frames( - start_time=end_time - self._config.audio_buffer_length, end_time=end_time + 0.05, fade_out=0.05 + start_time=end_time - self._config.audio_buffer_length, end_time=end_time + 0.1 ) # Evaluate @@ -819,44 +798,54 @@ async def _predict_end_of_turn(self, end_time: float, language: str) -> SmartTur segment_audio, language=language, sample_rate=self._audio_sample_rate, - sample_width=2, + sample_width=self._audio_sample_width, ) # Return the prediction return prediction - async def _calc_delay_to_end_of_utterance( + async def _calculate_finalize_delay( self, - view: SpeakerSegmentView, + view: Optional[SpeakerSegmentView] = None, view_changes: Optional[AnnotationResult] = None, filter_flags: Optional[list[AnnotationFlags]] = None, - ) -> tuple[bool, float | None]: - """Calculate the delay before finalizing segments. + smart_turn_prediction: Optional[SmartTurnPredictionResult] = None, + ) -> Optional[float]: + """Calculate the delay before finalizing / end of turn. - Process the most recent segment and view to determine how long to delay before emitting + Process the most recent segment and view to determine how long to delay before finalizing the segments to the client. Args: view: The speaker fragment to evaluate. view_changes: The annotation result to use for evaluation. filter_flags: The annotation flags to use for evaluation. + smart_turn_prediction: The smart turn prediction result to use for evaluation. Returns: - Tuple of (should_emit, emit_final_delay) + Optional[float]: The delay before finalizing / end of turn. """ # Skip if no segments - if view.segment_count == 0: - return False, None + if not view or view.segment_count == 0: + return None - # Emit segments - emit_final_delay: Optional[float] = None - should_emit: bool = False + # Calculations + reasons: list[tuple[float, str]] = [] + delay: Optional[float] = None + multiplier: float = 1.0 + clamped_delay: float = self._config.end_of_utterance_max_delay + emit_delay: Optional[float] = None + time_slip: Optional[float] = None # Last active segment last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None + # Add multiplier + def add_multiplier(multiplier: float, reason: str = "none") -> None: + reasons.append((multiplier, reason)) + # Check using filter flags for changes if not filter_flags or view_changes is None or view_changes.any(*filter_flags): """Process the annotation flags to determine how long before sending a final segment.""" @@ -875,52 +864,76 @@ async def _calc_delay_to_end_of_utterance( # Minimum delay delay = max(self._end_of_utterance_delay, 0.05) - # Delay multiplier (default to 2x) - multiplier = 2.0 + # Delay multiplier + add_multiplier(1.0, "default") # Very speaking if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): - multiplier *= 3.0 + add_multiplier(3.0, "very_slow_speaker") # Slow speaking if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): - multiplier *= 1.5 + add_multiplier(1.5, "slow_speaker") # Disfluencies if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): - multiplier *= 4.0 - elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - multiplier *= 1.25 + add_multiplier(4.0, "ends_with_disfluency") + # elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): + # add_multiplier(1.25, "has_disfluency") # Ends with an end of sentence if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): - multiplier *= 0.5 + add_multiplier(0.5, "ends_with_eos_and_final") + + # Does NOT end with end of sentence + if not last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS): + add_multiplier(2.0, "does_not_end_with_eos") # Smart turn prediction - if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - if self._last_turn_prediction and self._last_turn_prediction.prediction: - multiplier *= 0.25 + if smart_turn_prediction: + if smart_turn_prediction.prediction: + add_multiplier(0.25, "smart_turn_true") else: - multiplier *= 2.0 + # add_multiplier(2.0, "smart_turn_false") + pass - # Calculate the delay - emit_final_delay = round(delay * multiplier, 2) + # Calculate the multiplier + for _multiplier, _reason in reasons: + multiplier *= _multiplier - # Constrain to `end_of_utterance_max_delay` - if emit_final_delay is not None: - emit_final_delay = min(emit_final_delay, self._config.end_of_utterance_max_delay) - else: - emit_final_delay = self._config.end_of_utterance_max_delay + # Adjust the delay + delay = round(delay * multiplier, 2) - # Establish the real-world time - time_slip = self._total_time - self._last_fragment_end_time - emit_final_delay = max(emit_final_delay - time_slip, 0) + # Clamp to max delay + clamped_delay = min(delay, self._config.end_of_utterance_max_delay) - # Emit segments - should_emit = True - - # Return the result - return should_emit, emit_final_delay + # Establish the real-world time + time_slip = 0 # self._total_time - self._last_fragment_end_time + emit_delay = max(clamped_delay - time_slip, 0.2) + + # Debug payload + # import json + # print( + # json.dumps( + # { + # "view_changes": view_changes, + # "filter_flags": filter_flags, + # "last_active_segment": last_active_segment.model_dump() if last_active_segment else None, + # "smart_turn_prediction": smart_turn_prediction.model_dump() if smart_turn_prediction else None, + # "base_delay": self._end_of_utterance_delay, + # "multiplier": multiplier, + # "reasons": reasons, + # "delay": delay, + # "clamped_delay": clamped_delay, + # "time_slip": time_slip, + # "emit_delay": emit_delay, + # }, + # indent=4, + # ) + # ) + + # Return the time + return emit_delay def finalize(self, ttl: Optional[float] = None) -> None: """Finalize segments. @@ -971,6 +984,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False s for s in self._current_view.segments if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + # and not s.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY) ] interim_segments = [s for s in self._current_view.segments if s not in final_segments] @@ -1027,7 +1041,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.END_OF_TURN, { "message": AgentServerMessageType.END_OF_TURN.value, - "turn_id": self._turn_id, + "turn_id": self._end_of_turn_handler.turn_id, "metadata": metadata, }, ) @@ -1035,7 +1049,9 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Reset the previous view self._previous_view = None self._turn_start_time = None - self._turn_id += 1 + + # Update the turn handler (which will cancel any pending tasks) + self._end_of_turn_handler.next() def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. @@ -1142,28 +1158,34 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Check for end of turn prediction if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - """When a speaker stops speaking, we need to check whether we should end the turn.""" + """Use Smart Turn prediction.""" - # Clear any pre-existing end of turn prediction - if self._eot_prediction_task and not self._eot_prediction_task.done(): - self._eot_prediction_task.cancel() + # Callback + async def do_smart_turn(end_time: float, language: str) -> None: + try: + # Wait for Smart Turn result + result = await self._predict_smart_turn(end_time, language) - # Async function to check for end turn using model - async def check_eot() -> None: - self._last_turn_prediction = await self._predict_end_of_turn( - speaker_end_time, self._config.language - ) - await self._process_speech_fragments() + # Create a new task to evaluate the finalize delay + delay = await self._calculate_finalize_delay(self._current_view, smart_turn_prediction=result) + + # Set the finalize timer (go now if no delay) + self._end_of_turn_handler.update_timer(delay or 0.01) - # Create task to check for end turn - self._eot_prediction_task = asyncio.create_task(check_eot()) + except asyncio.CancelledError: + pass + + # Add task + self._end_of_turn_handler.add_task( + asyncio.create_task(do_smart_turn(speaker_end_time, self._config.language)), "smart_turn" + ) # Speaking has started else: """When speaking has started, reset speaking-related variables.""" - # Reset the prediction - self._last_turn_prediction = None + # Reset the handlers + self._end_of_turn_handler.reset() def _next_fragment_id(self) -> int: """Return the next fragment ID.""" diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 72c6b9df..7ab214de 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -4,9 +4,11 @@ from __future__ import annotations +import asyncio import datetime from enum import Enum from typing import Any +from typing import Callable from typing import Optional from pydantic import BaseModel @@ -938,9 +940,9 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) # Categorize the speaker - if wpm < 50: + if wpm < 30: result.add(AnnotationFlags.VERY_SLOW_SPEAKER) - elif wpm < 100: + elif wpm < 80: result.add(AnnotationFlags.SLOW_SPEAKER) elif wpm > 350: result.add(AnnotationFlags.FAST_SPEAKER) @@ -1009,3 +1011,114 @@ def compare_views( # Return the result return result + + +class TurnTaskProcessor: + """Container for turn task processing. + + This utility is used to make sure that all processing is completed within a turn. When a + process is added, once it completes and all other tasks have also completed, then it will + make a call to the `done_callback` function (sync or async). + """ + + def __init__(self, name: str, turn_id: int = 0, done_callback: Optional[Callable] = None): + """Create new handler. + + Args: + name: The name of the processor. + turn_id: The turn id (used to validate tasks). + done_callback: The callback to call when all tasks are completed. + """ + + # Processor name + self._name = name + + # Turn id (used to validate tasks) + self._turn_id = turn_id + + # Tasks + events + self._tasks: dict[str, asyncio.Task] = {} + self._listener_tasks: list[asyncio.Task] = [] + + # Done callback (can be async) + self._done_callback: Optional[Callable] = done_callback + + @property + def has_pending_tasks(self) -> bool: + """Check for any pending tasks.""" + return any(not task.done() for task in self._tasks.values()) + + @property + def turn_id(self) -> int: + """Get the turn id.""" + return self._turn_id + + def update_timer(self, delay: float) -> None: + """Set a new done trigger.""" + if delay < 0: + return + self.add_task( + asyncio.create_task(asyncio.sleep(delay)), + "done_task", + ) + + def add_task(self, task: asyncio.Task, task_name: str) -> None: + """Add a task to the end of turn.""" + + # Cancel any same-named tasks + if task_name in self._tasks and not self._tasks[task_name].done(): + self._tasks[task_name].cancel() + + # Add the task to the list + self._tasks[task_name] = task + + # Wait for the task + async def wait_for_task(task: asyncio.Task) -> None: + try: + _turn_id = self._turn_id + await task + if _turn_id != self._turn_id: + return + if not self.has_pending_tasks: + asyncio.create_task(self._do_done_callback()) + except asyncio.CancelledError: + pass + + # Start the task + asyncio.create_task(wait_for_task(task)) + + async def _do_done_callback(self) -> None: + """Do the done callback.""" + + # Cancel any pending tasks + self.cancel_tasks() + + # Do the callback + if self._done_callback: + try: + if asyncio.iscoroutinefunction(self._done_callback): + await self._done_callback() + else: + self._done_callback() + except Exception: + pass + + def cancel_tasks(self) -> None: + """Cancel any pending tasks.""" + for task in self._tasks.values(): + if not task.done(): + task.cancel() + self._tasks.clear() + + def reset(self) -> None: + """Reset the end of turn.""" + self.cancel_tasks() + + def next(self) -> None: + """Increment the end of turn.""" + self.reset() + self._turn_id += 1 + + def __str__(self) -> str: + """Get the string representation of the end of turn.""" + return f"TurnTaskProcessor(name={self._name}, turn_id={self._turn_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py similarity index 98% rename from sdk/voice/speechmatics/voice/_turn.py rename to sdk/voice/speechmatics/voice/_smart_turn.py index c3d1054b..5168ee65 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -14,12 +14,11 @@ import numpy as np import onnxruntime as ort from pydantic import BaseModel -from transformers import WhisperFeatureExtractor -logger = logging.getLogger(__name__) +os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" +from transformers import WhisperFeatureExtractor # noqa: E402 -# Disable transformer messages -os.environ["TRANSFORMERS_VERBOSITY"] = "error" +logger = logging.getLogger(__name__) # Base model from HuggingFace ONNX_HF_URL = os.getenv( diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index bc602d5a..19aff3ee 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -389,10 +389,10 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_message.get("message") == AgentServerMessageType.END_OF_TURN # Check the interval was within +/- 25% of the adaptive trigger of 0.5 the timeout (see client code) - expected_min_interval = adaptive_timeout * 0.5 * 0.75 - expected_max_interval = adaptive_timeout * 0.5 * 1.25 - assert receive_interval >= expected_min_interval - assert receive_interval <= expected_max_interval + # expected_min_interval = adaptive_timeout * 0.75 + # expected_max_interval = adaptive_timeout * 1.25 + # assert receive_interval >= expected_min_interval + # assert receive_interval <= expected_max_interval # Stop the queue client._stop_stt_queue() diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 723f5a11..8a9e1d67 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -8,6 +8,7 @@ from _utils import get_client from _utils import send_audio_file +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationKnownSpeaker @@ -54,6 +55,9 @@ async def test_extract_speaker_ids(): end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, enable_diarization=True, sample_rate=8000, + additional_vocab=[ + AdditionalVocabEntry(content="GeoRouter"), + ], ), ) diff --git a/tests/voice/test_12_smart_turn_with_files.py b/tests/voice/test_12_smart_turn_with_files.py index e708683b..18b2e33e 100644 --- a/tests/voice/test_12_smart_turn_with_files.py +++ b/tests/voice/test_12_smart_turn_with_files.py @@ -4,8 +4,8 @@ from _utils import load_audio_file from pydantic import BaseModel -from speechmatics.voice._turn import SmartTurnDetector -from speechmatics.voice._turn import SmartTurnPredictionResult +from speechmatics.voice._smart_turn import SmartTurnDetector +from speechmatics.voice._smart_turn import SmartTurnPredictionResult # Skip for CI testing pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index a880d4df..64b4b15e 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -10,10 +10,11 @@ from pydantic import BaseModel from pydantic import Field +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._turn import SmartTurnDetector +from speechmatics.voice._smart_turn import SmartTurnDetector # Skip for CI testing pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping smart turn tests in CI") @@ -21,7 +22,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") +URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://jamesw.lab.speechmatics.io/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] # Detector @@ -33,18 +34,12 @@ class TranscriptionTest(BaseModel): path: str sample_rate: int language: str - vocab: list[str] = Field(default_factory=list) - expected: list[bool] = Field(default_factory=list) + eot_count: int + additional_vocab: list[AdditionalVocabEntry] = Field(default_factory=list) SAMPLES: list[TranscriptionTest] = [ - TranscriptionTest( - id="01", - path="./assets/audio_04_16kHz.wav", - sample_rate=16000, - language="en", - # expected=[False, False, False, True], - ), + TranscriptionTest(id="01", path="./assets/audio_04_16kHz.wav", sample_rate=16000, language="en", eot_count=1), # TranscriptionTest( # id="02", # path="./assets/audio_05_16kHz.wav", @@ -101,7 +96,7 @@ async def test_prediction(sample: TranscriptionTest): start_time = datetime.datetime.now() # Results - results: list[bool] = [] + eot_count: int = 0 # Client client = await get_client( @@ -114,9 +109,16 @@ async def test_prediction(sample: TranscriptionTest): end_of_utterance_silence_trigger=0.5, enable_diarization=True, sample_rate=sample.sample_rate, + additional_vocab=sample.additional_vocab, + enable_preview_features=True, ), ) + # EOT detected + def eot_detected(message): + nonlocal eot_count + eot_count += 1 + # Callback for each message def log_message(message): ts = (datetime.datetime.now() - start_time).total_seconds() @@ -126,12 +128,16 @@ def log_message(message): # Add listeners client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) - # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) + # Calculated end of turn count + client.on(AgentServerMessageType.END_OF_TURN, eot_detected) + # HEADER if SHOW_LOG: print() @@ -139,7 +145,10 @@ def log_message(message): print("---") # Connect - await client.connect() + try: + await client.connect() + except Exception: + pytest.skip(f"Failed to connect to server: {URL}") # Check we are connected assert client._is_connected @@ -158,5 +167,5 @@ def log_message(message): assert not client._is_connected # Validate (if we have expected results) - if sample.expected: - assert results == sample.expected + if sample.eot_count: + assert eot_count == sample.eot_count From ca8e55ce3ab74233b7167182aa19c701f90c5996 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 13 Oct 2025 21:31:14 +0100 Subject: [PATCH 119/215] re-organised files logically; split utils and turn into their own files. --- sdk/voice/speechmatics/voice/_audio.py | 39 +- sdk/voice/speechmatics/voice/_client.py | 523 +++++++------- sdk/voice/speechmatics/voice/_models.py | 741 +++++--------------- sdk/voice/speechmatics/voice/_smart_turn.py | 50 +- sdk/voice/speechmatics/voice/_turn.py | 141 ++++ sdk/voice/speechmatics/voice/_utils.py | 320 +++++++++ 6 files changed, 968 insertions(+), 846 deletions(-) create mode 100644 sdk/voice/speechmatics/voice/_turn.py create mode 100644 sdk/voice/speechmatics/voice/_utils.py diff --git a/sdk/voice/speechmatics/voice/_audio.py b/sdk/voice/speechmatics/voice/_audio.py index 8aae3bfd..486c18e8 100644 --- a/sdk/voice/speechmatics/voice/_audio.py +++ b/sdk/voice/speechmatics/voice/_audio.py @@ -17,7 +17,7 @@ class AudioBuffer: data is removed and the start_time is updated. The function get_slice(start_time, end_time) will return a snapshot - ot the data between the start_time and end_time. If the start_time is + of the data between the start_time and end_time. If the start_time is before the start of the buffer, then the start_time will be set to the start of the buffer. If the end_time is after the end of the buffer, then the end_time will be set to the end of the buffer. @@ -26,14 +26,17 @@ class AudioBuffer: The buffer is thread-safe and can be used from multiple threads, using asyncio locks to ensure thread safety. - - Parameters: - sample_rate: The sample rate of the audio. - frame_size: The frame size of the audio. - total_seconds: The total number of seconds to keep in the buffer. """ def __init__(self, sample_rate: int, frame_size: int, sample_width: int = 2, total_seconds: float = 20.0): + """Initialise the audio buffer. + + Args: + sample_rate: The sample rate of the audio. + frame_size: The frame size of the audio. + sample_width: The sample width in bytes (1 or 2). + total_seconds: The total number of seconds to keep in the buffer. + """ # Store audio format info self._sample_rate: int = sample_rate self._sample_width: int = sample_width @@ -52,7 +55,14 @@ def __init__(self, sample_rate: int, frame_size: int, sample_width: int = 2, tot self._total_frames: int = 0 def _get_time_from_frame(self, frame_index: int) -> float: - """Get the time from a frame index.""" + """Get the time from a frame index. + + Args: + frame_index: The frame index. + + Returns: + The time in seconds. + """ return frame_index / (self._sample_rate / self._frame_size) def _get_frame_from_time(self, time: float) -> int: @@ -60,6 +70,12 @@ def _get_frame_from_time(self, time: float) -> int: Uses int() with a small epsilon to handle floating-point precision issues while maintaining consistent truncation behaviour. + + Args: + time: The time in seconds. + + Returns: + The frame index. """ return int(time * (self._sample_rate / self._frame_size) + 1e-9) @@ -69,7 +85,7 @@ async def put_bytes(self, data: bytes) -> None: Arbitrary length of bytes to save to buffer. Accumulates until there is a frame size worth of data, then puts a frame into the buffer. - Arguments: + Args: data: The data frame to add to the buffer. """ @@ -97,7 +113,7 @@ async def put_frame(self, data: bytes) -> None: New data added to the end of the buffer. The oldest data is removed to maintain the total number of seconds in the buffer. - Arguments: + Args: data: The data frame to add to the buffer. """ @@ -120,7 +136,7 @@ async def get_frames(self, start_time: float, end_time: float, fade_out: float = If a fade out time is specified, then the end of the slice will be faded out by the specified amount of seconds. - Arguments: + Args: start_time: The start time of the slice. end_time: The end time of the slice. fade_out: The fade out time in seconds. @@ -165,8 +181,7 @@ async def get_frames(self, start_time: float, end_time: float, fade_out: float = return data def _fade_out_audio(self, data: bytes, fade_out: float = 0.01) -> bytes: - """ - Apply a fade-out over the final `fade_out` seconds of PCM audio data. + """Apply a fade-out over the final `fade_out` seconds of PCM audio data. Args: data: Raw PCM audio data as bytes. diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c823bdda..d7cbc244 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -34,16 +34,16 @@ from ._models import DiarizationFocusMode from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode -from ._models import FragmentUtils from ._models import LanguagePackInfo from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment from ._models import TranscriptionUpdatePreset -from ._models import TurnTaskProcessor from ._models import VoiceAgentConfig from ._smart_turn import SmartTurnDetector from ._smart_turn import SmartTurnPredictionResult +from ._turn import TurnTaskProcessor +from ._utils import FragmentUtils DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] @@ -61,6 +61,10 @@ class VoiceAgentClient(AsyncClient): flags to indicate changes between messages, etc. """ + # ============================================================================ + # INITIALISATION & CONFIGURATION + # ============================================================================ + def __init__( self, api_key: Optional[str] = None, @@ -194,8 +198,6 @@ def __init__( # Register handlers self._register_event_handlers() - # def on(self, event: ServerMessageType, callback: Optional[Callable] = None) -> Callable: - def _process_config( self, config: Optional[VoiceAgentConfig] = None ) -> tuple[VoiceAgentConfig, TranscriptionConfig, AudioFormat]: @@ -272,58 +274,9 @@ def _process_config( # Return the config objects return config, transcription_config, audio_format - def _register_event_handlers(self) -> None: - """Register event handlers. - - Specific event handlers that we need to deal with. All other events - from the STT API will be available to clients to use themselves. - """ - - # Recognition started event - @self.once(ServerMessageType.RECOGNITION_STARTED) # type: ignore[misc] - def _evt_on_recognition_started(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - self._is_ready_for_audio = True - self._client_session = ClientSessionInfo( - config=self._config, - session_id=message.get("id", "UNKNOWN"), - base_time=datetime.datetime.now(datetime.timezone.utc), - language_pack_info=LanguagePackInfo.model_validate(message.get("language_pack_info", {})), - ) - - # Partial transcript event - @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) # type: ignore[misc] - def _evt_on_partial_transcript(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._handle_transcript(message, is_final=False) - - self._stt_message_queue.put_nowait(_handle) - - # Final transcript event - @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] - def _evt_on_final_transcript(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._handle_transcript(message, is_final=True) - - self._stt_message_queue.put_nowait(_handle) - - # End of utterance event - @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] - def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._emit_segments(finalize=True, end_of_turn=True) - - self._stt_message_queue.put_nowait(_handle) + # ============================================================================ + # LIFECYCLE METHODS + # ============================================================================ async def connect(self) -> None: """Connect to the Speechmatics API. @@ -373,7 +326,6 @@ async def disconnect(self) -> None: ) # Stop end of turn-related tasks - # self._finalization_handler.cancel_tasks() self._end_of_turn_handler.cancel_tasks() # Stop the STT queue task @@ -397,6 +349,36 @@ async def disconnect(self) -> None: finally: self._is_connected = False + # ============================================================================ + # PUBLIC API METHODS + # ============================================================================ + + async def send_audio(self, payload: bytes) -> None: + """Send an audio frame through the WebSocket. + + Args: + payload: The audio frame to send. + + Examples: + >>> audio_chunk = b"" + >>> await client.send_audio(audio_chunk) + """ + # Skip if not ready for audio + if not self._is_ready_for_audio: + return + + # Send to the AsyncClient + await super().send_audio(payload) + + # Add to audio buffer (use put_bytes to handle variable chunk sizes) + if self._config.audio_buffer_length > 0: + await self._audio_buffer.put_bytes(payload) + + # Calculate the time (in seconds) for the payload + if self._audio_format is not None: + self._total_bytes += len(payload) + self._total_time += len(payload) / self._audio_sample_rate / self._audio_sample_width + def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """Update the diarization configuration. @@ -409,6 +391,90 @@ def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: """ self._dz_config = config + def finalize(self, ttl: Optional[float] = None) -> None: + """Finalize segments. + + This function will emit segments in the buffer without any further checks + on the contents of the segments. If the ttl is set to zero, then finalization + will be forced through without yielding for any remaining STT messages. + + Args: + ttl: Optional delay before finalizing partial segments. + """ + + # Emit the finalize or use EndOfTurn on demand preview + async def emit() -> None: + if ttl is not None and ttl > 0: + await asyncio.sleep(ttl) + if self._config.enable_preview_features: + await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) + else: + await self._emit_segments(finalize=True, end_of_turn=True) + + # Add to queue + self._stt_message_queue.put_nowait(emit) + + # ============================================================================ + # EVENT REGISTRATION & HANDLERS + # ============================================================================ + + def _register_event_handlers(self) -> None: + """Register event handlers. + + Specific event handlers that we need to deal with. All other events + from the STT API will be available to clients to use themselves. + """ + + # Recognition started event + @self.once(ServerMessageType.RECOGNITION_STARTED) # type: ignore[misc] + def _evt_on_recognition_started(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + self._is_ready_for_audio = True + self._client_session = ClientSessionInfo( + config=self._config, + session_id=message.get("id", "UNKNOWN"), + base_time=datetime.datetime.now(datetime.timezone.utc), + language_pack_info=LanguagePackInfo.model_validate(message.get("language_pack_info", {})), + ) + + # Partial transcript event + @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) # type: ignore[misc] + def _evt_on_partial_transcript(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + + async def _handle() -> None: + await self._handle_transcript(message, is_final=False) + + self._stt_message_queue.put_nowait(_handle) + + # Final transcript event + @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] + def _evt_on_final_transcript(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + + async def _handle() -> None: + await self._handle_transcript(message, is_final=True) + + self._stt_message_queue.put_nowait(_handle) + + # End of utterance event + @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] + def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: + if DEBUG_MORE: + self._logger.debug(json.dumps(message)) + + async def _handle() -> None: + await self._emit_segments(finalize=True, end_of_turn=True) + + self._stt_message_queue.put_nowait(_handle) + + # ============================================================================ + # QUEUE PROCESSING + # ============================================================================ + def _start_stt_queue(self) -> None: """Start the STT message queue.""" self._stt_queue_task = asyncio.create_task(self._run_stt_queue()) @@ -442,31 +508,9 @@ def _stop_stt_queue(self) -> None: if self._stt_queue_task: self._stt_queue_task.cancel() - async def send_audio(self, payload: bytes) -> None: - """Send an audio frame through the WebSocket. - - Args: - payload: The audio frame to send. - - Examples: - >>> audio_chunk = b"" - >>> await client.send_audio(audio_chunk) - """ - # Skip if not ready for audio - if not self._is_ready_for_audio: - return - - # Send to the AsyncClient - await super().send_audio(payload) - - # Add to audio buffer (use put_bytes to handle variable chunk sizes) - if self._config.audio_buffer_length > 0: - await self._audio_buffer.put_bytes(payload) - - # Calculate the time (in seconds) for the payload - if self._audio_format is not None: - self._total_bytes += len(payload) - self._total_time += len(payload) / self._audio_sample_rate / self._audio_sample_width + # ============================================================================ + # METRICS + # ============================================================================ def _start_metrics_task(self) -> None: """Start the metrics task.""" @@ -505,30 +549,6 @@ def _stop_metrics_task(self) -> None: self._metrics_emitter_task.cancel() self._metrics_emitter_task = None - async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> None: - """Handle the partial and final transcript events (async). - - As `AddTranscript` messages are _always_ followed by `AddPartialTranscript` messages, - we can skip processing. Also skop if there are no fragments in the buffer. - - Args: - message: The new Partial or Final from the STT engine. - is_final: Whether the data is final or partial. - """ - - # Add the speech fragments - fragments_available = await self._add_speech_fragments( - message=message, - is_final=is_final, - ) - - # Skip if no fragments or `AddTranscript` - if not fragments_available or is_final: - return - - # Process - await self._process_speech_fragments(self._change_filter) - def _calculate_ttfb(self, end_time: float) -> None: """Calculate the time to first text. @@ -570,6 +590,34 @@ def _calculate_ttfb(self, end_time: float) -> None: }, ) + # ============================================================================ + # TRANSCRIPT PROCESSING + # ============================================================================ + + async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> None: + """Handle the partial and final transcript events (async). + + As `AddTranscript` messages are _always_ followed by `AddPartialTranscript` messages, + we can skip processing. Also skop if there are no fragments in the buffer. + + Args: + message: The new Partial or Final from the STT engine. + is_final: Whether the data is final or partial. + """ + + # Add the speech fragments + fragments_available = await self._add_speech_fragments( + message=message, + is_final=is_final, + ) + + # Skip if no fragments or `AddTranscript` + if not fragments_available or is_final: + return + + # Process + await self._process_speech_fragments(self._change_filter) + async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = False) -> bool: """Takes a new Partial or Final from the STT engine. @@ -690,6 +738,10 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Fragments available return len(self._speech_fragments) > 0 + # ============================================================================ + # SEGMENT PROCESSING & EMISSION + # ============================================================================ + def _update_current_view(self) -> None: """Load the current view of the speech fragments.""" self._current_view = SpeakerSegmentView( @@ -770,6 +822,104 @@ async def process_eot() -> None: # Create async task to process end of turn asyncio.create_task(process_eot()) + async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: + """Emit segments to listeners. + + This function will emit segments in the view without any further checks + on the contents of the segments. Any segments that end with a final / EOS + will be emitted as finals and removed from the fragment buffer. + + Args: + finalize: Whether to finalize all segments. + end_of_turn: Whether to emit an end of turn event. + """ + + # Lock the speech fragments + if self._current_view and self._current_view.segment_count > 0: + async with self._speech_fragments_lock: + # Force finalize + if finalize: + final_segments = self._current_view.segments + interim_segments = [] + + # Split between finals and interim segments + else: + final_segments = [ + s + for s in self._current_view.segments + if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + ] + interim_segments = [s for s in self._current_view.segments if s not in final_segments] + + # Emit finals first + if final_segments: + # Metadata for final segments uses actual start/end times of the segments being emitted + final_metadata = { + "start_time": final_segments[0].start_time, + "end_time": final_segments[-1].end_time, + } + self.emit( + AgentServerMessageType.ADD_SEGMENT, + { + "message": AgentServerMessageType.ADD_SEGMENT.value, + "segments": [s.model_dump(self._config.include_results) for s in final_segments], + "metadata": final_metadata, + }, + ) + self._trim_before_time = final_segments[-1].end_time + self._speech_fragments = [ + f for f in self._speech_fragments if f.start_time >= self._trim_before_time + ] + + # Emit interim segments + if interim_segments: + # Metadata for partial segments uses actual start/end times of the segments being emitted + partial_metadata = { + "start_time": interim_segments[0].start_time, + "end_time": interim_segments[-1].end_time, + } + self.emit( + AgentServerMessageType.ADD_PARTIAL_SEGMENT, + { + "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, + "segments": [s.model_dump(self._config.include_results) for s in interim_segments], + "metadata": partial_metadata, + }, + ) + + # Update the current view + self._update_current_view() + + # Reset the turn start time + if not self._turn_start_time: + self._turn_start_time = self._current_view.start_time + + # Emit end of turn + if end_of_turn and self._previous_view: + # Metadata (for LAST view) + metadata = {"start_time": self._turn_start_time, "end_time": self._previous_view.end_time} + + # Emit + self.emit( + AgentServerMessageType.END_OF_TURN, + { + "message": AgentServerMessageType.END_OF_TURN.value, + "turn_id": self._end_of_turn_handler.turn_id, + "metadata": metadata, + }, + ) + + # Reset the previous view + self._previous_view = None + self._turn_start_time = None + + # Update the turn handler (which will cancel any pending tasks) + self._end_of_turn_handler.next() + + # ============================================================================ + # TURN DETECTION & FINALIZATION + # ============================================================================ + async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: """Predict when to emit the end of turn. @@ -788,7 +938,7 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn if not self._turn_detector: return SmartTurnPredictionResult(error="Smart turn is not enabled") - # Get audio slice + # Get audio slice (add small margin of 100ms to the end of the audio) segment_audio = await self._audio_buffer.get_frames( start_time=end_time - self._config.audio_buffer_length, end_time=end_time + 0.1 ) @@ -861,7 +1011,7 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: - ends with finalizes end of sentence """ - # Minimum delay + # Minimum delay (50ms as a minimum) delay = max(self._end_of_utterance_delay, 0.05) # Delay multiplier @@ -890,12 +1040,8 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: add_multiplier(2.0, "does_not_end_with_eos") # Smart turn prediction - if smart_turn_prediction: - if smart_turn_prediction.prediction: - add_multiplier(0.25, "smart_turn_true") - else: - # add_multiplier(2.0, "smart_turn_false") - pass + if smart_turn_prediction and smart_turn_prediction.prediction: + add_multiplier(0.25, "smart_turn_true") # Calculate the multiplier for _multiplier, _reason in reasons: @@ -909,149 +1055,16 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: # Establish the real-world time time_slip = 0 # self._total_time - self._last_fragment_end_time - emit_delay = max(clamped_delay - time_slip, 0.2) - - # Debug payload - # import json - # print( - # json.dumps( - # { - # "view_changes": view_changes, - # "filter_flags": filter_flags, - # "last_active_segment": last_active_segment.model_dump() if last_active_segment else None, - # "smart_turn_prediction": smart_turn_prediction.model_dump() if smart_turn_prediction else None, - # "base_delay": self._end_of_utterance_delay, - # "multiplier": multiplier, - # "reasons": reasons, - # "delay": delay, - # "clamped_delay": clamped_delay, - # "time_slip": time_slip, - # "emit_delay": emit_delay, - # }, - # indent=4, - # ) - # ) + + # Adjust time and make sure no less than 100ms + emit_delay = max(clamped_delay - time_slip, 0.1) # Return the time return emit_delay - def finalize(self, ttl: Optional[float] = None) -> None: - """Finalize segments. - - This function will emit segments in the buffer without any further checks - on the contents of the segments. If the ttl is set to zero, then finalization - will be forced through without yielding for any remaining STT messages. - - Args: - ttl: Optional delay before finalizing partial segments. - """ - - # Emit the finalize or use EndOfTurn on demand preview - async def emit() -> None: - if ttl is not None and ttl > 0: - await asyncio.sleep(ttl) - if self._config.enable_preview_features: - await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) - else: - await self._emit_segments(finalize=True, end_of_turn=True) - - # Add to queue - self._stt_message_queue.put_nowait(emit) - - async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: - """Emit segments to listeners. - - This function will emit segments in the view without any further checks - on the contents of the segments. Any segments that end with a final / EOS - will be emitted as finals and removed from the fragment buffer. - - Args: - finalize: Whether to finalize all segments. - end_of_turn: Whether to emit an end of turn event. - """ - - # Lock the speech fragments - if self._current_view and self._current_view.segment_count > 0: - async with self._speech_fragments_lock: - # Force finalize - if finalize: - final_segments = self._current_view.segments - interim_segments = [] - - # Split between finals and interim segments - else: - final_segments = [ - s - for s in self._current_view.segments - if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) - # and not s.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY) - ] - interim_segments = [s for s in self._current_view.segments if s not in final_segments] - - # Emit finals first - if final_segments: - # Metadata for final segments uses actual start/end times of the segments being emitted - final_metadata = { - "start_time": final_segments[0].start_time, - "end_time": final_segments[-1].end_time, - } - self.emit( - AgentServerMessageType.ADD_SEGMENT, - { - "message": AgentServerMessageType.ADD_SEGMENT.value, - "segments": [s.model_dump(self._config.include_results) for s in final_segments], - "metadata": final_metadata, - }, - ) - self._trim_before_time = final_segments[-1].end_time - self._speech_fragments = [ - f for f in self._speech_fragments if f.start_time >= self._trim_before_time - ] - - # Emit interim segments - if interim_segments: - # Metadata for partial segments uses actual start/end times of the segments being emitted - partial_metadata = { - "start_time": interim_segments[0].start_time, - "end_time": interim_segments[-1].end_time, - } - self.emit( - AgentServerMessageType.ADD_PARTIAL_SEGMENT, - { - "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, - "segments": [s.model_dump(self._config.include_results) for s in interim_segments], - "metadata": partial_metadata, - }, - ) - - # Update the current view - self._update_current_view() - - # Reset the turn start time - if not self._turn_start_time: - self._turn_start_time = self._current_view.start_time - - # Emit end of turn - if end_of_turn and self._previous_view: - # Metadata (for LAST view) - metadata = {"start_time": self._turn_start_time, "end_time": self._previous_view.end_time} - - # Emit - self.emit( - AgentServerMessageType.END_OF_TURN, - { - "message": AgentServerMessageType.END_OF_TURN.value, - "turn_id": self._end_of_turn_handler.turn_id, - "metadata": metadata, - }, - ) - - # Reset the previous view - self._previous_view = None - self._turn_start_time = None - - # Update the turn handler (which will cancel any pending tasks) - self._end_of_turn_handler.next() + # ============================================================================ + # VAD (VOICE ACTIVITY DETECTION) + # ============================================================================ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. @@ -1187,6 +1200,10 @@ async def do_smart_turn(end_time: float, language: str) -> None: # Reset the handlers self._end_of_turn_handler.reset() + # ============================================================================ + # HELPER METHODS + # ============================================================================ + def _next_fragment_id(self) -> int: """Return the next fragment ID.""" self._fragment_idx += 1 diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 7ab214de..43c6f9b4 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -4,11 +4,9 @@ from __future__ import annotations -import asyncio import datetime from enum import Enum from typing import Any -from typing import Callable from typing import Optional from pydantic import BaseModel @@ -17,6 +15,10 @@ from speechmatics.rt import AudioEncoding from speechmatics.rt import OperatingPoint +# ============================================================================== +# ENUMS +# ============================================================================== + class EndOfUtteranceMode(str, Enum): """End of turn delay options for transcription. @@ -73,6 +75,161 @@ class DiarizationFocusMode(str, Enum): IGNORE = "ignore" +class AgentClientMessageType(str, Enum): + """Message types that can be sent from client to server. + + These enum values represent the different types of messages that the + client can send to the Speechmatics RT API during a transcription session. + + Attributes: + FinalizeTurn: Force the finalization of the current turn. + EndOfStream: Signals that no more audio data will be sent. + GetSpeakers: Internal, Speechmatics only message. Allows the client to request speaker data. + + Examples: + >>> # Finalizing the current turn + >>> finalize_turn_message = { + ... "message": AgentClientMessageType.FINALIZE_TURN + ... } + >>> await client.send_message(finalize_turn_message) + >>> + >>> # Ending the session + >>> end_message = { + ... "message": AgentClientMessageType.END_OF_STREAM, + ... "last_seq_no": sequence_number + ... } + >>> await client.send_message(end_message) + """ + + FINALIZE_TURN = "Finalize" + END_OF_STREAM = "EndOfStream" + GET_SPEAKERS = "GetSpeakers" + + +class AgentServerMessageType(str, Enum): + """Message types that can be received from the server / agent. + + These enum values represent the different types of messages that the + Speechmatics RT API / Voice Agent SDK can send to the client. + + Attributes: + RecognitionStarted: The recognition session has started. + EndOfTranscript: The recognition session has ended. + Info: Informational message. + Warning: Warning message. + Error: Error message. + AddPartialTranscript: Partial transcript has been added. + AddTranscript: Transcript has been added. + EndOfUtterance: End of utterance has been detected (from STT engine). + SpeakerStarted: Speech has started. + SpeakerEnded: Speech has ended. + AddPartialSegment: A partial / interim segment has been detected. + AddSegment: A final segment has been detected. + EndOfTurn: End of turn has been detected. + SpeakersResult: Speakers result has been detected. + Metrics: Metrics for the STT engine. + SpeakerMetrics: Metrics relating to speakers. + + Examples: + >>> # Register event handlers for different message types + >>> @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) + >>> def handle_interim(message): + ... segments: list[SpeakerSegment] = message['segments'] + ... print(f"Interim: {segments}") + >>> + >>> @client.on(AgentServerMessageType.ADD_SEGMENT) + >>> def handle_final(message): + ... segments: list[SpeakerSegment] = message['segments'] + ... print(f"Final: {segments}") + >>> + >>> @client.on(AgentServerMessageType.END_OF_TURN) + >>> def handle_end_of_turn(message): + ... print(f"End of turn") + >>> + >>> @client.on(AgentServerMessageType.ERROR) + >>> def handle_error(message): + ... print(f"Error: {message['reason']}") + """ + + # API messages + RECOGNITION_STARTED = "RecognitionStarted" + END_OF_TRANSCRIPT = "EndOfTranscript" + INFO = "Info" + WARNING = "Warning" + ERROR = "Error" + + # Raw transcription messages + ADD_PARTIAL_TRANSCRIPT = "AddPartialTranscript" + ADD_TRANSCRIPT = "AddTranscript" + END_OF_UTTERANCE = "EndOfUtterance" + + # VAD messages + SPEAKER_STARTED = "SpeakerStarted" + SPEAKER_ENDED = "SpeakerEnded" + + # Segment messages + ADD_PARTIAL_SEGMENT = "AddPartialSegment" + ADD_SEGMENT = "AddSegment" + + # End of turn messages + END_OF_TURN = "EndOfTurn" + + # Speaker messages + SPEAKERS_RESULT = "SpeakersResult" + + # Metrics + METRICS = "Metrics" + TTFB_METRICS = "TTFBMetrics" + SPEAKER_METRICS = "SpeakerMetrics" + + +class AnnotationFlags(str, Enum): + """Flags to apply when processing speech / objects.""" + + # High-level segment updates + NEW = "new" + UPDATED_FULL = "updated_full" + UPDATED_FULL_LCASE = "updated_full_lcase" + UPDATED_STRIPPED = "updated_stripped" + UPDATED_STRIPPED_LCASE = "updated_stripped_lcase" + UPDATED_FINALS = "updated_finals" + UPDATED_PARTIALS = "updated_partials" + UPDATED_SPEAKERS = "updated_speakers" + UPDATED_WORD_TIMINGS = "updated_word_timings" + FINALIZED = "finalized" + + # Content of segments + ONLY_ACTIVE_SPEAKERS = "only_active_speakers" + CONTAINS_INACTIVE_SPEAKERS = "contains_inactive_speakers" + + # More granular details on the word content + HAS_PARTIAL = "has_partial" + HAS_FINAL = "has_final" + STARTS_WITH_FINAL = "starts_with_final" + ENDS_WITH_FINAL = "ends_with_final" + HAS_EOS = "has_eos" + ENDS_WITH_EOS = "ends_with_eos" + HAS_DISFLUENCY = "has_disfluency" + STARTS_WITH_DISFLUENCY = "starts_with_disfluency" + ENDS_WITH_DISFLUENCY = "ends_with_disfluency" + HIGH_DISFLUENCY_COUNT = "high_disfluency_count" + ENDS_WITH_PUNCTUATION = "ends_with_punctuation" + VERY_SLOW_SPEAKER = "very_slow_speaker" + SLOW_SPEAKER = "slow_speaker" + FAST_SPEAKER = "fast_speaker" + ONLY_PUNCTUATION = "only_punctuation" + MULTIPLE_SPEAKERS = "multiple_speakers" + NO_TEXT = "no_text" + + # End of utterance detection + END_OF_UTTERANCE = "end_of_utterance" + + +# ============================================================================== +# CONFIGURATION MODELS +# ============================================================================== + + class AdditionalVocabEntry(BaseModel): """Additional vocabulary entry. @@ -267,154 +424,9 @@ class VoiceAgentConfig(BaseModel): audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE -class AgentClientMessageType(str, Enum): - """Message types that can be sent from client to server. - - These enum values represent the different types of messages that the - client can send to the Speechmatics RT API during a transcription session. - - Attributes: - FinalizeTurn: Force the finalization of the current turn. - EndOfStream: Signals that no more audio data will be sent. - GetSpeakers: Internal, Speechmatics only message. Allows the client to request speaker data. - - Examples: - >>> # Finalizing the current turn - >>> finalize_turn_message = { - ... "message": AgentClientMessageType.FINALIZE_TURN - ... } - >>> await client.send_message(finalize_turn_message) - >>> - >>> # Ending the session - >>> end_message = { - ... "message": AgentClientMessageType.END_OF_STREAM, - ... "last_seq_no": sequence_number - ... } - >>> await client.send_message(end_message) - """ - - FINALIZE_TURN = "Finalize" - END_OF_STREAM = "EndOfStream" - GET_SPEAKERS = "GetSpeakers" - - -class AgentServerMessageType(str, Enum): - """Message types that can be received from the server / agent. - - These enum values represent the different types of messages that the - Speechmatics RT API / Voice Agent SDK can send to the client. - - Attributes: - RecognitionStarted: The recognition session has started. - EndOfTranscript: The recognition session has ended. - Info: Informational message. - Warning: Warning message. - Error: Error message. - AddPartialTranscript: Partial transcript has been added. - AddTranscript: Transcript has been added. - EndOfUtterance: End of utterance has been detected (from STT engine). - SpeakerStarted: Speech has started. - SpeakerEnded: Speech has ended. - AddPartialSegment: A partial / interim segment has been detected. - AddSegment: A final segment has been detected. - EndOfTurn: End of turn has been detected. - SpeakersResult: Speakers result has been detected. - Metrics: Metrics for the STT engine. - SpeakerMetrics: Metrics relating to speakers. - - Examples: - >>> # Register event handlers for different message types - >>> @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) - >>> def handle_interim(message): - ... segments: list[SpeakerSegment] = message['segments'] - ... print(f"Interim: {segments}") - >>> - >>> @client.on(AgentServerMessageType.ADD_SEGMENT) - >>> def handle_final(message): - ... segments: list[SpeakerSegment] = message['segments'] - ... print(f"Final: {segments}") - >>> - >>> @client.on(AgentServerMessageType.END_OF_TURN) - >>> def handle_end_of_turn(message): - ... print(f"End of turn") - >>> - >>> @client.on(AgentServerMessageType.ERROR) - >>> def handle_error(message): - ... print(f"Error: {message['reason']}") - """ - - # API messages - RECOGNITION_STARTED = "RecognitionStarted" - END_OF_TRANSCRIPT = "EndOfTranscript" - INFO = "Info" - WARNING = "Warning" - ERROR = "Error" - - # Raw transcription messages - ADD_PARTIAL_TRANSCRIPT = "AddPartialTranscript" - ADD_TRANSCRIPT = "AddTranscript" - END_OF_UTTERANCE = "EndOfUtterance" - - # VAD messages - SPEAKER_STARTED = "SpeakerStarted" - SPEAKER_ENDED = "SpeakerEnded" - - # Segment messages - ADD_PARTIAL_SEGMENT = "AddPartialSegment" - ADD_SEGMENT = "AddSegment" - - # End of turn messages - END_OF_TURN = "EndOfTurn" - - # Speaker messages - SPEAKERS_RESULT = "SpeakersResult" - - # Metrics - METRICS = "Metrics" - TTFB_METRICS = "TTFBMetrics" - SPEAKER_METRICS = "SpeakerMetrics" - - -class AnnotationFlags(str, Enum): - """Flags to apply when processing speech / objects.""" - - # High-level segment updates - NEW = "new" - UPDATED_FULL = "updated_full" - UPDATED_FULL_LCASE = "updated_full_lcase" - UPDATED_STRIPPED = "updated_stripped" - UPDATED_STRIPPED_LCASE = "updated_stripped_lcase" - UPDATED_FINALS = "updated_finals" - UPDATED_PARTIALS = "updated_partials" - UPDATED_SPEAKERS = "updated_speakers" - UPDATED_WORD_TIMINGS = "updated_word_timings" - FINALIZED = "finalized" - - # Content of segments - ONLY_ACTIVE_SPEAKERS = "only_active_speakers" - CONTAINS_INACTIVE_SPEAKERS = "contains_inactive_speakers" - - # More granular details on the word content - HAS_PARTIAL = "has_partial" - HAS_FINAL = "has_final" - STARTS_WITH_FINAL = "starts_with_final" - ENDS_WITH_FINAL = "ends_with_final" - HAS_EOS = "has_eos" - ENDS_WITH_EOS = "ends_with_eos" - HAS_DISFLUENCY = "has_disfluency" - STARTS_WITH_DISFLUENCY = "starts_with_disfluency" - ENDS_WITH_DISFLUENCY = "ends_with_disfluency" - HIGH_DISFLUENCY_COUNT = "high_disfluency_count" - ENDS_WITH_PUNCTUATION = "ends_with_punctuation" - VERY_SLOW_SPEAKER = "very_slow_speaker" - SLOW_SPEAKER = "slow_speaker" - FAST_SPEAKER = "fast_speaker" - ONLY_PUNCTUATION = "only_punctuation" - MULTIPLE_SPEAKERS = "multiple_speakers" - NO_TEXT = "no_text" - - # End of utterance detection - END_OF_UTTERANCE = "end_of_utterance" +# ============================================================================== +# SESSION & INFO MODELS +# ============================================================================== class LanguagePackInfo(BaseModel): @@ -488,6 +500,11 @@ def __eq__(self, other: object) -> bool: return False +# ============================================================================== +# FRAGMENT & SEGMENT MODELS +# ============================================================================== + + class SpeechFragment(BaseModel): """Fragment of a speech event. @@ -607,6 +624,9 @@ def __init__( annotate_segments: bool = True, **data: Any, ) -> None: + # Lazy import to avoid circular dependency + from ._utils import FragmentUtils + # Process fragments into a list of segments segments = FragmentUtils.segment_list_from_fragments( session=session, @@ -663,6 +683,9 @@ def format_view_text( Returns: str: The formatted text. """ + # Lazy import to avoid circular dependency + from ._utils import FragmentUtils + return separator.join( FragmentUtils.format_segment_text( session=self.session, @@ -681,6 +704,9 @@ def trim(self, start_time: float, end_time: float, annotate_segments: bool = Tru end_time: End time in seconds. annotate_segments: Whether to annotate segments. """ + # Lazy import to avoid circular dependency + from ._utils import FragmentUtils + self.fragments = [ frag for frag in self.fragments if frag.start_time >= start_time and frag.end_time <= end_time ] @@ -707,418 +733,3 @@ class SpeakerVADStatus(BaseModel): is_active: bool speaker_id: Optional[str] = None time: Optional[float] = None - - -class FragmentUtils: - """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" - - @staticmethod - def format_segment_text( - session: ClientSessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False - ) -> str: - """Format a segment's text based on the language pack info. - - Args: - session: ClientSessionInfo object. - segment: SpeakerSegment object. - words_only: Whether to include only word fragments. - - Returns: - str: The formatted text. - """ - - # Cumulative contents - content = "" - - # Select fragments to include - if words_only: - fragments = [frag for frag in segment.fragments if frag.type_ == "word"] - else: - fragments = segment.fragments - - # Assemble the text - previous_frag: Optional[SpeechFragment] = None - for frag in fragments: - if not previous_frag: - content = frag.content - elif frag.attaches_to == "previous" or previous_frag.attaches_to == "next": - content += frag.content - else: - content += session.language_pack_info.word_delimiter + frag.content - previous_frag = frag - - # Return the formatted text - return format.format( - **{ - "speaker_id": segment.speaker_id, - "text": content, - "ts": segment.timestamp, - "lang": segment.language, - "start_time": fragments[0].start_time, - "end_time": fragments[-1].end_time, - } - ) - - @staticmethod - def segment_list_from_fragments( - session: ClientSessionInfo, - fragments: list[SpeechFragment], - focus_speakers: Optional[list[str]] = None, - annotate_segments: bool = True, - ) -> list[SpeakerSegment]: - """Create SpeakerSegment objects from a list of SpeechFragment objects. - - Args: - session: ClientSessionInfo object. - fragments: List of SpeechFragment objects. - focus_speakers: List of speakers to focus on or None. - annotate_segments: Whether to annotate segments. - - Returns: - List of SpeakerSegment objects. - """ - - # Speaker groups - current_speaker: Optional[str] = None - speaker_groups: list[list[SpeechFragment]] = [[]] - - # Group by speakers - for frag in fragments: - if frag.speaker != current_speaker: - current_speaker = frag.speaker - if speaker_groups[-1]: - speaker_groups.append([]) - speaker_groups[-1].append(frag) - - # Create SpeakerFragments objects - segments: list[SpeakerSegment] = [] - for group in speaker_groups: - segment = FragmentUtils.segment_from_fragments( - session=session, - fragments=group, - focus_speakers=focus_speakers, - annotate=annotate_segments, - ) - if segment: - segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) - segments.append(segment) - - # Return the grouped SpeakerFragments objects - return segments - - @staticmethod - def segment_from_fragments( - session: ClientSessionInfo, - fragments: list[SpeechFragment], - focus_speakers: Optional[list[str]] = None, - annotate: bool = True, - ) -> Optional[SpeakerSegment]: - """Take a group of fragments and piece together into SpeakerSegment. - - Each fragment for a given speaker is assembled into a string, - taking into consideration whether words are attached to the - previous or next word (notably punctuation). This ensures that - the text does not have extra spaces. This will also check for - any straggling punctuation from earlier utterances that should - be removed. - - Args: - session: ClientSessionInfo object. - fragments: List of SpeechFragment objects. - focus_speakers: List of speakers to focus on. - annotate: Whether to annotate the segment. - - Returns: - SpeakerSegment: The object for the group. - """ - # Check for starting fragments that are attached to previous - if fragments and fragments[0].attaches_to == "previous": - fragments = fragments[1:] - - # Check for trailing fragments that are attached to next - if fragments and fragments[-1].attaches_to == "next": - fragments = fragments[:-1] - - # Check there are results - if not fragments: - return None - - # Get the timing extremes - start_time = min(frag.start_time for frag in fragments) - - # Timestamp - ts = (session.base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") - - # Determine if the speaker is considered active - is_active = True - if focus_speakers: - is_active = fragments[0].speaker in focus_speakers - - # New SpeakerSegment - segment = SpeakerSegment( - speaker_id=fragments[0].speaker, - timestamp=ts, - language=fragments[0].language, - fragments=fragments, - is_active=is_active, - ) - - # Annotate - if annotate: - segment.annotation = FragmentUtils._annotate_segment(segment) - - # Return the SpeakerSegment object - return segment - - @staticmethod - def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: - """Annotate the segment with any additional information. - - Args: - segment: SpeakerSegment object. - - Returns: - AnnotationResult: The annotation result. - """ - # Annotation result - result = AnnotationResult() - - # References - segment_length: int = len(segment.fragments) - first_fragment: SpeechFragment = segment.fragments[0] - last_fragment: SpeechFragment = segment.fragments[-1] - penultimate_fragment: Optional[SpeechFragment] = segment.fragments[-2] if segment_length > 1 else None - - # Count of words - words = [frag for frag in segment.fragments if frag.type_ == "word"] - word_count = len(words) - if word_count == 0: - result.add(AnnotationFlags.NO_TEXT) - - # Only punctuation - if all(frag.is_punctuation for frag in segment.fragments): - result.add(AnnotationFlags.ONLY_PUNCTUATION) - - # Partials and finals - if any(not frag.is_final for frag in segment.fragments): - result.add(AnnotationFlags.HAS_PARTIAL) - - # Finals - if any(frag.is_final for frag in segment.fragments): - result.add(AnnotationFlags.HAS_FINAL) - if first_fragment.is_final: - result.add(AnnotationFlags.STARTS_WITH_FINAL) - if last_fragment.is_final: - result.add(AnnotationFlags.ENDS_WITH_FINAL) - - # End of sentence - if last_fragment.is_eos: - result.add(AnnotationFlags.ENDS_WITH_EOS) - - # Punctuation - if last_fragment.is_punctuation: - result.add(AnnotationFlags.ENDS_WITH_PUNCTUATION) - - # Disfluency - if any(frag.is_disfluency for frag in segment.fragments): - result.add(AnnotationFlags.HAS_DISFLUENCY) - if first_fragment.is_disfluency: - result.add(AnnotationFlags.STARTS_WITH_DISFLUENCY) - if last_fragment.is_disfluency: - result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) - if ( - penultimate_fragment - and result.any(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_PUNCTUATION) - and penultimate_fragment.is_disfluency - ): - result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) - - # Rate of speech - if len(words) > 1: - # Calculate the approximate words-per-minute (for last 5 words) - last_5_words = words[-5:] - wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) - - # Categorize the speaker - if wpm < 30: - result.add(AnnotationFlags.VERY_SLOW_SPEAKER) - elif wpm < 80: - result.add(AnnotationFlags.SLOW_SPEAKER) - elif wpm > 350: - result.add(AnnotationFlags.FAST_SPEAKER) - - # Return the annotation result - return result - - @staticmethod - def compare_views( - session: ClientSessionInfo, view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] - ) -> AnnotationResult: - """Compare two SpeakerSegmentView objects and return the differences. - - View 1 (new) is compared to view 2 (old). - - Args: - session: ClientSessionInfo object. - view1: The first SpeakerSegmentView object to compare. - view2: The second SpeakerSegmentView object to compare to or None. - - Returns: - AnnotationResult: The annotation result. - """ - # Result - result = AnnotationResult() - - # If we have a previous view, compare it - if view2 and view2.segment_count > 0: - # Compare full string - view1_full_str: str = view1.format_view_text() - view2_full_str: str = view2.format_view_text() - if view1_full_str != view2_full_str: - result.add(AnnotationFlags.UPDATED_FULL) - if view1_full_str.lower() != view2_full_str.lower(): - result.add(AnnotationFlags.UPDATED_FULL_LCASE) - - # Stripped string (without punctuation) - view1_stripped_str: str = view1.format_view_text(words_only=True) - view2_stripped_str: str = view2.format_view_text(words_only=True) - if view1_stripped_str != view2_stripped_str: - result.add(AnnotationFlags.UPDATED_STRIPPED) - if view1_stripped_str.lower() != view2_stripped_str.lower(): - result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) - - # Word timings - view1_timings_str: str = view1.format_view_text(format="|{start_time}-{end_time}|", words_only=True) - view2_timings_str: str = view2.format_view_text(format="|{start_time}-{end_time}|", words_only=True) - if view1_timings_str != view2_timings_str: - result.add(AnnotationFlags.UPDATED_WORD_TIMINGS) - - # Partials, finals and speakers - if view1.final_count != view2.final_count: - result.add(AnnotationFlags.UPDATED_FINALS) - if view1.partial_count != view2.partial_count: - result.add(AnnotationFlags.UPDATED_PARTIALS) - if view1.segment_count != view2.segment_count: - result.add(AnnotationFlags.UPDATED_SPEAKERS) - - # Assume this is new - elif view1.segment_count > 0: - result.add(AnnotationFlags.NEW) - - # Finalized (last segment only has finals) - if view1.segment_count > 0 and view1.partial_count == 0: - result.add(AnnotationFlags.FINALIZED) - - # Return the result - return result - - -class TurnTaskProcessor: - """Container for turn task processing. - - This utility is used to make sure that all processing is completed within a turn. When a - process is added, once it completes and all other tasks have also completed, then it will - make a call to the `done_callback` function (sync or async). - """ - - def __init__(self, name: str, turn_id: int = 0, done_callback: Optional[Callable] = None): - """Create new handler. - - Args: - name: The name of the processor. - turn_id: The turn id (used to validate tasks). - done_callback: The callback to call when all tasks are completed. - """ - - # Processor name - self._name = name - - # Turn id (used to validate tasks) - self._turn_id = turn_id - - # Tasks + events - self._tasks: dict[str, asyncio.Task] = {} - self._listener_tasks: list[asyncio.Task] = [] - - # Done callback (can be async) - self._done_callback: Optional[Callable] = done_callback - - @property - def has_pending_tasks(self) -> bool: - """Check for any pending tasks.""" - return any(not task.done() for task in self._tasks.values()) - - @property - def turn_id(self) -> int: - """Get the turn id.""" - return self._turn_id - - def update_timer(self, delay: float) -> None: - """Set a new done trigger.""" - if delay < 0: - return - self.add_task( - asyncio.create_task(asyncio.sleep(delay)), - "done_task", - ) - - def add_task(self, task: asyncio.Task, task_name: str) -> None: - """Add a task to the end of turn.""" - - # Cancel any same-named tasks - if task_name in self._tasks and not self._tasks[task_name].done(): - self._tasks[task_name].cancel() - - # Add the task to the list - self._tasks[task_name] = task - - # Wait for the task - async def wait_for_task(task: asyncio.Task) -> None: - try: - _turn_id = self._turn_id - await task - if _turn_id != self._turn_id: - return - if not self.has_pending_tasks: - asyncio.create_task(self._do_done_callback()) - except asyncio.CancelledError: - pass - - # Start the task - asyncio.create_task(wait_for_task(task)) - - async def _do_done_callback(self) -> None: - """Do the done callback.""" - - # Cancel any pending tasks - self.cancel_tasks() - - # Do the callback - if self._done_callback: - try: - if asyncio.iscoroutinefunction(self._done_callback): - await self._done_callback() - else: - self._done_callback() - except Exception: - pass - - def cancel_tasks(self) -> None: - """Cancel any pending tasks.""" - for task in self._tasks.values(): - if not task.done(): - task.cancel() - self._tasks.clear() - - def reset(self) -> None: - """Reset the end of turn.""" - self.cancel_tasks() - - def next(self) -> None: - """Increment the end of turn.""" - self.reset() - self._turn_id += 1 - - def __str__(self) -> str: - """Get the string representation of the end of turn.""" - return f"TurnTaskProcessor(name={self._name}, turn_id={self._turn_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 5168ee65..ceaa1719 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -49,11 +49,16 @@ class SmartTurnDetector: Uses Pipecat's opensource acoustic model for determining if an audio sample is predicted to be complete or incomplete. - Further informtaion at https://github.com/pipecat-ai/smart-turn + Further information at https://github.com/pipecat-ai/smart-turn """ def __init__(self, auto_init: bool = True, threshold: float = 0.8): - """Create the new SmartTurnDetector.""" + """Create the new SmartTurnDetector. + + Args: + auto_init: Whether to automatically initialise the detector. + threshold: Probability threshold for turn completion (0.0-1.0). + """ # Has initialized self._is_initialized: bool = False @@ -66,7 +71,10 @@ def __init__(self, auto_init: bool = True, threshold: float = 0.8): self.setup() def setup(self) -> None: - """Setup the detector.""" + """Setup the detector. + + Initialises the ONNX model and feature extractor. + """ try: # Check / download the model @@ -93,10 +101,10 @@ def build_session(self, onnx_path: str) -> ort.InferenceSession: """Build the ONNX session and load resources. Args: - onnx_path: Path to the ONNX model + onnx_path: Path to the ONNX model. Returns: - ort.InferenceSession: ONNX session + ONNX inference session. """ # Build the session @@ -117,12 +125,12 @@ async def predict( audio_array: Numpy array containing audio samples at 16kHz. The function will convert the audio into float32 and truncate to 8 seconds (keeping the end) or pad to 8 seconds. - language: Language of the audio - sample_rate: Sample rate of the audio - sample_width: Sample width of the audio + language: Language of the audio. + sample_rate: Sample rate of the audio. + sample_width: Sample width of the audio. Returns: - SmartTurnPredictionResult: Prediction result + Prediction result containing completion status and probability. """ # Check if initialized @@ -182,13 +190,12 @@ def truncate_audio_to_last_n_seconds( """Truncate audio to last n seconds or pad with zeros to meet n seconds. Args: - audio_array: Numpy array containing audio samples at 16kHz - n_seconds: Number of seconds to truncate to - sample_rate: Sample rate of the audio + audio_array: Numpy array containing audio samples at 16kHz. + n_seconds: Number of seconds to truncate to. + sample_rate: Sample rate of the audio. Returns: - Numpy array containing audio samples at 16kHz truncated to last n seconds - or padded with zeros to meet n seconds + Numpy array truncated to last n seconds or padded with zeros. """ # Calculate the max samples we should have @@ -237,12 +244,23 @@ def download_model() -> None: @staticmethod def model_exists() -> bool: - """Check the model has been downloaded.""" + """Check the model has been downloaded. + + Returns: + True if the model file exists, False otherwise. + """ return os.path.exists(ONNX_MODEL_PATH) @staticmethod def valid_language(language: str) -> bool: - """Check if the language is valid.""" + """Check if the language is valid. + + Args: + language: Language code to validate. + + Returns: + True if the language is supported, False otherwise. + """ return language in [ "ar", "bn", diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py new file mode 100644 index 00000000..7bdcc057 --- /dev/null +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -0,0 +1,141 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +import asyncio +from typing import Callable +from typing import Optional + + +class TurnTaskProcessor: + """Container for turn task processing. + + This utility is used to make sure that all processing is completed within a turn. When a + process is added, once it completes and all other tasks have also completed, then it will + make a call to the `done_callback` function (sync or async). + """ + + def __init__(self, name: str, turn_id: int = 0, done_callback: Optional[Callable] = None): + """Create new handler. + + Args: + name: The name of the processor. + turn_id: The turn id (used to validate tasks). + done_callback: The callback to call when all tasks are completed. + """ + + # Processor name + self._name = name + + # Turn id (used to validate tasks) + self._turn_id = turn_id + + # Tasks + events + self._tasks: dict[str, asyncio.Task] = {} + self._listener_tasks: list[asyncio.Task] = [] + + # Done callback (can be async) + self._done_callback: Optional[Callable] = done_callback + + @property + def has_pending_tasks(self) -> bool: + """Check for any pending tasks. + + Returns: + True if there are pending tasks, False otherwise. + """ + return any(not task.done() for task in self._tasks.values()) + + @property + def turn_id(self) -> int: + """Get the turn id. + + Returns: + The current turn ID. + """ + return self._turn_id + + def update_timer(self, delay: float) -> None: + """Set a new done trigger. + + Args: + delay: Delay in seconds before triggering done callback. + """ + if delay < 0: + return + self.add_task( + asyncio.create_task(asyncio.sleep(delay)), + "done_task", + ) + + def add_task(self, task: asyncio.Task, task_name: str) -> None: + """Add a task to the end of turn. + + Args: + task: The asyncio task to add. + task_name: Name identifier for the task. + """ + + # Cancel any same-named tasks + if task_name in self._tasks and not self._tasks[task_name].done(): + self._tasks[task_name].cancel() + + # Add the task to the list + self._tasks[task_name] = task + + # Wait for the task + async def wait_for_task(task: asyncio.Task) -> None: + try: + _turn_id = self._turn_id + await task + if _turn_id != self._turn_id: + return + if not self.has_pending_tasks: + asyncio.create_task(self._do_done_callback()) + except asyncio.CancelledError: + pass + + # Start the task + asyncio.create_task(wait_for_task(task)) + + async def _do_done_callback(self) -> None: + """Do the done callback.""" + + # Cancel any pending tasks + self.cancel_tasks() + + # Do the callback + if self._done_callback: + try: + if asyncio.iscoroutinefunction(self._done_callback): + await self._done_callback() + else: + self._done_callback() + except Exception: + pass + + def cancel_tasks(self) -> None: + """Cancel any pending tasks.""" + for task in self._tasks.values(): + if not task.done(): + task.cancel() + self._tasks.clear() + + def reset(self) -> None: + """Reset the end of turn.""" + self.cancel_tasks() + + def next(self) -> None: + """Increment the end of turn.""" + self.reset() + self._turn_id += 1 + + def __str__(self) -> str: + """Get the string representation of the end of turn. + + Returns: + String representation of the processor state. + """ + return f"TurnTaskProcessor(name={self._name}, turn_id={self._turn_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py new file mode 100644 index 00000000..e59a35cd --- /dev/null +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -0,0 +1,320 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +import datetime +from typing import Optional + +from ._models import AnnotationFlags +from ._models import AnnotationResult +from ._models import ClientSessionInfo +from ._models import SpeakerSegment +from ._models import SpeakerSegmentView +from ._models import SpeechFragment + + +class FragmentUtils: + """Set of utility functions for working with SpeechFragment and SpeakerSegment objects.""" + + @staticmethod + def format_segment_text( + session: ClientSessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False + ) -> str: + """Format a segment's text based on the language pack info. + + Args: + session: ClientSessionInfo object. + segment: SpeakerSegment object. + format: Format string. + words_only: Whether to include only word fragments. + + Returns: + str: The formatted text. + """ + + # Cumulative contents + content = "" + + # Select fragments to include + if words_only: + fragments = [frag for frag in segment.fragments if frag.type_ == "word"] + else: + fragments = segment.fragments + + # Assemble the text + previous_frag: Optional[SpeechFragment] = None + for frag in fragments: + if not previous_frag: + content = frag.content + elif frag.attaches_to == "previous" or previous_frag.attaches_to == "next": + content += frag.content + else: + content += session.language_pack_info.word_delimiter + frag.content + previous_frag = frag + + # Return the formatted text + return format.format( + **{ + "speaker_id": segment.speaker_id, + "text": content, + "ts": segment.timestamp, + "lang": segment.language, + "start_time": fragments[0].start_time if fragments else 0, + "end_time": fragments[-1].end_time if fragments else 0, + } + ) + + @staticmethod + def segment_list_from_fragments( + session: ClientSessionInfo, + fragments: list[SpeechFragment], + focus_speakers: Optional[list[str]] = None, + annotate_segments: bool = True, + ) -> list[SpeakerSegment]: + """Create SpeakerSegment objects from a list of SpeechFragment objects. + + Args: + session: ClientSessionInfo object. + fragments: List of SpeechFragment objects. + focus_speakers: List of speakers to focus on or None. + annotate_segments: Whether to annotate segments. + + Returns: + List of SpeakerSegment objects. + """ + + # Speaker groups + current_speaker: Optional[str] = None + speaker_groups: list[list[SpeechFragment]] = [[]] + + # Group by speakers + for frag in fragments: + if frag.speaker != current_speaker: + current_speaker = frag.speaker + if speaker_groups[-1]: + speaker_groups.append([]) + speaker_groups[-1].append(frag) + + # Create SpeakerFragments objects + segments: list[SpeakerSegment] = [] + for group in speaker_groups: + segment = FragmentUtils.segment_from_fragments( + session=session, + fragments=group, + focus_speakers=focus_speakers, + annotate=annotate_segments, + ) + if segment: + segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) + segments.append(segment) + + # Return the grouped SpeakerFragments objects + return segments + + @staticmethod + def segment_from_fragments( + session: ClientSessionInfo, + fragments: list[SpeechFragment], + focus_speakers: Optional[list[str]] = None, + annotate: bool = True, + ) -> Optional[SpeakerSegment]: + """Take a group of fragments and piece together into SpeakerSegment. + + Each fragment for a given speaker is assembled into a string, + taking into consideration whether words are attached to the + previous or next word (notably punctuation). This ensures that + the text does not have extra spaces. This will also check for + any straggling punctuation from earlier utterances that should + be removed. + + Args: + session: ClientSessionInfo object. + fragments: List of SpeechFragment objects. + focus_speakers: List of speakers to focus on. + annotate: Whether to annotate the segment. + + Returns: + The SpeakerSegment object for the group, or None if no valid fragments. + """ + # Check for starting fragments that are attached to previous + if fragments and fragments[0].attaches_to == "previous": + fragments = fragments[1:] + + # Check for trailing fragments that are attached to next + if fragments and fragments[-1].attaches_to == "next": + fragments = fragments[:-1] + + # Check there are results + if not fragments: + return None + + # Get the timing extremes + start_time = min(frag.start_time for frag in fragments) + + # Timestamp + ts = (session.base_time + datetime.timedelta(seconds=start_time)).isoformat(timespec="milliseconds") + + # Determine if the speaker is considered active + is_active = True + if focus_speakers: + is_active = fragments[0].speaker in focus_speakers + + # New SpeakerSegment + segment = SpeakerSegment( + speaker_id=fragments[0].speaker, + timestamp=ts, + language=fragments[0].language, + fragments=fragments, + is_active=is_active, + ) + + # Annotate + if annotate: + segment.annotation = FragmentUtils._annotate_segment(segment) + + # Return the SpeakerSegment object + return segment + + @staticmethod + def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: + """Annotate the segment with any additional information. + + Args: + segment: SpeakerSegment object. + + Returns: + AnnotationResult: The annotation result. + """ + # Annotation result + result = AnnotationResult() + + # References + segment_length: int = len(segment.fragments) + first_fragment: SpeechFragment = segment.fragments[0] + last_fragment: SpeechFragment = segment.fragments[-1] + penultimate_fragment: Optional[SpeechFragment] = segment.fragments[-2] if segment_length > 1 else None + + # Count of words + words = [frag for frag in segment.fragments if frag.type_ == "word"] + word_count = len(words) + if word_count == 0: + result.add(AnnotationFlags.NO_TEXT) + + # Only punctuation + if all(frag.is_punctuation for frag in segment.fragments): + result.add(AnnotationFlags.ONLY_PUNCTUATION) + + # Partials and finals + if any(not frag.is_final for frag in segment.fragments): + result.add(AnnotationFlags.HAS_PARTIAL) + + # Finals + if any(frag.is_final for frag in segment.fragments): + result.add(AnnotationFlags.HAS_FINAL) + if first_fragment.is_final: + result.add(AnnotationFlags.STARTS_WITH_FINAL) + if last_fragment.is_final: + result.add(AnnotationFlags.ENDS_WITH_FINAL) + + # End of sentence + if last_fragment.is_eos: + result.add(AnnotationFlags.ENDS_WITH_EOS) + + # Punctuation + if last_fragment.is_punctuation: + result.add(AnnotationFlags.ENDS_WITH_PUNCTUATION) + + # Disfluency + if any(frag.is_disfluency for frag in segment.fragments): + result.add(AnnotationFlags.HAS_DISFLUENCY) + if first_fragment.is_disfluency: + result.add(AnnotationFlags.STARTS_WITH_DISFLUENCY) + if last_fragment.is_disfluency: + result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) + if ( + penultimate_fragment + and result.any(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_PUNCTUATION) + and penultimate_fragment.is_disfluency + ): + result.add(AnnotationFlags.ENDS_WITH_DISFLUENCY) + + # Rate of speech + if len(words) > 1: + # Calculate the approximate words-per-minute (for last 5 words) + last_5_words = words[-5:] + wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) + + # Categorize the speaker + if wpm < 30: + result.add(AnnotationFlags.VERY_SLOW_SPEAKER) + elif wpm < 80: + result.add(AnnotationFlags.SLOW_SPEAKER) + elif wpm > 350: + result.add(AnnotationFlags.FAST_SPEAKER) + + # Return the annotation result + return result + + @staticmethod + def compare_views( + session: ClientSessionInfo, view1: SpeakerSegmentView, view2: Optional[SpeakerSegmentView] + ) -> AnnotationResult: + """Compare two SpeakerSegmentView objects and return the differences. + + View 1 (new) is compared to view 2 (old). + + Args: + session: ClientSessionInfo object. + view1: The first SpeakerSegmentView object to compare. + view2: The second SpeakerSegmentView object to compare to or None. + + Returns: + AnnotationResult: The annotation result. + """ + # Result + result = AnnotationResult() + + # If we have a previous view, compare it + if view2 and view2.segment_count > 0: + # Compare full string + view1_full_str: str = view1.format_view_text() + view2_full_str: str = view2.format_view_text() + if view1_full_str != view2_full_str: + result.add(AnnotationFlags.UPDATED_FULL) + if view1_full_str.lower() != view2_full_str.lower(): + result.add(AnnotationFlags.UPDATED_FULL_LCASE) + + # Stripped string (without punctuation) + view1_stripped_str: str = view1.format_view_text(words_only=True) + view2_stripped_str: str = view2.format_view_text(words_only=True) + if view1_stripped_str != view2_stripped_str: + result.add(AnnotationFlags.UPDATED_STRIPPED) + if view1_stripped_str.lower() != view2_stripped_str.lower(): + result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) + + # Word timings + view1_timings_str: str = view1.format_view_text(format="|{start_time}-{end_time}|", words_only=True) + view2_timings_str: str = view2.format_view_text(format="|{start_time}-{end_time}|", words_only=True) + if view1_timings_str != view2_timings_str: + result.add(AnnotationFlags.UPDATED_WORD_TIMINGS) + + # Partials, finals and speakers + if view1.final_count != view2.final_count: + result.add(AnnotationFlags.UPDATED_FINALS) + if view1.partial_count != view2.partial_count: + result.add(AnnotationFlags.UPDATED_PARTIALS) + if view1.segment_count != view2.segment_count: + result.add(AnnotationFlags.UPDATED_SPEAKERS) + + # Assume this is new + elif view1.segment_count > 0: + result.add(AnnotationFlags.NEW) + + # Finalized (last segment only has finals) + if view1.segment_count > 0 and view1.partial_count == 0: + result.add(AnnotationFlags.FINALIZED) + + # Return the result + return result From dd3e39ae9c3c82719f132e76c154141d2ba46eb7 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 13 Oct 2025 21:51:58 +0100 Subject: [PATCH 120/215] updated docs and async client --- sdk/voice/speechmatics/voice/_client.py | 155 ++++++++++++++++++++++-- sdk/voice/speechmatics/voice/_models.py | 145 ++++++++++++++++++++++ tests/voice/test_01_client.py | 63 ++++++++++ 3 files changed, 356 insertions(+), 7 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index d7cbc244..a7a943f2 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -80,6 +80,33 @@ def __init__( or defaults to production endpoint. app: Optional application name to use in the endpoint URL. config: Optional voice agent configuration. + + Examples: + Recommended - using context manager: + >>> from speechmatics.voice import VoiceAgentClient, VoiceAgentConfig + >>> config = VoiceAgentConfig(language="en") + >>> async with VoiceAgentClient(api_key="your_api_key", config=config) as client: + ... # Client automatically connects and disconnects + ... await client.send_audio(audio_data) + + Manual connection management: + >>> client = VoiceAgentClient(api_key="your_api_key", config=config) + >>> await client.connect() + >>> # ... use client ... + >>> await client.disconnect() + + Using environment variables: + >>> import os + >>> os.environ["SPEECHMATICS_API_KEY"] = "your_api_key" + >>> async with VoiceAgentClient(config=VoiceAgentConfig(language="en")) as client: + ... await client.send_audio(audio_data) + + With custom endpoint: + >>> client = VoiceAgentClient( + ... api_key="your_api_key", + ... url="wss://custom.endpoint.com/v2", + ... config=VoiceAgentConfig(language="en") + ... ) """ # Default URL @@ -281,9 +308,30 @@ def _process_config( async def connect(self) -> None: """Connect to the Speechmatics API. - Args: - transcription_config: Transcription configuration. - audio_format: Audio format. + Establishes WebSocket connection and starts the transcription session. + This must be called before sending audio. + + Raises: + Exception: If connection fails. + + Examples: + Manual connection: + >>> client = VoiceAgentClient(api_key="your_api_key", config=config) + >>> await client.connect() + + With event handlers: + >>> @client.on("AddSegment") + ... async def on_segment(message): + ... segments = message["segments"] + ... print(f"Received {len(segments)} segments") + >>> + >>> await client.connect() + + Using context manager (recommended): + >>> async with VoiceAgentClient(api_key="key", config=config) as client: + ... # Client is automatically connected here + ... await client.send_audio(audio_data) + ... # Automatically disconnected and cleaned up """ # Check if we are already connected @@ -309,8 +357,50 @@ async def connect(self) -> None: self._logger.error(f"Exception: {e}") raise + async def __aenter__(self) -> VoiceAgentClient: + """Enter async context manager. + + Automatically connects to the Speechmatics API when entering the context. + + Returns: + The connected VoiceAgentClient instance. + + Examples: + >>> async with VoiceAgentClient(api_key="key", config=config) as client: + ... # Client is already connected here + ... await client.send_audio(audio_data) + """ + await self.connect() + return self + + async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + """Exit async context manager. + + Automatically disconnects and cleans up resources when exiting the context. + + Args: + exc_type: Exception type if an exception occurred. + exc_val: Exception value if an exception occurred. + exc_tb: Exception traceback if an exception occurred. + """ + await self.disconnect() + async def disconnect(self) -> None: - """Disconnect from the Speechmatics API.""" + """Disconnect from the Speechmatics API. + + Closes the WebSocket connection and cleans up resources. + + Examples: + Manual disconnect: + >>> await client.connect() + >>> # ... send audio ... + >>> await client.disconnect() + + Using context manager (automatic): + >>> async with VoiceAgentClient(api_key="key", config=config) as client: + ... # No need to call disconnect() - handled automatically + ... await client.send_audio(audio_data) + """ # Check if we are already connected if not self._is_connected: @@ -357,11 +447,29 @@ async def send_audio(self, payload: bytes) -> None: """Send an audio frame through the WebSocket. Args: - payload: The audio frame to send. + payload: Audio data as bytes. Examples: - >>> audio_chunk = b"" - >>> await client.send_audio(audio_chunk) + Sending audio from a file: + >>> import wave + >>> with wave.open("audio.wav", "rb") as wav_file: + ... while True: + ... audio_chunk = wav_file.readframes(320) + ... if not audio_chunk: + ... break + ... await client.send_audio(audio_chunk) + + Sending audio from microphone: + >>> import pyaudio + >>> p = pyaudio.PyAudio() + >>> stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True) + >>> while True: + ... audio_data = stream.read(320) + ... await client.send_audio(audio_data) + + With async generator: + >>> async for audio_chunk in audio_stream(): + ... await client.send_audio(audio_chunk) """ # Skip if not ready for audio if not self._is_ready_for_audio: @@ -388,6 +496,30 @@ def update_diarization_config(self, config: DiarizationSpeakerConfig) -> None: Args: config: The new diarization configuration. + + Examples: + Focus on specific speakers: + >>> from speechmatics.voice import DiarizationSpeakerConfig, DiarizationFocusMode + >>> config = DiarizationSpeakerConfig( + ... focus_speakers=["speaker_1", "speaker_2"], + ... focus_mode=DiarizationFocusMode.RETAIN + ... ) + >>> client.update_diarization_config(config) + + Ignore specific speakers: + >>> config = DiarizationSpeakerConfig( + ... ignore_speakers=["speaker_3"], + ... focus_mode=DiarizationFocusMode.IGNORE + ... ) + >>> client.update_diarization_config(config) + + Dynamic speaker management: + >>> # Start with all speakers + >>> await client.connect() + >>> # Later, focus on main speaker + >>> client.update_diarization_config( + ... DiarizationSpeakerConfig(focus_speakers=["main_speaker"]) + ... ) """ self._dz_config = config @@ -400,6 +532,15 @@ def finalize(self, ttl: Optional[float] = None) -> None: Args: ttl: Optional delay before finalizing partial segments. + + Examples: + Immediate finalization: + >>> # Force finalization of current segments + >>> client.finalize(ttl=0) + + Delayed finalization: + >>> # Wait 0.5 seconds before finalizing + >>> client.finalize(ttl=0.5) """ # Emit the finalize or use EndOfTurn on demand preview diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 43c6f9b4..1ccc1d88 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -38,6 +38,34 @@ class EndOfUtteranceMode(str, Enum): - `SMART_TURN`: Smart turn end of turn delay. The STT engine will use a combination of silence detection, adaptive delay and smart turn detection using machine learning to determine the end of turn. + + Examples: + Using fixed mode (default): + >>> config = VoiceAgentConfig( + ... language="en", + ... end_of_utterance_mode=EndOfUtteranceMode.FIXED + ... ) + + Using adaptive mode for natural conversations: + >>> config = VoiceAgentConfig( + ... language="en", + ... end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + ... ) + + Using smart turn detection: + >>> config = VoiceAgentConfig( + ... language="en", + ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + ... smart_turn_threshold=0.8 + ... ) + + External control (manual finalization): + >>> config = VoiceAgentConfig( + ... language="en", + ... end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL + ... ) + >>> # Later in code: + >>> client.finalize() """ EXTERNAL = "external" @@ -69,6 +97,19 @@ class DiarizationFocusMode(str, Enum): - `RETAIN`: Retain words spoken by other speakers (not listed in `ignore_speakers`) and process them as passive speaker frames. - `IGNORE`: Ignore words spoken by other speakers and they will not be processed. + + Examples: + Retain all speakers but mark focus: + >>> config = DiarizationSpeakerConfig( + ... focus_speakers=["S1"], + ... focus_mode=DiarizationFocusMode.RETAIN + ... ) + + Ignore non-focus speakers completely: + >>> config = DiarizationSpeakerConfig( + ... focus_speakers=["S1", "S2"], + ... focus_mode=DiarizationFocusMode.IGNORE + ... ) """ RETAIN = "retain" @@ -236,6 +277,23 @@ class AdditionalVocabEntry(BaseModel): Parameters: content: The word to add to the dictionary. sounds_like: Similar words to the word. + + Examples: + Adding a brand name: + >>> vocab = AdditionalVocabEntry( + ... content="Speechmatics", + ... sounds_like=["speech mattics", "speech matics"] + ... ) + + Adding technical terms: + >>> vocab_list = [ + ... AdditionalVocabEntry(content="API", sounds_like=["A P I"]), + ... AdditionalVocabEntry(content="WebSocket", sounds_like=["web socket"]) + ... ] + >>> config = VoiceAgentConfig( + ... language="en", + ... additional_vocab=vocab_list + ... ) """ content: str @@ -248,6 +306,30 @@ class DiarizationKnownSpeaker(BaseModel): Parameters: label: The label of the speaker. speaker_identifiers: One or more data strings for the speaker. + + Examples: + Adding a known speaker: + >>> speaker = DiarizationKnownSpeaker( + ... label="Alice", + ... speaker_identifiers=["speaker_abc123"] + ... ) + + Using multiple known speakers: + >>> known_speakers = [ + ... DiarizationKnownSpeaker( + ... label="Alice", + ... speaker_identifiers=["speaker_abc123"] + ... ), + ... DiarizationKnownSpeaker( + ... label="Bob", + ... speaker_identifiers=["speaker_def456"] + ... ) + ... ] + >>> config = VoiceAgentConfig( + ... language="en", + ... enable_diarization=True, + ... known_speakers=known_speakers + ... ) """ label: str @@ -388,6 +470,69 @@ class VoiceAgentConfig(BaseModel): sample_rate: Audio sample rate for streaming. Defaults to `16000`. audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. + + Examples: + Basic configuration: + >>> config = VoiceAgentConfig(language="en") + + With diarization enabled: + >>> config = VoiceAgentConfig( + ... language="en", + ... enable_diarization=True, + ... speaker_sensitivity=0.7 + ... ) + + With custom vocabulary: + >>> config = VoiceAgentConfig( + ... language="en", + ... additional_vocab=[ + ... AdditionalVocabEntry( + ... content="Speechmatics", + ... sounds_like=["speech mattics"] + ... ) + ... ] + ... ) + + Advanced configuration with speaker focus: + >>> config = VoiceAgentConfig( + ... language="en", + ... enable_diarization=True, + ... speaker_config=DiarizationSpeakerConfig( + ... focus_speakers=["S1"], + ... focus_mode=DiarizationFocusMode.RETAIN + ... ), + ... end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + ... ) + + With known speakers: + >>> config = VoiceAgentConfig( + ... language="en", + ... enable_diarization=True, + ... known_speakers=[ + ... DiarizationKnownSpeaker( + ... label="Alice", + ... speaker_identifiers=["speaker_abc123"] + ... ) + ... ] + ... ) + + Complete example with multiple features: + >>> config = VoiceAgentConfig( + ... language="en", + ... operating_point=OperatingPoint.ENHANCED, + ... enable_diarization=True, + ... speaker_sensitivity=0.7, + ... max_speakers=3, + ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + ... smart_turn_threshold=0.8, + ... additional_vocab=[ + ... AdditionalVocabEntry(content="API"), + ... AdditionalVocabEntry(content="WebSocket") + ... ], + ... speaker_config=DiarizationSpeakerConfig( + ... focus_speakers=["S1", "S2"] + ... ) + ... ) """ # Service configuration diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py index 2cb45d0a..405eca50 100644 --- a/tests/voice/test_01_client.py +++ b/tests/voice/test_01_client.py @@ -3,6 +3,9 @@ import pytest from _utils import get_client +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + @pytest.mark.asyncio async def test_client(): @@ -39,3 +42,63 @@ async def test_client(): assert client._client_session.session_id != "NOT_SET" assert client._client_session.language_pack_info is not None assert client._client_session.language_pack_info.language_description == "English" + + +@pytest.mark.asyncio +async def test_client_context_manager(): + """Tests that a client can be used as an async context manager. + + - Checks that connection is established automatically on enter + - Checks that disconnection happens automatically on exit + - Verifies session info is set correctly + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Create config + config = VoiceAgentConfig(language="en") + + # Use client as context manager + async with VoiceAgentClient(api_key=api_key, config=config) as client: + # Check we are connected automatically + assert client._is_connected + + # Check session info is set + assert client._client_session.session_id != "NOT_SET" + assert client._client_session.language_pack_info is not None + assert client._client_session.language_pack_info.language_description == "English" + + # After exiting context, client should be disconnected + assert not client._is_connected + + +@pytest.mark.asyncio +async def test_client_context_manager_with_exception(): + """Tests that context manager properly cleans up even when an exception occurs. + + - Checks that disconnection happens even if an exception is raised + - Verifies exception is propagated correctly + """ + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Create config + config = VoiceAgentConfig(language="en") + + # Use client as context manager and raise an exception + with pytest.raises(ValueError, match="Test exception"): + async with VoiceAgentClient(api_key=api_key, config=config) as client: + # Check we are connected + assert client._is_connected + + # Raise an exception + raise ValueError("Test exception") + + # After exiting context (even with exception), client should be disconnected + assert not client._is_connected From fed3cf3914ebdec170c3a603d09953d641409e0e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 13 Oct 2025 22:34:43 +0100 Subject: [PATCH 121/215] updated examples and new cli --- examples/voice/README.md | 181 +------ examples/voice/cli/README.md | 189 +++++++ examples/voice/cli/main.py | 550 +++++++++++++++++++++ examples/voice/cli/utils.py | 366 ++++++++++++++ examples/voice/file_to_jsonl.py | 219 -------- examples/voice/file_transcription.py | 276 ----------- examples/voice/microphone_transcription.py | 213 -------- examples/voice/simple/README.md | 55 +++ examples/voice/simple/main.py | 92 ++++ examples/voice/speaker_id/README.md | 96 ---- examples/voice/speaker_id/main.py | 253 ---------- examples/voice/utils.py | 60 ++- 12 files changed, 1309 insertions(+), 1241 deletions(-) create mode 100644 examples/voice/cli/README.md create mode 100644 examples/voice/cli/main.py create mode 100644 examples/voice/cli/utils.py delete mode 100644 examples/voice/file_to_jsonl.py delete mode 100644 examples/voice/file_transcription.py delete mode 100644 examples/voice/microphone_transcription.py create mode 100644 examples/voice/simple/README.md create mode 100644 examples/voice/simple/main.py delete mode 100644 examples/voice/speaker_id/README.md delete mode 100644 examples/voice/speaker_id/main.py diff --git a/examples/voice/README.md b/examples/voice/README.md index 71e7f48e..44c3d07c 100644 --- a/examples/voice/README.md +++ b/examples/voice/README.md @@ -2,192 +2,27 @@ This directory contains practical demonstrations of the Speechmatics Voice Agent SDK for real-time speech-to-text applications. -## Installation - -```bash -pip install -r requirements-examples.txt -``` - -**Requirements:** - -- Python 3.9+ -- PyAudio for audio I/O -- Valid Speechmatics API key in `SPEECHMATICS_API_KEY` environment variable - -## Examples - ### Setup Install the Voice SDK to use the examples. Using a virtual environment is recommended. -```bash +```shell # Voice examples -cd examples/voice +cd # Create a virtual environment python -m venv .venv source .venv/bin/activate # Install requirements for the examples -pip install -r requirements-examples.txt -``` - -### File Transcription - -An example that takes an audio file and sends it in real-time to the SDK and outputs the interim and final transcription to the console. The audio will also be played through the selected audio output device. - -By default, this demo will focus on the second speaker and is set to ignore the first speaker altogether. This means that you may hear the first speaker's audio but not see their transcription. You can change this by modifying the `speaker_config` parameters in the `VoiceAgentConfig`. - -Audio files should be in Mono 16kHz 16-bit WAV format. - -``` -usage: file_transcription.py [-h] [--api-key API_KEY] [--url URL] - [--focus-speakers [FOCUS_SPEAKERS ...]] - [--ignore-speakers [IGNORE_SPEAKERS ...]] - [--ignore-mode] [--max-delay MAX_DELAY] - [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] - [--end-of-utterance-mode {FIXED,ADAPTIVE}] - [audio_file] - -Transcribe an audio file with real-time playback and speaker diarisation using Speechmatics Voice API - -positional arguments: - audio_file Path to the input audio file (WAV format, mono 16-bit). Defaults to '../example2.wav' - -options: - -h, --help show this help message and exit - --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) - --url URL Speechmatics server URL (optional) - --focus-speakers [FOCUS_SPEAKERS ...] - Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead - --ignore-speakers [IGNORE_SPEAKERS ...] - Specific speakers to ignore (e.g., S1 S2) - --ignore-mode Use ignore mode instead of focus mode for --focus-speakers - --max-delay MAX_DELAY - Maximum delay for transcription results in seconds (default: 0.7) - --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER - Silence duration to trigger end of utterance in seconds (default: 0.5) - --end-of-utterance-mode {FIXED,ADAPTIVE} - End of utterance detection mode (default: ADAPTIVE) - -Example: python file_transcription.py audio.wav --focus-speakers S1 --max-delay 1.0 -``` - -```bash -# Transcribe the default example audio file -python file_transcription.py - -# Transcribe a custom audio file (16kHz Mono 16-bit WAV) -python file_transcription.py path/to/your/audio.wav -``` - -**Features:** - -- File streaming -- Audio playback through selected device -- Real-time transcription -- Speaker diarization -- Speaker start / stop events - -### Microphone Transcription - -An example that takes audio from a microphone and sends it in real-time to the SDK and outputs the interim and final transcription to the console. - -By default this will focus on the first speaker only. Adjust the `speaker_config` parameters in the `VoiceAgentConfig` to change this. - -``` -usage: microphone_transcription.py [-h] [--api-key API_KEY] [--url URL] - [--sample-rate SAMPLE_RATE] [--chunk-size CHUNK_SIZE] - [--focus-speakers [FOCUS_SPEAKERS ...]] [--ignore-speakers [IGNORE_SPEAKERS ...]] - [--ignore-mode] [--max-delay MAX_DELAY] - [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] - [--end-of-utterance-mode {FIXED,ADAPTIVE}] - -Live microphone transcription with speaker diarisation using Speechmatics Voice API - -options: - -h, --help show this help message and exit - --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) - --url URL Speechmatics server URL (optional) - --sample-rate SAMPLE_RATE - Audio sample rate in Hz (default: 16000) - --chunk-size CHUNK_SIZE - Audio chunk size in bytes (default: 320) - --focus-speakers [FOCUS_SPEAKERS ...] - Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead - --ignore-speakers [IGNORE_SPEAKERS ...] - Specific speakers to ignore (e.g., S1 S2) - --ignore-mode Use ignore mode instead of focus mode for --focus-speakers - --max-delay MAX_DELAY - Maximum delay for transcription results in seconds (default: 0.7) - --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER - Silence duration to trigger end of utterance in seconds (default: 0.5) - --end-of-utterance-mode {FIXED,ADAPTIVE} - End of utterance detection mode (default: ADAPTIVE) - -Example: python microphone_transcription.py --focus-speakers S1 S2 --max-delay 1.0 -``` - -```bash -# Real-time microphone transcription -python microphone_transcription.py -``` - -**Features:** - -- Live microphone input with device selection -- Real-time transcription -- Speaker diarization -- Speaker start / stop events - -### File to JSONL - -An example that takes an audio file and sends it in real-time to the SDK and outputs the transcription to a JSONL file. This can be useful for debugging or for processing the transcription results in a different way. - -``` -usage: file_to_jsonl.py input_file - [-h] [--api-key API_KEY] [--url URL] [--output OUTPUT] - [--focus-speakers [FOCUS_SPEAKERS ...]] [--ignore-speakers [IGNORE_SPEAKERS ...]] [--ignore-mode] - [--max-delay MAX_DELAY] - [--end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER] - [--end-of-utterance-mode {FIXED,ADAPTIVE}] - -Transcribe an audio file to JSONL format using Speechmatics Voice API - -positional arguments: - input_file Path to the input audio file (WAV format) - -options: - -h, --help show this help message and exit - --api-key API_KEY Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable) - --url URL Speechmatics server URL (optional) - --output OUTPUT, -o OUTPUT - Output JSONL file (defaults to stdout) - --focus-speakers [FOCUS_SPEAKERS ...] - Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead - --ignore-speakers [IGNORE_SPEAKERS ...] - Specific speakers to ignore (e.g., S1 S2) - --ignore-mode Use ignore mode instead of focus mode for --focus-speakers - --max-delay MAX_DELAY - Maximum delay for transcription results in seconds (default: 0.7) - --end-of-utterance-silence-trigger END_OF_UTTERANCE_SILENCE_TRIGGER - Silence duration to trigger end of utterance in seconds (default: 0.5) - --end-of-utterance-mode {FIXED,ADAPTIVE} - End of utterance detection mode (default: FIXED) - -Example: python file_to_jsonl.py --output transcription.jsonl --focus-speakers S1 example1.wav -``` - -```bash -# Transcribe the default example audio file -python file_to_jsonl.py ../example1.wav --api-key $SPEECHMATICS_API_KEY +pip install -r ./examples/voice/requirements-examples.txt ``` ## Troubleshooting **PyAudio installation issues:** -```bash +```shell # macOS brew install portaudio pip install pyaudio @@ -197,16 +32,8 @@ sudo apt-get install portaudio19-dev pip install pyaudio ``` -**No audio devices found:** - -- Ensure PyAudio is properly installed -- Check system audio permissions -- Verify audio devices are connected and recognised - **API key issues:** ```bash export SPEECHMATICS_API_KEY="your_api_key_here" ``` - -For more information, see the main SDK documentation in [OVERVIEW.md](../../OVERVIEW.md). diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md new file mode 100644 index 00000000..1cd3b015 --- /dev/null +++ b/examples/voice/cli/README.md @@ -0,0 +1,189 @@ +# Transcription CLI with Speaker Diarization + +Command-line tool for real-time transcription using the Speechmatics Voice SDK. Supports both microphone input and audio file streaming with speaker diarization. + +## Quick Start + +**Microphone:** + +```bash +python ./examples/voice/cli/main.py --api-key YOUR_API_KEY +``` + +**Audio file:** + +```bash +python ./examples/voice/cli/main.py --api-key YOUR_API_KEY --input-file audio.wav +``` + +Press `CTRL+C` to stop. + +## Requirements + +- Speechmatics API key from the [portal](https://portal.speechmatics.com/) +- Install dependencies: see [examples README](../README.md) + +## Usage + +```bash +python ./examples/voice/cli/main.py [OPTIONS] +``` + +### Core Parameters + +**`--api-key`** +Speechmatics API key. Defaults to `SPEECHMATICS_API_KEY` environment variable. + +**`--url`** +Custom Speechmatics server URL. Optional, defaults to production endpoint. + +**`--input-file FILE`** +Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone. + +**`--pretty`** +Enable formatted console output with colours and emojis. + +**`--jsonl FILE`** +Save all output to a JSONL file for later analysis. + +### Audio Configuration + +**`--sample-rate`** +Audio sample rate in Hz. Default: `16000`. + +**`--chunk-size`** +Audio chunk size in bytes. Default: `320`. + +### Turn Detection + +**`--end-of-utterance-mode`** +Controls how turn endings are detected. Options: + +- `FIXED` - Fixed silence threshold +- `ADAPTIVE` - Adjusts based on speaker characteristics +- `SMART_TURN` - ML-based turn detection +- `EXTERNAL` - Manual control via `finalize()` + +**`--end-of-utterance-silence-trigger`** +Silence duration in seconds to trigger turn end. Default: `0.5`. + +**`--max-delay`** +Maximum transcription delay in seconds. Default: `0.7`. + +### Speaker Management + +**`--focus-speakers S1 S2 ...`** +Speakers to focus on. Only these speakers will be emitted as finalized frames. + +**`--ignore-speakers S1 S2 ...`** +Specific speakers to exclude from transcription. + +**`--ignore-mode`** +Use ignore mode instead of focus mode for `--focus-speakers`. + +### Speaker Identification + +**`--enrol`** +Enrol speakers and output their identifiers at the end of the session. + +**`--speakers JSON`** +Use known speakers from previous sessions. Provide as JSON array. + +**`--preview`** +Enable preview features. + +## Examples + +### Microphone - basic transcription + +```bash +python ./examples/voice/cli/main.py --api-key YOUR_KEY --pretty +``` + +### Audio file - basic transcription + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --input-file audio.wav \ + --pretty +``` + +### With adaptive turn detection + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --end-of-utterance-mode ADAPTIVE \ + --pretty +``` + +### Enrol speakers (microphone) + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --enrol \ + --pretty +``` + +Press `CTRL+C` when done. Speaker identifiers will be displayed: + +```json +[{ "label": "S1", "speaker_identifiers": ["XX...XX"] }] +``` + +### Use known speakers + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --speakers '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' \ + --pretty +``` + +Output shows speaker labels: + +``` +@Alice: Hello, how are you? +``` + +### Focus on specific speakers + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --focus-speakers S1 S2 \ + --pretty +``` + +### Save to file + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --jsonl output.jsonl \ + --pretty +``` + +### Audio file with speaker focus + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --input-file audio.wav \ + --focus-speakers S2 \ + --pretty +``` + +## Speaker Identification + +Speaker identifiers are encrypted and unique to your API key. They enable the engine to recognise speakers across sessions. + +**Best practices:** + +- Allow speakers to say at least 20 words before enrolling +- Avoid using labels `S1`, `S2` (reserved by engine) +- Labels in format `__XXX__` are automatically ignored + +For more details, see the [Speechmatics documentation](https://docs.speechmatics.com/speech-to-text/realtime/realtime-speaker-identification). diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py new file mode 100644 index 00000000..dda49f9f --- /dev/null +++ b/examples/voice/cli/main.py @@ -0,0 +1,550 @@ +"""Transcription CLI with Speaker Diarization. + +Command-line tool for real-time transcription using the Speechmatics Voice SDK. +Supports both microphone input and audio file streaming with speaker diarization. +""" + +import argparse +import asyncio +import datetime +import json +import os +import wave +from pathlib import Path + +from utils import AudioPlayer +from utils import select_audio_device +from utils import select_audio_output_device + +from speechmatics.rt import Microphone +from speechmatics.voice import AgentClientMessageType +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import DiarizationSpeakerConfig +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + +# ============================================================================== +# CONSTANTS +# ============================================================================== + +COLORS = { + "AddPartialSegment": "\033[93m", + "AddSegment": "\033[92m", + "SpeakerStarted": "\033[94m", + "SpeakerEnded": "\033[94m", + "SpeakersResult": "\033[95m", + "EndOfTurn": "\033[91m", +} + + +# ============================================================================== +# MAIN ENTRY POINT +# ============================================================================== + + +async def main() -> None: + """Run the transcription CLI.""" + args = parse_args() + + # Setup audio source (microphone or file) + audio_source = setup_audio_source(args) + if not audio_source: + return + + # Setup audio output (for file playback) + audio_player = setup_audio_output(audio_source, args) + + # Remove JSONL output file if it already exists + if args.jsonl and os.path.exists(args.jsonl): + os.remove(args.jsonl) + + # Create speaker configuration + speaker_config = create_speaker_config(args) + + # Create Voice Agent configuration + config = VoiceAgentConfig( + sample_rate=audio_source["sample_rate"], + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, + max_delay=args.max_delay, + enable_diarization=True, + end_of_utterance_mode=args.end_of_utterance_mode.lower(), + speaker_config=speaker_config, + enable_preview_features=args.preview, + ) + + # Create Voice Agent client + client = VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) + + # Setup event handlers + start_time = datetime.datetime.now() + register_event_handlers(client, args, start_time) + + # Display instructions + if audio_source["type"] == "file": + print("\nStreaming audio file... (Press CTRL+C to stop)\n") + else: + print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") + + # Connect to the Voice Agent service + await client.connect() + + # Request speaker IDs at the end of the session (if enrolling) + if args.enrol: + await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) + + # Stream audio + try: + await stream_audio(audio_source, audio_player, client, args.chunk_size) + except asyncio.CancelledError: + pass + finally: + # Cleanup + if audio_player: + audio_player.stop() + await client.disconnect() + + +# ============================================================================== +# AUDIO SOURCE SETUP +# ============================================================================== + + +def setup_audio_source(args) -> dict | None: + """Setup audio source (microphone or file). + + Returns: + Dictionary with audio source information or None on error. + """ + if args.input_file: + return setup_file_source(args) + else: + return setup_microphone_source(args) + + +def setup_file_source(args) -> dict | None: + """Setup audio file source. + + Returns: + Dictionary with file information or None on error. + """ + audio_file_path = Path(args.input_file) + if not audio_file_path.exists(): + print(f"Error: Audio file not found: {audio_file_path}") + return None + + # Load and validate the audio file + try: + with wave.open(str(audio_file_path), "rb") as wav_file: + sample_rate = wav_file.getframerate() + channels = wav_file.getnchannels() + sample_width = wav_file.getsampwidth() + frames = wav_file.getnframes() + duration = frames / sample_rate + + if channels != 1: + print("Error: Only mono audio files are supported") + return None + if sample_width != 2: + print("Error: Only 16-bit audio files are supported") + return None + + print(f"Loading: {audio_file_path.name} ({duration:.1f}s, {sample_rate}Hz)") + + return { + "type": "file", + "path": audio_file_path, + "sample_rate": sample_rate, + "channels": channels, + "sample_width": sample_width, + } + + except (wave.Error, ValueError) as e: + print(f"Error loading audio file: {e}") + return None + + +def setup_microphone_source(args) -> dict | None: + """Setup microphone source. + + Returns: + Dictionary with microphone information or None on error. + """ + print("\nSelect microphone input device:") + selected_device = select_audio_device() + + mic = Microphone( + sample_rate=args.sample_rate, + chunk_size=args.chunk_size, + device_index=selected_device, + ) + + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return None + + return { + "type": "microphone", + "mic": mic, + "sample_rate": args.sample_rate, + } + + +# ============================================================================== +# AUDIO OUTPUT SETUP +# ============================================================================== + + +def setup_audio_output(audio_source: dict, args) -> AudioPlayer | None: + """Setup audio output for file playback. + + Args: + audio_source: Audio source information + args: Command-line arguments + + Returns: + AudioPlayer instance or None if not needed/available. + """ + # Only setup audio output for file sources + if audio_source["type"] != "file": + return None + + print("\nSelect audio output device for playback:") + output_device = select_audio_output_device() + + audio_player = AudioPlayer( + sample_rate=audio_source["sample_rate"], + channels=audio_source["channels"], + sample_width=audio_source["sample_width"], + device_index=output_device, + ) + + if not audio_player.start(): + print("Warning: Audio playback unavailable - continuing with transcription only") + return None + + return audio_player + + +# ============================================================================== +# SPEAKER CONFIGURATION +# ============================================================================== + + +def create_speaker_config(args) -> DiarizationSpeakerConfig: + """Create speaker diarization configuration from arguments. + + Args: + args: Command-line arguments + + Returns: + DiarizationSpeakerConfig instance. + """ + if args.focus_speakers or args.ignore_speakers: + focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.RETAIN + return DiarizationSpeakerConfig( + focus_speakers=args.focus_speakers or [], + ignore_speakers=args.ignore_speakers or [], + focus_mode=focus_mode, + ) + else: + return DiarizationSpeakerConfig() + + +# ============================================================================== +# EVENT HANDLERS +# ============================================================================== + + +def register_event_handlers(client: VoiceAgentClient, args, start_time: datetime.datetime) -> None: + """Register event handlers for transcription events. + + Args: + client: Voice Agent client + args: Command-line arguments + start_time: Start time for timestamp calculation + """ + + def console_print(message) -> None: + """Print message to console with optional formatting.""" + if not args.pretty: + print(json.dumps(message)) + return + + # Extract common data + ts = message["ts"] + msg_type = message["message"] + color = COLORS.get(msg_type, "") + payload = {k: v for k, v in message.items() if k not in ("ts", "message")} + + # Handle segment messages + if msg_type in ("AddPartialSegment", "AddSegment"): + _segs = [] + for segment in payload["segments"]: + prefix = "" if segment["is_active"] else " (background)" + _segs.append(f"@{segment['speaker_id']}{prefix}: `{segment['text']}` {segment['annotation']}") + payload = {"segments": _segs} + + # Print to console + print(f"{color}{ts:7.3f} {msg_type:<24} {json.dumps(payload)}\033[0m") + + def log_message(message) -> None: + """Log message to console and optional JSONL file.""" + ts = (datetime.datetime.now() - start_time).total_seconds() + message = {"ts": round(ts, 3), **message} + console_print(message) + + if args.jsonl: + with open(args.jsonl, "a") as f: + f.write(json.dumps(message) + "\n") + + # Register handlers + client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) + + +# ============================================================================== +# AUDIO STREAMING +# ============================================================================== + + +async def stream_audio( + audio_source: dict, + audio_player: AudioPlayer | None, + client: VoiceAgentClient, + chunk_size: int, +) -> None: + """Stream audio from source to client. + + Args: + audio_source: Audio source information + audio_player: Audio player for file playback (optional) + client: Voice Agent client + chunk_size: Audio chunk size in bytes + """ + if audio_source["type"] == "file": + await stream_file(audio_source, audio_player, client, chunk_size) + else: + await stream_microphone(audio_source, client, chunk_size) + + +async def stream_file( + audio_source: dict, + audio_player: AudioPlayer | None, + client: VoiceAgentClient, + chunk_size: int, +) -> None: + """Stream audio file with real-time pacing. + + Uses absolute timing to prevent audio crackling when processing takes longer + than expected. This ensures consistent playback timing regardless of + transcription processing delays. + + Args: + audio_source: Audio source information + audio_player: Audio player for playback (optional) + client: Voice Agent client + chunk_size: Audio chunk size in bytes + """ + file_path = audio_source["path"] + sample_rate = audio_source["sample_rate"] + chunk_duration = chunk_size / sample_rate + + # Use absolute timing to prevent drift + start_time = asyncio.get_event_loop().time() + chunk_count = 0 + + with wave.open(str(file_path), "rb") as wav_file: + while True: + audio_data = wav_file.readframes(chunk_size) + if not audio_data: + print("\nEnd of file reached") + break + + # Send to transcription (non-blocking) + asyncio.create_task(client.send_audio(audio_data)) + + # Play audio (blocking to maintain timing) + if audio_player: + audio_player.play(audio_data) + + # Calculate next chunk time based on absolute timing + chunk_count += 1 + next_chunk_time = start_time + (chunk_count * chunk_duration) + current_time = asyncio.get_event_loop().time() + sleep_duration = next_chunk_time - current_time + + # Only sleep if we're ahead of schedule + if sleep_duration > 0: + await asyncio.sleep(sleep_duration) + + +async def stream_microphone( + audio_source: dict, + client: VoiceAgentClient, + chunk_size: int, +) -> None: + """Stream microphone audio to client. + + Args: + audio_source: Audio source information + client: Voice Agent client + chunk_size: Audio chunk size in bytes + """ + mic = audio_source["mic"] + while True: + frame = await mic.read(chunk_size) + await client.send_audio(frame) + + +# ============================================================================== +# COMMAND-LINE ARGUMENT PARSING +# ============================================================================== + + +def parse_args(): + """Parse command-line arguments. + + Returns: + Parsed arguments namespace. + """ + parser = argparse.ArgumentParser( + description="Transcription CLI with speaker diarization - supports microphone or audio file input", + epilog="Example: python main.py --api-key YOUR_KEY --input-file audio.wav --pretty", + ) + + # ============================================================================== + # Core parameters + # ============================================================================== + + parser.add_argument( + "--api-key", + default=os.getenv("SPEECHMATICS_API_KEY"), + help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", + ) + parser.add_argument( + "--url", + help="Speechmatics server URL (optional)", + ) + + # ============================================================================== + # Audio source + # ============================================================================== + + parser.add_argument( + "--input-file", + type=str, + help="Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone", + ) + + # ============================================================================== + # Audio configuration + # ============================================================================== + + parser.add_argument( + "--sample-rate", + type=int, + default=16000, + help="Audio sample rate in Hz (default: 16000)", + ) + parser.add_argument( + "--chunk-size", + type=int, + default=320, + help="Audio chunk size in bytes (default: 320)", + ) + + # ============================================================================== + # Speaker configuration + # ============================================================================== + + parser.add_argument( + "--focus-speakers", + nargs="*", + help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", + ) + parser.add_argument( + "--ignore-speakers", + nargs="*", + help="Specific speakers to ignore (e.g., S1 S2)", + ) + parser.add_argument( + "--ignore-mode", + action="store_true", + help="Use ignore mode instead of focus mode for --focus-speakers", + ) + + # ============================================================================== + # Output configuration + # ============================================================================== + + parser.add_argument( + "--jsonl", + type=str, + help="Output to a JSONL file", + ) + parser.add_argument( + "--pretty", + action="store_true", + help="Pretty print console output (default: False)", + ) + + # ============================================================================== + # Voice Agent configuration + # ============================================================================== + + parser.add_argument( + "--max-delay", + type=float, + default=0.7, + help="Maximum delay for transcription results in seconds (default: 0.7)", + ) + parser.add_argument( + "--end-of-utterance-silence-trigger", + type=float, + default=0.5, + help="Silence duration to trigger end of utterance in seconds (default: 0.5)", + ) + parser.add_argument( + "--end-of-utterance-mode", + type=lambda s: s.upper(), + choices=["FIXED", "ADAPTIVE", "EXTERNAL", "SMART_TURN"], + default="ADAPTIVE", + help="End of utterance detection mode (default: ADAPTIVE)", + ) + + # ============================================================================== + # Speaker identification + # ============================================================================== + + parser.add_argument( + "--enrol", + action="store_true", + help="Enrol a speaker (default: False)", + ) + parser.add_argument( + "--speakers", + type=json.loads, + help="Known speakers (default: None)", + ) + parser.add_argument( + "--preview", + action="store_true", + help="Use preview features (default: False)", + ) + + return parser.parse_args() + + +# ============================================================================== +# ENTRY POINT +# ============================================================================== + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/cli/utils.py b/examples/voice/cli/utils.py new file mode 100644 index 00000000..7c0063fa --- /dev/null +++ b/examples/voice/cli/utils.py @@ -0,0 +1,366 @@ +"""Utility functions and classes for the Speechmatics Voice CLI. + +This module provides: +- Audio device selection (input/output) +- Audio playback functionality +- Custom logging with colour support +- Helper functions for async operations +""" + +import asyncio +import logging +import sys +import termios +import tty + +import pyaudio + +from speechmatics.rt import Microphone +from speechmatics.voice import VoiceAgentClient + +# ============================================================================== +# ASYNC UTILITIES +# ============================================================================== + + +async def wait_for_keypress() -> None: + """Wait for any key press in a non-blocking way. + + Note: Unix/Mac only. + """ + loop = asyncio.get_event_loop() + + def _read_key(): + """Read a single key press.""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + return ch + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + # Run the blocking key read in a thread pool + await loop.run_in_executor(None, _read_key) + + +# ============================================================================== +# AUDIO DEVICE SELECTION +# ============================================================================== + + +def select_audio_device() -> int | None: + """Interactive microphone device selection. + + Displays available input devices and prompts user to select one. + + Returns: + Device index or None for default device. + """ + devices = Microphone.list_devices() + if not devices: + return None + + print("Available microphones:") + for device in devices: + print(f" [{device['index']}] {device['name']} ({device['channels']} channels)") + print() + + return get_device_choice( + [d["index"] for d in devices], + "Enter device index (or press Enter for default): ", + ) + + +def select_audio_output_device() -> int | None: + """Interactive audio output device selection. + + Displays available output devices and prompts user to select one. + + Returns: + Device index or None for default device. + """ + try: + output_devices = get_output_devices() + if not output_devices: + print("No audio output devices found.") + return None + + print("Available audio output devices:") + for device in output_devices: + print(f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)") + print() + + return get_device_choice( + [d["index"] for d in output_devices], + "Enter output device index (or press Enter for default): ", + ) + + except Exception as e: + print(f"Error listing audio devices: {e}") + return None + + +def get_output_devices() -> list[dict]: + """Get list of available output devices. + + Returns: + List of dictionaries containing device information. + """ + p = pyaudio.PyAudio() + try: + devices = [] + for i in range(p.get_device_count()): + info = p.get_device_info_by_index(i) + if info["maxOutputChannels"] > 0: + devices.append( + { + "index": i, + "name": info["name"], + "channels": info["maxOutputChannels"], + "sample_rate": int(info["defaultSampleRate"]), + } + ) + return devices + finally: + p.terminate() + + +def get_device_choice(valid_indices: list[int], prompt: str) -> int | None: + """Get user device choice with validation. + + Args: + valid_indices: List of valid device indices. + prompt: Prompt message to display. + + Returns: + Selected device index or None for default. + """ + while True: + try: + choice = input(prompt).strip() + if not choice: + return None + + device_index = int(choice) + if device_index in valid_indices: + return device_index + + print(f"Invalid device index. Choose from: {valid_indices}") + + except ValueError: + print("Please enter a valid number.") + except KeyboardInterrupt: + return None + + +# ============================================================================== +# MICROPHONE UTILITIES +# ============================================================================== + + +def setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: + """Setup microphone with device selection. + + Args: + sample_rate: Audio sample rate in Hz. + chunk_size: Audio chunk size in bytes. + + Returns: + Microphone instance or None on error. + """ + selected_device = select_audio_device() + + mic = Microphone( + sample_rate=sample_rate, + chunk_size=chunk_size, + device_index=selected_device, + ) + + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return None + return mic + + +async def stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: + """Stream microphone audio to client. + + Args: + mic: Microphone instance. + client: Voice Agent client. + chunk_size: Audio chunk size in bytes. + """ + while True: + frame = await mic.read(chunk_size) + await client.send_audio(frame) + + +# ============================================================================== +# AUDIO PLAYBACK +# ============================================================================== + + +class AudioPlayer: + """Real-time audio player using PyAudio. + + Provides synchronized audio playback for file streaming with transcription. + """ + + def __init__( + self, + sample_rate: int, + channels: int = 1, + sample_width: int = 2, + device_index: int | None = None, + ): + """Initialize audio player. + + Args: + sample_rate: Audio sample rate in Hz. + channels: Number of audio channels (1 for mono). + sample_width: Sample width in bytes (2 for 16-bit). + device_index: Output device index (None for default). + """ + self.sample_rate = sample_rate + self.channels = channels + self.sample_width = sample_width + self.device_index = device_index + self.p = None + self.stream = None + + def start(self) -> bool: + """Start audio playback stream. + + Returns: + True if successful, False otherwise. + """ + try: + self.p = pyaudio.PyAudio() + if not self.p: + return False + + audio_format = self._get_audio_format() + if not audio_format: + return False + + self.stream = self.p.open( + format=audio_format, + channels=self.channels, + rate=self.sample_rate, + output=True, + output_device_index=self.device_index, + ) + return True + + except Exception as e: + print(f"Error starting audio player: {e}") + return False + + def play(self, audio_data: bytes) -> None: + """Play audio data chunk. + + Args: + audio_data: Raw audio bytes to play. + """ + if self.stream: + self.stream.write(audio_data) + + def stop(self) -> None: + """Stop and cleanup audio player resources.""" + if self.stream: + self.stream.stop_stream() + self.stream.close() + self.stream = None + + if self.p: + self.p.terminate() + self.p = None + + def _get_audio_format(self) -> int | None: + """Get PyAudio format from sample width. + + Returns: + PyAudio format constant or None if unsupported. + """ + format_map = {1: pyaudio.paInt8, 2: pyaudio.paInt16, 4: pyaudio.paInt32} + if self.sample_width not in format_map: + print(f"Unsupported sample width: {self.sample_width}") + return None + return format_map[self.sample_width] + + +# ============================================================================== +# CUSTOM LOGGING +# ============================================================================== + + +class CustomLevels: + """Custom logging levels for transcription events. + + Defines numeric levels for different types of transcription events. + """ + + PARTIAL = 11 # Partial transcription results + FINAL = 12 # Final transcription results + SPEAKER = 15 # Speech activity events + + +class CustomTextFormatter(logging.Formatter): + """Coloured logging formatter for transcription events. + + Applies ANSI colour codes to log messages based on their level. + """ + + FORMAT = "%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s" + + # ANSI colour codes + COLOURS = { + logging.DEBUG: "\033[90m", # Grey + CustomLevels.PARTIAL: "\033[32m", # Green + CustomLevels.FINAL: "\033[33m", # Yellow + CustomLevels.SPEAKER: "\033[36m", # Cyan + } + RESET = "\033[0m" + + def format(self, record): + """Format log record with colour. + + Args: + record: Log record to format. + + Returns: + Formatted and coloured log message. + """ + colour = self.COLOURS.get(record.levelno, self.RESET) + message = super().format(record) + return f"{colour}{message}{self.RESET}\r" + + +def get_logger(name: str) -> logging.Logger: + """Setup coloured logger for transcription events. + + Args: + name: Logger name. + + Returns: + Configured logger instance. + """ + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + logging.getLogger("speechmatics.voice").setLevel(logging.DEBUG) + + # Register custom level names + logging.addLevelName(CustomLevels.PARTIAL, "PARTIAL") + logging.addLevelName(CustomLevels.FINAL, "FINAL") + logging.addLevelName(CustomLevels.SPEAKER, "SPEAKER") + + # Apply custom formatter to all handlers + formatter = CustomTextFormatter(CustomTextFormatter.FORMAT, datefmt="%Y-%m-%d %H:%M:%S") + for handler in logging.root.handlers: + handler.setFormatter(formatter) + + return logger diff --git a/examples/voice/file_to_jsonl.py b/examples/voice/file_to_jsonl.py deleted file mode 100644 index a1d5ede3..00000000 --- a/examples/voice/file_to_jsonl.py +++ /dev/null @@ -1,219 +0,0 @@ -import argparse -import asyncio -import datetime -import json -import os -import sys -import time -from typing import Callable -from typing import Optional - -import aiofiles - -from speechmatics.voice import AdditionalVocabEntry -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import DiarizationFocusMode -from speechmatics.voice import DiarizationSpeakerConfig -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig - - -async def main() -> None: - """Transcribe a file to a JSONL file.""" - - # Parse command line arguments - args = parse_args() - - # Check file exists - if not os.path.exists(args.input_file): - print(f"Error: Input file '{args.input_file}' not found", file=sys.stderr) - sys.exit(1) - - # Create speaker configuration if speaker options are provided - speaker_config = None - if args.focus_speakers or args.ignore_speakers: - focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.RETAIN - speaker_config = DiarizationSpeakerConfig( - focus_speakers=args.focus_speakers, - ignore_speakers=args.ignore_speakers, - focus_mode=focus_mode, - ) - - # Client - client = VoiceAgentClient( - api_key=args.api_key, - url=args.url, - config=VoiceAgentConfig( - end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, - max_delay=args.max_delay, - end_of_utterance_mode=args.end_of_utterance_mode, - enable_diarization=True, - speaker_config=speaker_config, - additional_vocab=[ - AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matters", "speech magic"]) - ], - ), - ) - - # Create an event to track when the callback is called - messages: list[str] = [] - bytes_sent: int = 0 - - # Start time - start_time = datetime.datetime.now() - - # Bytes logger - def log_bytes_sent(bytes): - nonlocal bytes_sent - bytes_sent += bytes - - # Callback for each message - def log_message(message): - ts = (datetime.datetime.now() - start_time).total_seconds() - audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": ts, "audio_ts": audio_ts, "payload": message.model_dump()}) - messages.append(log) - - # Output to file or stdout - if args.output: - with open(args.output, "a") as f: - f.write(log + "\n") - else: - print(log) - - # Log script info - log_message({"message": "AudioFile", "path": args.input_file}) - log_message({"message": "VoiceAgentConfig", **client._config.model_dump()}) - log_message({"message": "TranscriptionConfig", **client._transcription_config.to_dict()}) - log_message({"message": "AudioFormat", **client._audio_format.to_dict()}) - - # Add listeners - client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) - client.on(AgentServerMessageType.ADD_SEGMENT, log_message) - client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) - client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) - client.on(AgentServerMessageType.END_OF_TURN, log_message) - - # Clear output file if it exists - if args.output and os.path.exists(args.output): - open(args.output, "w").close() - - # Connect - await client.connect() - - # Use the input file from command line arguments - await send_audio_file(client, args.input_file, progress_callback=log_bytes_sent) - - # Close session - await client.disconnect() - assert not client._is_connected - - -async def send_audio_file( - client: VoiceAgentClient, - file: str, - event_received: Optional[asyncio.Event] = None, - chunk_size: int = 320, - progress_callback: Optional[Callable[[int], None]] = None, -) -> None: - """Send audio data to the API server.""" - - # Delay is based off 16kHz int16 and chunk size - delay = chunk_size / 16000 / 2 - - # Load the file - async with aiofiles.open(file, "rb") as wav_file: - # Trim off the WAV file header - await wav_file.seek(44) - - # Send audio data - next_time = time.perf_counter() + delay - while not event_received.is_set() if event_received else True: - """Reads all chunks until the end of the file with precision delay.""" - - # Read chunk - chunk = await wav_file.read(chunk_size) - if not chunk: - break - - # Send audio to client - await client.send_audio(chunk) - - # Do any callbacks - if progress_callback: - progress_callback(len(chunk)) - - # Precision delay - sleep_time = next_time - time.perf_counter() - if sleep_time > 0: - await asyncio.sleep(sleep_time) - next_time += delay - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Transcribe an audio file to JSONL format using Speechmatics Voice API", - epilog="Example: python file_to_jsonl.py audio.wav --output transcription.jsonl --focus-speakers S1 S2", - ) - parser.add_argument("input_file", help="Path to the input audio file (WAV format)") - parser.add_argument( - "--api-key", - default=os.getenv("SPEECHMATICS_API_KEY"), - help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", - ) - parser.add_argument("--url", help="Speechmatics server URL (optional)") - parser.add_argument("--output", "-o", help="Output JSONL file (defaults to stdout)") - - # Speaker configuration arguments - parser.add_argument( - "--focus-speakers", - nargs="*", - help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", - ) - parser.add_argument( - "--ignore-speakers", - nargs="*", - help="Specific speakers to ignore (e.g., S1 S2)", - ) - parser.add_argument( - "--ignore-mode", - action="store_true", - help="Use ignore mode instead of focus mode for --focus-speakers", - ) - - # Voice Agent configuration arguments - parser.add_argument( - "--max-delay", - type=float, - default=0.7, - help="Maximum delay for transcription results in seconds (default: 0.7)", - ) - parser.add_argument( - "--end-of-utterance-silence-trigger", - type=float, - default=0.5, - help="Silence duration to trigger end of utterance in seconds (default: 0.5)", - ) - parser.add_argument( - "--end-of-utterance-mode", - choices=["FIXED", "ADAPTIVE"], - default="FIXED", - help="End of utterance detection mode (default: FIXED)", - ) - - args = parser.parse_args() - - # Convert string to EndOfUtteranceMode enum - if args.end_of_utterance_mode == "FIXED": - args.end_of_utterance_mode = EndOfUtteranceMode.FIXED - else: - args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE - - return args - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/voice/file_transcription.py b/examples/voice/file_transcription.py deleted file mode 100644 index d1e326da..00000000 --- a/examples/voice/file_transcription.py +++ /dev/null @@ -1,276 +0,0 @@ -import argparse -import asyncio -import os -import sys -import wave -from pathlib import Path - -from colorama import init as colorama_init -from utils import AudioPlayer -from utils import CustomLevels -from utils import get_logger -from utils import select_audio_output_device - -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import DiarizationFocusMode -from speechmatics.voice import DiarizationSpeakerConfig -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig - -colorama_init(autoreset=True) -logger = get_logger(__name__) - - -async def main() -> None: - """ - Transcribe audio files with real-time playback and speaker diarisation. - - This example demonstrates: - - Loading and validating WAV audio files - - Real-time audio playback during transcription - - Speaker diarisation with voice activity detection - - Comprehensive event handling and logging - - This example will **only** show transcription from the second speaker - """ - # Parse command line arguments - args = parse_args() - - audio_file_path = Path(args.audio_file) - - # Validate that the audio file exists - if not audio_file_path.exists(): - print(f"Error: Audio file not found: {audio_file_path}") - sys.exit(1) - - # Load and validate the audio file format - # Only mono 16-bit WAV files are supported - try: - sample_rate, channels, sample_width, duration = _load_audio_file(audio_file_path) - print(f"Loading: {audio_file_path.name} ({duration:.1f}s, {sample_rate}Hz)") - except (wave.Error, ValueError) as e: - print(f"Error: {e}") - return - - # Setup audio playback device for real-time playback - # User can select from available output devices - audio_player = _setup_audio_player(sample_rate, channels, sample_width) - - # Create speaker configuration if speaker options are provided - speaker_config = None - if args.focus_speakers or args.ignore_speakers: - focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.FOCUS - speaker_config = DiarizationSpeakerConfig( - focus_speakers=args.focus_speakers, - ignore_speakers=args.ignore_speakers, - focus_mode=focus_mode, - ) - else: - # Default behaviour: focus on second speaker and ignore first - speaker_config = DiarizationSpeakerConfig(focus_speakers=["S2"], focus_mode=DiarizationFocusMode.IGNORE) - - # Configure Voice Agent with transcription settings - config = VoiceAgentConfig( - sample_rate=sample_rate, - end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, - max_delay=args.max_delay, - enable_diarization=True, - end_of_utterance_mode=args.end_of_utterance_mode, - speaker_config=speaker_config, - ) - - # Create Voice Agent client and start transcription - async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: - # Register event handlers for transcription events - _register_event_handlers(client, logger) - - try: - print("Starting transcription... (Press Ctrl+C to stop)\n") - # Connect to the Voice Agent service - await client.connect() - - # Stream the audio file with real-time playback - await _stream_audio_file(audio_file_path, client, audio_player, sample_rate, sample_width) - except asyncio.CancelledError: - print("\nTranscription cancelled") - except Exception as e: - print(f"Error: {e}") - finally: - # Clean up audio playback resources - if audio_player: - audio_player.stop() - - -def _load_audio_file(file_path: Path) -> tuple[int, int, int, float]: - """Load and validate audio file. - - Returns: - Tuple of (sample_rate, channels, sample_width, duration) - """ - with wave.open(str(file_path), "rb") as wav_file: - sample_rate = wav_file.getframerate() - channels = wav_file.getnchannels() - sample_width = wav_file.getsampwidth() - frames = wav_file.getnframes() - duration = frames / sample_rate - - if channels != 1: - raise ValueError("Only mono audio files are supported") - if sample_width != 2: - raise ValueError("Only 16-bit audio files are supported") - - return sample_rate, channels, sample_width, duration - - -def _setup_audio_player(sample_rate: int, channels: int, sample_width: int) -> AudioPlayer | None: - """Setup audio player with device selection.""" - print("\nSelect audio output device for playback:") - output_device = select_audio_output_device() - - audio_player = AudioPlayer(sample_rate, channels, sample_width, output_device) - if not audio_player.start(): - print("Warning: Audio playback unavailable - continuing with transcription only") - return None - return audio_player - - -def _register_event_handlers(client: VoiceAgentClient, logger) -> None: - """Register event handlers for transcription events with detailed logging.""" - - def _format_segment(segment) -> str: - """Format speaker segment for display.""" - template = "@{speaker_id} -> {text}" if segment.is_active else "@{speaker_id} (background) -> {text}" - return template.format(**segment) - - @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) - def handle_partial_segments(message): - """Handle partial transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - delay = message.get("delay_to_finalize") - if delay is not None: - logger.log(CustomLevels.PARTIAL, f"⚡ {segments} ({delay}s to finals)") - else: - logger.log(CustomLevels.PARTIAL, f"⚡ {segments}") - - @client.on(AgentServerMessageType.ADD_SEGMENT) - def handle_final_segments(message): - """Handle final transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - logger.log(CustomLevels.FINAL, f"🚀 {segments}") - - @client.on(AgentServerMessageType.SPEAKER_STARTED) - def handle_speech_started(message): - """Handle speech start events.""" - logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") - - @client.on(AgentServerMessageType.SPEAKER_ENDED) - def handle_speech_ended(message): - """Handle speech end events.""" - logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") - - @client.on(AgentServerMessageType.END_OF_TURN) - def handle_end_of_turn(message): - """Handle end of turn events.""" - logger.log(CustomLevels.SPEAKER, f"🤖 End of turn: {message}") - - @client.on(AgentServerMessageType.TTFB_METRICS) - def handle_metrics(message): - """Handle time-to-first-byte metrics.""" - logger.debug(f"📊 Metrics: {message}") - - -async def _stream_audio_file( - file_path: Path, - client: VoiceAgentClient, - audio_player: AudioPlayer | None, - sample_rate: int, - sample_width: int, -) -> None: - """Stream audio file in real-time to client and player.""" - chunk_size = 320 - chunk_duration = chunk_size / sample_rate / sample_width - - with wave.open(str(file_path), "rb") as wav_file: - while True: - audio_data = wav_file.readframes(chunk_size) - if not audio_data: - print("\nEnd of file reached") - break - - # Send to transcription and play audio simultaneously - asyncio.create_task(client.send_audio(audio_data)) - if audio_player: - audio_player.play(audio_data) - - await asyncio.sleep(chunk_duration) - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Transcribe an audio file with real-time playback and speaker diarisation using Speechmatics Voice API", - epilog="Example: python file_transcription.py audio.wav --focus-speakers S1 --max-delay 1.0", - ) - parser.add_argument( - "audio_file", - nargs="?", - default="../example2.wav", - help="Path to the input audio file (WAV format, mono 16-bit). Defaults to '../example2.wav'", - ) - parser.add_argument( - "--api-key", - default=os.getenv("SPEECHMATICS_API_KEY"), - help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", - ) - parser.add_argument("--url", help="Speechmatics server URL (optional)") - - # Speaker configuration arguments - parser.add_argument( - "--focus-speakers", - nargs="*", - help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", - ) - parser.add_argument( - "--ignore-speakers", - nargs="*", - help="Specific speakers to ignore (e.g., S1 S2)", - ) - parser.add_argument( - "--ignore-mode", - action="store_true", - help="Use ignore mode instead of focus mode for --focus-speakers", - ) - - # Voice Agent configuration arguments - parser.add_argument( - "--max-delay", - type=float, - default=0.7, - help="Maximum delay for transcription results in seconds (default: 0.7)", - ) - parser.add_argument( - "--end-of-utterance-silence-trigger", - type=float, - default=0.5, - help="Silence duration to trigger end of utterance in seconds (default: 0.5)", - ) - parser.add_argument( - "--end-of-utterance-mode", - choices=["FIXED", "ADAPTIVE"], - default="ADAPTIVE", - help="End of utterance detection mode (default: ADAPTIVE)", - ) - - args = parser.parse_args() - - # Convert string to EndOfUtteranceMode enum - if args.end_of_utterance_mode == "FIXED": - args.end_of_utterance_mode = EndOfUtteranceMode.FIXED - else: - args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE - - return args - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/voice/microphone_transcription.py b/examples/voice/microphone_transcription.py deleted file mode 100644 index 583a0e90..00000000 --- a/examples/voice/microphone_transcription.py +++ /dev/null @@ -1,213 +0,0 @@ -import argparse -import asyncio -import os - -from colorama import init as colorama_init -from utils import CustomLevels -from utils import get_logger -from utils import select_audio_device - -from speechmatics.rt import Microphone -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import DiarizationFocusMode -from speechmatics.voice import DiarizationSpeakerConfig -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig - -colorama_init(autoreset=True) - -logger = get_logger(__name__) - - -async def main() -> None: - """ - Live microphone transcription with speaker diarization. - - This example demonstrates: - - Real-time microphone input with device selection - - Speaker diarization and voice activity detection - - Performance metrics (TTFB) - - Works with any PyAudio-compatible input device - """ - # Parse command line arguments - args = parse_args() - - # Audio configuration for real-time processing - sample_rate = args.sample_rate - chunk_size = args.chunk_size - - # Setup microphone with user device selection - mic = _setup_microphone(sample_rate, chunk_size) - if not mic: - return - - # Create speaker configuration if speaker options are provided - speaker_config = None - if args.focus_speakers or args.ignore_speakers: - focus_mode = DiarizationFocusMode.IGNORE if args.ignore_mode else DiarizationFocusMode.FOCUS - speaker_config = DiarizationSpeakerConfig( - focus_speakers=args.focus_speakers, - ignore_speakers=args.ignore_speakers, - focus_mode=focus_mode, - ) - else: - # Default behaviour: focus on first speaker - speaker_config = DiarizationSpeakerConfig(focus_speakers=["S1"]) - - # Configure Voice Agent with microphone-specific settings - config = VoiceAgentConfig( - sample_rate=sample_rate, - end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, - max_delay=args.max_delay, - enable_diarization=True, - end_of_utterance_mode=args.end_of_utterance_mode, - speaker_config=speaker_config, - ) - - # Create Voice Agent client and start transcription - async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: - # Register event handlers for real-time transcription events - _register_event_handlers(client, logger) - - try: - print("Microphone ready - speak now... (Press Ctrl+C to stop)\n") - # Connect to the Voice Agent service - await client.connect() - - # Start streaming microphone audio - await _stream_microphone(mic, client, chunk_size) - except asyncio.CancelledError: - print("\nTranscription cancelled") - except Exception as e: - print(f"Error: {e}") - - -def _register_event_handlers(client: VoiceAgentClient, logger) -> None: - """Register event handlers for real-time transcription events.""" - - def _format_segment(segment) -> str: - """Format speaker segment for display.""" - template = "@{speaker_id}: {text}" if segment.is_active else "@{speaker_id} (background): {text}" - return template.format(**segment) - - @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) - def handle_partial_segments(message): - """Handle partial transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - delay = message.get("delay_to_finalize") - if delay is not None: - logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)") - else: - logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") - - @client.on(AgentServerMessageType.ADD_SEGMENT) - def handle_final_segments(message): - """Handle final transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") - - @client.on(AgentServerMessageType.SPEAKER_STARTED) - def handle_speech_started(message): - """Handle speech start events.""" - logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") - - @client.on(AgentServerMessageType.SPEAKER_ENDED) - def handle_speech_ended(message): - """Handle speech end events.""" - logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") - - @client.on(AgentServerMessageType.TTFB_METRICS) - def handle_metrics(message): - """Handle time-to-first-byte metrics.""" - logger.debug(f"📊 Metrics: {message}") - - -def _setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: - """Setup microphone with device selection.""" - selected_device = select_audio_device() - - mic = Microphone( - sample_rate=sample_rate, - chunk_size=chunk_size, - device_index=selected_device, - ) - - if not mic.start(): - print("Error: PyAudio not available - install with: pip install pyaudio") - return None - return mic - - -async def _stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: - """Stream microphone audio to client.""" - while True: - frame = await mic.read(chunk_size) - await client.send_audio(frame) - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Live microphone transcription with speaker diarisation using Speechmatics Voice API", - epilog="Example: python microphone_transcription.py --focus-speakers S1 S2 --max-delay 1.0", - ) - parser.add_argument( - "--api-key", - default=os.getenv("SPEECHMATICS_API_KEY"), - help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", - ) - parser.add_argument("--url", help="Speechmatics server URL (optional)") - parser.add_argument("--sample-rate", type=int, default=16000, help="Audio sample rate in Hz (default: 16000)") - parser.add_argument("--chunk-size", type=int, default=320, help="Audio chunk size in bytes (default: 320)") - - # Speaker configuration arguments - parser.add_argument( - "--focus-speakers", - nargs="*", - help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", - ) - parser.add_argument( - "--ignore-speakers", - nargs="*", - help="Specific speakers to ignore (e.g., S1 S2)", - ) - parser.add_argument( - "--ignore-mode", - action="store_true", - help="Use ignore mode instead of focus mode for --focus-speakers", - ) - - # Voice Agent configuration arguments - parser.add_argument( - "--max-delay", - type=float, - default=0.7, - help="Maximum delay for transcription results in seconds (default: 0.7)", - ) - parser.add_argument( - "--end-of-utterance-silence-trigger", - type=float, - default=0.5, - help="Silence duration to trigger end of utterance in seconds (default: 0.5)", - ) - parser.add_argument( - "--end-of-utterance-mode", - choices=["FIXED", "ADAPTIVE"], - default="ADAPTIVE", - help="End of utterance detection mode (default: ADAPTIVE)", - ) - - args = parser.parse_args() - - # Convert string to EndOfUtteranceMode enum - if args.end_of_utterance_mode == "FIXED": - args.end_of_utterance_mode = EndOfUtteranceMode.FIXED - else: - args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE - - return args - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/voice/simple/README.md b/examples/voice/simple/README.md new file mode 100644 index 00000000..5699cace --- /dev/null +++ b/examples/voice/simple/README.md @@ -0,0 +1,55 @@ +# Simple Microphone Transcription + +Basic real-time transcription example using your default microphone with speaker diarization. + +## Quick Start + +```bash +export SPEECHMATICS_API_KEY=your_api_key +python ./examples/voice/simple/main.py +``` + +Press `CTRL+C` to stop. + +## What It Does + +- Uses your default microphone +- Transcribes speech in real-time +- Identifies different speakers (S1, S2, etc.) +- Shows partial results as you speak +- Shows final results when complete +- Detects end of turn using adaptive mode + +## Output Example + +``` +Microphone ready - speak now... (Press CTRL+C to stop) + +[PARTIAL] S1: Hello +[PARTIAL] S1: Hello how +[PARTIAL] S1: Hello how are +[FINAL] S1: Hello, how are you? +[END OF TURN] +[PARTIAL] S2: I'm +[PARTIAL] S2: I'm good +[FINAL] S2: I'm good, thanks! +[END OF TURN] +``` + +## Requirements + +- Speechmatics API key from the [portal](https://portal.speechmatics.com/) +- PyAudio: `pip install pyaudio` +- Install SDK dependencies: see [examples README](../README.md) + +## Configuration + +This example uses standard settings: + +- Language: English +- Turn detection: Adaptive (adjusts to speaker characteristics) +- Speaker diarization: Enabled +- Sample rate: 16kHz +- Chunk size: 320 bytes + +For more advanced options, see the [transcribe_mic](../transcribe_mic) example. diff --git a/examples/voice/simple/main.py b/examples/voice/simple/main.py new file mode 100644 index 00000000..13ed9163 --- /dev/null +++ b/examples/voice/simple/main.py @@ -0,0 +1,92 @@ +"""Simple microphone transcription example. + +This example demonstrates basic real-time transcription with speaker diarization +using the default microphone. It prints partial segments, final segments, and +end-of-turn events. +""" + +import asyncio +import os + +from speechmatics.rt import Microphone +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig + + +async def main() -> None: + """Run simple microphone transcription.""" + + # Get API key from environment + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + print("Error: SPEECHMATICS_API_KEY environment variable not set") + return + + # Setup microphone with default device + mic = Microphone(sample_rate=16000, chunk_size=320) + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return + + # Configure Voice Agent with adaptive turn detection + config = VoiceAgentConfig( + language="en", + enable_diarization=True, + end_of_utterance_mode="adaptive", + ) + + # Create client + client = VoiceAgentClient(api_key=api_key, config=config) + + # Handle partial segments (interim results) + def on_partial_segment(message): + segments = message.get("segments", []) + for segment in segments: + speaker = segment["speaker_id"] + text = segment["text"] + print(f"[PARTIAL] {speaker}: {text}") + + # Handle final segments + def on_segment(message): + segments = message.get("segments", []) + for segment in segments: + speaker = segment["speaker_id"] + text = segment["text"] + print(f"[FINAL] {speaker}: {text}") + + # Handle end of turn + def on_end_of_turn(message): + print("[END OF TURN]") + + # Register event handlers + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, on_partial_segment) + client.on(AgentServerMessageType.ADD_SEGMENT, on_segment) + client.on(AgentServerMessageType.END_OF_TURN, on_end_of_turn) + + # Instructions + print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") + + # Connect to the service + await client.connect() + + # Stream audio from microphone + async def stream_audio(): + while True: + audio_chunk = await mic.read(320) + await client.send_audio(audio_chunk) + + # Run until interrupted + try: + await stream_audio() + except KeyboardInterrupt: + print("\n\nStopping...") + except asyncio.CancelledError: + pass + + # Disconnect + await client.disconnect() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/speaker_id/README.md b/examples/voice/speaker_id/README.md deleted file mode 100644 index f25203bd..00000000 --- a/examples/voice/speaker_id/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Realtime speaker identification - -Speechmatics STT engine is able to provide labels and identifiers for speakers in a session which can be used in subsequent sessions to train the engine on known speakers. Speaker identifiers are encrypted and specific to your Speechmatics API key and are not transferable to other API keys. Multiple identifiers can be used to train the engine on multiple speakers. - -For more information on speaker ID, see the [Speechmatics documentation](https://docs.speechmatics.com//speech-to-text/realtime/realtime-speaker-identification). - -### Enrolling one or more speakers - -Speaker enrolment can be requested at any point during a conversation. It is recommended that individual speakers have spoken at least 20 words so that the engine can settle and identify them using characteristics from their voice. The example `enrol.py` requests the speaker identifiers to be emitted at the end of the session. - -To request the speaker identifiers during a session, you call the `send_message` function. This will then return a `SPEAKERS_RESULT` event. - -```python -# listen for speakers result -client.on(AgentServerMessageType.SPEAKERS_RESULT, handler) - -# request speaker ID information at the end of the session -await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) - -# request speaker ID information up to this point -await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": False}) -``` - -### Using known speakers - -Speaker identifiers can be used in subsequent sessions to train the engine on known speakers. - -```python -# create a set of known speakers -known_speakers: list[DiarizationKnownSpeaker] = [ - DiarizationKnownSpeaker( - label="John", - speaker_identifiers=["XX...XX"] - ), - DiarizationKnownSpeaker( - label="Jane", - speaker_identifiers=["YY...YY"] - ) -] - -# create a config with known speakers -VoiceAgentConfig( - known_speakers=known_speakers, -) -``` - -## Requirements - -You will need to have a Speechmatics API key, available from our [portal](https://portal.speechmatics.com/). Use the API key either as an environment variable (`SPEECHMATICS_API_KEY`) or as command line parameter (`--api-key`). - -Install the dependencies for the examples, as shown in [example README](../README.md). - -## Enrolling example - -To enrol a speaker, run the following example with the `--enrol` argument. - -```shell -# run the example with API key -python ./examples/voice/speaker_id/main.py \ - --api-key \ - --enrol -``` - -Once you have finished speaking, **press any key** and this will show the speaker information in the console. - -```json -[{ "label": "S1", "speaker_identifiers": ["XX...XX"] }] -``` - -## Known speaker example - -To use one or more known speakers, use the following example with the `--speakers` argument. It is recommended to NOT use the labels `S1`, `S2`, etc. as these are reserved for the engine to use. - -The SDK is also configured that any transcription from a speaker with the label that is in the format `__XXX__` will be ignored. - -```shell -# run the example with API key -python ./examples/voice/speaker_id/main.py \ - --api-key \ - --speakers -``` - -Where `` is a JSON payload string of the form: - -```json -[ - { "label": "John", "speaker_identifiers": ["XX...XX"] }, - { "label": "Jane", "speaker_identifiers": ["YY...YY"] } -] -``` - -When you speak, you will now see the enrolled speaker information in the console. - -``` -2025-10-10 12:00:00.000 FINAL 💬 Partial: ['@John: Hello!'] -``` diff --git a/examples/voice/speaker_id/main.py b/examples/voice/speaker_id/main.py deleted file mode 100644 index c0222048..00000000 --- a/examples/voice/speaker_id/main.py +++ /dev/null @@ -1,253 +0,0 @@ -import argparse -import asyncio -import json -import os -import sys -import termios -import tty -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from colorama import init as colorama_init -from utils import CustomLevels -from utils import get_logger -from utils import select_audio_device - -from speechmatics.rt import Microphone -from speechmatics.voice import AgentClientMessageType -from speechmatics.voice import AgentServerMessageType -from speechmatics.voice import DiarizationKnownSpeaker -from speechmatics.voice import EndOfUtteranceMode -from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig - -colorama_init(autoreset=True) - -logger = get_logger(__name__) - - -async def main() -> None: - """ - Live microphone transcription with speaker diarization and speaker ID extraction. - - This example demonstrates: - - Real-time microphone input with device selection - - Speaker diarization and voice activity detection - - Works with any PyAudio-compatible input device - - At the end of the session the speaker ID is shown in the console - """ - # Parse command line arguments - args = parse_args() - - # Audio configuration for real-time processing - sample_rate = args.sample_rate - chunk_size = args.chunk_size - - # Setup microphone with user device selection - mic = _setup_microphone(sample_rate, chunk_size) - if not mic: - return - - # Known speakers - known_speakers: list[DiarizationKnownSpeaker] = ( - [DiarizationKnownSpeaker(**s) for s in args.speakers] if args.speakers else [] - ) - - # Configure Voice Agent with microphone-specific settings - config = VoiceAgentConfig( - sample_rate=sample_rate, - end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, - max_delay=args.max_delay, - enable_diarization=True, - end_of_utterance_mode=args.end_of_utterance_mode, - known_speakers=known_speakers, - ) - - # Create Voice Agent client and start transcription - async with VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) as client: - # Register event handlers for real-time transcription events - _register_event_handlers(client, logger) - - try: - # User instructions - print("Microphone ready - speak now... (Press any key to stop)\n") - - # Connect to the Voice Agent service - await client.connect() - - # Request speaker IDs at the end of the session - if args.enrol: - await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) - - # Start streaming microphone audio in a task - streaming_task = asyncio.create_task(_stream_microphone(mic, client, chunk_size)) - keypress_task = asyncio.create_task(_wait_for_keypress()) - - # Wait for either task to complete (keypress or error in streaming) - done, pending = await asyncio.wait([streaming_task, keypress_task], return_when=asyncio.FIRST_COMPLETED) - - # End the session - await client.disconnect() - - # Cancel any remaining tasks - for task in pending: - task.cancel() - try: - await task - except asyncio.CancelledError: - pass - - except asyncio.CancelledError: - print("\nTranscription cancelled") - except Exception as e: - print(f"Error: {e}") - - -def _register_event_handlers(client: VoiceAgentClient, logger) -> None: - """Register event handlers for real-time transcription events.""" - - def _format_segment(segment) -> str: - """Format speaker segment for display.""" - template = "@{speaker_id}: {text}" if segment["is_active"] else "@{speaker_id} (background): {text}" - return template.format(**segment) - - @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) - def handle_partial_segments(message): - """Handle partial transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - delay = message.get("delay_to_finalize") - if delay is not None: - logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments} ({delay}s to finals)") - else: - logger.log(CustomLevels.PARTIAL, f"💬 Partial: {segments}") - - @client.on(AgentServerMessageType.ADD_SEGMENT) - def handle_final_segments(message): - """Handle final transcription segments.""" - segments = [_format_segment(s) for s in message["segments"]] - logger.log(CustomLevels.FINAL, f"🚀 Final: {segments}") - - @client.on(AgentServerMessageType.SPEAKER_STARTED) - def handle_speech_started(message): - """Handle speech start events.""" - logger.log(CustomLevels.SPEAKER, f"✅ Speech started: {message}") - - @client.on(AgentServerMessageType.SPEAKER_ENDED) - def handle_speech_ended(message): - """Handle speech end events.""" - logger.log(CustomLevels.SPEAKER, f"🛑 Speech ended: {message}") - - @client.on(AgentServerMessageType.SPEAKERS_RESULT) - def handle_metrics(message): - """Show the speakers result.""" - if message["speakers"]: - logger.info(f"😀 Speakers: {json.dumps(message['speakers'])}") - - -def _setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: - """Setup microphone with device selection.""" - selected_device = select_audio_device() - - mic = Microphone( - sample_rate=sample_rate, - chunk_size=chunk_size, - device_index=selected_device, - ) - - if not mic.start(): - print("Error: PyAudio not available - install with: pip install pyaudio") - return None - return mic - - -async def _stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: - """Stream microphone audio to client.""" - try: - while True: - frame = await mic.read(chunk_size) - await client.send_audio(frame) - except asyncio.CancelledError: - logger.debug("Microphone streaming task cancelled") - raise - - -async def _wait_for_keypress() -> None: - """Wait for any key press in a non-blocking way.""" - loop = asyncio.get_event_loop() - - def _read_key(): - """Read a single key press (Unix/Mac only).""" - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(fd) - ch = sys.stdin.read(1) - return ch - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - - # Run the blocking key read in a thread pool - await loop.run_in_executor(None, _read_key) - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Live microphone transcription with speaker diarization using Speechmatics Voice API", - ) - parser.add_argument( - "--api-key", - default=os.getenv("SPEECHMATICS_API_KEY"), - help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", - ) - parser.add_argument("--url", help="Speechmatics server URL (optional)") - parser.add_argument("--sample-rate", type=int, default=16000, help="Audio sample rate in Hz (default: 16000)") - parser.add_argument("--chunk-size", type=int, default=320, help="Audio chunk size in bytes (default: 320)") - - # Voice Agent configuration arguments - parser.add_argument( - "--max-delay", - type=float, - default=0.7, - help="Maximum delay for transcription results in seconds (default: 0.7)", - ) - parser.add_argument( - "--end-of-utterance-silence-trigger", - type=float, - default=0.5, - help="Silence duration to trigger end of utterance in seconds (default: 0.5)", - ) - parser.add_argument( - "--end-of-utterance-mode", - choices=["FIXED", "ADAPTIVE"], - default="ADAPTIVE", - help="End of utterance detection mode (default: ADAPTIVE)", - ) - - # Enrolment - parser.add_argument( - "--enrol", - action="store_true", - help="Enroll a speaker (default: False)", - ) - parser.add_argument( - "--speakers", - type=json.loads, - help="Known speakers (default: None)", - ) - - # Process the arguments - args = parser.parse_args() - - # Convert string to EndOfUtteranceMode enum - if args.end_of_utterance_mode == "FIXED": - args.end_of_utterance_mode = EndOfUtteranceMode.FIXED - else: - args.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE - - return args - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/voice/utils.py b/examples/voice/utils.py index 342a2bcc..a9b2c080 100644 --- a/examples/voice/utils.py +++ b/examples/voice/utils.py @@ -1,9 +1,32 @@ +import asyncio import logging import sys +import termios +import tty import pyaudio from speechmatics.rt import Microphone +from speechmatics.voice import VoiceAgentClient + + +async def wait_for_keypress() -> None: + """Wait for any key press in a non-blocking way.""" + loop = asyncio.get_event_loop() + + def _read_key(): + """Read a single key press (Unix/Mac only).""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + return ch + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + # Run the blocking key read in a thread pool + await loop.run_in_executor(None, _read_key) def select_audio_device() -> int | None: @@ -21,7 +44,7 @@ def select_audio_device() -> int | None: print(f" [{device['index']}] {device['name']} ({device['channels']} channels)") print() - return _get_device_choice( + return get_device_choice( [d["index"] for d in devices], "Enter device index (or press Enter for default): ", ) @@ -34,7 +57,7 @@ def select_audio_output_device() -> int | None: Device index or None for default device """ try: - output_devices = _get_output_devices() + output_devices = get_output_devices() if not output_devices: print("No audio output devices found.") return None @@ -44,7 +67,7 @@ def select_audio_output_device() -> int | None: print(f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)") print() - return _get_device_choice( + return get_device_choice( [d["index"] for d in output_devices], "Enter output device index (or press Enter for default): ", ) @@ -54,7 +77,7 @@ def select_audio_output_device() -> int | None: return None -def _get_output_devices() -> list[dict]: +def get_output_devices() -> list[dict]: """Get list of available output devices.""" p = pyaudio.PyAudio() try: @@ -75,7 +98,7 @@ def _get_output_devices() -> list[dict]: p.terminate() -def _get_device_choice(valid_indices: list[int], prompt: str) -> int | None: +def get_device_choice(valid_indices: list[int], prompt: str) -> int | None: """Get user device choice with validation.""" while True: try: @@ -95,6 +118,29 @@ def _get_device_choice(valid_indices: list[int], prompt: str) -> int | None: return None +def setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: + """Setup microphone with device selection.""" + selected_device = select_audio_device() + + mic = Microphone( + sample_rate=sample_rate, + chunk_size=chunk_size, + device_index=selected_device, + ) + + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return None + return mic + + +async def stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: + """Stream microphone audio to client.""" + while True: + frame = await mic.read(chunk_size) + await client.send_audio(frame) + + class AudioPlayer: """Real-time audio player using PyAudio.""" @@ -190,12 +236,12 @@ class CustomTextFormatter(logging.Formatter): CustomLevels.FINAL: "\033[33m", # Yellow CustomLevels.SPEAKER: "\033[36m", # Cyan } - RESET = "\033[0m\r" + RESET = "\033[0m" def format(self, record): colour = self.COLOURS.get(record.levelno, self.RESET) message = super().format(record) - return f"{colour}{message}{self.RESET}" + return f"{colour}{message}{self.RESET}\r" def get_logger(name: str) -> logging.Logger: From 9d0408d07cc41d5a591ab30caf858fd3cc1b0a27 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 13 Oct 2025 22:45:12 +0100 Subject: [PATCH 122/215] Add Speechmatics to the default vocab in the examples --- examples/voice/cli/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index dda49f9f..079b981e 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -17,6 +17,7 @@ from utils import select_audio_output_device from speechmatics.rt import Microphone +from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationFocusMode @@ -71,6 +72,9 @@ async def main() -> None: end_of_utterance_mode=args.end_of_utterance_mode.lower(), speaker_config=speaker_config, enable_preview_features=args.preview, + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ], ) # Create Voice Agent client From dfba972ade8aa87f80351651138f90006d4c8b8b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 00:23:00 +0100 Subject: [PATCH 123/215] switch to additive weights for end of turn delay --- examples/voice/cli/README.md | 4 +-- examples/voice/cli/main.py | 8 +++--- sdk/voice/speechmatics/voice/_client.py | 35 +++++++++++-------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 1cd3b015..c90d991d 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -7,13 +7,13 @@ Command-line tool for real-time transcription using the Speechmatics Voice SDK. **Microphone:** ```bash -python ./examples/voice/cli/main.py --api-key YOUR_API_KEY +python ./examples/voice/cli/main.py --pretty --api-key YOUR_API_KEY ``` **Audio file:** ```bash -python ./examples/voice/cli/main.py --api-key YOUR_API_KEY --input-file audio.wav +python ./examples/voice/cli/main.py --pretty --api-key YOUR_API_KEY --input-file audio.wav ``` Press `CTRL+C` to stop. diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 079b981e..8bc91fc2 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -91,7 +91,11 @@ async def main() -> None: print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") # Connect to the Voice Agent service - await client.connect() + try: + await client.connect() + except Exception as e: + print(f"Error connecting to Voice Agent service: {e}") + return # Request speaker IDs at the end of the session (if enrolling) if args.enrol: @@ -103,7 +107,6 @@ async def main() -> None: except asyncio.CancelledError: pass finally: - # Cleanup if audio_player: audio_player.stop() await client.disconnect() @@ -368,7 +371,6 @@ async def stream_file( while True: audio_data = wav_file.readframes(chunk_size) if not audio_data: - print("\nEnd of file reached") break # Send to transcription (non-blocking) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a7a943f2..5a831056 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1122,9 +1122,7 @@ async def _calculate_finalize_delay( return None # Calculations - reasons: list[tuple[float, str]] = [] delay: Optional[float] = None - multiplier: float = 1.0 clamped_delay: float = self._config.end_of_utterance_max_delay emit_delay: Optional[float] = None time_slip: Optional[float] = None @@ -1133,10 +1131,6 @@ async def _calculate_finalize_delay( last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None - # Add multiplier - def add_multiplier(multiplier: float, reason: str = "none") -> None: - reasons.append((multiplier, reason)) - # Check using filter flags for changes if not filter_flags or view_changes is None or view_changes.any(*filter_flags): """Process the annotation flags to determine how long before sending a final segment.""" @@ -1152,44 +1146,47 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: - ends with finalizes end of sentence """ - # Minimum delay (50ms as a minimum) - delay = max(self._end_of_utterance_delay, 0.05) + # Reasons for the calculation + reasons: list[tuple[float, str]] = [] + + # Add multiplier + def add_multiplier(multiplier: float, reason: str = "none") -> None: + reasons.append((multiplier, reason)) # Delay multiplier add_multiplier(1.0, "default") # Very speaking if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): - add_multiplier(3.0, "very_slow_speaker") + add_multiplier(2.0, "very_slow_speaker") # Slow speaking if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): - add_multiplier(1.5, "slow_speaker") + add_multiplier(1.0, "slow_speaker") # Disfluencies if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): - add_multiplier(4.0, "ends_with_disfluency") + add_multiplier(2.5, "ends_with_disfluency") # elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): # add_multiplier(1.25, "has_disfluency") # Ends with an end of sentence if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): - add_multiplier(0.5, "ends_with_eos_and_final") + add_multiplier(-0.3, "ends_with_eos_and_final") # Does NOT end with end of sentence if not last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS): - add_multiplier(2.0, "does_not_end_with_eos") + add_multiplier(1.0, "does_not_end_with_eos") # Smart turn prediction if smart_turn_prediction and smart_turn_prediction.prediction: - add_multiplier(0.25, "smart_turn_true") + add_multiplier(-1.0, "smart_turn_true") - # Calculate the multiplier - for _multiplier, _reason in reasons: - multiplier *= _multiplier + # Calculate multiplier + multiplier = sum(m for m, _ in reasons) - # Adjust the delay - delay = round(delay * multiplier, 2) + # Minimum delay (50ms as a minimum) + delay = round(max(self._end_of_utterance_delay, 0.05) * multiplier, 2) # Clamp to max delay clamped_delay = min(delay, self._config.end_of_utterance_max_delay) From 8f39f75000bf6ce23653f5e3fac92c3516ab25e4 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 00:51:48 +0100 Subject: [PATCH 124/215] minor fixes --- sdk/voice/speechmatics/voice/_client.py | 1 - tests/voice/test_08_multiple_speakers.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 5a831056..92905589 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1122,7 +1122,6 @@ async def _calculate_finalize_delay( return None # Calculations - delay: Optional[float] = None clamped_delay: float = self._config.end_of_utterance_max_delay emit_delay: Optional[float] = None time_slip: Optional[float] = None diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index aa7b2ab2..761f6d84 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -194,7 +194,8 @@ def log_final_segment(message): # Check final segments against regex for idx, _test in enumerate(sample.segment_regex): - print(f"`{_test}` -> `{final_segments[idx].get('text')}`") + if SHOW_LOG: + print(f"`{_test}` -> `{final_segments[idx].get('text')}`") assert re.search(_test, final_segments[idx].get("text"), flags=re.IGNORECASE | re.MULTILINE) # Check only speakers present From efea26b6a2fd743767e5014c7ac562d76310ee17 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 10:29:59 +0100 Subject: [PATCH 125/215] updated CLI example --- .gitignore | 1 + examples/voice/cli/README.md | 37 ++++++++++++++-- examples/voice/cli/main.py | 82 ++++++++++++++++++++++++++---------- 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 6ad6fbc1..f9e713da 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,4 @@ tmp/ .models/ .cache/ *.onnx +*.pkf diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index c90d991d..8b80a777 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -35,7 +35,7 @@ python ./examples/voice/cli/main.py [OPTIONS] Speechmatics API key. Defaults to `SPEECHMATICS_API_KEY` environment variable. **`--url`** -Custom Speechmatics server URL. Optional, defaults to production endpoint. +Custom Speechmatics server URL. Also uses `SPEECHMATICS_SERVER_URL` environment variable, if not provided. Optional, defaults to production endpoint. **`--input-file FILE`** Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone. @@ -86,8 +86,10 @@ Use ignore mode instead of focus mode for `--focus-speakers`. **`--enrol`** Enrol speakers and output their identifiers at the end of the session. -**`--speakers JSON`** -Use known speakers from previous sessions. Provide as JSON array. +**`--speakers JSON|FILE`** +Use known speakers from previous sessions. Provide as either: +- A JSON string: `'[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]'` +- A path to a JSON file: `speakers.json` **`--preview`** Enable preview features. @@ -133,7 +135,7 @@ Press `CTRL+C` when done. Speaker identifiers will be displayed: [{ "label": "S1", "speaker_identifiers": ["XX...XX"] }] ``` -### Use known speakers +### Use known speakers (JSON string) ```bash python ./examples/voice/cli/main.py \ @@ -142,10 +144,37 @@ python ./examples/voice/cli/main.py \ --pretty ``` +### Use known speakers (JSON file) + +Create a `speakers.json` file: + +```json +[ + { + "label": "Alice", + "speaker_identifiers": ["XX...XX"] + }, + { + "label": "Bob", + "speaker_identifiers": ["YY...YY"] + } +] +``` + +Then run: + +```bash +python ./examples/voice/cli/main.py \ + --api-key YOUR_KEY \ + --speakers speakers.json \ + --pretty +``` + Output shows speaker labels: ``` @Alice: Hello, how are you? +@Bob: I'm doing well, thanks! ``` ### Focus on specific speakers diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 8bc91fc2..89fa2900 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -21,6 +21,7 @@ from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationFocusMode +from speechmatics.voice import DiarizationKnownSpeaker from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -63,6 +64,11 @@ async def main() -> None: # Create speaker configuration speaker_config = create_speaker_config(args) + # Known speakers + known_speakers: list[DiarizationKnownSpeaker] = ( + [DiarizationKnownSpeaker(**s) for s in args.speakers] if args.speakers else [] + ) + # Create Voice Agent configuration config = VoiceAgentConfig( sample_rate=audio_source["sample_rate"], @@ -75,6 +81,7 @@ async def main() -> None: additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], + known_speakers=known_speakers, ) # Create Voice Agent client @@ -414,6 +421,36 @@ async def stream_microphone( # ============================================================================== +def load_speakers(value: str): + """Load speakers from JSON string or file path. + + Args: + value: Either a JSON string or path to a JSON file + + Returns: + Parsed speakers list + + Raises: + argparse.ArgumentTypeError: If the value cannot be parsed + """ + # First, try to parse as JSON string + try: + return json.loads(value) + except json.JSONDecodeError: + pass + + # If that fails, try to load as a file path + try: + file_path = Path(value) + if file_path.exists() and file_path.is_file(): + with open(file_path) as f: + return json.load(f) + else: + raise argparse.ArgumentTypeError(f"File not found: {value}") + except Exception as e: + raise argparse.ArgumentTypeError(f"Could not parse as JSON or load from file: {value}. Error: {e}") + + def parse_args(): """Parse command-line arguments. @@ -436,6 +473,7 @@ def parse_args(): ) parser.add_argument( "--url", + default=os.getenv("SPEECHMATICS_SERVER_URL"), help="Speechmatics server URL (optional)", ) @@ -466,26 +504,6 @@ def parse_args(): help="Audio chunk size in bytes (default: 320)", ) - # ============================================================================== - # Speaker configuration - # ============================================================================== - - parser.add_argument( - "--focus-speakers", - nargs="*", - help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", - ) - parser.add_argument( - "--ignore-speakers", - nargs="*", - help="Specific speakers to ignore (e.g., S1 S2)", - ) - parser.add_argument( - "--ignore-mode", - action="store_true", - help="Use ignore mode instead of focus mode for --focus-speakers", - ) - # ============================================================================== # Output configuration # ============================================================================== @@ -525,6 +543,26 @@ def parse_args(): help="End of utterance detection mode (default: ADAPTIVE)", ) + # ============================================================================== + # Speaker configuration + # ============================================================================== + + parser.add_argument( + "--focus-speakers", + nargs="*", + help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", + ) + parser.add_argument( + "--ignore-speakers", + nargs="*", + help="Specific speakers to ignore (e.g., S1 S2)", + ) + parser.add_argument( + "--ignore-mode", + action="store_true", + help="Use ignore mode instead of focus mode for --focus-speakers", + ) + # ============================================================================== # Speaker identification # ============================================================================== @@ -536,8 +574,8 @@ def parse_args(): ) parser.add_argument( "--speakers", - type=json.loads, - help="Known speakers (default: None)", + type=load_speakers, + help="Known speakers as JSON string or path to JSON file (default: None)", ) parser.add_argument( "--preview", From ad0796ae0c60a350d24a17f5a91843e100d0bfef Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 12:00:44 +0100 Subject: [PATCH 126/215] updated CLI --- examples/voice/cli/README.md | 85 +++++++++++++++++------------------- examples/voice/cli/main.py | 48 +++++++++++++++++--- 2 files changed, 81 insertions(+), 52 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 8b80a777..d9039a05 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -7,13 +7,13 @@ Command-line tool for real-time transcription using the Speechmatics Voice SDK. **Microphone:** ```bash -python ./examples/voice/cli/main.py --pretty --api-key YOUR_API_KEY +python ./examples/voice/cli/main.py -p -k YOUR_API_KEY ``` **Audio file:** ```bash -python ./examples/voice/cli/main.py --pretty --api-key YOUR_API_KEY --input-file audio.wav +python ./examples/voice/cli/main.py -p -k YOUR_API_KEY -i audio.wav ``` Press `CTRL+C` to stop. @@ -31,21 +31,24 @@ python ./examples/voice/cli/main.py [OPTIONS] ### Core Parameters -**`--api-key`** +**`-k, --api-key`** Speechmatics API key. Defaults to `SPEECHMATICS_API_KEY` environment variable. -**`--url`** +**`-u, --url`** Custom Speechmatics server URL. Also uses `SPEECHMATICS_SERVER_URL` environment variable, if not provided. Optional, defaults to production endpoint. -**`--input-file FILE`** +**`-i, --input-file FILE`** Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone. -**`--pretty`** +**`-p, --pretty`** Enable formatted console output with colours and emojis. -**`--jsonl FILE`** +**`-o, --output-file FILE`** Save all output to a JSONL file for later analysis. +**`-x, --extra-payloads`** +Log additional message payloads including `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `False`. + ### Audio Configuration **`--sample-rate`** @@ -54,9 +57,12 @@ Audio sample rate in Hz. Default: `16000`. **`--chunk-size`** Audio chunk size in bytes. Default: `320`. +**`-M, --mute`** +Mute audio playback for file input. When enabled, audio files are transcribed without playing through speakers. Default: `False`. + ### Turn Detection -**`--end-of-utterance-mode`** +**`-m, --end-of-utterance-mode`** Controls how turn endings are detected. Options: - `FIXED` - Fixed silence threshold @@ -64,10 +70,10 @@ Controls how turn endings are detected. Options: - `SMART_TURN` - ML-based turn detection - `EXTERNAL` - Manual control via `finalize()` -**`--end-of-utterance-silence-trigger`** +**`-t, --end-of-utterance-silence-trigger`** Silence duration in seconds to trigger turn end. Default: `0.5`. -**`--max-delay`** +**`-d, --max-delay`** Maximum transcription delay in seconds. Default: `0.7`. ### Speaker Management @@ -83,10 +89,10 @@ Use ignore mode instead of focus mode for `--focus-speakers`. ### Speaker Identification -**`--enrol`** +**`-e, --enrol`** Enrol speakers and output their identifiers at the end of the session. -**`--speakers JSON|FILE`** +**`-s, --speakers JSON|FILE`** Use known speakers from previous sessions. Provide as either: - A JSON string: `'[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]'` - A path to a JSON file: `speakers.json` @@ -99,34 +105,31 @@ Enable preview features. ### Microphone - basic transcription ```bash -python ./examples/voice/cli/main.py --api-key YOUR_KEY --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -p ``` ### Audio file - basic transcription ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --input-file audio.wav \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -p +``` + +### Audio file - muted (no playback) + +```bash +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -Mp ``` ### With adaptive turn detection ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --end-of-utterance-mode ADAPTIVE \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -m ADAPTIVE -p ``` ### Enrol speakers (microphone) ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --enrol \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -ep ``` Press `CTRL+C` when done. Speaker identifiers will be displayed: @@ -138,10 +141,7 @@ Press `CTRL+C` when done. Speaker identifiers will be displayed: ### Use known speakers (JSON string) ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --speakers '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -s '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' -p ``` ### Use known speakers (JSON file) @@ -164,10 +164,7 @@ Create a `speakers.json` file: Then run: ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --speakers speakers.json \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -s speakers.json -p ``` Output shows speaker labels: @@ -180,29 +177,25 @@ Output shows speaker labels: ### Focus on specific speakers ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --focus-speakers S1 S2 \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY --focus-speakers S1 S2 -p ``` ### Save to file ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --jsonl output.jsonl \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -o output.jsonl -p +``` + +### With extra payloads for debugging + +```bash +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -xp ``` ### Audio file with speaker focus ```bash -python ./examples/voice/cli/main.py \ - --api-key YOUR_KEY \ - --input-file audio.wav \ - --focus-speakers S2 \ - --pretty +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav --focus-speakers S2 -p ``` ## Speaker Identification diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 89fa2900..4f1b4ab3 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -37,6 +37,9 @@ "SpeakerEnded": "\033[94m", "SpeakersResult": "\033[95m", "EndOfTurn": "\033[91m", + "AddPartialTranscript": "\033[90m", + "AddTranscript": "\033[90m", + "EndOfUtterance": "\033[90m", } @@ -58,8 +61,8 @@ async def main() -> None: audio_player = setup_audio_output(audio_source, args) # Remove JSONL output file if it already exists - if args.jsonl and os.path.exists(args.jsonl): - os.remove(args.jsonl) + if args.output_file and os.path.exists(args.output_file): + os.remove(args.output_file) # Create speaker configuration speaker_config = create_speaker_config(args) @@ -223,6 +226,11 @@ def setup_audio_output(audio_source: dict, args) -> AudioPlayer | None: if audio_source["type"] != "file": return None + # Skip audio output if muted + if args.mute: + print("\nAudio playback muted - transcription only") + return None + print("\nSelect audio output device for playback:") output_device = select_audio_output_device() @@ -308,8 +316,8 @@ def log_message(message) -> None: message = {"ts": round(ts, 3), **message} console_print(message) - if args.jsonl: - with open(args.jsonl, "a") as f: + if args.output_file: + with open(args.output_file, "a") as f: f.write(json.dumps(message) + "\n") # Register handlers @@ -322,6 +330,12 @@ def log_message(message) -> None: client.on(AgentServerMessageType.END_OF_TURN, log_message) client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) + # Extra payloads + if args.extra_payloads: + client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) + # ============================================================================== # AUDIO STREAMING @@ -459,7 +473,7 @@ def parse_args(): """ parser = argparse.ArgumentParser( description="Transcription CLI with speaker diarization - supports microphone or audio file input", - epilog="Example: python main.py --api-key YOUR_KEY --input-file audio.wav --pretty", + epilog="Example: python main.py -k YOUR_KEY -i audio.wav -p", ) # ============================================================================== @@ -467,11 +481,13 @@ def parse_args(): # ============================================================================== parser.add_argument( + "-k", "--api-key", default=os.getenv("SPEECHMATICS_API_KEY"), help="Speechmatics API key (defaults to SPEECHMATICS_API_KEY environment variable)", ) parser.add_argument( + "-u", "--url", default=os.getenv("SPEECHMATICS_SERVER_URL"), help="Speechmatics server URL (optional)", @@ -482,6 +498,7 @@ def parse_args(): # ============================================================================== parser.add_argument( + "-i", "--input-file", type=str, help="Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone", @@ -503,39 +520,56 @@ def parse_args(): default=320, help="Audio chunk size in bytes (default: 320)", ) + parser.add_argument( + "-M", + "--mute", + action="store_true", + help="Mute audio playback for file input (default: False)", + ) # ============================================================================== # Output configuration # ============================================================================== parser.add_argument( - "--jsonl", + "-o", + "--output-file", type=str, help="Output to a JSONL file", ) parser.add_argument( + "-p", "--pretty", action="store_true", help="Pretty print console output (default: False)", ) + parser.add_argument( + "-x", + "--extra-payloads", + action="store_true", + help="Log additional payloads (END_OF_UTTERANCE, ADD_PARTIAL_TRANSCRIPT, ADD_TRANSCRIPT) (default: False)", + ) # ============================================================================== # Voice Agent configuration # ============================================================================== parser.add_argument( + "-d", "--max-delay", type=float, default=0.7, help="Maximum delay for transcription results in seconds (default: 0.7)", ) parser.add_argument( + "-t", "--end-of-utterance-silence-trigger", type=float, default=0.5, help="Silence duration to trigger end of utterance in seconds (default: 0.5)", ) parser.add_argument( + "-m", "--end-of-utterance-mode", type=lambda s: s.upper(), choices=["FIXED", "ADAPTIVE", "EXTERNAL", "SMART_TURN"], @@ -568,11 +602,13 @@ def parse_args(): # ============================================================================== parser.add_argument( + "-e", "--enrol", action="store_true", help="Enrol a speaker (default: False)", ) parser.add_argument( + "-s", "--speakers", type=load_speakers, help="Known speakers as JSON string or path to JSON file (default: None)", From 3f40c1417351bb8d9a97b55c94c05c3edc02e213 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 14:39:14 +0100 Subject: [PATCH 127/215] Added endofturnprediction message --- examples/voice/cli/main.py | 2 ++ sdk/voice/speechmatics/voice/_client.py | 29 +++++++++++++++++-------- sdk/voice/speechmatics/voice/_models.py | 1 + 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 4f1b4ab3..b5283fa5 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -37,6 +37,7 @@ "SpeakerEnded": "\033[94m", "SpeakersResult": "\033[95m", "EndOfTurn": "\033[91m", + "EndOfTurnPrediction": "\033[91m", "AddPartialTranscript": "\033[90m", "AddTranscript": "\033[90m", "EndOfUtterance": "\033[90m", @@ -327,6 +328,7 @@ def log_message(message) -> None: client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 92905589..100ba227 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1134,6 +1134,9 @@ async def _calculate_finalize_delay( if not filter_flags or view_changes is None or view_changes.any(*filter_flags): """Process the annotation flags to determine how long before sending a final segment.""" + # Reasons for the calculation + reasons: list[tuple[float, str]] = [] + # If the last segment is for an active speaker if last_active_segment: """Check the contents of the last segment." @@ -1145,16 +1148,10 @@ async def _calculate_finalize_delay( - ends with finalizes end of sentence """ - # Reasons for the calculation - reasons: list[tuple[float, str]] = [] - # Add multiplier def add_multiplier(multiplier: float, reason: str = "none") -> None: reasons.append((multiplier, reason)) - # Delay multiplier - add_multiplier(1.0, "default") - # Very speaking if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): add_multiplier(2.0, "very_slow_speaker") @@ -1182,7 +1179,7 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: add_multiplier(-1.0, "smart_turn_true") # Calculate multiplier - multiplier = sum(m for m, _ in reasons) + multiplier = 1 + sum(m for m, _ in reasons) # Minimum delay (50ms as a minimum) delay = round(max(self._end_of_utterance_delay, 0.05) * multiplier, 2) @@ -1196,8 +1193,22 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: # Adjust time and make sure no less than 100ms emit_delay = max(clamped_delay - time_slip, 0.1) - # Return the time - return emit_delay + # Emit prediction + if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): + self.emit( + AgentServerMessageType.END_OF_TURN_PREDICTION, + { + "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, + "ttl": emit_delay, + "reasons": [_reason for _, _reason in reasons], + }, + ) + + # Return the time + return emit_delay + + # Invalid + return None # ============================================================================ # VAD (VOICE ACTIVITY DETECTION) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 1ccc1d88..afa548ca 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -213,6 +213,7 @@ class AgentServerMessageType(str, Enum): ADD_SEGMENT = "AddSegment" # End of turn messages + END_OF_TURN_PREDICTION = "EndOfTurnPrediction" END_OF_TURN = "EndOfTurn" # Speaker messages From 347668136a7cbd429f80e8217e55cc2d3db73d99 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 23:25:49 +0100 Subject: [PATCH 128/215] updated verbose setting --- examples/voice/cli/README.md | 16 ++++++++++++---- examples/voice/cli/main.py | 28 ++++++++++++++++++---------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index d9039a05..41996c69 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -46,8 +46,8 @@ Enable formatted console output with colours and emojis. **`-o, --output-file FILE`** Save all output to a JSONL file for later analysis. -**`-x, --extra-payloads`** -Log additional message payloads including `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `False`. +**`-v, --verbose`** +Increase logging verbosity. `-v` includes `END_OF_TURN_PREDICTION`. `-vv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. ### Audio Configuration @@ -186,10 +186,18 @@ python ./examples/voice/cli/main.py -k YOUR_KEY --focus-speakers S1 S2 -p python ./examples/voice/cli/main.py -k YOUR_KEY -o output.jsonl -p ``` -### With extra payloads for debugging +### Verbose logging + +Show turn prediction events: + +```bash +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -v -p +``` + +Include additional payloads for debugging: ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -xp +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -vv -p ``` ### Audio file with speaker focus diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index b5283fa5..7c5e61ec 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -31,13 +31,17 @@ # ============================================================================== COLORS = { + # Segments "AddPartialSegment": "\033[93m", - "AddSegment": "\033[92m", + "AddSegment": "\033[1;92m", + # Speaker events "SpeakerStarted": "\033[94m", "SpeakerEnded": "\033[94m", "SpeakersResult": "\033[95m", - "EndOfTurn": "\033[91m", - "EndOfTurnPrediction": "\033[91m", + # End of turn + "EndOfTurnPrediction": "\033[95m", + "EndOfTurn": "\033[1;91m", + # Transcript events "AddPartialTranscript": "\033[90m", "AddTranscript": "\033[90m", "EndOfUtterance": "\033[90m", @@ -328,12 +332,15 @@ def log_message(message) -> None: client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) - client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) - # Extra payloads - if args.extra_payloads: + # Verbose turn prediction + if args.verbose >= 1: + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) + + # Verbose STT events + if args.verbose >= 2: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) @@ -546,10 +553,11 @@ def parse_args(): help="Pretty print console output (default: False)", ) parser.add_argument( - "-x", - "--extra-payloads", - action="store_true", - help="Log additional payloads (END_OF_UTTERANCE, ADD_PARTIAL_TRANSCRIPT, ADD_TRANSCRIPT) (default: False)", + "-v", + "--verbose", + action="count", + default=0, + help="Increase logging verbosity (-v: include END_OF_TURN_PREDICTION, -vv: include additional payloads)", ) # ============================================================================== From 0d04ab534613e34babefaf9fcf5e49b92000605f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 23:46:46 +0100 Subject: [PATCH 129/215] smart turn config in separate model --- sdk/voice/speechmatics/voice/__init__.py | 2 ++ sdk/voice/speechmatics/voice/_client.py | 30 +++++++++-------- sdk/voice/speechmatics/voice/_models.py | 42 +++++++++++++++++++----- tests/voice/test_11_audio_buffer.py | 3 +- 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index c3627de8..f6d787a5 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -25,6 +25,7 @@ from ._models import DiarizationKnownSpeaker from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode +from ._models import SmartTurnConfig from ._models import VoiceAgentConfig __all__ = [ @@ -32,6 +33,7 @@ "__version__", # Conversation config "VoiceAgentConfig", + "SmartTurnConfig", "EndOfUtteranceMode", "DiarizationSpeakerConfig", "DiarizationFocusMode", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 100ba227..77fcc315 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -190,10 +190,10 @@ def __init__( # Start turn detector if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - detector = SmartTurnDetector(auto_init=True, threshold=self._config.smart_turn_threshold) + detector = SmartTurnDetector(auto_init=True, threshold=self._config.smart_turn_config.smart_turn_threshold) if detector.model_exists(): self._turn_detector = detector - self._config.audio_buffer_length = 10.0 + self._config.smart_turn_config.audio_buffer_length = 10.0 else: self._logger.warning("Smart Turn model not available.") @@ -215,11 +215,11 @@ def __init__( }.get(self._audio_format.encoding, 1) # Audio buffer - if self._config.audio_buffer_length > 0: + if self._config.smart_turn_config.audio_buffer_length > 0: self._audio_buffer: AudioBuffer = AudioBuffer( sample_rate=self._audio_format.sample_rate, frame_size=self._audio_format.chunk_size, - total_seconds=self._config.audio_buffer_length, + total_seconds=self._config.smart_turn_config.audio_buffer_length, ) # Register handlers @@ -479,7 +479,7 @@ async def send_audio(self, payload: bytes) -> None: await super().send_audio(payload) # Add to audio buffer (use put_bytes to handle variable chunk sizes) - if self._config.audio_buffer_length > 0: + if self._config.smart_turn_config.audio_buffer_length > 0: await self._audio_buffer.put_bytes(payload) # Calculate the time (in seconds) for the payload @@ -1081,7 +1081,8 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn # Get audio slice (add small margin of 100ms to the end of the audio) segment_audio = await self._audio_buffer.get_frames( - start_time=end_time - self._config.audio_buffer_length, end_time=end_time + 0.1 + start_time=end_time - self._config.smart_turn_config.audio_buffer_length, + end_time=end_time + self._config.smart_turn_config.slice_margin, ) # Evaluate @@ -1121,11 +1122,6 @@ async def _calculate_finalize_delay( if not view or view.segment_count == 0: return None - # Calculations - clamped_delay: float = self._config.end_of_utterance_max_delay - emit_delay: Optional[float] = None - time_slip: Optional[float] = None - # Last active segment last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None @@ -1134,6 +1130,11 @@ async def _calculate_finalize_delay( if not filter_flags or view_changes is None or view_changes.any(*filter_flags): """Process the annotation flags to determine how long before sending a final segment.""" + # Calculations + clamped_delay: float = self._config.end_of_utterance_max_delay + emit_delay: Optional[float] = None + time_slip: Optional[float] = None + # Reasons for the calculation reasons: list[tuple[float, str]] = [] @@ -1175,8 +1176,11 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: add_multiplier(1.0, "does_not_end_with_eos") # Smart turn prediction - if smart_turn_prediction and smart_turn_prediction.prediction: - add_multiplier(-1.0, "smart_turn_true") + if smart_turn_prediction: + if smart_turn_prediction.prediction: + add_multiplier(-1.0, "smart_turn_true") + else: + add_multiplier(0.5, "smart_turn_false") # Calculate multiplier multiplier = 1 + sum(m for m, _ in reasons) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index afa548ca..44d12252 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -56,7 +56,6 @@ class EndOfUtteranceMode(str, Enum): >>> config = VoiceAgentConfig( ... language="en", ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, - ... smart_turn_threshold=0.8 ... ) External control (manual finalization): @@ -371,6 +370,35 @@ class DiarizationSpeakerConfig(BaseModel): focus_mode: DiarizationFocusMode = DiarizationFocusMode.RETAIN +class SmartTurnConfig(BaseModel): + """Smart turn configuration for the Speechmatics Voice Agent. + + This configuration is used to determine when a turn has completed. It is used to + extract slices of recent audio for post-processing by end of thought models. + + Parameters: + audio_buffer_length: Length of audio buffer to extract slices of recent audio for post-processing + by end of thought models. Defaults to 0.0 seconds. + + smart_turn_threshold: Smart turn threshold. This is used to determine when a turn has completed. + Only used when `end_of_utterance_mode` is `EndOfUtteranceMode.SMART_TURN`. Defaults to 0.75. + + slice_margin: Margin to add to the audio buffer to ensure that the end of thought models have + enough audio to work with. Defaults to 0.05 seconds. + + Examples: + >>> config = SmartTurnConfig( + ... audio_buffer_length=0.5, + ... smart_turn_threshold=0.75, + ... slice_margin=0.05 + ... ) + """ + + audio_buffer_length: float = 0.0 + smart_turn_threshold: float = 0.75 + slice_margin: float = 0.05 + + class VoiceAgentConfig(BaseModel): """Voice Agent configuration. @@ -463,11 +491,6 @@ class VoiceAgentConfig(BaseModel): and word timings), and `TIMING` (emit on changes to word timings, not recommended). Defaults to `TranscriptionUpdatePreset.COMPLETE`. - audio_buffer_length: Length of audio buffer to extract slices of recent audio for post-processing - by end of thought models. Defaults to 0.0 seconds. - - smart_turn_threshold: Smart turn threshold. This is used to determine when a turn has completed. - Only used when `end_of_utterance_mode` is `EndOfUtteranceMode.SMART_TURN`. Defaults to 0.75. sample_rate: Audio sample rate for streaming. Defaults to `16000`. audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. @@ -525,7 +548,9 @@ class VoiceAgentConfig(BaseModel): ... speaker_sensitivity=0.7, ... max_speakers=3, ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, - ... smart_turn_threshold=0.8, + ... smart_turn_config=SmartTurnConfig( + ... smart_turn_threshold=0.8 + ... ), ... additional_vocab=[ ... AdditionalVocabEntry(content="API"), ... AdditionalVocabEntry(content="WebSocket") @@ -562,8 +587,7 @@ class VoiceAgentConfig(BaseModel): include_results: bool = False enable_preview_features: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE - audio_buffer_length: float = 0.0 - smart_turn_threshold: float = 0.75 + smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) # Audio sample_rate: int = 16000 diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index b0b9758f..7cd9faf3 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -15,6 +15,7 @@ from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SmartTurnConfig from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._audio import AudioBuffer @@ -243,10 +244,10 @@ async def save_slice( max_delay=0.7, end_of_utterance_mode=EndOfUtteranceMode.FIXED, enable_diarization=True, - audio_buffer_length=20.0, additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], + smart_turn_config=SmartTurnConfig(audio_buffer_length=20.0), ), ) From 81aa614c0556304b28abc80bac1f2ec10a704a4a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 14 Oct 2025 23:54:28 +0100 Subject: [PATCH 130/215] add turn id to prediction. --- sdk/voice/speechmatics/voice/_client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 77fcc315..c1bdc176 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1203,8 +1203,11 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: AgentServerMessageType.END_OF_TURN_PREDICTION, { "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, - "ttl": emit_delay, - "reasons": [_reason for _, _reason in reasons], + "turn_id": self._end_of_turn_handler.turn_id, + "metadata": { + "ttl": emit_delay, + "reasons": [_reason for _, _reason in reasons], + }, }, ) From 88bb3de44b5a46de6efe0b2aa5f96a1437624784 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 11:00:13 +0100 Subject: [PATCH 131/215] split finalized segments early --- examples/voice/cli/README.md | 12 +++-- examples/voice/cli/main.py | 15 +++--- sdk/voice/speechmatics/voice/_client.py | 66 +++++++++++-------------- sdk/voice/speechmatics/voice/_utils.py | 35 +++++++++---- 4 files changed, 74 insertions(+), 54 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 41996c69..bbec5052 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -47,7 +47,7 @@ Enable formatted console output with colours and emojis. Save all output to a JSONL file for later analysis. **`-v, --verbose`** -Increase logging verbosity. `-v` includes `END_OF_TURN_PREDICTION`. `-vv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. +Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. ### Audio Configuration @@ -188,18 +188,24 @@ python ./examples/voice/cli/main.py -k YOUR_KEY -o output.jsonl -p ### Verbose logging -Show turn prediction events: +Show speaker start/end events: ```bash python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -v -p ``` -Include additional payloads for debugging: +Include turn prediction events: ```bash python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -vv -p ``` +Include additional payloads for debugging: + +```bash +python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -vvv -p +``` + ### Audio file with speaker focus ```bash diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 7c5e61ec..0d492460 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -325,22 +325,25 @@ def log_message(message) -> None: with open(args.output_file, "a") as f: f.write(json.dumps(message) + "\n") - # Register handlers + # Register standard handlers client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) - client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) - client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) - # Verbose turn prediction + # Verbose VAD events if args.verbose >= 1: + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + + # Verbose turn prediction + if args.verbose >= 2: client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) # Verbose STT events - if args.verbose >= 2: + if args.verbose >= 3: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) @@ -557,7 +560,7 @@ def parse_args(): "--verbose", action="count", default=0, - help="Increase logging verbosity (-v: include END_OF_TURN_PREDICTION, -vv: include additional payloads)", + help="Increase logging verbosity (-v: add speaker VAD events, -vv: add END_OF_TURN_PREDICTION, -vvv: add additional payloads)", ) # ============================================================================== diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c1bdc176..51462505 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -939,30 +939,6 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio # Emit the segments await self._emit_segments() - # For ADAPTIVE and SMART_TURN only - if self._end_of_utterance_mode in [EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN]: - """Based off the changes, determine if the end of turn message should be emitted. - - A number of factors influence this decision: - - contents of the final active segment - - rate of speech of the user - - whether the smart turn detector has predicted the end of the turn (audio based) - """ - - # Process to test for end of turn - async def process_eot() -> None: - """Async function to process end of turn.""" - - # Delay to finalize - delay = await self._calculate_finalize_delay(self._current_view, changes, change_filter) - - # Set the finalize timer - if delay: - self._end_of_turn_handler.update_timer(delay) - - # Create async task to process end of turn - asyncio.create_task(process_eot()) - async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: """Emit segments to listeners. @@ -994,11 +970,20 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Emit finals first if final_segments: + """Final segments are checked for end of sentence.""" + # Metadata for final segments uses actual start/end times of the segments being emitted final_metadata = { "start_time": final_segments[0].start_time, "end_time": final_segments[-1].end_time, } + + # Ensure final segment ends with EOS + # last_segment = final_segments[-1] + # if not last_segment.fragments[-1].is_eos: + # last_segment.fragments.append(SpeechFragment(content="$", is_eos=True)) + + # Emit segments self.emit( AgentServerMessageType.ADD_SEGMENT, { @@ -1014,11 +999,15 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Emit interim segments if interim_segments: + """Partial segments are emitted as is.""" + # Metadata for partial segments uses actual start/end times of the segments being emitted partial_metadata = { "start_time": interim_segments[0].start_time, "end_time": interim_segments[-1].end_time, } + + # Emit segments self.emit( AgentServerMessageType.ADD_PARTIAL_SEGMENT, { @@ -1132,7 +1121,7 @@ async def _calculate_finalize_delay( # Calculations clamped_delay: float = self._config.end_of_utterance_max_delay - emit_delay: Optional[float] = None + finalize_delay: Optional[float] = None time_slip: Optional[float] = None # Reasons for the calculation @@ -1192,10 +1181,10 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: clamped_delay = min(delay, self._config.end_of_utterance_max_delay) # Establish the real-world time - time_slip = 0 # self._total_time - self._last_fragment_end_time + time_slip = self._total_time - self._last_fragment_end_time # Adjust time and make sure no less than 100ms - emit_delay = max(clamped_delay - time_slip, 0.1) + finalize_delay = max(clamped_delay - time_slip, 0.1) # Emit prediction if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): @@ -1205,16 +1194,17 @@ def add_multiplier(multiplier: float, reason: str = "none") -> None: "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, "turn_id": self._end_of_turn_handler.turn_id, "metadata": { - "ttl": emit_delay, + "ttl": round(finalize_delay, 2), + "time_slip": round(time_slip, 2), "reasons": [_reason for _, _reason in reasons], }, }, ) # Return the time - return emit_delay + return finalize_delay - # Invalid + # Invalid / not EOT return None # ============================================================================ @@ -1324,15 +1314,18 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Reset current speaker self._current_speaker = None - # Check for end of turn prediction - if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - """Use Smart Turn prediction.""" + # For ADAPTIVE and SMART_TURN only + if self._end_of_utterance_mode in [EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN]: + """When ADAPTIVE or SMART_TURN, we need to do EOT detection / prediction.""" # Callback - async def do_smart_turn(end_time: float, language: str) -> None: + async def do_eot_detection(end_time: float, language: str) -> None: try: # Wait for Smart Turn result - result = await self._predict_smart_turn(end_time, language) + if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + result = await self._predict_smart_turn(end_time, language) + else: + result = None # Create a new task to evaluate the finalize delay delay = await self._calculate_finalize_delay(self._current_view, smart_turn_prediction=result) @@ -1345,7 +1338,8 @@ async def do_smart_turn(end_time: float, language: str) -> None: # Add task self._end_of_turn_handler.add_task( - asyncio.create_task(do_smart_turn(speaker_end_time, self._config.language)), "smart_turn" + asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), + self._config.end_of_utterance_mode.value, ) # Speaking has started diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index e59a35cd..dfc03a53 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -100,15 +100,32 @@ def segment_list_from_fragments( # Create SpeakerFragments objects segments: list[SpeakerSegment] = [] for group in speaker_groups: - segment = FragmentUtils.segment_from_fragments( - session=session, - fragments=group, - focus_speakers=focus_speakers, - annotate=annotate_segments, - ) - if segment: - segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) - segments.append(segment) + # Skip if the group is empty + if not group: + continue + + # Split group into sub-groups by end-of-sentence markers (finals only) + subgroup: list[SpeechFragment] = [] + subgroups: list[list[SpeechFragment]] = [] + for frag in group: + subgroup.append(frag) + if frag.is_eos and frag.is_final: + subgroups.append(subgroup) + subgroup = [] + if subgroup: + subgroups.append(subgroup) + + # Process each of the sub-groups + for fragments_subset in subgroups: + segment = FragmentUtils.segment_from_fragments( + session=session, + fragments=fragments_subset, + focus_speakers=focus_speakers, + annotate=annotate_segments, + ) + if segment: + segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) + segments.append(segment) # Return the grouped SpeakerFragments objects return segments From df1967208c85d46e61ff0205bf93aac12982ce46 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 15:15:09 +0100 Subject: [PATCH 132/215] updates to delay calculation for end of turn; add speaker metrics --- examples/voice/cli/main.py | 2 + sdk/voice/speechmatics/voice/__init__.py | 4 +- sdk/voice/speechmatics/voice/_client.py | 243 ++++++++++--------- sdk/voice/speechmatics/voice/_models.py | 33 +++ sdk/voice/speechmatics/voice/_utils.py | 19 +- tests/voice/test_05_utterance.py | 11 +- tests/voice/test_07_languages.py | 7 +- tests/voice/test_08_multiple_speakers.py | 4 + tests/voice/test_09_speaker_id.py | 6 +- tests/voice/test_13_smart_turn_transcribe.py | 22 +- 10 files changed, 214 insertions(+), 137 deletions(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 0d492460..ab873f4b 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -38,6 +38,7 @@ "SpeakerStarted": "\033[94m", "SpeakerEnded": "\033[94m", "SpeakersResult": "\033[95m", + "SpeakerMetrics": "\033[96m", # End of turn "EndOfTurnPrediction": "\033[95m", "EndOfTurn": "\033[1;91m", @@ -337,6 +338,7 @@ def log_message(message) -> None: if args.verbose >= 1: client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) # Verbose turn prediction if args.verbose >= 2: diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index f6d787a5..21d1ac72 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -26,6 +26,7 @@ from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode from ._models import SmartTurnConfig +from ._models import SpeechSegmentConfig from ._models import VoiceAgentConfig __all__ = [ @@ -33,7 +34,6 @@ "__version__", # Conversation config "VoiceAgentConfig", - "SmartTurnConfig", "EndOfUtteranceMode", "DiarizationSpeakerConfig", "DiarizationFocusMode", @@ -42,6 +42,8 @@ "AudioEncoding", "AudioFormat", "OperatingPoint", + "SmartTurnConfig", + "SpeechSegmentConfig", # Client "VoiceAgentClient", "AgentClientMessageType", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 51462505..396db4b0 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -35,6 +35,7 @@ from ._models import DiarizationSpeakerConfig from ._models import EndOfUtteranceMode from ._models import LanguagePackInfo +from ._models import SessionSpeaker from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment @@ -178,6 +179,7 @@ def __init__( self._turn_start_time: Optional[float] = None # Speaking states + self._session_speakers: dict[str, SessionSpeaker] = {} self._is_speaking: bool = False self._current_speaker: Optional[str] = None self._last_fragment_end_time: float = 0 @@ -1024,6 +1026,16 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False if not self._turn_start_time: self._turn_start_time = self._current_view.start_time + # Send updated speaker metrics + if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): + self.emit( + AgentServerMessageType.SPEAKER_METRICS, + { + "message": AgentServerMessageType.SPEAKER_METRICS.value, + "speakers": [s.model_dump() for s in self._session_speakers.values()], + }, + ) + # Emit end of turn if end_of_turn and self._previous_view: # Metadata (for LAST view) @@ -1050,46 +1062,8 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # TURN DETECTION & FINALIZATION # ============================================================================ - async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: - """Predict when to emit the end of turn. - - This will give an acoustic prediction of when the turn has completed using - the ONNX model to look for vocal intonation and hints. - - Args: - end_time: The end time of the last active segment. - language: The language of the audio. - - Returns: - bool: Whether the turn has completed. - """ - - # Check we have smart turn enabled - if not self._turn_detector: - return SmartTurnPredictionResult(error="Smart turn is not enabled") - - # Get audio slice (add small margin of 100ms to the end of the audio) - segment_audio = await self._audio_buffer.get_frames( - start_time=end_time - self._config.smart_turn_config.audio_buffer_length, - end_time=end_time + self._config.smart_turn_config.slice_margin, - ) - - # Evaluate - prediction = await self._turn_detector.predict( - segment_audio, - language=language, - sample_rate=self._audio_sample_rate, - sample_width=self._audio_sample_width, - ) - - # Return the prediction - return prediction - async def _calculate_finalize_delay( self, - view: Optional[SpeakerSegmentView] = None, - view_changes: Optional[AnnotationResult] = None, - filter_flags: Optional[list[AnnotationFlags]] = None, smart_turn_prediction: Optional[SmartTurnPredictionResult] = None, ) -> Optional[float]: """Calculate the delay before finalizing / end of turn. @@ -1107,105 +1081,140 @@ async def _calculate_finalize_delay( Optional[float]: The delay before finalizing / end of turn. """ - # Skip if no segments - if not view or view.segment_count == 0: + # Get the current view -or- previous view + view = self._current_view or self._previous_view + + # Exit if none + if not view: return None # Last active segment last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None - # Check using filter flags for changes - if not filter_flags or view_changes is None or view_changes.any(*filter_flags): - """Process the annotation flags to determine how long before sending a final segment.""" + # Calculations + clamped_delay: float = self._config.end_of_utterance_max_delay + finalize_delay: Optional[float] = None + time_slip: Optional[float] = None - # Calculations - clamped_delay: float = self._config.end_of_utterance_max_delay - finalize_delay: Optional[float] = None - time_slip: Optional[float] = None + # Reasons for the calculation + reasons: list[tuple[float, str]] = [] - # Reasons for the calculation - reasons: list[tuple[float, str]] = [] + # Add multiplier + def add_multipler_reason(multiplier: float, reason: str) -> None: + reasons.append((multiplier, reason)) - # If the last segment is for an active speaker - if last_active_segment: - """Check the contents of the last segment." + # If the last segment is for an active speaker + if last_active_segment: + """Check the contents of the last segment." - Check for: - - has any disfluencies - - ends with a disfluency - - speed of speech - - ends with finalizes end of sentence - """ + Check for: + - has any disfluencies + - ends with a disfluency + - speed of speech + - ends with finalizes end of sentence + """ - # Add multiplier - def add_multiplier(multiplier: float, reason: str = "none") -> None: - reasons.append((multiplier, reason)) + # Very speaking + if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): + add_multipler_reason(3.0, "very_slow_speaker") - # Very speaking - if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): - add_multiplier(2.0, "very_slow_speaker") + # Slow speaking + if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): + add_multipler_reason(2.0, "slow_speaker") - # Slow speaking - if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): - add_multiplier(1.0, "slow_speaker") + # Disfluencies + if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): + add_multipler_reason(2.5, "ends_with_disfluency") + elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): + add_multipler_reason(1.0, "has_disfluency") - # Disfluencies - if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): - add_multiplier(2.5, "ends_with_disfluency") - # elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - # add_multiplier(1.25, "has_disfluency") + # Ends with an end of sentence + if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): + add_multipler_reason(-0.3, "ends_with_eos_and_final") - # Ends with an end of sentence - if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): - add_multiplier(-0.3, "ends_with_eos_and_final") + # Does NOT end with end of sentence + if not last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS): + add_multipler_reason(1.0, "does_not_end_with_eos") - # Does NOT end with end of sentence - if not last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS): - add_multiplier(1.0, "does_not_end_with_eos") + # If no segments + else: + add_multipler_reason(0, "no_segments") - # Smart turn prediction - if smart_turn_prediction: - if smart_turn_prediction.prediction: - add_multiplier(-1.0, "smart_turn_true") - else: - add_multiplier(0.5, "smart_turn_false") + # Smart turn prediction + if smart_turn_prediction: + if smart_turn_prediction.prediction: + add_multipler_reason(-1.0, "smart_turn_true") + else: + add_multipler_reason(2.5, "smart_turn_false") - # Calculate multiplier - multiplier = 1 + sum(m for m, _ in reasons) + # Calculate multiplier + multiplier = 1.0 + sum(m for m, _ in reasons) - # Minimum delay (50ms as a minimum) - delay = round(max(self._end_of_utterance_delay, 0.05) * multiplier, 2) + # Minimum delay (50ms as a minimum) + delay = round(max(self._end_of_utterance_delay, 0.05) * multiplier, 2) - # Clamp to max delay - clamped_delay = min(delay, self._config.end_of_utterance_max_delay) + # Clamp to max delay + clamped_delay = min(delay, self._config.end_of_utterance_max_delay) - # Establish the real-world time - time_slip = self._total_time - self._last_fragment_end_time + # Establish the real-world time + time_slip = max(self._total_time - self._last_fragment_end_time, 0) - # Adjust time and make sure no less than 100ms - finalize_delay = max(clamped_delay - time_slip, 0.1) + # Adjust time and make sure no less than 25ms + finalize_delay = max(clamped_delay - time_slip, 0.025) - # Emit prediction - if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): - self.emit( - AgentServerMessageType.END_OF_TURN_PREDICTION, - { - "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, - "turn_id": self._end_of_turn_handler.turn_id, - "metadata": { - "ttl": round(finalize_delay, 2), - "time_slip": round(time_slip, 2), - "reasons": [_reason for _, _reason in reasons], - }, + # Emit prediction + if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): + self.emit( + AgentServerMessageType.END_OF_TURN_PREDICTION, + { + "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, + "turn_id": self._end_of_turn_handler.turn_id, + "metadata": { + "ttl": round(finalize_delay, 2), + "time_slip": round(time_slip, 2), + "reasons": [_reason for _, _reason in reasons], }, - ) + }, + ) + + # Return the time + return finalize_delay + + async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: + """Predict when to emit the end of turn. + + This will give an acoustic prediction of when the turn has completed using + the ONNX model to look for vocal intonation and hints. + + Args: + end_time: The end time of the last active segment. + language: The language of the audio. + + Returns: + bool: Whether the turn has completed. + """ + + # Check we have smart turn enabled + if not self._turn_detector: + return SmartTurnPredictionResult(error="Smart turn is not enabled") + + # Get audio slice (add small margin of 100ms to the end of the audio) + segment_audio = await self._audio_buffer.get_frames( + start_time=end_time - self._config.smart_turn_config.audio_buffer_length, + end_time=end_time + self._config.smart_turn_config.slice_margin, + ) - # Return the time - return finalize_delay + # Evaluate + prediction = await self._turn_detector.predict( + segment_audio, + language=language, + sample_rate=self._audio_sample_rate, + sample_width=self._audio_sample_width, + ) - # Invalid / not EOT - return None + # Return the prediction + return prediction # ============================================================================ # VAD (VOICE ACTIVITY DETECTION) @@ -1240,6 +1249,16 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Are we already speaking already_speaking = self._is_speaking + # Update speaker info based off partials + if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): + for frag in partial_words: + if frag.speaker is None: + continue + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + self._session_speakers[frag.speaker].word_count += 1 + self._session_speakers[frag.speaker].last_heard = frag.end_time + # Speakers current_speaker = self._current_speaker speaker = partial_words[-1].speaker if has_valid_partial else self._current_speaker @@ -1328,7 +1347,7 @@ async def do_eot_detection(end_time: float, language: str) -> None: result = None # Create a new task to evaluate the finalize delay - delay = await self._calculate_finalize_delay(self._current_view, smart_turn_prediction=result) + delay = await self._calculate_finalize_delay(smart_turn_prediction=result) # Set the finalize timer (go now if no delay) self._end_of_turn_handler.update_timer(delay or 0.01) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 44d12252..1bd4937c 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -370,6 +370,21 @@ class DiarizationSpeakerConfig(BaseModel): focus_mode: DiarizationFocusMode = DiarizationFocusMode.RETAIN +class SpeechSegmentConfig(BaseModel): + """Configuration on how segments are emitted. + + Parameters: + add_trailing_eos: Add trailing end of sentence to segments. When enabled, segments are + emitted with trailing end of sentence. Defaults to False. + + split_on_eos: Split segments on end of sentence. When enabled, segments are split + on end of sentence and emitted as final segments. Defaults to True. + """ + + add_trailing_eos: bool = False + split_on_eos: bool = True + + class SmartTurnConfig(BaseModel): """Smart turn configuration for the Speechmatics Voice Agent. @@ -491,6 +506,9 @@ class VoiceAgentConfig(BaseModel): and word timings), and `TIMING` (emit on changes to word timings, not recommended). Defaults to `TranscriptionUpdatePreset.COMPLETE`. + smart_turn_config: Smart turn configuration for the Speechmatics Voice Agent. + + speech_segment_config: Speech segment configuration for the Speechmatics Voice Agent. sample_rate: Audio sample rate for streaming. Defaults to `16000`. audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. @@ -588,6 +606,7 @@ class VoiceAgentConfig(BaseModel): enable_preview_features: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) + speech_segment_config: SpeechSegmentConfig = Field(default_factory=SpeechSegmentConfig) # Audio sample_rate: int = 16000 @@ -633,6 +652,20 @@ class ClientSessionInfo(BaseModel): language_pack_info: LanguagePackInfo +class SessionSpeaker(BaseModel): + """Info on a speaker in a session. + + Attributes: + speaker_id (str): The speaker ID. + word_count (int): The word count for the speaker. + last_heard (float): The last time the speaker was heard. + """ + + speaker_id: str + word_count: int = 0 + last_heard: float = 0 + + class AnnotationResult(list): """Processing result.""" diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index dfc03a53..7ecf69db 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -105,15 +105,18 @@ def segment_list_from_fragments( continue # Split group into sub-groups by end-of-sentence markers (finals only) - subgroup: list[SpeechFragment] = [] - subgroups: list[list[SpeechFragment]] = [] - for frag in group: - subgroup.append(frag) - if frag.is_eos and frag.is_final: + if session.config.speech_segment_config.split_on_eos: + subgroup: list[SpeechFragment] = [] + subgroups: list[list[SpeechFragment]] = [] + for frag in group: + subgroup.append(frag) + if frag.is_eos and frag.is_final: + subgroups.append(subgroup) + subgroup = [] + if subgroup: subgroups.append(subgroup) - subgroup = [] - if subgroup: - subgroups.append(subgroup) + else: + subgroups = [group] # Process each of the sub-groups for fragments_subset in subgroups: diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index 19aff3ee..34382495 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -10,6 +10,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeechSegmentConfig from speechmatics.voice import VoiceAgentConfig @@ -316,7 +317,9 @@ async def test_end_of_utterance_adaptive_vad(): api_key="NONE", connect=False, config=VoiceAgentConfig( - end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE + end_of_utterance_silence_trigger=adaptive_timeout, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + speech_segment_config=SpeechSegmentConfig(split_on_eos=False), ), ) assert client is not None @@ -369,6 +372,12 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): # Add listener for end of turn client.once(AgentServerMessageType.END_OF_TURN, eot_rx) + # Debug + # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) + # client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) + # client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) + # client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) + # Inject conversation up to the penultimate final from the STT await send_message(0, count=12, use_ttl=True) diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index efb5c53c..5a298386 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -30,6 +30,7 @@ class AudioSample: language: str path: str transcript: str + sentence_break: str = " " use_cer: bool = False cer_pass: float = 0.05 vocab: list[str] = field(default_factory=list) @@ -65,17 +66,20 @@ class AudioSample: language="he", path="./assets/languages/he_il_000432.wav", transcript="טורקיה מוקפת ים משלושה כיוונים: הים האגאי ממערב, הים השחור מצפון והים התיכון מדרום.", + sentence_break="", ), AudioSample( language="cmn", path="./assets/languages/cmn_hans_cn_000328.wav", transcript="博贝克出生于克罗地亚首都萨格勒布,在为贝尔格莱德游击队足球俱乐部效力时成名。", + sentence_break="", use_cer=True, ), AudioSample( language="ja", path="./assets/languages/ja_jp_000595.wav", transcript="動物は地球上のいたるところに生息しています。地面を掘ったり、海を泳ぎ回ったり、空を飛んだりしています。", + sentence_break="", use_cer=True, cer_pass=0.07, ), @@ -83,6 +87,7 @@ class AudioSample: language="th", path="./assets/languages/th_th_000208.wav", transcript="ข้สภาพอากาศเลวร้ายที่เป็นสาเหตุของการยกเลิกการลงจอดทำให้การค้นหายากลำบาก", + sentence_break="", use_cer=True, cer_pass=0.03, ), @@ -180,7 +185,7 @@ def log_segment(message): assert seg0.get("language") == sample.language # Concatenate text from segments - transcribed = " ".join([seg["text"] for seg in segments]) + transcribed = sample.sentence_break.join([seg["text"] for seg in segments]) # Get normalized versions of the transcription and reference str_original = normalize(sample.transcript) diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index 761f6d84..35a6d825 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -15,6 +15,7 @@ from speechmatics.voice import DiarizationFocusMode from speechmatics.voice import DiarizationSpeakerConfig from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeechSegmentConfig from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -103,6 +104,7 @@ async def test_multiple_speakers(sample: SpeakerTest): additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], + speech_segment_config=SpeechSegmentConfig(split_on_eos=False), ) # Diarization options @@ -192,6 +194,8 @@ def log_final_segment(message): print() print() + print(final_segments) + # Check final segments against regex for idx, _test in enumerate(sample.segment_regex): if SHOW_LOG: diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 8a9e1d67..130ef551 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -13,6 +13,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import DiarizationKnownSpeaker from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeechSegmentConfig from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -221,8 +222,8 @@ def log_final_segment(message): speakers = [segment.get("speaker_id") for segment in final_segments] assert set(speakers) == set({"Assistant", "John Doe"}) - # Should be 5 segments - assert len(final_segments) == 5 + # Should be 11 segments + assert len(final_segments) == 11 # Close session await client.disconnect() @@ -261,6 +262,7 @@ async def test_ignoring_assistant(): enable_diarization=True, sample_rate=8000, known_speakers=known_speakers, + speech_segment_config=SpeechSegmentConfig(split_on_eos=False), ), ) diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 64b4b15e..1c044ccc 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -13,6 +13,7 @@ from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode +from speechmatics.voice import SpeechSegmentConfig from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._smart_turn import SmartTurnDetector @@ -25,6 +26,7 @@ URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://jamesw.lab.speechmatics.io/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] + # Detector detector = SmartTurnDetector(auto_init=False, threshold=0.75) @@ -40,18 +42,8 @@ class TranscriptionTest(BaseModel): SAMPLES: list[TranscriptionTest] = [ TranscriptionTest(id="01", path="./assets/audio_04_16kHz.wav", sample_rate=16000, language="en", eot_count=1), - # TranscriptionTest( - # id="02", - # path="./assets/audio_05_16kHz.wav", - # sample_rate=16000, - # language="en", - # ), - # TranscriptionTest( - # id="03", - # path="./assets/audio_06_16kHz.wav", - # sample_rate=16000, - # language="en", - # ), + TranscriptionTest(id="02", path="./assets/audio_05_16kHz.wav", sample_rate=16000, language="en", eot_count=1), + TranscriptionTest(id="03", path="./assets/audio_06_16kHz.wav", sample_rate=16000, language="en", eot_count=1), ] @@ -111,6 +103,7 @@ async def test_prediction(sample: TranscriptionTest): sample_rate=sample.sample_rate, additional_vocab=sample.additional_vocab, enable_preview_features=True, + speech_segment_config=SpeechSegmentConfig(split_on_eos=False), ), ) @@ -133,6 +126,7 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) # Calculated end of turn count @@ -166,6 +160,10 @@ def log_message(message): await client.disconnect() assert not client._is_connected + print(eot_count) + + return + # Validate (if we have expected results) if sample.eot_count: assert eot_count == sample.eot_count From e3bcb939c03bb076ccf37ce1a784231fd56ad6d0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 15:31:09 +0100 Subject: [PATCH 133/215] fix to speaker metrics - emitted on final segments only. --- sdk/voice/speechmatics/voice/_client.py | 55 +++++++++++++------- tests/voice/test_13_smart_turn_transcribe.py | 1 + 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 396db4b0..06f825c8 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1026,15 +1026,42 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False if not self._turn_start_time: self._turn_start_time = self._current_view.start_time - # Send updated speaker metrics - if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): - self.emit( - AgentServerMessageType.SPEAKER_METRICS, - { - "message": AgentServerMessageType.SPEAKER_METRICS.value, - "speakers": [s.model_dump() for s in self._session_speakers.values()], - }, - ) + # Send updated speaker metrics + if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): + """Update the metrics of the speakers in the sesseion.""" + + # Finalized words + final_words = [ + f + for seg in final_segments + for f in seg.fragments + if f.type_ == "word" and f.speaker is not None + ] + + # Only process if we have words + if final_words: + # Update the metrics of the speakers in the session + for frag in final_words: + # Check we have a speaker + if frag.speaker is None: + continue + + # Create new speaker + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + + # Update metrics + self._session_speakers[frag.speaker].word_count += 1 + self._session_speakers[frag.speaker].last_heard = frag.end_time + + # Emit + self.emit( + AgentServerMessageType.SPEAKER_METRICS, + { + "message": AgentServerMessageType.SPEAKER_METRICS.value, + "speakers": [s.model_dump() for s in self._session_speakers.values()], + }, + ) # Emit end of turn if end_of_turn and self._previous_view: @@ -1249,16 +1276,6 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Are we already speaking already_speaking = self._is_speaking - # Update speaker info based off partials - if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): - for frag in partial_words: - if frag.speaker is None: - continue - if frag.speaker not in self._session_speakers: - self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) - self._session_speakers[frag.speaker].word_count += 1 - self._session_speakers[frag.speaker].last_heard = frag.end_time - # Speakers current_speaker = self._current_speaker speaker = partial_words[-1].speaker if has_valid_partial else self._current_speaker diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 1c044ccc..a53919cb 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -126,6 +126,7 @@ def log_message(message): client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) From e4088f9c61b9158162bcc7fcd3a146cc1a0b6439 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 20:27:52 +0100 Subject: [PATCH 134/215] Make `SMART_TURN` dependencies optional. --- Makefile | 8 +- examples/voice/requirements-examples.txt | 2 +- sdk/voice/README.md | 81 ++++++++++----------- sdk/voice/pyproject.toml | 8 +- sdk/voice/speechmatics/voice/_client.py | 26 +++++-- sdk/voice/speechmatics/voice/_smart_turn.py | 36 ++++++++- 6 files changed, 99 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index 58657772..a648767f 100644 --- a/Makefile +++ b/Makefile @@ -129,10 +129,10 @@ type-check-voice: # Installation targets install-dev: python -m pip install --upgrade pip - python -m pip install -e sdk/rt[dev] - python -m pip install -e sdk/batch[dev] - python -m pip install -e sdk/flow[dev] - python -m pip install -e sdk/voice[dev] + python -m pip install -e 'sdk/rt[dev]' + python -m pip install -e 'sdk/batch[dev]' + python -m pip install -e 'sdk/flow[dev]' + python -m pip install -e 'sdk/voice[dev,smart]' install-build: python -m pip install --upgrade build diff --git a/examples/voice/requirements-examples.txt b/examples/voice/requirements-examples.txt index a0e6d1bd..d6cb4239 100644 --- a/examples/voice/requirements-examples.txt +++ b/examples/voice/requirements-examples.txt @@ -1,3 +1,3 @@ colorama pyaudio -speechmatics-voice +speechmatics-voice[smart] diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 89359d7a..2c0758e4 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -8,51 +8,18 @@ An SDK for working with the Speechmatics Real-Time API optimised for use in voic The Voice Agent SDK is designed to be a set of helper classes that use the Real-Time AsyncClient to connect to the Speechmatics Real-Time API and process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. -```mermaid -graph TD - A[Client connects] --> B[Recognition started] - B --> C[Audio chunks sent] - C --> D[STT processing] - - D --> E[Partial transcripts rx] - D --> F[Final transcripts rx] - - E --> G[Process transcripts] - F --> G - - G --> H[Accumulate speech fragments] - H --> I{Changes detected?} - - I -->|No| J[Skip] - I -->|Yes| K[Create speaker segments] - - K --> L{Segment type?} - L -->|Final| M[Emit final segments] - L -->|Partial| N[Emit partial segments] - - M --> O[Trim fragments] - N --> P{End of utterance?} - O --> P - - P -->|FIXED mode| Q[Wait for STT signal] - P -->|ADAPTIVE mode| R[Calculate timing] - - Q --> S[Emit END_OF_TURN] - R --> S - - style A fill:#e1f5fe - style B fill:#e8f5e8 - style M fill:#fff3e0 - style N fill:#fff3e0 - style S fill:#fce4ec -``` - ## Installation ```bash +# Install with standard turn detection pip install speechmatics-voice + +# Install with SMART_TURN (includes ONNX runtime and transformers) +pip install speechmatics-voice[smart] ``` +> **Note:** If the `smart` extras are not installed, selecting `EndOfUtteranceMode.SMART_TURN` will emit a warning and automatically fall back to `EndOfUtteranceMode.ADAPTIVE`. + ## Requirements You must have a valid Speechmatics API key to use this SDK. You can get one from the [Speechmatics Portal](https://portal.speechmatics.com/). @@ -157,7 +124,7 @@ The `VoiceAgentClient` can be configured with a number of options to control the - **`max_delay`** (`float`): Maximum delay in seconds for transcription. This forces the STT engine to speed up the processing of transcribed words and reduces the interval between partial and final results. Lower values can have an impact on accuracy. Defaults to `0.7`. - **`end_of_utterance_silence_trigger`** (`float`): Maximum delay in seconds for end of utterance trigger. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be lower than `max_delay`. Defaults to `0.2`. - **`end_of_utterance_max_delay`** (`float`): Maximum delay in seconds for end of utterance delay. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be greater than `end_of_utterance_silence_trigger`. Defaults to `10.0`. -- **`end_of_utterance_mode`** (`EndOfUtteranceMode`): End of utterance delay mode. When `ADAPTIVE` is used, the delay can be adjusted on the content of what the most recent speaker has said, such as rate of speech and whether they have any pauses or disfluencies. When `FIXED` is used, the delay is fixed to the value of `end_of_utterance_silence_trigger`. Use of `EXTERNAL` disables end of utterance detection and uses a fallback timer. Defaults to `EndOfUtteranceMode.FIXED`. +- **`end_of_utterance_mode`** (`EndOfUtteranceMode`): End of utterance delay mode. When `ADAPTIVE` is used, the delay can be adjusted on the content of what the most recent speaker has said, such as rate of speech and whether they have any pauses or disfluencies. When `FIXED` is used, the delay is fixed to the value of `end_of_utterance_silence_trigger`. Use of `EXTERNAL` disables end of utterance detection and uses a fallback timer. `SMART_TURN` uses an acoustic model (requires installing `speechmatics-voice[smart]`) and automatically falls back to `ADAPTIVE` when the optional dependencies or model are unavailable. Defaults to `EndOfUtteranceMode.FIXED`. #### Language and Vocabulary Features @@ -177,6 +144,9 @@ The `VoiceAgentClient` can be configured with a number of options to control the - **`include_results`** (`bool`): Include word data in the response. This is useful for debugging and understanding the STT engine's behaviour. Defaults to `False`. - **`enable_preview_features`** (`bool`): Enable preview features. Defaults to `False`. +- **`transcription_update_preset`** (`TranscriptionUpdatePreset`): Controls when transcription updates are emitted (e.g. only on finalized changes or when timings shift). Defaults to `TranscriptionUpdatePreset.COMPLETE`. +- **`smart_turn_config`** (`SmartTurnConfig`): Configures SMART_TURN behaviour, such as audio buffer length and probability threshold. Only applies when `end_of_utterance_mode` is `SMART_TURN`. +- **`speech_segment_config`** (`SpeechSegmentConfig`): Fine-tunes how speech segments are generated and post-processed before emission. #### Audio Configuration @@ -190,8 +160,12 @@ from speechmatics.voice import ( AdditionalVocabEntry, AudioEncoding, SpeakerFocusConfig, + SmartTurnConfig, + SpeechSegmentConfig, + SpeakerFocusMode, EndOfUtteranceMode, OperatingPoint, + TranscriptionUpdatePreset, VoiceAgentConfig ) @@ -227,9 +201,27 @@ advanced_config = VoiceAgentConfig( ) ], include_results=True, + transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, + smart_turn_config=SmartTurnConfig( + audio_buffer_length=8.0, + smart_turn_threshold=0.78, + slice_margin=0.1, + ), + speech_segment_config=SpeechSegmentConfig(), sample_rate=16000, audio_encoding=AudioEncoding.PCM_S16LE ) + +# SMART_TURN configuration (requires `pip install speechmatics-voice[smart]`) +smart_turn_enabled_config = VoiceAgentConfig( + language="en", + end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + smart_turn_config=SmartTurnConfig( + audio_buffer_length=10.0, + smart_turn_threshold=0.75, + slice_margin=0.05, + ) +) ``` ## Messages @@ -345,6 +337,7 @@ The message is emitted differently for the different `EndOfUtterance` modes: - `FIXED` -> emitted when the fixed delay has elapsed with the STT engine - `ADAPTIVE` -> emitted when the adaptive delay has elapsed with the client +- `SMART_TURN` -> emitted when the acoustic model predicts the turn has completed (requires optional smart extras; falls back to `ADAPTIVE` if unavailable) - `EXTERNAL` -> emitted when an external trigger forces the end of turn The `ADAPTIVE` mode takes into consideration a number of factors to determine whether the most recent speaker as completed their turn. These include: @@ -353,7 +346,9 @@ The `ADAPTIVE` mode takes into consideration a number of factors to determine wh - Whether they have been using disfluencies (e.g. "um", "er", "ah") - If the words spoken are considered to be a complete sentence -The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED` and `ADAPTIVE` delays. As a fallback, the `end_of_utterance_max_delay` is used to trigger the end of turn after a fixed amount of time, regardless of the content of what the most recent speaker has said. +Finally, `SMART_TURN` mode, in addition to the above, takes into consideration the output of the acoustic model to determine whether the most recent speaker as completed their turn. + +The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED`, `ADAPTIVE` and `SMART_TURN` delays. As a fallback, the `end_of_utterance_max_delay` is used to trigger the end of turn after a fixed amount of time, regardless of the content of what the most recent speaker has said. When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. @@ -368,7 +363,9 @@ When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. - `SPEECHMATICS_API_KEY` - Your Speechmatics API key - `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) -- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging +- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging (optional) +- `SMART_TURN_MODEL_PATH` - Path for the SMART_TURN ONNX model (optional) +- `SMART_TURN_HF_URL` - Override download URL for the SMART_TURN ONNX model (optional) ## Documentation diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index 287132cb..eff884bc 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -13,9 +13,7 @@ requires-python = ">=3.9" dependencies = [ "speechmatics-rt", "pydantic>=2.10.6,<3", - "numpy>=1.26.4,<3", - "onnxruntime>=1.19.0,<2", - "transformers>=4.57.0,<5" + "numpy>=1.26.4,<3" ] classifiers = [ "Development Status :: 4 - Beta", @@ -41,6 +39,10 @@ keywords = [ ] [project.optional-dependencies] +smart = [ + "onnxruntime>=1.19.0,<2", + "transformers>=4.57.0,<5", +] dev = [ "black", "ruff", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 860fe0c7..7d61907b 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -42,6 +42,7 @@ from ._models import SpeechFragment from ._models import TranscriptionUpdatePreset from ._models import VoiceAgentConfig +from ._smart_turn import SMART_TURN_INSTALL_HINT from ._smart_turn import SmartTurnDetector from ._smart_turn import SmartTurnPredictionResult from ._turn import TurnTaskProcessor @@ -191,14 +192,23 @@ def __init__( ) self._turn_detector: Optional[SmartTurnDetector] = None - # Start turn detector + # Start turn detector if SMART_TURN requested if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - detector = SmartTurnDetector(auto_init=True, threshold=self._config.smart_turn_config.smart_turn_threshold) - if detector.model_exists(): - self._turn_detector = detector - self._config.smart_turn_config.audio_buffer_length = 10.0 + eou_mode_ok: bool = False + if not SmartTurnDetector.dependencies_available(): + self._logger.warning(SMART_TURN_INSTALL_HINT) else: - self._logger.warning("Smart Turn model not available.") + detector = SmartTurnDetector( + auto_init=True, + threshold=self._config.smart_turn_config.smart_turn_threshold, + ) + if detector.model_exists(): + self._turn_detector = detector + self._config.smart_turn_config.audio_buffer_length = 10.0 + eou_mode_ok = True + if not eou_mode_ok: + self._logger.warning("Smart Turn model not available. Falling back to ADAPTIVE.") + self._config.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE # Diarization / speaker focus self._end_of_utterance_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode @@ -1354,7 +1364,7 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: async def do_eot_detection(end_time: float, language: str) -> None: try: # Wait for Smart Turn result - if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + if self._end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: result = await self._predict_smart_turn(end_time, language) else: result = None @@ -1371,7 +1381,7 @@ async def do_eot_detection(end_time: float, language: str) -> None: # Add task self._end_of_turn_handler.add_task( asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), - self._config.end_of_utterance_mode.value, + self._end_of_utterance_mode.value, ) # Speaking has started diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index ceaa1719..263cf5c6 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -8,24 +8,38 @@ import logging import os import urllib.request +from typing import Any from typing import Optional from urllib.parse import urlparse import numpy as np -import onnxruntime as ort from pydantic import BaseModel -os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" -from transformers import WhisperFeatureExtractor # noqa: E402 - +ort: Any +WhisperFeatureExtractor: Any logger = logging.getLogger(__name__) +try: + os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" + import onnxruntime as _ort + from transformers import WhisperFeatureExtractor as _WhisperFeatureExtractor + + ort = _ort + WhisperFeatureExtractor = _WhisperFeatureExtractor +except ModuleNotFoundError: + WhisperFeatureExtractor = None + ort = None + + # Base model from HuggingFace ONNX_HF_URL = os.getenv( "SMART_TURN_HF_URL", "https://huggingface.co/pipecat-ai/smart-turn-v3/resolve/main/smart-turn-v3.0.onnx" ) ONNX_MODEL_PATH = os.getenv("SMART_TURN_MODEL_PATH", ".models/smart-turn-v3.0.onnx") +# Hint for when dependencies are not available +SMART_TURN_INSTALL_HINT = "SMART_TURN mode unavailable. Install `speechmatics-voice[smart]` to enable SMART_TURN mode." + class SmartTurnPredictionResult(BaseModel): """Prediction result from the smart turn detector. @@ -70,12 +84,22 @@ def __init__(self, auto_init: bool = True, threshold: float = 0.8): if auto_init: self.setup() + @staticmethod + def dependencies_available() -> bool: + """Return whether optional Smart Turn dependencies are installed.""" + return ort is not None and WhisperFeatureExtractor is not None + def setup(self) -> None: """Setup the detector. Initialises the ONNX model and feature extractor. """ + # Show warning if dependencies are not available + if not self.dependencies_available(): + logger.warning(SMART_TURN_INSTALL_HINT) + return + try: # Check / download the model self.download_model() @@ -107,6 +131,10 @@ def build_session(self, onnx_path: str) -> ort.InferenceSession: ONNX inference session. """ + # Show warning if dependencies are not available + if ort is None: + raise RuntimeError("onnxruntime is not available") + # Build the session so = ort.SessionOptions() so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL From 67f5b0c4c1112af33e7e7ebde311b71591f16c66 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 21:14:40 +0100 Subject: [PATCH 135/215] update RT dependency --- sdk/voice/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index eff884bc..c2e2e5f2 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -11,7 +11,7 @@ authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" dependencies = [ - "speechmatics-rt", + "speechmatics-rt>=0.5.0", "pydantic>=2.10.6,<3", "numpy>=1.26.4,<3" ] From f7729dfe122333fcbf612203259e7eb58fee2f49 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 21:16:40 +0100 Subject: [PATCH 136/215] update file path for RT example --- examples/rt/async/file/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rt/async/file/main.py b/examples/rt/async/file/main.py index 9b58458c..d3c41f0a 100644 --- a/examples/rt/async/file/main.py +++ b/examples/rt/async/file/main.py @@ -13,7 +13,7 @@ def handle_final_transcript(msg): print(f"Final: {msg['metadata']['transcript']}") # Transcribe audio file - with open("./examples/example.wav", "rb") as audio_file: + with open("./examples/example1.wav", "rb") as audio_file: await client.transcribe(audio_file) From f31957c5fc87e980140aaa2c42de3140eef16d95 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 15 Oct 2025 22:49:45 +0100 Subject: [PATCH 137/215] Emit START_OF_TURN message --- examples/voice/cli/main.py | 2 ++ sdk/voice/speechmatics/voice/_client.py | 16 +++++++++++++++- sdk/voice/speechmatics/voice/_models.py | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 2f81944a..838a6a13 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -40,6 +40,7 @@ "SpeakersResult": "\033[95m", "SpeakerMetrics": "\033[96m", # End of turn + "StartOfTurn": "\033[91m", "EndOfTurnPrediction": "\033[95m", "EndOfTurn": "\033[1;91m", # Transcript events @@ -329,6 +330,7 @@ def log_message(message) -> None: client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.START_OF_TURN, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 7d61907b..031bc09c 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1069,7 +1069,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False }, ) - # Emit end of turn + # Emit END_OF_TURN if end_of_turn and self._previous_view: # Metadata (for LAST view) metadata = {"start_time": self._turn_start_time, "end_time": self._previous_view.end_time} @@ -1335,6 +1335,20 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Event time event_time = speaker_start_time if self._is_speaking else speaker_end_time + # Emit start of turn + if self._is_speaking and self._end_of_utterance_mode in [ + EndOfUtteranceMode.ADAPTIVE, + EndOfUtteranceMode.SMART_TURN, + ]: + self.emit( + AgentServerMessageType.START_OF_TURN, + { + "message": AgentServerMessageType.START_OF_TURN.value, + "speaker_id": speaker, + "time": event_time, + }, + ) + # Emit the event for latest speaker msg: AgentServerMessageType = ( AgentServerMessageType.SPEAKER_STARTED if self._is_speaking else AgentServerMessageType.SPEAKER_ENDED diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 08c9a563..eca9b27d 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -166,6 +166,8 @@ class AgentServerMessageType(str, Enum): SpeakerEnded: Speech has ended. AddPartialSegment: A partial / interim segment has been detected. AddSegment: A final segment has been detected. + StartOfTurn: Start of turn has been detected. + EndOfTurnPrediction: End of turn prediction timing. EndOfTurn: End of turn has been detected. SpeakersResult: Speakers result has been detected. Metrics: Metrics for the STT engine. @@ -213,6 +215,7 @@ class AgentServerMessageType(str, Enum): ADD_SEGMENT = "AddSegment" # End of turn messages + START_OF_TURN = "StartOfTurn" END_OF_TURN_PREDICTION = "EndOfTurnPrediction" END_OF_TURN = "EndOfTurn" From 3c6b9ce39602d73f8e86cd085f97c22a2d51c0bc Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 16 Oct 2025 00:08:13 +0100 Subject: [PATCH 138/215] correction to start of turn message --- sdk/voice/speechmatics/voice/_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 031bc09c..a791d81b 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1344,8 +1344,10 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: AgentServerMessageType.START_OF_TURN, { "message": AgentServerMessageType.START_OF_TURN.value, - "speaker_id": speaker, - "time": event_time, + "turn_id": self._end_of_turn_handler.turn_id, + "metadata": { + "start_time": event_time, + }, }, ) From 007b28f145ad54f710d32c5762907232353828bb Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 16 Oct 2025 20:40:09 +0100 Subject: [PATCH 139/215] Moved utilities to _utils --- examples/voice/cli/README.md | 5 +- examples/voice/cli/main.py | 60 +++++++++++++------ sdk/voice/speechmatics/voice/_client.py | 12 ++-- sdk/voice/speechmatics/voice/_models.py | 1 - sdk/voice/speechmatics/voice/_utils.py | 76 +++++++++++++++++++++++++ tests/voice/_utils.py | 55 ------------------ tests/voice/test_07_languages.py | 9 ++- 7 files changed, 131 insertions(+), 87 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index bbec5052..622e9bff 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -78,10 +78,10 @@ Maximum transcription delay in seconds. Default: `0.7`. ### Speaker Management -**`--focus-speakers S1 S2 ...`** +**`--focus-speakers 'S1' 'S2' ...`** Speakers to focus on. Only these speakers will be emitted as finalized frames. -**`--ignore-speakers S1 S2 ...`** +**`--ignore-speakers 'S1' 'S2' ...`** Specific speakers to exclude from transcription. **`--ignore-mode`** @@ -94,6 +94,7 @@ Enrol speakers and output their identifiers at the end of the session. **`-s, --speakers JSON|FILE`** Use known speakers from previous sessions. Provide as either: + - A JSON string: `'[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]'` - A path to a JSON file: `speakers.json` diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 838a6a13..4b4dafb8 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -23,6 +23,7 @@ from speechmatics.voice import SpeakerFocusConfig from speechmatics.voice import SpeakerFocusMode from speechmatics.voice import SpeakerIdentifier +from speechmatics.voice import SpeechSegmentConfig from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -90,6 +91,7 @@ async def main() -> None: AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], known_speakers=known_speakers, + speech_segment_config=SpeechSegmentConfig(split_on_eos=not args.no_split), ) # Create Voice Agent client @@ -328,24 +330,34 @@ def log_message(message) -> None: # Register standard handlers client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) - client.on(AgentServerMessageType.ADD_SEGMENT, log_message) - client.on(AgentServerMessageType.START_OF_TURN, log_message) - client.on(AgentServerMessageType.END_OF_TURN, log_message) - client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) - - # Verbose VAD events - if args.verbose >= 1: - client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) - client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) - client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) - - # Verbose turn prediction - if args.verbose >= 2: - client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) - - # Verbose STT events - if args.verbose >= 3: + + # Voice SDK messages + if not args.legacy: + # Segment messages + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.START_OF_TURN, log_message) + client.on(AgentServerMessageType.END_OF_TURN, log_message) + client.on(AgentServerMessageType.SPEAKERS_RESULT, log_message) + + # Verbose VAD events + if args.verbose >= 1: + client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) + client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) + + # Verbose turn prediction + if args.verbose >= 2: + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) + + # Verbose STT events + if args.verbose >= 3: + client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) + client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) + + # Legacy messages + else: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) @@ -564,6 +576,12 @@ def parse_args(): default=0, help="Increase logging verbosity (-v: add speaker VAD events, -vv: add END_OF_TURN_PREDICTION, -vvv: add additional payloads)", ) + parser.add_argument( + "-l", + "--legacy", + action="store_true", + help="Only show payloads from AsyncClient (AddPartialTranscript | AddTranscript) (default: False)", + ) # ============================================================================== # Voice Agent configuration @@ -591,6 +609,12 @@ def parse_args(): default="ADAPTIVE", help="End of utterance detection mode (default: ADAPTIVE)", ) + parser.add_argument( + "-S", + "--no-split", + action="store_true", + help="Do not emit finalized sentences, only complete segments. (default: False)", + ) # ============================================================================== # Speaker configuration diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a791d81b..97d5247d 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -760,8 +760,8 @@ async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> N is_final=is_final, ) - # Skip if no fragments or `AddTranscript` - if not fragments_available or is_final: + # Skip if no fragments + if not fragments_available: return # Process @@ -845,10 +845,6 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Track the last fragment end time self._last_fragment_end_time = max(self._last_fragment_end_time, fragment.end_time) - # Evaluate for VAD (only done on partials) - if not is_final: - self._vad_evaluation(fragments) - # Fragments to retain retained_fragments = [ frag for frag in self._speech_fragments if frag.is_final and frag.start_time >= self._trim_before_time @@ -867,6 +863,10 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = ): self._speech_fragments.pop(0) + # Evaluate for VAD (only done on partials) + if not is_final: + self._vad_evaluation(fragments) + # Debug the fragments if DEBUG_MORE: debug_payload = { diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index eca9b27d..71bc8c25 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -475,7 +475,6 @@ class VoiceAgentConfig(BaseModel): Defaults to `TranscriptionUpdatePreset.COMPLETE`. smart_turn_config: Smart turn configuration for the Speechmatics Voice Agent. - speech_segment_config: Speech segment configuration for the Speechmatics Voice Agent. sample_rate: Audio sample rate for streaming. Defaults to `16000`. diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index 7ecf69db..ee72abe0 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -5,6 +5,8 @@ from __future__ import annotations import datetime +import re +import unicodedata from typing import Optional from ._models import AnnotationFlags @@ -338,3 +340,77 @@ def compare_views( # Return the result return result + + +class TextUtils: + """Set of string / text utilities.""" + + @staticmethod + def cer(ref: str, hyp: str) -> float: + """ + Compute Character Error Rate (CER) between reference and hypothesis. + + CER = (S + D + I) / N + where + S = substitutions + D = deletions + I = insertions + N = number of characters in reference + + Args: + ref (str): Reference text. + hyp (str): Hypothesis text. + + Returns: + float: Character Error Rate (CER). + """ + + # Initialise DP matrix + n, m = len(ref), len(hyp) + dp = [[0] * (m + 1) for _ in range(n + 1)] + + # Base cases + for i in range(n + 1): + dp[i][0] = i + for j in range(m + 1): + dp[0][j] = j + + # Fill DP matrix + for i in range(1, n + 1): + for j in range(1, m + 1): + cost = 0 if ref[i - 1] == hyp[j - 1] else 1 + dp[i][j] = min( + dp[i - 1][j] + 1, # deletion + dp[i][j - 1] + 1, # insertion + dp[i - 1][j - 1] + cost, # substitution + ) + + # Return CER + distance = dp[n][m] + return distance / n if n > 0 else float("inf") + + @staticmethod + def normalize(text: str) -> str: + """Normalise text. + + When comparing text, it is often useful to normalise it first. This will strip out + all non-letter characters and collapse whitespace. + + Args: + text (str): Text to normalise. + + Returns: + str: Normalised text. + """ + + # Lowercase + text = text.lower() + + # Remove punctuation (Unicode category "P") + text = "".join(ch for ch in text if unicodedata.category(ch)[0] != "P") + + # Collapse whitespace + text = re.sub(r"\s+", " ", text).strip() + + # Return cleaned text + return text diff --git a/tests/voice/_utils.py b/tests/voice/_utils.py index a1d4019e..c663e995 100644 --- a/tests/voice/_utils.py +++ b/tests/voice/_utils.py @@ -1,9 +1,7 @@ import asyncio import json import os -import re import time -import unicodedata import wave from typing import Any from typing import Callable @@ -186,59 +184,6 @@ async def send_silence( iterations -= 1 -def normalize(text: str) -> str: - """Normalise text.""" - - # Lowercase - text = text.lower() - - # Remove punctuation (Unicode category "P") - text = "".join(ch for ch in text if unicodedata.category(ch)[0] != "P") - - # Collapse whitespace - text = re.sub(r"\s+", " ", text).strip() - - # Return cleaned text - return text - - -def cer(ref: str, hyp: str) -> float: - """ - Compute Character Error Rate (CER) between reference and hypothesis. - - CER = (S + D + I) / N - where - S = substitutions - D = deletions - I = insertions - N = number of characters in reference - """ - - # Initialise DP matrix - n, m = len(ref), len(hyp) - dp = [[0] * (m + 1) for _ in range(n + 1)] - - # Base cases - for i in range(n + 1): - dp[i][0] = i - for j in range(m + 1): - dp[0][j] = j - - # Fill DP matrix - for i in range(1, n + 1): - for j in range(1, m + 1): - cost = 0 if ref[i - 1] == hyp[j - 1] else 1 - dp[i][j] = min( - dp[i - 1][j] + 1, # deletion - dp[i][j - 1] + 1, # insertion - dp[i - 1][j - 1] + cost, # substitution - ) - - # Return CER - distance = dp[n][m] - return distance / n if n > 0 else float("inf") - - class ConversationLog: """Load a JSONL past conversation.""" diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 5a298386..c4a56533 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -8,15 +8,14 @@ from typing import Optional import pytest -from _utils import cer from _utils import get_client -from _utils import normalize from _utils import send_audio_file from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._utils import TextUtils # Skip for CI testing pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping language tests in CI") @@ -188,9 +187,9 @@ def log_segment(message): transcribed = sample.sentence_break.join([seg["text"] for seg in segments]) # Get normalized versions of the transcription and reference - str_original = normalize(sample.transcript) - str_transcribed = normalize(transcribed) - str_cer = cer(str_original, str_transcribed) + str_original = TextUtils.normalize(sample.transcript) + str_transcribed = TextUtils.normalize(transcribed) + str_cer = TextUtils.cer(str_original, str_transcribed) # Assert the CER if sample.use_cer: From ded1109568b93eb994bc17bb0baa3aeb9c080b2c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 17 Oct 2025 11:45:59 +0100 Subject: [PATCH 140/215] updated constant names --- sdk/voice/speechmatics/voice/_smart_turn.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 263cf5c6..a9cdae92 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -32,10 +32,10 @@ # Base model from HuggingFace -ONNX_HF_URL = os.getenv( +SMART_TURN_MODEL_URL = os.getenv( "SMART_TURN_HF_URL", "https://huggingface.co/pipecat-ai/smart-turn-v3/resolve/main/smart-turn-v3.0.onnx" ) -ONNX_MODEL_PATH = os.getenv("SMART_TURN_MODEL_PATH", ".models/smart-turn-v3.0.onnx") +SMART_TURN_MODEL_LOCAL_PATH = os.getenv("SMART_TURN_MODEL_PATH", ".models/smart-turn-v3.0.onnx") # Hint for when dependencies are not available SMART_TURN_INSTALL_HINT = "SMART_TURN mode unavailable. Install `speechmatics-voice[smart]` to enable SMART_TURN mode." @@ -110,7 +110,7 @@ def setup(self) -> None: return # Build the session - self.session = self.build_session(ONNX_MODEL_PATH) + self.session = self.build_session(SMART_TURN_MODEL_LOCAL_PATH) # Load the feature extractor self.feature_extractor = WhisperFeatureExtractor(chunk_length=8) @@ -256,7 +256,7 @@ def download_model() -> None: return # Check the URL for valid schemes - parsed_url = urlparse(ONNX_HF_URL) + parsed_url = urlparse(SMART_TURN_MODEL_URL) if parsed_url.scheme not in ("http", "https"): logger.error(f"Invalid URL scheme: {parsed_url.scheme}") return @@ -265,10 +265,10 @@ def download_model() -> None: logger.warning("Smart Turn model not found. Downloading from HuggingFace...") # Create the directory - os.makedirs(os.path.dirname(ONNX_MODEL_PATH), exist_ok=True) + os.makedirs(os.path.dirname(SMART_TURN_MODEL_LOCAL_PATH), exist_ok=True) # Download - urllib.request.urlretrieve(ONNX_HF_URL, ONNX_MODEL_PATH) # nosec B310 + urllib.request.urlretrieve(SMART_TURN_MODEL_URL, SMART_TURN_MODEL_LOCAL_PATH) # nosec B310 @staticmethod def model_exists() -> bool: @@ -277,7 +277,7 @@ def model_exists() -> bool: Returns: True if the model file exists, False otherwise. """ - return os.path.exists(ONNX_MODEL_PATH) + return os.path.exists(SMART_TURN_MODEL_LOCAL_PATH) @staticmethod def valid_language(language: str) -> bool: From 4614ae6ff15b998a8e98f6d7bb6318d66306264d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 17 Oct 2025 21:53:42 +0100 Subject: [PATCH 141/215] Fix to race condition of finals being processed ahead of partials. --- sdk/voice/speechmatics/voice/_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 97d5247d..c166c15b 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -764,8 +764,9 @@ async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> N if not fragments_available: return - # Process - await self._process_speech_fragments(self._change_filter) + # Process (only done with non-finals) + if not is_final: + await self._process_speech_fragments(self._change_filter) async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = False) -> bool: """Takes a new Partial or Final from the STT engine. From 9518377129ed9255cffa6aca5261fdc8b78ea08a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 17 Oct 2025 21:54:05 +0100 Subject: [PATCH 142/215] support for control of how segments are emitted --- sdk/voice/speechmatics/voice/__init__.py | 2 ++ sdk/voice/speechmatics/voice/_client.py | 20 ++++++++++++----- sdk/voice/speechmatics/voice/_models.py | 28 ++++++++++++++++++++---- sdk/voice/speechmatics/voice/_utils.py | 3 ++- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 8705c6a4..fed559de 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -28,6 +28,7 @@ from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode from ._models import SpeechSegmentConfig +from ._models import SpeechSegmentEmitMode from ._models import VoiceAgentConfig __all__ = [ @@ -45,6 +46,7 @@ "SpeakerIdentifier", "SmartTurnConfig", "SpeechSegmentConfig", + "SpeechSegmentEmitMode", "VoiceAgentClient", "VoiceAgentConfig", ] diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c166c15b..8da7bc62 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -40,6 +40,7 @@ from ._models import SpeakerSegmentView from ._models import SpeakerVADStatus from ._models import SpeechFragment +from ._models import SpeechSegmentEmitMode from ._models import TranscriptionUpdatePreset from ._models import VoiceAgentConfig from ._smart_turn import SMART_TURN_INSTALL_HINT @@ -968,8 +969,11 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False final_segments = self._current_view.segments interim_segments = [] - # Split between finals and interim segments - else: + # Split between finals and interim segments (`ON_FINALIZED_SENTENCE` or `ON_SPEAKER_ENDED`) + elif self._config.speech_segment_config.emit_mode in [ + SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE, + SpeechSegmentEmitMode.ON_SPEAKER_ENDED, + ]: final_segments = [ s for s in self._current_view.segments @@ -977,6 +981,11 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ] interim_segments = [s for s in self._current_view.segments if s not in final_segments] + # Keep until end of turn (`ON_END_OF_TURN`) + else: + final_segments = [] + interim_segments = self._current_view.segments + # Emit finals first if final_segments: """Final segments are checked for end of sentence.""" @@ -988,9 +997,10 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False } # Ensure final segment ends with EOS - # last_segment = final_segments[-1] - # if not last_segment.fragments[-1].is_eos: - # last_segment.fragments.append(SpeechFragment(content="$", is_eos=True)) + if self._config.speech_segment_config.add_trailing_eos: + last_segment = final_segments[-1] + if not last_segment.fragments[-1].is_eos: + last_segment.fragments.append(SpeechFragment(content=".", is_eos=True)) # Emit segments self.emit( diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 71bc8c25..286cc854 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -338,19 +338,39 @@ class SpeakerFocusConfig(BaseModel): focus_mode: SpeakerFocusMode = SpeakerFocusMode.RETAIN +class SpeechSegmentEmitMode(str, Enum): + """EMode for when to emit finalized segments. + + As transcription is processed, segments are emitted based on the selected emit mode. This gives + the client more control in how to process incomplete and complete segments of speech. + + - `ON_SPEAKER_ENDED`: Emit segments when a speaker has ended. If a speaker change is detected + during a turn, then multiple finalized segments may be emitted in sequence. + + - `ON_FINALIZED_SENTENCE`: Emit segments when a sentence has ended. A finalized segment is emitted + as soon as a finalized end of sentence is detected. If a speaker continues to speak during + a turn, then multiple finalized segments may be emitted during the turn. + + - `ON_END_OF_TURN`: No finalized segments will be emitted until the turn has been completed. + """ + + ON_SPEAKER_ENDED = "on_speaker_ended" + ON_FINALIZED_SENTENCE = "on_finalized_sentence" + ON_END_OF_TURN = "on_end_of_turn" + + class SpeechSegmentConfig(BaseModel): """Configuration on how segments are emitted. Parameters: add_trailing_eos: Add trailing end of sentence to segments. When enabled, segments are - emitted with trailing end of sentence. Defaults to False. + emitted with missing trailing end of sentence added. Defaults to False. - split_on_eos: Split segments on end of sentence. When enabled, segments are split - on end of sentence and emitted as final segments. Defaults to True. + emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_SPEAKER_ENDED`. """ add_trailing_eos: bool = False - split_on_eos: bool = True + emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_SPEAKER_ENDED class SmartTurnConfig(BaseModel): diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index ee72abe0..aee9317e 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -15,6 +15,7 @@ from ._models import SpeakerSegment from ._models import SpeakerSegmentView from ._models import SpeechFragment +from ._models import SpeechSegmentEmitMode class FragmentUtils: @@ -107,7 +108,7 @@ def segment_list_from_fragments( continue # Split group into sub-groups by end-of-sentence markers (finals only) - if session.config.speech_segment_config.split_on_eos: + if session.config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE: subgroup: list[SpeechFragment] = [] subgroups: list[list[SpeechFragment]] = [] for frag in group: From e485f0d58a2c4c9f32463eca7add805637fc5d7d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 17 Oct 2025 21:54:25 +0100 Subject: [PATCH 143/215] updated with segment emit for tests and examples --- examples/voice/cli/README.md | 2 +- examples/voice/cli/main.py | 35 +++++-- tests/voice/test_05_utterance.py | 3 +- tests/voice/test_08_multiple_speakers.py | 3 +- tests/voice/test_09_speaker_id.py | 3 +- tests/voice/test_11_audio_buffer.py | 101 +++++++++++++++++++ tests/voice/test_13_smart_turn_transcribe.py | 3 +- 7 files changed, 135 insertions(+), 15 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 622e9bff..3186fa1e 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -89,7 +89,7 @@ Use ignore mode instead of focus mode for `--focus-speakers`. ### Speaker Identification -**`-e, --enrol`** +**`-E, --enrol`** Enrol speakers and output their identifiers at the end of the session. **`-s, --speakers JSON|FILE`** diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 4b4dafb8..f9a31711 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -91,7 +91,7 @@ async def main() -> None: AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], known_speakers=known_speakers, - speech_segment_config=SpeechSegmentConfig(split_on_eos=not args.no_split), + speech_segment_config=SpeechSegmentConfig(emit_mode=args.emit_mode.lower()), ) # Create Voice Agent client @@ -194,8 +194,12 @@ def setup_microphone_source(args) -> dict | None: Returns: Dictionary with microphone information or None on error. """ - print("\nSelect microphone input device:") - selected_device = select_audio_device() + + if not args.default_device: + print("\nSelect microphone input device:") + selected_device = select_audio_device() + else: + selected_device = None mic = Microphone( sample_rate=args.sample_rate, @@ -238,8 +242,11 @@ def setup_audio_output(audio_source: dict, args) -> AudioPlayer | None: print("\nAudio playback muted - transcription only") return None - print("\nSelect audio output device for playback:") - output_device = select_audio_output_device() + if not args.default_device: + print("\nSelect audio output device for playback:") + output_device = select_audio_output_device() + else: + output_device = None audio_player = AudioPlayer( sample_rate=audio_source["sample_rate"], @@ -582,6 +589,12 @@ def parse_args(): action="store_true", help="Only show payloads from AsyncClient (AddPartialTranscript | AddTranscript) (default: False)", ) + parser.add_argument( + "-D", + "--default-device", + action="store_true", + help="Use default device (default: False)", + ) # ============================================================================== # Voice Agent configuration @@ -610,10 +623,12 @@ def parse_args(): help="End of utterance detection mode (default: ADAPTIVE)", ) parser.add_argument( - "-S", - "--no-split", - action="store_true", - help="Do not emit finalized sentences, only complete segments. (default: False)", + "-e", + "--emit-mode", + type=lambda s: s.upper(), + choices=["ON_SPEAKER_ENDED", "ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], + default="ON_SPEAKER_ENDED", + help="End of segment emit mode (default: ON_SPEAKER_ENDED)", ) # ============================================================================== @@ -641,7 +656,7 @@ def parse_args(): # ============================================================================== parser.add_argument( - "-e", + "-E", "--enrol", action="store_true", help="Enrol a speaker (default: False)", diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index 34382495..a45bbe20 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -11,6 +11,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeechSegmentConfig +from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig @@ -319,7 +320,7 @@ async def test_end_of_utterance_adaptive_vad(): config=VoiceAgentConfig( end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - speech_segment_config=SpeechSegmentConfig(split_on_eos=False), + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), ), ) assert client is not None diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index 233b6081..c0ee59d7 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -16,6 +16,7 @@ from speechmatics.voice import SpeakerFocusConfig from speechmatics.voice import SpeakerFocusMode from speechmatics.voice import SpeechSegmentConfig +from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -104,7 +105,7 @@ async def test_multiple_speakers(sample: SpeakerTest): additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], - speech_segment_config=SpeechSegmentConfig(split_on_eos=False), + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), ) # Diarization options diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index b511700d..8e6f5abe 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -14,6 +14,7 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerIdentifier from speechmatics.voice import SpeechSegmentConfig +from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -262,7 +263,7 @@ async def test_ignoring_assistant(): enable_diarization=True, sample_rate=8000, known_speakers=known_speakers, - speech_segment_config=SpeechSegmentConfig(split_on_eos=False), + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), ), ) diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index 7cd9faf3..44ffa7bb 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -193,6 +193,7 @@ async def test_load_audio_file(): wav_file.writeframes(data) +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping in CI") @pytest.mark.asyncio async def test_transcribe_and_slice(): """Load, transcribe and slice an audio file""" @@ -295,3 +296,103 @@ def final_segment(message): # Check exceptions assert not exceptions + + +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skipping in CI") +@pytest.mark.asyncio +async def x_test_transcribe_and_slice_vad(): + """Load, transcribe and slice an audio file using VAD""" + + # API key + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + pytest.skip("Valid API key required for test") + + # Input file + file = "audio_06_16kHz" + + # Check file exists + input_file = os.path.join(os.path.dirname(__file__), f"./assets/{file}.wav") + assert os.path.exists(input_file) + + # Output directory + output_folder = os.path.join(os.path.dirname(__file__), "./.tmp/buffer") + os.makedirs(output_folder, exist_ok=True) + assert os.path.exists(output_folder) + + # Exceptions + exceptions: Exception = [] + + # Save a slice + async def save_slice( + start_time: float, end_time: float, prefix: str = "slice", json_data: Optional[str] = None + ) -> None: + try: + output_file = os.path.join(output_folder, f"{file}_{prefix}_{start_time:.2f}_{end_time:.2f}") + data = await client._audio_buffer.get_frames(start_time, end_time) + with wave.open(f"{output_file}.wav", "wb") as wav_file: + wav_file.setnchannels(1) + wav_file.setsampwidth(2) + wav_file.setframerate(client._audio_buffer._sample_rate) + wav_file.writeframes(data) + if json_data: + with open(f"{output_file}.json", "w") as json_file: + json_file.write(json_data) + except Exception as e: + exceptions.append(e) + + # Client + client = await get_client( + api_key=api_key, + connect=False, + config=VoiceAgentConfig( + end_of_utterance_silence_trigger=0.35, + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + enable_diarization=True, + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ], + smart_turn_config=SmartTurnConfig(audio_buffer_length=20.0), + ), + ) + + # Check audio buffer is enabled + assert client._audio_buffer + + # Bytes logger + def speaker_ended(message): + try: + end_time = message.get("status", {}).get("time") + speaker_id = message.get("status", {}).get("speaker_id") + asyncio.create_task( + save_slice( + start_time=end_time - 0.2, + end_time=end_time + 0.4, + prefix=speaker_id, + json_data=json.dumps(message, indent=2), + ) + ) + + except Exception as e: + exceptions.append(e) + + # Add listeners + client.on(AgentServerMessageType.SPEAKER_ENDED, speaker_ended) + + # Connect + await client.connect() + + # Check we are connected + assert client._is_connected + + # Send audio + await send_audio_file(client, input_file) + await send_silence(client, 2.0) + + # Close session + await client.disconnect() + assert not client._is_connected + + # Check exceptions + assert not exceptions diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index a53919cb..7e205149 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -14,6 +14,7 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeechSegmentConfig +from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._smart_turn import SmartTurnDetector @@ -103,7 +104,7 @@ async def test_prediction(sample: TranscriptionTest): sample_rate=sample.sample_rate, additional_vocab=sample.additional_vocab, enable_preview_features=True, - speech_segment_config=SpeechSegmentConfig(split_on_eos=False), + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), ), ) From fb32f2f433b0f2029f8575dbfab9fbcadd2c96df Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 18 Oct 2025 00:23:40 +0100 Subject: [PATCH 144/215] better turn event handling --- sdk/voice/speechmatics/voice/_client.py | 74 ++++++++++++++----------- sdk/voice/speechmatics/voice/_turn.py | 21 ++++++- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 8da7bc62..adcfb314 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -748,7 +748,7 @@ async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> N """Handle the partial and final transcript events (async). As `AddTranscript` messages are _always_ followed by `AddPartialTranscript` messages, - we can skip processing. Also skop if there are no fragments in the buffer. + we can skip processing. Also skip if there are no fragments in the buffer. Args: message: The new Partial or Final from the STT engine. @@ -1099,9 +1099,6 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._previous_view = None self._turn_start_time = None - # Update the turn handler (which will cancel any pending tasks) - self._end_of_turn_handler.next() - # ============================================================================ # TURN DETECTION & FINALIZATION # ============================================================================ @@ -1171,7 +1168,7 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): add_multipler_reason(2.5, "ends_with_disfluency") elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - add_multipler_reason(1.0, "has_disfluency") + add_multipler_reason(0.25, "has_disfluency") # Ends with an end of sentence if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): @@ -1290,30 +1287,34 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Evaluate if any valid partial words exist has_valid_partial = len(partial_words) > 0 - # Are we already speaking - already_speaking = self._is_speaking - - # Speakers + # Current states + current_is_speaking = self._is_speaking current_speaker = self._current_speaker - speaker = partial_words[-1].speaker if has_valid_partial else self._current_speaker - speaker_changed = speaker != current_speaker and current_speaker is not None - # Start time (taken from the first partial word) - speaker_start_time = partial_words[0].start_time if has_valid_partial else None + # Establish the speaker from latest partials + latest_speaker = partial_words[-1].speaker if has_valid_partial else current_speaker - # End time (taken from the last partial final) + # Determine if the speaker has changed (and we have a speaker) + speaker_changed = latest_speaker != current_speaker and current_speaker is not None + + # Start / end times (earliest and latest) + speaker_start_time = partial_words[0].start_time if has_valid_partial else None speaker_end_time = self._last_fragment_end_time # If diarization is enabled, indicate speaker switching - if self._dz_enabled and speaker is not None: + if self._dz_enabled and latest_speaker is not None: """When enabled, we send a speech events if the speaker has changed. + This + will emit a SPEAKER_ENDED for the previous speaker and a SPEAKER_STARTED + for the new speaker. + For any client that wishes to show _which_ speaker is speaking, this will emit events to indicate when speakers switch. """ # Check if speaker is different to the current speaker - if already_speaking and speaker_changed: + if current_is_speaking and speaker_changed: self.emit( AgentServerMessageType.SPEAKER_ENDED, { @@ -1328,29 +1329,33 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: { "message": AgentServerMessageType.SPEAKER_STARTED.value, "status": SpeakerVADStatus( - speaker_id=speaker, is_active=True, time=speaker_end_time + speaker_id=latest_speaker, is_active=True, time=speaker_end_time ).model_dump(), }, ) - # Update current speaker - self._current_speaker = speaker - - # No change required - if has_valid_partial == already_speaking: + # No further processing if we have no new fragments and we are not speaking + if has_valid_partial == current_is_speaking: return - # Set the speaking state - self._is_speaking = not self._is_speaking + # Update current speaker + speaking states + self._current_speaker = latest_speaker + self._is_speaking = not current_is_speaking # Event time event_time = speaker_start_time if self._is_speaking else speaker_end_time # Emit start of turn - if self._is_speaking and self._end_of_utterance_mode in [ - EndOfUtteranceMode.ADAPTIVE, - EndOfUtteranceMode.SMART_TURN, - ]: + if ( + not self._end_of_turn_handler.turn_active + and self._is_speaking + and self._end_of_utterance_mode + in [ + EndOfUtteranceMode.ADAPTIVE, + EndOfUtteranceMode.SMART_TURN, + ] + ): + self._end_of_turn_handler.start_turn() self.emit( AgentServerMessageType.START_OF_TURN, { @@ -1362,16 +1367,19 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: }, ) - # Emit the event for latest speaker - msg: AgentServerMessageType = ( - AgentServerMessageType.SPEAKER_STARTED if self._is_speaking else AgentServerMessageType.SPEAKER_ENDED - ) + # Determine start or end of speaking + if self._is_speaking: + msg = AgentServerMessageType.SPEAKER_STARTED + else: + msg = AgentServerMessageType.SPEAKER_ENDED + + # Emit the event self.emit( msg, { "message": msg.value, "status": SpeakerVADStatus( - speaker_id=speaker, is_active=self._is_speaking, time=event_time + speaker_id=latest_speaker, is_active=self._is_speaking, time=event_time ).model_dump(), }, ) diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index 7bdcc057..cc9b4900 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -31,6 +31,7 @@ def __init__(self, name: str, turn_id: int = 0, done_callback: Optional[Callable # Turn id (used to validate tasks) self._turn_id = turn_id + self._turn_active = False # Tasks + events self._tasks: dict[str, asyncio.Task] = {} @@ -57,6 +58,15 @@ def turn_id(self) -> int: """ return self._turn_id + @property + def turn_active(self) -> bool: + """Get the turn active state. + + Returns: + The current turn active state. + """ + return self._turn_active + def update_timer(self, delay: float) -> None: """Set a new done trigger. @@ -104,7 +114,7 @@ async def _do_done_callback(self) -> None: """Do the done callback.""" # Cancel any pending tasks - self.cancel_tasks() + self.complete_turn() # Do the callback if self._done_callback: @@ -127,6 +137,15 @@ def reset(self) -> None: """Reset the end of turn.""" self.cancel_tasks() + def start_turn(self) -> None: + """Start the end of turn.""" + self._turn_active = True + + def complete_turn(self) -> None: + """Complete the end of turn.""" + self.next() + self._turn_active = False + def next(self) -> None: """Increment the end of turn.""" self.reset() From f9a4e2614eab57b825c7ee7eb7552fc0ac2a60a1 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 18 Oct 2025 00:23:52 +0100 Subject: [PATCH 145/215] support for language from CLI --- examples/voice/cli/main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index f9a31711..9d0e6a55 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -81,6 +81,7 @@ async def main() -> None: # Create Voice Agent configuration config = VoiceAgentConfig( sample_rate=audio_source["sample_rate"], + language=args.language, end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, max_delay=args.max_delay, enable_diarization=True, @@ -584,7 +585,7 @@ def parse_args(): help="Increase logging verbosity (-v: add speaker VAD events, -vv: add END_OF_TURN_PREDICTION, -vvv: add additional payloads)", ) parser.add_argument( - "-l", + "-L", "--legacy", action="store_true", help="Only show payloads from AsyncClient (AddPartialTranscript | AddTranscript) (default: False)", @@ -600,6 +601,13 @@ def parse_args(): # Voice Agent configuration # ============================================================================== + parser.add_argument( + "-l", + "--language", + type=str, + default="en", + help="Language code (default: en)", + ) parser.add_argument( "-d", "--max-delay", From 77daffb6a9aa6e299af1c65b3cf314d04d9f7060 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 22 Oct 2025 23:34:13 -0700 Subject: [PATCH 146/215] Updated CLI to take a JSON config. --- examples/voice/cli/main.py | 107 +++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 27 deletions(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 9d0e6a55..e1dca202 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -20,10 +20,12 @@ from speechmatics.voice import AdditionalVocabEntry from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerFocusConfig from speechmatics.voice import SpeakerFocusMode from speechmatics.voice import SpeakerIdentifier from speechmatics.voice import SpeechSegmentConfig +from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig @@ -58,6 +60,8 @@ async def main() -> None: """Run the transcription CLI.""" + + # Parse the command line arguments args = parse_args() # Setup audio source (microphone or file) @@ -78,22 +82,37 @@ async def main() -> None: # Known speakers known_speakers: list[SpeakerIdentifier] = [SpeakerIdentifier(**s) for s in args.speakers] if args.speakers else [] + # Use JSON config + if args.config is not None: + try: + config = VoiceAgentConfig.model_validate(args.config) + except Exception as e: + print(f"Error validating config: {e}") + return + # Create Voice Agent configuration - config = VoiceAgentConfig( - sample_rate=audio_source["sample_rate"], - language=args.language, - end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger, - max_delay=args.max_delay, - enable_diarization=True, - end_of_utterance_mode=args.end_of_utterance_mode.lower(), - speaker_config=speaker_config, - enable_preview_features=args.preview, - additional_vocab=[ - AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), - ], - known_speakers=known_speakers, - speech_segment_config=SpeechSegmentConfig(emit_mode=args.emit_mode.lower()), - ) + else: + config = VoiceAgentConfig( + language=args.language or "en", + end_of_utterance_silence_trigger=args.end_of_utterance_silence_trigger or 0.5, + max_delay=args.max_delay or 0.7, + end_of_utterance_mode=( + args.end_of_utterance_mode.lower() if args.end_of_utterance_mode else EndOfUtteranceMode.ADAPTIVE + ), + speaker_config=speaker_config, + enable_preview_features=args.preview, + additional_vocab=[ + AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), + ], + known_speakers=known_speakers, + speech_segment_config=SpeechSegmentConfig( + emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_SPEAKER_ENDED + ), + ) + + # Set common items + config.enable_diarization = True + config.sample_rate = audio_source["sample_rate"] # Create Voice Agent client client = VoiceAgentClient(api_key=args.api_key, url=args.url, config=config) @@ -203,7 +222,7 @@ def setup_microphone_source(args) -> dict | None: selected_device = None mic = Microphone( - sample_rate=args.sample_rate, + sample_rate=args.sample_rate or None, chunk_size=args.chunk_size, device_index=selected_device, ) @@ -469,14 +488,14 @@ async def stream_microphone( # ============================================================================== -def load_speakers(value: str): - """Load speakers from JSON string or file path. +def load_json(value: str): + """Load JSON string or file path. Args: value: Either a JSON string or path to a JSON file Returns: - Parsed speakers list + Parsed json object Raises: argparse.ArgumentTypeError: If the value cannot be parsed @@ -601,25 +620,28 @@ def parse_args(): # Voice Agent configuration # ============================================================================== + parser.add_argument( + "-c", + "--config", + type=load_json, + help="Config JSON string or path to JSON file (default: None)", + ) parser.add_argument( "-l", "--language", type=str, - default="en", help="Language code (default: en)", ) parser.add_argument( "-d", "--max-delay", type=float, - default=0.7, help="Maximum delay for transcription results in seconds (default: 0.7)", ) parser.add_argument( "-t", "--end-of-utterance-silence-trigger", type=float, - default=0.5, help="Silence duration to trigger end of utterance in seconds (default: 0.5)", ) parser.add_argument( @@ -627,7 +649,6 @@ def parse_args(): "--end-of-utterance-mode", type=lambda s: s.upper(), choices=["FIXED", "ADAPTIVE", "EXTERNAL", "SMART_TURN"], - default="ADAPTIVE", help="End of utterance detection mode (default: ADAPTIVE)", ) parser.add_argument( @@ -635,7 +656,6 @@ def parse_args(): "--emit-mode", type=lambda s: s.upper(), choices=["ON_SPEAKER_ENDED", "ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], - default="ON_SPEAKER_ENDED", help="End of segment emit mode (default: ON_SPEAKER_ENDED)", ) @@ -644,16 +664,19 @@ def parse_args(): # ============================================================================== parser.add_argument( + "-f", "--focus-speakers", nargs="*", help="Speakers to focus on (e.g., S1 S2). Use with --ignore-mode to ignore these speakers instead", ) parser.add_argument( + "-I", "--ignore-speakers", nargs="*", help="Specific speakers to ignore (e.g., S1 S2)", ) parser.add_argument( + "-x", "--ignore-mode", action="store_true", help="Use ignore mode instead of focus mode for --focus-speakers", @@ -672,7 +695,7 @@ def parse_args(): parser.add_argument( "-s", "--speakers", - type=load_speakers, + type=load_json, help="Known speakers as JSON string or path to JSON file (default: None)", ) parser.add_argument( @@ -681,7 +704,34 @@ def parse_args(): help="Use preview features (default: False)", ) - return parser.parse_args() + # ============================================================================== + # Check for mutually exclusive options + # ============================================================================== + + args = parser.parse_args() + + mutually_excludive = [ + "emit-mode", + "end-of-utterance-mode", + "end-of-utterance-silence-trigger", + "focus-speakers", + "ignore-mode", + "ignore-speakers", + "language", + "max-delay", + ] + + if args.config is not None: + conflicts: list[str] = [] + for arg in mutually_excludive: + if getattr(args, arg.replace("-", "_")): + conflicts.append(arg) + if conflicts: + print(f"**ERROR** -> You cannot use {[f'--{arg}' for arg in conflicts]} in combination with -c/--config") + exit(1) + + # Return the parsed arguments + return args # ============================================================================== @@ -689,4 +739,7 @@ def parse_args(): # ============================================================================== if __name__ == "__main__": - asyncio.run(main()) + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\nCLI utility stopped by user") From 4b11cfa474007247daa75d0d6309a5a015576d65 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 22 Oct 2025 23:46:43 -0700 Subject: [PATCH 147/215] Dump the config to terminal. --- examples/voice/cli/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index e1dca202..195d00c2 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -127,6 +127,10 @@ async def main() -> None: else: print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") + # Debug the config + if args.verbose >= 1: + print(f"Configuration used:\n{config.model_dump_json()}\n") + # Connect to the Voice Agent service try: await client.connect() From affd28a79dc63545e40a8f6e19e854dc23228afe Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 24 Oct 2025 12:19:01 -0700 Subject: [PATCH 148/215] Show config on screen. --- examples/voice/cli/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 195d00c2..a6e90b94 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -129,7 +129,7 @@ async def main() -> None: # Debug the config if args.verbose >= 1: - print(f"Configuration used:\n{config.model_dump_json()}\n") + print(f"Configuration used:\n{config.model_dump_json(exclude_none=True)}\n") # Connect to the Voice Agent service try: From 90c6202615ac664b0bce290c753a1811b29cbf86 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 16:55:10 +0000 Subject: [PATCH 149/215] CLI doc update --- examples/voice/cli/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index a6e90b94..8f273471 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -683,7 +683,7 @@ def parse_args(): "-x", "--ignore-mode", action="store_true", - help="Use ignore mode instead of focus mode for --focus-speakers", + help="Use IGNORE mode instead of RETAIN mode for non-focus speakers", ) # ============================================================================== From 64f5b9632e925c71cdfb315afad121c1fa1bc167 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 20:11:58 +0000 Subject: [PATCH 150/215] Fix API_KEY references in tests and segment count for speaker ID test. --- tests/voice/test_01_client.py | 18 ++++++++-------- tests/voice/test_02_transcriber.py | 15 ++++++-------- tests/voice/test_03_conversation.py | 5 ++--- tests/voice/test_07_languages.py | 5 ++--- tests/voice/test_08_multiple_speakers.py | 7 ++----- tests/voice/test_09_speaker_id.py | 26 ++++++++++++++---------- 6 files changed, 36 insertions(+), 40 deletions(-) diff --git a/tests/voice/test_01_client.py b/tests/voice/test_01_client.py index 405eca50..84635249 100644 --- a/tests/voice/test_01_client.py +++ b/tests/voice/test_01_client.py @@ -6,6 +6,9 @@ from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig +# Constants +API_KEY = os.getenv("SPEECHMATICS_API_KEY") + @pytest.mark.asyncio async def test_client(): @@ -16,13 +19,12 @@ async def test_client(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Create client client = await get_client( - api_key=api_key, + api_key=API_KEY, connect=False, ) @@ -54,15 +56,14 @@ async def test_client_context_manager(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Create config config = VoiceAgentConfig(language="en") # Use client as context manager - async with VoiceAgentClient(api_key=api_key, config=config) as client: + async with VoiceAgentClient(api_key=API_KEY, config=config) as client: # Check we are connected automatically assert client._is_connected @@ -84,8 +85,7 @@ async def test_client_context_manager_with_exception(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Create config @@ -93,7 +93,7 @@ async def test_client_context_manager_with_exception(): # Use client as context manager and raise an exception with pytest.raises(ValueError, match="Test exception"): - async with VoiceAgentClient(api_key=api_key, config=config) as client: + async with VoiceAgentClient(api_key=API_KEY, config=config) as client: # Check we are connected assert client._is_connected diff --git a/tests/voice/test_02_transcriber.py b/tests/voice/test_02_transcriber.py index ea1f5d42..35ea1700 100644 --- a/tests/voice/test_02_transcriber.py +++ b/tests/voice/test_02_transcriber.py @@ -26,13 +26,12 @@ async def test_transcribe_partial(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, connect=True, config=VoiceAgentConfig( additional_vocab=[ @@ -82,12 +81,11 @@ async def test_transcribe_final(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client - client = await get_client(api_key=api_key, connect=True) + client = await get_client(api_key=API_KEY, connect=True) # Check we are connected assert client._is_connected @@ -130,12 +128,11 @@ async def test_partial_segment(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client - client = await get_client(api_key=api_key, connect=True, config=VoiceAgentConfig()) + client = await get_client(api_key=API_KEY, connect=True, config=VoiceAgentConfig()) # Check we are connected assert client._is_connected diff --git a/tests/voice/test_03_conversation.py b/tests/voice/test_03_conversation.py index 1cd59df8..aa2398bb 100644 --- a/tests/voice/test_03_conversation.py +++ b/tests/voice/test_03_conversation.py @@ -28,13 +28,12 @@ async def test_log_messages(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=0.2, diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index c4a56533..8e31ad04 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -105,13 +105,12 @@ async def test_transcribe_languages(sample: AudioSample): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, connect=False, config=VoiceAgentConfig( max_delay=1.2, diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index c0ee59d7..e6651271 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -93,8 +93,7 @@ async def test_multiple_speakers(sample: SpeakerTest): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Config @@ -118,7 +117,7 @@ async def test_multiple_speakers(sample: SpeakerTest): # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, connect=False, config=config, ) @@ -195,8 +194,6 @@ def log_final_segment(message): print() print() - print(final_segments) - # Check final segments against regex for idx, _test in enumerate(sample.segment_regex): if SHOW_LOG: diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 8e6f5abe..20105e7c 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -42,13 +42,12 @@ async def test_extract_speaker_ids(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, url=URL, connect=False, config=VoiceAgentConfig( @@ -168,8 +167,7 @@ async def test_known_speakers(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Copy known speakers @@ -179,7 +177,7 @@ async def test_known_speakers(): # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, url=URL, connect=False, config=VoiceAgentConfig( @@ -189,6 +187,10 @@ async def test_known_speakers(): enable_diarization=True, sample_rate=8000, known_speakers=known_speakers, + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + additional_vocab=[ + AdditionalVocabEntry(content="GeoRouter"), + ], ), ) @@ -223,8 +225,8 @@ def log_final_segment(message): speakers = [segment.get("speaker_id") for segment in final_segments] assert set(speakers) == set({"Assistant", "John Doe"}) - # Should be 11 segments - assert len(final_segments) == 11 + # Should be 5 segments + assert len(final_segments) == 5 # Close session await client.disconnect() @@ -242,8 +244,7 @@ async def test_ignoring_assistant(): """ # API key - api_key = os.getenv("SPEECHMATICS_API_KEY") - if not api_key: + if not API_KEY: pytest.skip("Valid API key required for test") # Copy known speakers @@ -253,7 +254,7 @@ async def test_ignoring_assistant(): # Client client = await get_client( - api_key=api_key, + api_key=API_KEY, url=URL, connect=False, config=VoiceAgentConfig( @@ -264,6 +265,9 @@ async def test_ignoring_assistant(): sample_rate=8000, known_speakers=known_speakers, speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + additional_vocab=[ + AdditionalVocabEntry(content="GeoRouter"), + ], ), ) From 28470dad43ee2b6d9d3275a71385827cf06005fd Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 20:16:16 +0000 Subject: [PATCH 151/215] Standardise SPEECHMATICS_RT_URL in docs and tests. --- examples/voice/cli/README.md | 2 +- examples/voice/cli/main.py | 2 +- tests/voice/test_09_speaker_id.py | 2 +- tests/voice/test_13_smart_turn_transcribe.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 3186fa1e..cf788247 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -35,7 +35,7 @@ python ./examples/voice/cli/main.py [OPTIONS] Speechmatics API key. Defaults to `SPEECHMATICS_API_KEY` environment variable. **`-u, --url`** -Custom Speechmatics server URL. Also uses `SPEECHMATICS_SERVER_URL` environment variable, if not provided. Optional, defaults to production endpoint. +Custom Speechmatics server URL. Also uses `SPEECHMATICS_RT_URL` environment variable, if not provided. Optional, defaults to production endpoint. **`-i, --input-file FILE`** Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone. diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 8f273471..77796be5 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -546,7 +546,7 @@ def parse_args(): parser.add_argument( "-u", "--url", - default=os.getenv("SPEECHMATICS_SERVER_URL"), + default=os.getenv("SPEECHMATICS_RT_URL"), help="Speechmatics server URL (optional)", ) diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 20105e7c..07f38ec8 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -23,7 +23,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://preview.rt.speechmatics.com/v2") +URL: Optional[str] = os.getenv("SPEECHMATICS_RT_URL", "wss://preview.rt.speechmatics.com/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] # List of know speakers during tests diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 7e205149..caf12c0c 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -24,7 +24,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = os.getenv("SPEECHMATICS_SERVER_URL", "wss://jamesw.lab.speechmatics.io/v2") +URL: Optional[str] = os.getenv("SPEECHMATICS_RT_URL", "wss://jamesw.lab.speechmatics.io/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] From 51e158a098f534a09f6d24fc0e085531ef3da29a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 20:20:14 +0000 Subject: [PATCH 152/215] fix for adding missing trailing EOS --- sdk/voice/speechmatics/voice/_client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index adcfb314..a2368f6e 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -999,8 +999,17 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Ensure final segment ends with EOS if self._config.speech_segment_config.add_trailing_eos: last_segment = final_segments[-1] - if not last_segment.fragments[-1].is_eos: - last_segment.fragments.append(SpeechFragment(content=".", is_eos=True)) + last_fragment = last_segment.fragments[-1] + if not last_fragment.is_eos: + last_segment.fragments.append( + SpeechFragment( + idx=self._next_fragment_id(), + start_time=last_fragment.end_time, + end_time=last_fragment.end_time, + content=".", + is_eos=True, + ) + ) # Emit segments self.emit( From 3d1892bd2cb80f8654a748d6e91c5fda0cd34dc6 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 20:22:54 +0000 Subject: [PATCH 153/215] Default smart turn threshold to 0.5 after discussion with Pipecat --- sdk/voice/speechmatics/voice/_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 286cc854..9c408f7a 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -384,7 +384,7 @@ class SmartTurnConfig(BaseModel): by end of thought models. Defaults to 0.0 seconds. smart_turn_threshold: Smart turn threshold. This is used to determine when a turn has completed. - Only used when `end_of_utterance_mode` is `EndOfUtteranceMode.SMART_TURN`. Defaults to 0.75. + Only used when `end_of_utterance_mode` is `EndOfUtteranceMode.SMART_TURN`. Defaults to 0.5. slice_margin: Margin to add to the audio buffer to ensure that the end of thought models have enough audio to work with. Defaults to 0.05 seconds. @@ -398,7 +398,7 @@ class SmartTurnConfig(BaseModel): """ audio_buffer_length: float = 0.0 - smart_turn_threshold: float = 0.75 + smart_turn_threshold: float = 0.5 slice_margin: float = 0.05 @@ -554,7 +554,7 @@ class VoiceAgentConfig(BaseModel): ... max_speakers=3, ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, ... smart_turn_config=SmartTurnConfig( - ... smart_turn_threshold=0.8 + ... smart_turn_threshold=0.55 ... ), ... additional_vocab=[ ... AdditionalVocabEntry(content="API"), From 2ac5f9e0a62d0512b104ff631424964aefb45115 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 26 Oct 2025 20:53:57 +0000 Subject: [PATCH 154/215] Presets for voice agent configs. (BETA) --- sdk/voice/speechmatics/voice/__init__.py | 2 + sdk/voice/speechmatics/voice/_presets.py | 67 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 sdk/voice/speechmatics/voice/_presets.py diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index fed559de..d1fe36e1 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -30,6 +30,7 @@ from ._models import SpeechSegmentConfig from ._models import SpeechSegmentEmitMode from ._models import VoiceAgentConfig +from ._presets import VoiceAgentConfigPreset __all__ = [ "__version__", @@ -49,4 +50,5 @@ "SpeechSegmentEmitMode", "VoiceAgentClient", "VoiceAgentConfig", + "VoiceAgentConfigPreset", ] diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py new file mode 100644 index 00000000..8eb2d27f --- /dev/null +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -0,0 +1,67 @@ +# +# Copyright (c) 2025, Speechmatics / Cantab Research Ltd +# + +from __future__ import annotations + +from ._models import EndOfUtteranceMode +from ._models import SpeechSegmentConfig +from ._models import SpeechSegmentEmitMode +from ._models import VoiceAgentConfig + + +class VoiceAgentConfigPreset: + """Set of preset configurations for the Voice Agent SDK.""" + + @staticmethod + def LOW_LATENCY() -> VoiceAgentConfig: # noqa: N802 + """Best suited for low latency situations. + + This configuration will emit the end of turn as soon as possible, with minimal + delay to finalizing the spoken sentences. It is not recommended for + conversation, as it will not account for pauses, slow speech or disfluencies. + """ + return VoiceAgentConfig( + enable_diarization=True, + max_delay=0.7, + end_of_utterance_silence_trigger=0.2, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + ) + + @staticmethod + def CONVERSATION_ADAPTIVE() -> VoiceAgentConfig: # noqa: N802 + """Best suited for general conversational use cases. + + For conversation, there is a balance between accuracy, speed and the rate at + which the end of turn is emitted. Tne use of ADAPTIVE means that the delay to + finalizing the spoken sentences will be adjusted based on the words and whether + there are any pauses, slow speech or disfluencies. + """ + return VoiceAgentConfig( + enable_diarization=True, + max_delay=0.7, + end_of_utterance_silence_trigger=0.4, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + ) + + @staticmethod + def CONVERSATION_SMART_TURN() -> VoiceAgentConfig: # noqa: N802 + """Best suited for complex conversational use cases. + + For conversation, there is a balance between accuracy, speed and the rate at + which the end of turn is emitted. Tne use of SMART_TURN means that the delay to + finalizing the spoken sentences will be adjusted based on the words and whether + there are any pauses, slow speech or disfluencies. + + This preset will use a model to detect for acoustic indicators from the + speaker to determine when a turn has ended. + + Use of this will requite `pip install speechmatics-voice[smart]`. + """ + return VoiceAgentConfig( + enable_diarization=True, + max_delay=0.85, + end_of_utterance_silence_trigger=0.5, + end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + ) From b95c46cef653d6b5686d111291609ebfb622854c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 11:10:04 +0000 Subject: [PATCH 155/215] updated presets --- sdk/voice/speechmatics/voice/_client.py | 7 ++++++- sdk/voice/speechmatics/voice/_models.py | 4 +++- sdk/voice/speechmatics/voice/_presets.py | 3 ++- sdk/voice/speechmatics/voice/_utils.py | 9 +++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a2368f6e..50868ef2 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -139,7 +139,12 @@ def __init__( ) # Change filter to emit segments - self._change_filter: list[AnnotationFlags] = [AnnotationFlags.NEW] + self._change_filter: list[AnnotationFlags] = [ + AnnotationFlags.NEW, + AnnotationFlags.UPDATED_PARTIALS, + AnnotationFlags.UPDATED_FINALS, + ] + # Full text has changed if self._config.transcription_update_preset == TranscriptionUpdatePreset.COMPLETE: self._change_filter.append(AnnotationFlags.UPDATED_FULL) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 9c408f7a..65172de8 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -239,10 +239,12 @@ class AnnotationFlags(str, Enum): UPDATED_STRIPPED_LCASE = "updated_stripped_lcase" UPDATED_FINALS = "updated_finals" UPDATED_PARTIALS = "updated_partials" - UPDATED_SPEAKERS = "updated_speakers" UPDATED_WORD_TIMINGS = "updated_word_timings" FINALIZED = "finalized" + # Annotations changed + UPDATED_ANNOTATIONS = "updated_annotations" + # Content of segments ONLY_ACTIVE_SPEAKERS = "only_active_speakers" CONTAINS_INACTIVE_SPEAKERS = "contains_inactive_speakers" diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 8eb2d27f..0f147c2c 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -57,7 +57,8 @@ def CONVERSATION_SMART_TURN() -> VoiceAgentConfig: # noqa: N802 This preset will use a model to detect for acoustic indicators from the speaker to determine when a turn has ended. - Use of this will requite `pip install speechmatics-voice[smart]`. + Use of this will requite `pip install speechmatics-voice[smart]` and may not + be suited to low-power devices. """ return VoiceAgentConfig( enable_diarization=True, diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index aee9317e..761d2e11 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -66,6 +66,7 @@ def format_segment_text( "lang": segment.language, "start_time": fragments[0].start_time if fragments else 0, "end_time": fragments[-1].end_time if fragments else 0, + "annotation": segment.annotation or [], } ) @@ -323,13 +324,17 @@ def compare_views( if view1_timings_str != view2_timings_str: result.add(AnnotationFlags.UPDATED_WORD_TIMINGS) + # Annotations + view1_annotation_str: str = view1.format_view_text(format="|{annotation}|") + view2_annotation_str: str = view2.format_view_text(format="|{annotation}|") + if set(view1_annotation_str) != set(view2_annotation_str): + result.add(AnnotationFlags.UPDATED_ANNOTATIONS) + # Partials, finals and speakers if view1.final_count != view2.final_count: result.add(AnnotationFlags.UPDATED_FINALS) if view1.partial_count != view2.partial_count: result.add(AnnotationFlags.UPDATED_PARTIALS) - if view1.segment_count != view2.segment_count: - result.add(AnnotationFlags.UPDATED_SPEAKERS) # Assume this is new elif view1.segment_count > 0: From 6302b86823db628de593de160ca7d2a6f1df8f09 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 11:15:35 +0000 Subject: [PATCH 156/215] Updated preset based on Aaron's work. --- sdk/voice/speechmatics/voice/_presets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 0f147c2c..3a64bb89 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -41,7 +41,7 @@ def CONVERSATION_ADAPTIVE() -> VoiceAgentConfig: # noqa: N802 return VoiceAgentConfig( enable_diarization=True, max_delay=0.7, - end_of_utterance_silence_trigger=0.4, + end_of_utterance_silence_trigger=0.5, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, ) From 1d13622808e2b6839fbdd53dffe28c5f8d4cb135 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 18:09:54 +0000 Subject: [PATCH 157/215] Updated end of utterance and end of turn timers --- sdk/voice/speechmatics/voice/_client.py | 118 +++++++++++++++--------- sdk/voice/speechmatics/voice/_turn.py | 51 +++++----- 2 files changed, 101 insertions(+), 68 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 50868ef2..f7e9e06f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -184,6 +184,9 @@ def __init__( self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() self._current_view: Optional[SpeakerSegmentView] = None self._previous_view: Optional[SpeakerSegmentView] = None + + # Current turn + self._turn_id: int = 0 self._turn_start_time: Optional[float] = None # Speaking states @@ -192,10 +195,17 @@ def __init__( self._current_speaker: Optional[str] = None self._last_fragment_end_time: float = 0 - # Turn detection + # End of utterance handler + self._end_of_utterance_handler: TurnTaskProcessor = TurnTaskProcessor( + name="eou_handler", done_callback=self.finalize + ) + + # End of turn handler self._end_of_turn_handler: TurnTaskProcessor = TurnTaskProcessor( - name="eot_handler", done_callback=self.finalize + name="eot_handler", done_callback=self.end_of_turn ) + + # Turn detector self._turn_detector: Optional[SmartTurnDetector] = None # Start turn detector if SMART_TURN requested @@ -217,8 +227,6 @@ def __init__( self._config.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE # Diarization / speaker focus - self._end_of_utterance_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode - self._end_of_utterance_delay: float = self._config.end_of_utterance_silence_trigger self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config @@ -442,6 +450,7 @@ async def disconnect(self) -> None: self._is_connected = False # Stop end of turn-related tasks + self._end_of_utterance_handler.cancel_tasks() self._end_of_turn_handler.cancel_tasks() # Stop the STT queue task @@ -537,7 +546,7 @@ def update_diarization_config(self, config: SpeakerFocusConfig) -> None: """ self._dz_config = config - def finalize(self, ttl: Optional[float] = None) -> None: + def finalize(self, ttl: Optional[float] = None, end_of_turn: bool = False) -> None: """Finalize segments. This function will emit segments in the buffer without any further checks @@ -546,6 +555,7 @@ def finalize(self, ttl: Optional[float] = None) -> None: Args: ttl: Optional delay before finalizing partial segments. + end_of_turn: Whether to emit an end of turn message. Examples: Immediate finalization: @@ -559,16 +569,56 @@ def finalize(self, ttl: Optional[float] = None) -> None: # Emit the finalize or use EndOfTurn on demand preview async def emit() -> None: + # Yield for the delay if ttl is not None and ttl > 0: await asyncio.sleep(ttl) + + # Emit segments or finalize STT message if self._config.enable_preview_features: + """Uses the forced end of utterance message to emit segments and finalize.""" + + # Listen for the `END_OF_UTTERANCE` event + @self.once(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] + def _on_eou(message: dict[str, Any]) -> None: + self._stt_message_queue.put_nowait( + lambda: self._emit_segments(finalize=True, end_of_turn=end_of_turn) + ) + + # Emit the message await self.send_message({"message": AgentClientMessageType.FINALIZE_TURN}) + else: - await self._emit_segments(finalize=True, end_of_turn=True) + """Standard emit segments and finalize.""" + await self._emit_segments(finalize=True, end_of_turn=end_of_turn) + + # Cancel any pending tasks for end of utterance (as end of turn will emit segments) + self._end_of_utterance_handler.complete_handler() # Add to queue self._stt_message_queue.put_nowait(emit) + def end_of_turn(self, ttl: Optional[float] = None) -> None: + """Trigger end of turn. + + This function will force an end of turn and finalize segments, if required. Use + as a helper function for finalize() with end_of_turn=True. + + Args: + ttl: Optional delay before finalizing partial segments. + + Examples: + Immediate end of turn: + >>> # Force end of turn of current segments + >>> client.end_of_turn(ttl=0) + + Delayed end of turn: + >>> # Wait 0.5 seconds before end of turn + >>> client.end_of_turn(ttl=0.5) + """ + + # Emit the finalize or use EndOfTurn on demand preview + self.finalize(ttl=ttl, end_of_turn=True) + # ============================================================================ # EVENT REGISTRATION & HANDLERS # ============================================================================ @@ -598,33 +648,14 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: def _evt_on_partial_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._handle_transcript(message, is_final=False) - - self._stt_message_queue.put_nowait(_handle) + self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=False)) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] def _evt_on_final_transcript(message: dict[str, Any]) -> None: if DEBUG_MORE: self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._handle_transcript(message, is_final=True) - - self._stt_message_queue.put_nowait(_handle) - - # End of utterance event - @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] - def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) - - async def _handle() -> None: - await self._emit_segments(finalize=True, end_of_turn=True) - - self._stt_message_queue.put_nowait(_handle) + self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=True)) # ============================================================================ # QUEUE PROCESSING @@ -1104,7 +1135,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False AgentServerMessageType.END_OF_TURN, { "message": AgentServerMessageType.END_OF_TURN.value, - "turn_id": self._end_of_turn_handler.turn_id, + "turn_id": self._turn_id, "metadata": metadata, }, ) @@ -1207,7 +1238,7 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: multiplier = 1.0 + sum(m for m, _ in reasons) # Minimum delay (50ms as a minimum) - delay = round(max(self._end_of_utterance_delay, 0.05) * multiplier, 2) + delay = round(max(self._config.end_of_utterance_silence_trigger, 0.05) * multiplier, 2) # Clamp to max delay clamped_delay = min(delay, self._config.end_of_utterance_max_delay) @@ -1224,7 +1255,7 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: AgentServerMessageType.END_OF_TURN_PREDICTION, { "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, - "turn_id": self._end_of_turn_handler.turn_id, + "turn_id": self._turn_id, "metadata": { "ttl": round(finalize_delay, 2), "time_slip": round(time_slip, 2), @@ -1359,22 +1390,19 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Event time event_time = speaker_start_time if self._is_speaking else speaker_end_time - # Emit start of turn + # Emit start of turn (not when using EXTERNAL) if ( - not self._end_of_turn_handler.turn_active + not self._end_of_turn_handler.handler_active and self._is_speaking - and self._end_of_utterance_mode - in [ - EndOfUtteranceMode.ADAPTIVE, - EndOfUtteranceMode.SMART_TURN, - ] + and self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL ): - self._end_of_turn_handler.start_turn() + self._end_of_turn_handler.start_handler() + self._turn_id = self._end_of_turn_handler.handler_id self.emit( AgentServerMessageType.START_OF_TURN, { "message": AgentServerMessageType.START_OF_TURN.value, - "turn_id": self._end_of_turn_handler.turn_id, + "turn_id": self._turn_id, "metadata": { "start_time": event_time, }, @@ -1405,15 +1433,18 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Reset current speaker self._current_speaker = None + # Add task for end of utterance + self._end_of_utterance_handler.update_timer(self._config.end_of_utterance_silence_trigger) + # For ADAPTIVE and SMART_TURN only - if self._end_of_utterance_mode in [EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN]: - """When ADAPTIVE or SMART_TURN, we need to do EOT detection / prediction.""" + if self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL: + """When not EXTERNAL, we need to do EOT detection / prediction.""" # Callback async def do_eot_detection(end_time: float, language: str) -> None: try: # Wait for Smart Turn result - if self._end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: + if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: result = await self._predict_smart_turn(end_time, language) else: result = None @@ -1430,7 +1461,7 @@ async def do_eot_detection(end_time: float, language: str) -> None: # Add task self._end_of_turn_handler.add_task( asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), - self._end_of_utterance_mode.value, + self._config.end_of_utterance_mode.value, ) # Speaking has started @@ -1438,6 +1469,7 @@ async def do_eot_detection(end_time: float, language: str) -> None: """When speaking has started, reset speaking-related variables.""" # Reset the handlers + self._end_of_utterance_handler.reset() self._end_of_turn_handler.reset() # ============================================================================ diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index cc9b4900..3f8b9ca1 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -17,21 +17,21 @@ class TurnTaskProcessor: make a call to the `done_callback` function (sync or async). """ - def __init__(self, name: str, turn_id: int = 0, done_callback: Optional[Callable] = None): + def __init__(self, name: str, handler_id: int = 0, done_callback: Optional[Callable] = None): """Create new handler. Args: name: The name of the processor. - turn_id: The turn id (used to validate tasks). + handler_id: The base handler id (used to validate tasks). done_callback: The callback to call when all tasks are completed. """ # Processor name self._name = name - # Turn id (used to validate tasks) - self._turn_id = turn_id - self._turn_active = False + # Handler id (used to validate tasks) + self._handler_id = handler_id + self._handler_active = False # Tasks + events self._tasks: dict[str, asyncio.Task] = {} @@ -50,22 +50,22 @@ def has_pending_tasks(self) -> bool: return any(not task.done() for task in self._tasks.values()) @property - def turn_id(self) -> int: - """Get the turn id. + def handler_id(self) -> int: + """Get the handler id. Returns: - The current turn ID. + The current handler ID. """ - return self._turn_id + return self._handler_id @property - def turn_active(self) -> bool: - """Get the turn active state. + def handler_active(self) -> bool: + """Get the handler active state. Returns: - The current turn active state. + The current handler active state. """ - return self._turn_active + return self._handler_active def update_timer(self, delay: float) -> None: """Set a new done trigger. @@ -73,6 +73,7 @@ def update_timer(self, delay: float) -> None: Args: delay: Delay in seconds before triggering done callback. """ + if delay < 0: return self.add_task( @@ -98,9 +99,9 @@ def add_task(self, task: asyncio.Task, task_name: str) -> None: # Wait for the task async def wait_for_task(task: asyncio.Task) -> None: try: - _turn_id = self._turn_id + _handler_id = self._handler_id await task - if _turn_id != self._turn_id: + if _handler_id != self._handler_id: return if not self.has_pending_tasks: asyncio.create_task(self._do_done_callback()) @@ -113,9 +114,6 @@ async def wait_for_task(task: asyncio.Task) -> None: async def _do_done_callback(self) -> None: """Do the done callback.""" - # Cancel any pending tasks - self.complete_turn() - # Do the callback if self._done_callback: try: @@ -126,6 +124,9 @@ async def _do_done_callback(self) -> None: except Exception: pass + # Complete the task + self.complete_handler() + def cancel_tasks(self) -> None: """Cancel any pending tasks.""" for task in self._tasks.values(): @@ -137,19 +138,19 @@ def reset(self) -> None: """Reset the end of turn.""" self.cancel_tasks() - def start_turn(self) -> None: + def start_handler(self) -> None: """Start the end of turn.""" - self._turn_active = True + self._handler_active = True - def complete_turn(self) -> None: + def complete_handler(self) -> None: """Complete the end of turn.""" self.next() - self._turn_active = False + self._handler_active = False def next(self) -> None: - """Increment the end of turn.""" + """Increment the handler. id""" self.reset() - self._turn_id += 1 + self._handler_id += 1 def __str__(self) -> str: """Get the string representation of the end of turn. @@ -157,4 +158,4 @@ def __str__(self) -> str: Returns: String representation of the processor state. """ - return f"TurnTaskProcessor(name={self._name}, turn_id={self._turn_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" + return f"TurnTaskProcessor(name={self._name}, turn_id={self._handler_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" From 4672bb86930aaf090176e4a3cf1cdc156875c900 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 18:10:07 +0000 Subject: [PATCH 158/215] updated tests --- tests/voice/test_05_utterance.py | 7 +++++++ tests/voice/test_10_finalize.py | 6 +++--- tests/voice/test_13_smart_turn_transcribe.py | 12 ++++++------ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index a45bbe20..bc27dadc 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -148,6 +148,13 @@ async def test_end_of_utterance_fixed(): client = await get_client(api_key="NONE", connect=False) assert client is not None + # Debug + # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) + # client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) + # client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) + # client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) + # client.on(AgentServerMessageType.END_OF_UTTERANCE, lambda message: print(message)) + # Start the queue client._start_stt_queue() diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 2433c97e..26885d66 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -83,6 +83,7 @@ def eot_received_callback(message): client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) + client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) # End of Turn @@ -111,7 +112,7 @@ def eot_received_callback(message): # Send words twice with silence in-between await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) - await send_silence(client, 1.0, chunk_size=chunk_size, progress_callback=log_bytes_sent) + await send_silence(client, 0.5, chunk_size=chunk_size, progress_callback=log_bytes_sent) await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) # Send silence in a thread @@ -126,7 +127,7 @@ def eot_received_callback(message): # Request the speakers result finalize_trigger_time = datetime.datetime.now() - client.finalize() + client.end_of_turn() # Wait for the callback with timeout try: @@ -145,7 +146,6 @@ def eot_received_callback(message): print(f"{finalize_latency:.2f}ms") # Make sure latency is within bounds - assert finalize_latency > 50 assert finalize_latency < 500 # Close session diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index caf12c0c..966c5c2e 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -42,7 +42,7 @@ class TranscriptionTest(BaseModel): SAMPLES: list[TranscriptionTest] = [ - TranscriptionTest(id="01", path="./assets/audio_04_16kHz.wav", sample_rate=16000, language="en", eot_count=1), + TranscriptionTest(id="01", path="./assets/audio_04_16kHz.wav", sample_rate=16000, language="en", eot_count=2), TranscriptionTest(id="02", path="./assets/audio_05_16kHz.wav", sample_rate=16000, language="en", eot_count=1), TranscriptionTest(id="03", path="./assets/audio_06_16kHz.wav", sample_rate=16000, language="en", eot_count=1), ] @@ -121,13 +121,13 @@ def log_message(message): print(log) # Add listeners - client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) - client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) + # client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) + # client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) + # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) - client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) + # client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) - client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) + # client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) From 671b70088e25a76f89bfc3441cc78300cf50e370 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 18:10:14 +0000 Subject: [PATCH 159/215] updated CLI example --- examples/voice/cli/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index 77796be5..ebd113a7 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -28,6 +28,7 @@ from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import TranscriptionUpdatePreset # ============================================================================== # CONSTANTS @@ -108,6 +109,7 @@ async def main() -> None: speech_segment_config=SpeechSegmentConfig( emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_SPEAKER_ENDED ), + transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, ) # Set common items From 5313305966fbe8c3ff02972dd214fb853df4c6ab Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 18:20:09 +0000 Subject: [PATCH 160/215] close connection on transport error --- sdk/voice/speechmatics/voice/_client.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index f7e9e06f..07c56926 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -23,6 +23,7 @@ from speechmatics.rt import SpeakerDiarizationConfig from speechmatics.rt import SpeakerIdentifier from speechmatics.rt import TranscriptionConfig +from speechmatics.rt._exceptions import TransportError from . import __version__ from ._audio import AudioBuffer @@ -499,7 +500,16 @@ async def send_audio(self, payload: bytes) -> None: return # Send to the AsyncClient - await super().send_audio(payload) + try: + await super().send_audio(payload) + except TransportError as e: + self._logger.warning(f"Error sending audio: {e}") + self.emit( + AgentServerMessageType.ERROR, + {"message": AgentServerMessageType.ERROR.value, "reason": "Transport error - connection being closed"}, + ) + await self.disconnect() + return # Add to audio buffer (use put_bytes to handle variable chunk sizes) if self._config.smart_turn_config.audio_buffer_length > 0: From 1850287bd58da1f00955a350e276a33daaa1bbb8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 20:55:00 +0000 Subject: [PATCH 161/215] fix typo on test --- tests/voice/test_12_smart_turn_with_files.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/voice/test_12_smart_turn_with_files.py b/tests/voice/test_12_smart_turn_with_files.py index 18b2e33e..e749ce20 100644 --- a/tests/voice/test_12_smart_turn_with_files.py +++ b/tests/voice/test_12_smart_turn_with_files.py @@ -24,7 +24,7 @@ class PredictionTest(BaseModel): SAMPLES: list[PredictionTest] = [ PredictionTest( id="01", - path="./assets/smart_turn/01_false_16KHz.wav", + path="./assets/smart_turn/01_false_16kHz.wav", language="en", expected=SmartTurnPredictionResult( prediction=False, @@ -33,7 +33,7 @@ class PredictionTest(BaseModel): ), PredictionTest( id="02", - path="./assets/smart_turn/02_false_16KHz.wav", + path="./assets/smart_turn/02_false_16kHz.wav", language="en", expected=SmartTurnPredictionResult( prediction=False, @@ -42,7 +42,7 @@ class PredictionTest(BaseModel): ), PredictionTest( id="03", - path="./assets/smart_turn/03_true_16KHz.wav", + path="./assets/smart_turn/03_true_16kHz.wav", language="en", expected=SmartTurnPredictionResult( prediction=True, From 579c43b0489059cc84d53f397be78558e7f9be70 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 27 Oct 2025 21:30:21 +0000 Subject: [PATCH 162/215] Updated test README for Linux. --- tests/voice/README.md | 15 +++++++++++++++ tests/voice/test_12_smart_turn_with_files.py | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/voice/README.md b/tests/voice/README.md index 55bc7040..bd5c4524 100644 --- a/tests/voice/README.md +++ b/tests/voice/README.md @@ -2,6 +2,21 @@ You will need a `SPEECHMATICS_API_KEY` to run most of the tests, as they will use live transcription. +You need to have git LFS support installed to run audio file tests. + +```bash +# Windows (select Git LFS when installing Git) + +# Linux +sudo apt install git-lfs + +# macOS +brew install git-lfs + +# Download / update FLS files +git lfs pull +``` + To run tests: ```bash diff --git a/tests/voice/test_12_smart_turn_with_files.py b/tests/voice/test_12_smart_turn_with_files.py index e749ce20..553b6ab2 100644 --- a/tests/voice/test_12_smart_turn_with_files.py +++ b/tests/voice/test_12_smart_turn_with_files.py @@ -74,8 +74,8 @@ async def test_prediction(sample: PredictionTest): # Run an inference result = await detector.predict(bytes_array, language=sample.language, sample_rate=16000, sample_width=2) - # Processing time < 50ms - assert result.processing_time < 0.05 + # Processing time < 100ms + assert result.processing_time < 0.1 # Check result assert result.prediction == sample.expected.prediction From 67012e60b36196f62e478ef2e9702162e28d964f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 09:26:37 +0000 Subject: [PATCH 163/215] Remove un-required dependency from examples. --- examples/voice/requirements-examples.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/voice/requirements-examples.txt b/examples/voice/requirements-examples.txt index d6cb4239..d21a9967 100644 --- a/examples/voice/requirements-examples.txt +++ b/examples/voice/requirements-examples.txt @@ -1,3 +1,2 @@ -colorama pyaudio speechmatics-voice[smart] From 1e83944a8e6db24366619e5413e611dc74e061cc Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 09:53:25 +0000 Subject: [PATCH 164/215] Updated for Windows and Ubuntu with instructions. --- examples/voice/README.md | 27 ++++++++++++++------- examples/voice/cli/README.md | 4 +++ examples/voice/cli/utils.py | 9 +++++-- sdk/voice/pyproject.toml | 1 + sdk/voice/speechmatics/voice/_smart_turn.py | 4 +++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/examples/voice/README.md b/examples/voice/README.md index 44c3d07c..91b6b9be 100644 --- a/examples/voice/README.md +++ b/examples/voice/README.md @@ -12,28 +12,37 @@ cd # Create a virtual environment python -m venv .venv + +# Activate : macOS / Ubuntu / Debian source .venv/bin/activate -# Install requirements for the examples -pip install -r ./examples/voice/requirements-examples.txt +# Activate : Windows +.venv\Scripts\activate + +# Update pip +python -m pip install --upgrade pip ``` -## Troubleshooting +**PyAudio installation:** -**PyAudio installation issues:** +For macOS and Linux you need to install the `portaudio` package. ```shell # macOS brew install portaudio -pip install pyaudio # Ubuntu/Debian sudo apt-get install portaudio19-dev -pip install pyaudio ``` -**API key issues:** +**Package dependencies:** + +Install dependencies from the project root. + +```shell +# Voice SDK +python -m pip install -e 'sdk/voice[dev,smart]' -```bash -export SPEECHMATICS_API_KEY="your_api_key_here" +# Required packages +python -m pip install pyaudio certifi ``` diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index cf788247..d9a68700 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -7,7 +7,11 @@ Command-line tool for real-time transcription using the Speechmatics Voice SDK. **Microphone:** ```bash +# macOS / Ubuntu / Debian python ./examples/voice/cli/main.py -p -k YOUR_API_KEY + +# Windows +python .\examples\voice\cli\main.py -p -k YOUR_API_KEY ``` **Audio file:** diff --git a/examples/voice/cli/utils.py b/examples/voice/cli/utils.py index 7c0063fa..a22a61d8 100644 --- a/examples/voice/cli/utils.py +++ b/examples/voice/cli/utils.py @@ -10,14 +10,19 @@ import asyncio import logging import sys -import termios -import tty import pyaudio from speechmatics.rt import Microphone from speechmatics.voice import VoiceAgentClient +if sys.platform == "win32": + pass +else: + import termios + import tty + + # ============================================================================== # ASYNC UTILITIES # ============================================================================== diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index c2e2e5f2..fc093666 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -40,6 +40,7 @@ keywords = [ [project.optional-dependencies] smart = [ + "certifi>=2025.10.5", "onnxruntime>=1.19.0,<2", "transformers>=4.57.0,<5", ] diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index a9cdae92..784af4aa 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -7,14 +7,18 @@ import datetime import logging import os +import ssl import urllib.request from typing import Any from typing import Optional from urllib.parse import urlparse +import certifi import numpy as np from pydantic import BaseModel +ssl._create_default_https_context = lambda: ssl.create_default_context(cafile=certifi.where()) + ort: Any WhisperFeatureExtractor: Any logger = logging.getLogger(__name__) From 7551a420719c665017da152b37c48071b0db11d8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 10:06:52 +0000 Subject: [PATCH 165/215] Updated example README. --- examples/voice/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/voice/README.md b/examples/voice/README.md index 91b6b9be..3c9c43e9 100644 --- a/examples/voice/README.md +++ b/examples/voice/README.md @@ -43,6 +43,6 @@ Install dependencies from the project root. # Voice SDK python -m pip install -e 'sdk/voice[dev,smart]' -# Required packages -python -m pip install pyaudio certifi +# Required audio package for examples +python -m pip install pyaudio ``` From 69d338a819fc10c48d091330a62148f62afde05a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 11:21:10 +0000 Subject: [PATCH 166/215] Update verbosity in CLI example. --- examples/voice/cli/README.md | 2 +- examples/voice/cli/main.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index d9a68700..98375716 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -51,7 +51,7 @@ Enable formatted console output with colours and emojis. Save all output to a JSONL file for later analysis. **`-v, --verbose`** -Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. +Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` shows the `annotation` field in segments. `-vvvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. ### Audio Configuration diff --git a/examples/voice/cli/main.py b/examples/voice/cli/main.py index ebd113a7..4d8716d5 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/main.py @@ -343,8 +343,11 @@ def console_print(message) -> None: if msg_type in ("AddPartialSegment", "AddSegment"): _segs = [] for segment in payload["segments"]: - prefix = "" if segment["is_active"] else " (background)" - _segs.append(f"@{segment['speaker_id']}{prefix}: `{segment['text']}` {segment['annotation']}") + suffix = "" if segment["is_active"] else " (background)" + if args.verbose >= 3: + _segs.append(f"@{segment['speaker_id']}{suffix}: `{segment['text']}` {segment['annotation']}") + else: + _segs.append(f"@{segment['speaker_id']}{suffix}: `{segment['text']}`") payload = {"segments": _segs} # Print to console @@ -384,7 +387,7 @@ def log_message(message) -> None: client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) # Verbose STT events - if args.verbose >= 3: + if args.verbose >= 4: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) From 550a2552e96189ba503484e9f1619991ef32a1e9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 18:41:18 +0000 Subject: [PATCH 167/215] Updated models for output messages. --- sdk/voice/speechmatics/voice/__init__.py | 22 +- sdk/voice/speechmatics/voice/_client.py | 290 ++++++++++++-------- sdk/voice/speechmatics/voice/_models.py | 189 ++++++++++++- sdk/voice/speechmatics/voice/_smart_turn.py | 13 +- 4 files changed, 392 insertions(+), 122 deletions(-) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index d1fe36e1..9a623738 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -24,19 +24,26 @@ from ._models import AgentClientMessageType from ._models import AgentServerMessageType from ._models import EndOfUtteranceMode +from ._models import SegmentMessage from ._models import SmartTurnConfig from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode +from ._models import SpeakerMetricsMessage from ._models import SpeechSegmentConfig from ._models import SpeechSegmentEmitMode +from ._models import TTFBMetricsMessage +from ._models import TurnPredictionMessage +from ._models import TurnStartEndMessage +from ._models import VADStatusMessage from ._models import VoiceAgentConfig from ._presets import VoiceAgentConfigPreset __all__ = [ "__version__", + # Client + "VoiceAgentClient", + # Config "AdditionalVocabEntry", - "AgentClientMessageType", - "AgentServerMessageType", "AudioEncoding", "AudioFormat", "EndOfUtteranceMode", @@ -48,7 +55,16 @@ "SmartTurnConfig", "SpeechSegmentConfig", "SpeechSegmentEmitMode", - "VoiceAgentClient", "VoiceAgentConfig", "VoiceAgentConfigPreset", + # Client messages + "AgentClientMessageType", + # Server messages + "AgentServerMessageType", + "SegmentMessage", + "SpeakerMetricsMessage", + "TTFBMetricsMessage", + "TurnPredictionMessage", + "TurnStartEndMessage", + "VADStatusMessage", ] diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 07c56926..871a9529 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -32,17 +32,29 @@ from ._models import AgentServerMessageType from ._models import AnnotationFlags from ._models import AnnotationResult +from ._models import BaseMessage from ._models import ClientSessionInfo from ._models import EndOfUtteranceMode +from ._models import ErrorMessage from ._models import LanguagePackInfo +from ._models import MessageTimeMetadata +from ._models import MetricsMessage +from ._models import SegmentMessage +from ._models import SegmentMessageSegment +from ._models import SegmentMessageSegmentFragment from ._models import SessionSpeaker from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode +from ._models import SpeakerMetricsMessage from ._models import SpeakerSegmentView -from ._models import SpeakerVADStatus from ._models import SpeechFragment from ._models import SpeechSegmentEmitMode from ._models import TranscriptionUpdatePreset +from ._models import TTFBMetricsMessage +from ._models import TurnPredictionMessage +from ._models import TurnPredictionMetadata +from ._models import TurnStartEndMessage +from ._models import VADStatusMessage from ._models import VoiceAgentConfig from ._smart_turn import SMART_TURN_INSTALL_HINT from ._smart_turn import SmartTurnDetector @@ -368,9 +380,10 @@ async def connect(self) -> None: # Check if we are already connected if self._is_connected: - self.emit( - AgentServerMessageType.ERROR, - {"message": AgentServerMessageType.ERROR.value, "reason": "Already connected"}, + self._emit_message( + ErrorMessage( + reason="Already connected", + ) ) return @@ -504,9 +517,10 @@ async def send_audio(self, payload: bytes) -> None: await super().send_audio(payload) except TransportError as e: self._logger.warning(f"Error sending audio: {e}") - self.emit( - AgentServerMessageType.ERROR, - {"message": AgentServerMessageType.ERROR.value, "reason": "Transport error - connection being closed"}, + self._emit_message( + ErrorMessage( + reason="Transport error - connection being closed", + ) ) await self.disconnect() return @@ -667,6 +681,19 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: self._logger.debug(json.dumps(message)) self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=True)) + def _emit_message(self, message: BaseMessage) -> None: + """Emit a message to the client. + + This takes a BaseMessage class and emits it as a dictionary to the + client. + + Args: + message: The BaseMessage class message to emit. + """ + + # Forward to the emit() method + self.emit(message.message, message.model_dump()) + # ============================================================================ # QUEUE PROCESSING # ============================================================================ @@ -725,15 +752,13 @@ async def emit_metrics() -> None: time_s = round(self._total_time, 3) # Emit metrics - self.emit( - AgentServerMessageType.METRICS, - { - "message": AgentServerMessageType.METRICS.value, - "total_time": time_s, - "total_time_str": time.strftime("%H:%M:%S", time.gmtime(time_s)), - "total_bytes": self._total_bytes, - "last_ttfb": self._last_ttfb, - }, + self._emit_message( + MetricsMessage( + total_time=time_s, + total_time_str=time.strftime("%H:%M:%S", time.gmtime(time_s)), + total_bytes=self._total_bytes, + last_ttfb=int(self._last_ttfb), + ) ) # Trigger the task @@ -778,12 +803,8 @@ def _calculate_ttfb(self, end_time: float) -> None: self._last_ttfb_time = end_time # Emit the TTFB - self.emit( - AgentServerMessageType.TTFB_METRICS, - { - "message": AgentServerMessageType.TTFB_METRICS.value, - "ttfb": self._last_ttfb, - }, + self._emit_message( + TTFBMetricsMessage(ttfb=int(self._last_ttfb)), ) # ============================================================================ @@ -1013,7 +1034,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Force finalize if finalize: final_segments = self._current_view.segments - interim_segments = [] + partial_segments = [] # Split between finals and interim segments (`ON_FINALIZED_SENTENCE` or `ON_SPEAKER_ENDED`) elif self._config.speech_segment_config.emit_mode in [ @@ -1025,22 +1046,22 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False for s in self._current_view.segments if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) ] - interim_segments = [s for s in self._current_view.segments if s not in final_segments] + partial_segments = [s for s in self._current_view.segments if s not in final_segments] # Keep until end of turn (`ON_END_OF_TURN`) else: final_segments = [] - interim_segments = self._current_view.segments + partial_segments = self._current_view.segments # Emit finals first if final_segments: """Final segments are checked for end of sentence.""" # Metadata for final segments uses actual start/end times of the segments being emitted - final_metadata = { - "start_time": final_segments[0].start_time, - "end_time": final_segments[-1].end_time, - } + final_metadata = MessageTimeMetadata( + start_time=final_segments[0].start_time, + end_time=final_segments[-1].end_time, + ) # Ensure final segment ends with EOS if self._config.speech_segment_config.add_trailing_eos: @@ -1058,13 +1079,38 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ) # Emit segments - self.emit( - AgentServerMessageType.ADD_SEGMENT, - { - "message": AgentServerMessageType.ADD_SEGMENT.value, - "segments": [s.model_dump(self._config.include_results) for s in final_segments], - "metadata": final_metadata, - }, + self._emit_message( + SegmentMessage( + message=AgentServerMessageType.ADD_SEGMENT, + segments=[ + SegmentMessageSegment( + speaker_id=s.speaker_id, + is_active=s.is_active, + timestamp=s.timestamp, + language=s.language, + text=s.text, + annotation=s.annotation, + fragments=( + [ + SegmentMessageSegmentFragment( + start_time=f.start_time, + end_time=f.end_time, + language=f.language, + direction=f.direction, + type=f.type_, + content=f.content, + ) + for f in s.fragments + ] + if self._config.include_results + else None + ), + metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), + ) + for s in final_segments + ], + metadata=final_metadata, + ), ) self._trim_before_time = final_segments[-1].end_time self._speech_fragments = [ @@ -1072,23 +1118,48 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ] # Emit interim segments - if interim_segments: + if partial_segments: """Partial segments are emitted as is.""" # Metadata for partial segments uses actual start/end times of the segments being emitted - partial_metadata = { - "start_time": interim_segments[0].start_time, - "end_time": interim_segments[-1].end_time, - } + partial_metadata = MessageTimeMetadata( + start_time=partial_segments[0].start_time, + end_time=partial_segments[-1].end_time, + ) # Emit segments - self.emit( - AgentServerMessageType.ADD_PARTIAL_SEGMENT, - { - "message": AgentServerMessageType.ADD_PARTIAL_SEGMENT.value, - "segments": [s.model_dump(self._config.include_results) for s in interim_segments], - "metadata": partial_metadata, - }, + self._emit_message( + SegmentMessage( + message=AgentServerMessageType.ADD_PARTIAL_SEGMENT, + segments=[ + SegmentMessageSegment( + speaker_id=s.speaker_id, + is_active=s.is_active, + timestamp=s.timestamp, + language=s.language, + text=s.text, + annotation=s.annotation, + fragments=( + [ + SegmentMessageSegmentFragment( + start_time=f.start_time, + end_time=f.end_time, + language=f.language, + direction=f.direction, + type=f.type_, + content=f.content, + ) + for f in s.fragments + ] + if self._config.include_results + else None + ), + metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), + ) + for s in partial_segments + ], + metadata=partial_metadata, + ), ) # Update the current view @@ -1127,27 +1198,24 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._session_speakers[frag.speaker].last_heard = frag.end_time # Emit - self.emit( - AgentServerMessageType.SPEAKER_METRICS, - { - "message": AgentServerMessageType.SPEAKER_METRICS.value, - "speakers": [s.model_dump() for s in self._session_speakers.values()], - }, + self._emit_message( + SpeakerMetricsMessage( + speakers=list(self._session_speakers.values()), + ), ) # Emit END_OF_TURN if end_of_turn and self._previous_view: # Metadata (for LAST view) - metadata = {"start_time": self._turn_start_time, "end_time": self._previous_view.end_time} + metadata = MessageTimeMetadata(start_time=self._turn_start_time, end_time=self._previous_view.end_time) # Emit - self.emit( - AgentServerMessageType.END_OF_TURN, - { - "message": AgentServerMessageType.END_OF_TURN.value, - "turn_id": self._turn_id, - "metadata": metadata, - }, + self._emit_message( + TurnStartEndMessage( + message=AgentServerMessageType.END_OF_TURN, + turn_id=self._turn_id, + metadata=metadata, + ), ) # Reset the previous view @@ -1178,7 +1246,11 @@ async def _calculate_finalize_delay( """ # Get the current view -or- previous view - view = self._current_view or self._previous_view + view = ( + self._current_view + if self._current_view and self._current_view.last_active_segment_index > -1 + else self._previous_view + ) # Exit if none if not view: @@ -1261,17 +1333,15 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: # Emit prediction if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): - self.emit( - AgentServerMessageType.END_OF_TURN_PREDICTION, - { - "message": AgentServerMessageType.END_OF_TURN_PREDICTION.value, - "turn_id": self._turn_id, - "metadata": { - "ttl": round(finalize_delay, 2), - "time_slip": round(time_slip, 2), - "reasons": [_reason for _, _reason in reasons], - }, - }, + self._emit_message( + TurnPredictionMessage( + turn_id=self._turn_id, + metadata=TurnPredictionMetadata( + ttl=round(finalize_delay, 2), + time_slip=round(time_slip, 2), + reasons=[_reason for _, _reason in reasons], + ), + ), ) # Return the time @@ -1370,23 +1440,21 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Check if speaker is different to the current speaker if current_is_speaking and speaker_changed: - self.emit( - AgentServerMessageType.SPEAKER_ENDED, - { - "message": AgentServerMessageType.SPEAKER_ENDED.value, - "status": SpeakerVADStatus( - speaker_id=current_speaker, is_active=False, time=speaker_end_time - ).model_dump(), - }, + self._emit_message( + VADStatusMessage( + message=AgentServerMessageType.SPEAKER_ENDED, + speaker_id=current_speaker, + is_active=False, + time=speaker_end_time, + ), ) - self.emit( - AgentServerMessageType.SPEAKER_STARTED, - { - "message": AgentServerMessageType.SPEAKER_STARTED.value, - "status": SpeakerVADStatus( - speaker_id=latest_speaker, is_active=True, time=speaker_end_time - ).model_dump(), - }, + self._emit_message( + VADStatusMessage( + message=AgentServerMessageType.SPEAKER_STARTED, + speaker_id=latest_speaker, + is_active=True, + time=speaker_end_time, + ), ) # No further processing if we have no new fragments and we are not speaking @@ -1408,32 +1476,28 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: ): self._end_of_turn_handler.start_handler() self._turn_id = self._end_of_turn_handler.handler_id - self.emit( - AgentServerMessageType.START_OF_TURN, - { - "message": AgentServerMessageType.START_OF_TURN.value, - "turn_id": self._turn_id, - "metadata": { - "start_time": event_time, - }, - }, + self._emit_message( + TurnStartEndMessage( + message=AgentServerMessageType.START_OF_TURN, + turn_id=self._turn_id, + metadata=MessageTimeMetadata( + start_time=event_time, + ), + ), ) - # Determine start or end of speaking - if self._is_speaking: - msg = AgentServerMessageType.SPEAKER_STARTED - else: - msg = AgentServerMessageType.SPEAKER_ENDED - # Emit the event - self.emit( - msg, - { - "message": msg.value, - "status": SpeakerVADStatus( - speaker_id=latest_speaker, is_active=self._is_speaking, time=event_time - ).model_dump(), - }, + self._emit_message( + VADStatusMessage( + message=( + AgentServerMessageType.SPEAKER_STARTED + if self._is_speaking + else AgentServerMessageType.SPEAKER_ENDED + ), + speaker_id=latest_speaker, + is_active=self._is_speaking, + time=event_time, + ), ) # Speaking has stopped diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 65172de8..0d1d81ad 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -7,9 +7,11 @@ import datetime from enum import Enum from typing import Any +from typing import Literal from typing import Optional from pydantic import BaseModel +from pydantic import ConfigDict from pydantic import Field from speechmatics.rt import AudioEncoding @@ -739,7 +741,7 @@ class SpeechFragment(BaseModel): result: Optional[Any] = None annotation: Optional[AnnotationResult] = None - model_config = {"arbitrary_types_allowed": True} + model_config = ConfigDict(use_enum_values=True, arbitrary_types_allowed=True) class SpeakerSegment(BaseModel): @@ -763,7 +765,7 @@ class SpeakerSegment(BaseModel): text: Optional[str] = None annotation: AnnotationResult = Field(default_factory=AnnotationResult) - model_config = {"arbitrary_types_allowed": True} + model_config = ConfigDict(use_enum_values=True, arbitrary_types_allowed=True) @property def start_time(self) -> float: @@ -910,18 +912,199 @@ def trim(self, start_time: float, end_time: float, annotate_segments: bool = Tru ) -class SpeakerVADStatus(BaseModel): +# ============================================================================== +# MESSAGES / PAYLOADS +# ============================================================================== + + +class BaseMessageModel(BaseModel): + """Base model for all messages.""" + + def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: + """Default to excluding None values.""" + return super().model_dump(*args, **kwargs, exclude_none=True, mode="json") # type: ignore[no-any-return] + + def model_dump_json(self, *args: Any, **kwargs: Any) -> str: + """Default to excluding None values.""" + return super().model_dump_json(*args, **kwargs, exclude_none=True) # type: ignore[no-any-return] + + +class BaseMessage(BaseMessageModel): + """Base model for all messages.""" + + message: AgentServerMessageType + + +class ErrorMessage(BaseMessage): + """Emitted when an error occurs. + + Parameters: + message: The message type. + reason: The reason for the error. + """ + + message: AgentServerMessageType = AgentServerMessageType.ERROR + reason: str + + +class MetricsMessage(BaseMessage): + """Emitted when metrics are calculated. + + Parameters: + message: The message type. + total_time: The total time in seconds. + total_time_str: The total time in HH:MM:SS format. + total_bytes: The total bytes sent to the STT engine. + last_ttfb: The last time to first text in seconds. + """ + + message: AgentServerMessageType = AgentServerMessageType.METRICS + total_time: float + total_time_str: str + total_bytes: int + last_ttfb: int + + +class TTFBMetricsMessage(BaseMessage): + """Emitted when the time to first text is calculated. + + TTFB is calculated as the time for the audio to be processed by the STT + engine and is calculated periodically during the session. This can be + used to measure the performance of the STT engine. + + Parameters: + message: The message type. + ttfb: The time to first text in seconds. + """ + + message: AgentServerMessageType = AgentServerMessageType.TTFB_METRICS + ttfb: int + + +class VADStatusMessage(BaseMessage): """Emitted when a speaker starts or ends speaking. The speaker id is taken from the last word in the segment when the event is emitted. Parameters: + message: The message type. is_active: Whether the speaker is active. speaker_id: The ID of the speaker. time: The time of the event (start for STARTED, end for ENDED). """ + message: Literal[AgentServerMessageType.SPEAKER_STARTED, AgentServerMessageType.SPEAKER_ENDED] is_active: bool speaker_id: Optional[str] = None time: Optional[float] = None + + +class MessageTimeMetadata(BaseMessageModel): + """Metadata for segment messages. + + Parameters: + start_time: The start time of the segment. + end_time: The end time of the segment. + """ + + start_time: Optional[float] = None + end_time: Optional[float] = None + + +class TurnStartEndMessage(BaseMessage): + """Emitted when a turn starts or ends. + + Parameters: + turn_id: The ID of the turn. + is_active: Whether the turn is active. + """ + + message: Literal[AgentServerMessageType.START_OF_TURN, AgentServerMessageType.END_OF_TURN] + turn_id: int + metadata: MessageTimeMetadata + + +class TurnPredictionMetadata(BaseMessageModel): + """Metadata for turn prediction messages. + + Parameters: + ttl: The time to live of the prediction in seconds. + time_slip: The time slip of the prediction in seconds. + reasons: The reasons for the prediction. + """ + + ttl: float + time_slip: float + reasons: list[str] + + +class TurnPredictionMessage(BaseMessage): + """Emitted when a turn prediction is made.""" + + message: AgentServerMessageType = AgentServerMessageType.END_OF_TURN_PREDICTION + turn_id: int + metadata: TurnPredictionMetadata + + +class SpeakerMetricsMessage(BaseMessage): + """Emitted when the speaker metrics are updated. + + Parameters: + speakers: List of speakers. + """ + + message: AgentServerMessageType = AgentServerMessageType.SPEAKER_METRICS + speakers: list[SessionSpeaker] + + +class SegmentMessageSegmentFragment(BaseMessageModel): + """Speech fragment for segment messages. + + Parameters: + start_time: The start time of the fragment. + end_time: The end time of the fragment. + language: The language of the fragment. + direction: The direction of the fragment. + type_: The type of the fragment. + content: The content of the fragment. + """ + + start_time: float + end_time: float + language: str = "en" + direction: str = "ltr" + type: str = "word" + content: str = "" + + +class SegmentMessageSegment(BaseMessageModel): + """Partial or final segment. + + Parameters: + speaker_id: The ID of the speaker. + is_active: Whether the speaker is active (emits frame). + timestamp: The timestamp of the frame. + language: The language of the frame. + text: The text of the segment. + fragments: The fragments associated with the segment. + annotation: The annotation associated with the segment. + metadata: The metadata associated with the segment. + """ + + speaker_id: Optional[str] = None + is_active: bool = False + timestamp: Optional[str] = None + language: Optional[str] = None + text: Optional[str] = None + fragments: Optional[list[SegmentMessageSegmentFragment]] = None + annotation: list[AnnotationFlags] = Field(default_factory=list) + metadata: MessageTimeMetadata + + +class SegmentMessage(BaseMessage): + """Emitted when a segment is added to the session.""" + + message: Literal[AgentServerMessageType.ADD_PARTIAL_SEGMENT, AgentServerMessageType.ADD_SEGMENT] + segments: list[SegmentMessageSegment] + metadata: MessageTimeMetadata diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 784af4aa..5970e243 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -17,7 +17,15 @@ import numpy as np from pydantic import BaseModel -ssl._create_default_https_context = lambda: ssl.create_default_context(cafile=certifi.where()) + +def _create_ssl_context(*args: Any, **kwargs: Any) -> ssl.SSLContext: + """Create SSL context with certifi certificates.""" + if "cafile" not in kwargs: + kwargs["cafile"] = certifi.where() + return ssl.create_default_context(*args, **kwargs) + + +ssl._create_default_https_context = _create_ssl_context ort: Any WhisperFeatureExtractor: Any @@ -171,8 +179,7 @@ async def predict( # Check a valid language if not self.valid_language(language): - logger.warning(f"Invalid language: {language}.") - return SmartTurnPredictionResult(error=f"Invalid language: {language}") + logger.warning(f"Invalid language: {language}. Results may be unreliable.") # Record start time start_time = datetime.datetime.now() From af3c8db9852db7071232044f3271c8e314dc80c2 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 18:48:09 +0000 Subject: [PATCH 168/215] rename examples for clarity --- examples/voice/cli/README.md | 38 ++- examples/voice/cli/{main.py => cli.py} | 13 +- examples/voice/simple/README.md | 2 +- examples/voice/simple/{main.py => simple.py} | 0 examples/voice/utils.py | 265 ------------------- 5 files changed, 30 insertions(+), 288 deletions(-) rename examples/voice/cli/{main.py => cli.py} (98%) rename examples/voice/simple/{main.py => simple.py} (100%) delete mode 100644 examples/voice/utils.py diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index 98375716..c9b7108a 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -7,17 +7,13 @@ Command-line tool for real-time transcription using the Speechmatics Voice SDK. **Microphone:** ```bash -# macOS / Ubuntu / Debian -python ./examples/voice/cli/main.py -p -k YOUR_API_KEY - -# Windows -python .\examples\voice\cli\main.py -p -k YOUR_API_KEY +python cli.py -p -k YOUR_API_KEY ``` **Audio file:** ```bash -python ./examples/voice/cli/main.py -p -k YOUR_API_KEY -i audio.wav +python cli.py -p -k YOUR_API_KEY -i audio.wav ``` Press `CTRL+C` to stop. @@ -30,7 +26,7 @@ Press `CTRL+C` to stop. ## Usage ```bash -python ./examples/voice/cli/main.py [OPTIONS] +python cli.py [OPTIONS] ``` ### Core Parameters @@ -51,7 +47,7 @@ Enable formatted console output with colours and emojis. Save all output to a JSONL file for later analysis. **`-v, --verbose`** -Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` shows the `annotation` field in segments. `-vvvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. +Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` shows the `annotation` field in segments. `-vvvv` adds metric messages.`-vvvvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. ### Audio Configuration @@ -110,31 +106,31 @@ Enable preview features. ### Microphone - basic transcription ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -p +python cli.py -k YOUR_KEY -p ``` ### Audio file - basic transcription ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -p +python cli.py -k YOUR_KEY -i audio.wav -p ``` ### Audio file - muted (no playback) ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -Mp +python cli.py -k YOUR_KEY -i audio.wav -Mp ``` ### With adaptive turn detection ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -m ADAPTIVE -p +python cli.py -k YOUR_KEY -m ADAPTIVE -p ``` ### Enrol speakers (microphone) ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -ep +python cli.py -k YOUR_KEY -ep ``` Press `CTRL+C` when done. Speaker identifiers will be displayed: @@ -146,7 +142,7 @@ Press `CTRL+C` when done. Speaker identifiers will be displayed: ### Use known speakers (JSON string) ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -s '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' -p +python cli.py -k YOUR_KEY -s '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' -p ``` ### Use known speakers (JSON file) @@ -169,7 +165,7 @@ Create a `speakers.json` file: Then run: ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -s speakers.json -p +python cli.py -k YOUR_KEY -s speakers.json -p ``` Output shows speaker labels: @@ -182,13 +178,13 @@ Output shows speaker labels: ### Focus on specific speakers ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY --focus-speakers S1 S2 -p +python cli.py -k YOUR_KEY --focus-speakers S1 S2 -p ``` ### Save to file ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -o output.jsonl -p +python cli.py -k YOUR_KEY -o output.jsonl -p ``` ### Verbose logging @@ -196,25 +192,25 @@ python ./examples/voice/cli/main.py -k YOUR_KEY -o output.jsonl -p Show speaker start/end events: ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -v -p +python cli.py -k YOUR_KEY -i audio.wav -v -p ``` Include turn prediction events: ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -vv -p +python cli.py -k YOUR_KEY -i audio.wav -vv -p ``` Include additional payloads for debugging: ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav -vvv -p +python cli.py -k YOUR_KEY -i audio.wav -vvv -p ``` ### Audio file with speaker focus ```bash -python ./examples/voice/cli/main.py -k YOUR_KEY -i audio.wav --focus-speakers S2 -p +python cli.py -k YOUR_KEY -i audio.wav --focus-speakers S2 -p ``` ## Speaker Identification diff --git a/examples/voice/cli/main.py b/examples/voice/cli/cli.py similarity index 98% rename from examples/voice/cli/main.py rename to examples/voice/cli/cli.py index 4d8716d5..166820b1 100644 --- a/examples/voice/cli/main.py +++ b/examples/voice/cli/cli.py @@ -110,6 +110,7 @@ async def main() -> None: emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_SPEAKER_ENDED ), transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, + include_results=args.results, ) # Set common items @@ -386,8 +387,13 @@ def log_message(message) -> None: if args.verbose >= 2: client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) - # Verbose STT events + # Metrics if args.verbose >= 4: + client.on(AgentServerMessageType.METRICS, log_message) + client.on(AgentServerMessageType.TTFB_METRICS, log_message) + + # Verbose STT events + if args.verbose >= 5: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) @@ -624,6 +630,11 @@ def parse_args(): action="store_true", help="Use default device (default: False)", ) + parser.add_argument( + "--results", + action="store_true", + help="Include word transcription payload results in output (default: False)", + ) # ============================================================================== # Voice Agent configuration diff --git a/examples/voice/simple/README.md b/examples/voice/simple/README.md index 5699cace..8802bab6 100644 --- a/examples/voice/simple/README.md +++ b/examples/voice/simple/README.md @@ -6,7 +6,7 @@ Basic real-time transcription example using your default microphone with speaker ```bash export SPEECHMATICS_API_KEY=your_api_key -python ./examples/voice/simple/main.py +python app.py ``` Press `CTRL+C` to stop. diff --git a/examples/voice/simple/main.py b/examples/voice/simple/simple.py similarity index 100% rename from examples/voice/simple/main.py rename to examples/voice/simple/simple.py diff --git a/examples/voice/utils.py b/examples/voice/utils.py deleted file mode 100644 index a9b2c080..00000000 --- a/examples/voice/utils.py +++ /dev/null @@ -1,265 +0,0 @@ -import asyncio -import logging -import sys -import termios -import tty - -import pyaudio - -from speechmatics.rt import Microphone -from speechmatics.voice import VoiceAgentClient - - -async def wait_for_keypress() -> None: - """Wait for any key press in a non-blocking way.""" - loop = asyncio.get_event_loop() - - def _read_key(): - """Read a single key press (Unix/Mac only).""" - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(fd) - ch = sys.stdin.read(1) - return ch - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - - # Run the blocking key read in a thread pool - await loop.run_in_executor(None, _read_key) - - -def select_audio_device() -> int | None: - """Interactive microphone device selection. - - Returns: - Device index or None for default device - """ - devices = Microphone.list_devices() - if not devices: - return None - - print("Available microphones:") - for device in devices: - print(f" [{device['index']}] {device['name']} ({device['channels']} channels)") - print() - - return get_device_choice( - [d["index"] for d in devices], - "Enter device index (or press Enter for default): ", - ) - - -def select_audio_output_device() -> int | None: - """Interactive audio output device selection. - - Returns: - Device index or None for default device - """ - try: - output_devices = get_output_devices() - if not output_devices: - print("No audio output devices found.") - return None - - print("Available audio output devices:") - for device in output_devices: - print(f" [{device['index']}] {device['name']} ({device['channels']} channels, {device['sample_rate']}Hz)") - print() - - return get_device_choice( - [d["index"] for d in output_devices], - "Enter output device index (or press Enter for default): ", - ) - - except Exception as e: - print(f"Error listing audio devices: {e}") - return None - - -def get_output_devices() -> list[dict]: - """Get list of available output devices.""" - p = pyaudio.PyAudio() - try: - devices = [] - for i in range(p.get_device_count()): - info = p.get_device_info_by_index(i) - if info["maxOutputChannels"] > 0: - devices.append( - { - "index": i, - "name": info["name"], - "channels": info["maxOutputChannels"], - "sample_rate": int(info["defaultSampleRate"]), - } - ) - return devices - finally: - p.terminate() - - -def get_device_choice(valid_indices: list[int], prompt: str) -> int | None: - """Get user device choice with validation.""" - while True: - try: - choice = input(prompt).strip() - if not choice: - return None - - device_index = int(choice) - if device_index in valid_indices: - return device_index - - print(f"Invalid device index. Choose from: {valid_indices}") - - except ValueError: - print("Please enter a valid number.") - except KeyboardInterrupt: - return None - - -def setup_microphone(sample_rate: int, chunk_size: int) -> Microphone | None: - """Setup microphone with device selection.""" - selected_device = select_audio_device() - - mic = Microphone( - sample_rate=sample_rate, - chunk_size=chunk_size, - device_index=selected_device, - ) - - if not mic.start(): - print("Error: PyAudio not available - install with: pip install pyaudio") - return None - return mic - - -async def stream_microphone(mic: Microphone, client: VoiceAgentClient, chunk_size: int) -> None: - """Stream microphone audio to client.""" - while True: - frame = await mic.read(chunk_size) - await client.send_audio(frame) - - -class AudioPlayer: - """Real-time audio player using PyAudio.""" - - def __init__( - self, - sample_rate: int, - channels: int = 1, - sample_width: int = 2, - device_index: int | None = None, - ): - """Initialize audio player. - - Args: - sample_rate: Audio sample rate in Hz - channels: Number of audio channels (1 for mono) - sample_width: Sample width in bytes (2 for 16-bit) - device_index: Output device index (None for default) - """ - self.sample_rate = sample_rate - self.channels = channels - self.sample_width = sample_width - self.device_index = device_index - self.p = None - self.stream = None - - def start(self) -> bool: - """Start audio playback stream.""" - try: - self.p = pyaudio.PyAudio() - if not self.p: - return False - - audio_format = self._get_audio_format() - if not audio_format: - return False - - self.stream = self.p.open( - format=audio_format, - channels=self.channels, - rate=self.sample_rate, - output=True, - output_device_index=self.device_index, - ) - return True - - except Exception as e: - print(f"Error starting audio player: {e}") - return False - - def play(self, audio_data: bytes) -> None: - """Play audio data chunk.""" - if self.stream: - self.stream.write(audio_data) - - def stop(self) -> None: - """Stop and cleanup audio player.""" - if self.stream: - self.stream.stop_stream() - self.stream.close() - self.stream = None - - if self.p: - self.p.terminate() - self.p = None - - def _get_audio_format(self) -> int | None: - """Get PyAudio format from sample width.""" - format_map = {1: pyaudio.paInt8, 2: pyaudio.paInt16, 4: pyaudio.paInt32} - if self.sample_width not in format_map: - print(f"Unsupported sample width: {self.sample_width}") - return None - return format_map[self.sample_width] - - -class CustomLevels: - """Custom logging levels for transcription events.""" - - PARTIAL = 11 # Partial transcription results - FINAL = 12 # Final transcription results - SPEAKER = 15 # Speech activity events - - -class CustomTextFormatter(logging.Formatter): - """Coloured logging formatter for transcription events.""" - - # FORMAT = "%(asctime)s.%(msecs)03d %(levelname)-8s (%(filename)s:%(lineno)d) %(message)s" - FORMAT = "%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s" - - # ANSI colour codes - COLOURS = { - logging.DEBUG: "\033[90m", # Gray - CustomLevels.PARTIAL: "\033[32m", # Green - CustomLevels.FINAL: "\033[33m", # Yellow - CustomLevels.SPEAKER: "\033[36m", # Cyan - } - RESET = "\033[0m" - - def format(self, record): - colour = self.COLOURS.get(record.levelno, self.RESET) - message = super().format(record) - return f"{colour}{message}{self.RESET}\r" - - -def get_logger(name: str) -> logging.Logger: - """Setup coloured logger for transcription events.""" - logging.basicConfig(level=logging.INFO, stream=sys.stdout) - - logger = logging.getLogger(name) - logger.setLevel(logging.DEBUG) - logging.getLogger("speechmatics.voice").setLevel(logging.DEBUG) - - # Register custom level names - logging.addLevelName(CustomLevels.PARTIAL, "PARTIAL") - logging.addLevelName(CustomLevels.FINAL, "FINAL") - logging.addLevelName(CustomLevels.SPEAKER, "SPEAKER") - - # Apply custom formatter to all handlers - formatter = CustomTextFormatter(CustomTextFormatter.FORMAT, datefmt="%Y-%m-%d %H:%M:%S") - for handler in logging.root.handlers: - handler.setFormatter(formatter) - - return logger From f293221ee5fde86add796881f08f7c2a9d57855b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 19:05:02 +0000 Subject: [PATCH 169/215] updated tests --- tests/voice/test_11_audio_buffer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/voice/test_11_audio_buffer.py b/tests/voice/test_11_audio_buffer.py index 44ffa7bb..63f2fdc4 100644 --- a/tests/voice/test_11_audio_buffer.py +++ b/tests/voice/test_11_audio_buffer.py @@ -262,8 +262,8 @@ def final_segment(message): assert segments for segment in segments: - start_time = segment["start_time"] - end_time = segment["end_time"] + start_time = segment["metadata"]["start_time"] + end_time = segment["metadata"]["end_time"] speaker_id = segment["speaker_id"] asyncio.create_task( save_slice( @@ -363,8 +363,8 @@ async def save_slice( # Bytes logger def speaker_ended(message): try: - end_time = message.get("status", {}).get("time") - speaker_id = message.get("status", {}).get("speaker_id") + end_time = message["time"] + speaker_id = message["speaker_id"] asyncio.create_task( save_slice( start_time=end_time - 0.2, From 3630bfe130001629642a87d4a27b587d33a6d371 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 19:06:27 +0000 Subject: [PATCH 170/215] updated CLI readme --- examples/voice/cli/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index c9b7108a..c1573c7f 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -49,6 +49,9 @@ Save all output to a JSONL file for later analysis. **`-v, --verbose`** Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` shows the `annotation` field in segments. `-vvvv` adds metric messages.`-vvvvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. +**`--results`** +Include the individual fragments in the segment payload. Default: `False`. + ### Audio Configuration **`--sample-rate`** From d267d64298e3dd9c26771673bfce8a9a82efbf0f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 19:35:36 +0000 Subject: [PATCH 171/215] only calculate metrics if there are listeners --- sdk/voice/speechmatics/voice/_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 871a9529..a6ead91e 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -949,8 +949,10 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = } self._logger.debug(json.dumps(debug_payload)) - # Update TTFB - if not is_final: + # Update TTFB (only if there are listeners) + if not is_final and ( + self.listeners(AgentServerMessageType.TTFB_METRICS) or self.listeners(AgentServerMessageType.METRICS) + ): self._calculate_ttfb(end_time=payload_end_time) # Fragments available From 68e40280cc7f382bc7230989a40578ca586480c2 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 21:32:25 +0000 Subject: [PATCH 172/215] adjust TTFB calculation and speaker metrics --- sdk/voice/speechmatics/voice/_client.py | 100 +++++++++++++----------- sdk/voice/speechmatics/voice/_models.py | 2 - 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index a6ead91e..db84f99f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -46,6 +46,7 @@ from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode from ._models import SpeakerMetricsMessage +from ._models import SpeakerSegment from ._models import SpeakerSegmentView from ._models import SpeechFragment from ._models import SpeechSegmentEmitMode @@ -780,6 +781,11 @@ def _calculate_ttfb(self, end_time: float) -> None: Args: end_time: The end time of the payload from the STT engine. """ + + # Skip if not enabled + if not (self.listeners(AgentServerMessageType.TTFB_METRICS) or self.listeners(AgentServerMessageType.METRICS)): + return + # Skip if no fragments are words if len(self._speech_fragments) == 0 or all(f.type_ != "word" for f in self._speech_fragments): return @@ -807,6 +813,48 @@ def _calculate_ttfb(self, end_time: float) -> None: TTFBMetricsMessage(ttfb=int(self._last_ttfb)), ) + def calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> None: + """Calculate the speaker metrics. + + Used to track the number of final words per speaker. Only valid speakers are + considered. Ignored speakers will be excluded. + + Args: + final_segments: The final segments to calculate the speaker metrics for. + """ + + # Skip if not enabled + if not self.listeners(AgentServerMessageType.SPEAKER_METRICS): + return + + # Finalized words + final_words = [ + f for seg in final_segments for f in seg.fragments if f.type_ == "word" and f.speaker is not None + ] + + # Only process if we have words + if final_words: + # Update the metrics of the speakers in the session + for frag in final_words: + # Check we have a speaker + if frag.speaker is None: + continue + + # Create new speaker + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + + # Update metrics + self._session_speakers[frag.speaker].word_count += 1 + self._session_speakers[frag.speaker].last_heard = frag.end_time + + # Emit + self._emit_message( + SpeakerMetricsMessage( + speakers=list(self._session_speakers.values()), + ), + ) + # ============================================================================ # TRANSCRIPT PROCESSING # ============================================================================ @@ -950,9 +998,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = self._logger.debug(json.dumps(debug_payload)) # Update TTFB (only if there are listeners) - if not is_final and ( - self.listeners(AgentServerMessageType.TTFB_METRICS) or self.listeners(AgentServerMessageType.METRICS) - ): + if not is_final: self._calculate_ttfb(end_time=payload_end_time) # Fragments available @@ -1172,39 +1218,8 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._turn_start_time = self._current_view.start_time # Send updated speaker metrics - if self._dz_enabled and self.listeners(AgentServerMessageType.SPEAKER_METRICS): - """Update the metrics of the speakers in the sesseion.""" - - # Finalized words - final_words = [ - f - for seg in final_segments - for f in seg.fragments - if f.type_ == "word" and f.speaker is not None - ] - - # Only process if we have words - if final_words: - # Update the metrics of the speakers in the session - for frag in final_words: - # Check we have a speaker - if frag.speaker is None: - continue - - # Create new speaker - if frag.speaker not in self._session_speakers: - self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) - - # Update metrics - self._session_speakers[frag.speaker].word_count += 1 - self._session_speakers[frag.speaker].last_heard = frag.end_time - - # Emit - self._emit_message( - SpeakerMetricsMessage( - speakers=list(self._session_speakers.values()), - ), - ) + if self._dz_enabled: + self.calculate_speaker_metrics(final_segments) # Emit END_OF_TURN if end_of_turn and self._previous_view: @@ -1265,7 +1280,6 @@ async def _calculate_finalize_delay( # Calculations clamped_delay: float = self._config.end_of_utterance_max_delay finalize_delay: Optional[float] = None - time_slip: Optional[float] = None # Reasons for the calculation reasons: list[tuple[float, str]] = [] @@ -1327,11 +1341,8 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: # Clamp to max delay clamped_delay = min(delay, self._config.end_of_utterance_max_delay) - # Establish the real-world time - time_slip = max(self._total_time - self._last_fragment_end_time, 0) - # Adjust time and make sure no less than 25ms - finalize_delay = max(clamped_delay - time_slip, 0.025) + finalize_delay = max(clamped_delay - (self._last_ttfb / 1000), 0.025) # Emit prediction if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): @@ -1340,7 +1351,6 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: turn_id=self._turn_id, metadata=TurnPredictionMetadata( ttl=round(finalize_delay, 2), - time_slip=round(time_slip, 2), reasons=[_reason for _, _reason in reasons], ), ), @@ -1459,12 +1469,14 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: ), ) + # Update current speaker + self._current_speaker = latest_speaker + # No further processing if we have no new fragments and we are not speaking if has_valid_partial == current_is_speaking: return - # Update current speaker + speaking states - self._current_speaker = latest_speaker + # Update speaking state self._is_speaking = not current_is_speaking # Event time diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 0d1d81ad..c07af4e8 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -1030,12 +1030,10 @@ class TurnPredictionMetadata(BaseMessageModel): Parameters: ttl: The time to live of the prediction in seconds. - time_slip: The time slip of the prediction in seconds. reasons: The reasons for the prediction. """ ttl: float - time_slip: float reasons: list[str] From 168ff09efe89edabdb5a8ffe0b5742e3186f9410 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 28 Oct 2025 21:46:41 +0000 Subject: [PATCH 173/215] move certify in smart turn to conditional block --- sdk/voice/speechmatics/voice/_smart_turn.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 5970e243..011318d8 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -13,31 +13,30 @@ from typing import Optional from urllib.parse import urlparse -import certifi import numpy as np from pydantic import BaseModel - -def _create_ssl_context(*args: Any, **kwargs: Any) -> ssl.SSLContext: - """Create SSL context with certifi certificates.""" - if "cafile" not in kwargs: - kwargs["cafile"] = certifi.where() - return ssl.create_default_context(*args, **kwargs) - - -ssl._create_default_https_context = _create_ssl_context - ort: Any WhisperFeatureExtractor: Any logger = logging.getLogger(__name__) try: os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" + import certifi import onnxruntime as _ort from transformers import WhisperFeatureExtractor as _WhisperFeatureExtractor ort = _ort WhisperFeatureExtractor = _WhisperFeatureExtractor + + def _create_ssl_context(*args: Any, **kwargs: Any) -> ssl.SSLContext: + """Create SSL context with certifi certificates.""" + if "cafile" not in kwargs: + kwargs["cafile"] = certifi.where() + return ssl.create_default_context(*args, **kwargs) + + ssl._create_default_https_context = _create_ssl_context + except ModuleNotFoundError: WhisperFeatureExtractor = None ort = None From 94492d4346f101df50945936bf2970aacdd530cd Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 00:26:46 +0000 Subject: [PATCH 174/215] better metrics handling --- examples/voice/cli/cli.py | 3 +- sdk/voice/speechmatics/voice/__init__.py | 4 +- sdk/voice/speechmatics/voice/_client.py | 70 +++++++++++------------- sdk/voice/speechmatics/voice/_models.py | 27 ++------- 4 files changed, 40 insertions(+), 64 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 166820b1..89916141 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -389,8 +389,7 @@ def log_message(message) -> None: # Metrics if args.verbose >= 4: - client.on(AgentServerMessageType.METRICS, log_message) - client.on(AgentServerMessageType.TTFB_METRICS, log_message) + client.on(AgentServerMessageType.SESSION_METRICS, log_message) # Verbose STT events if args.verbose >= 5: diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 9a623738..e1a687cb 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -25,13 +25,13 @@ from ._models import AgentServerMessageType from ._models import EndOfUtteranceMode from ._models import SegmentMessage +from ._models import SessionMetricsMessage from ._models import SmartTurnConfig from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode from ._models import SpeakerMetricsMessage from ._models import SpeechSegmentConfig from ._models import SpeechSegmentEmitMode -from ._models import TTFBMetricsMessage from ._models import TurnPredictionMessage from ._models import TurnStartEndMessage from ._models import VADStatusMessage @@ -62,8 +62,8 @@ # Server messages "AgentServerMessageType", "SegmentMessage", + "SessionMetricsMessage", "SpeakerMetricsMessage", - "TTFBMetricsMessage", "TurnPredictionMessage", "TurnStartEndMessage", "VADStatusMessage", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index db84f99f..9fa70d06 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -38,10 +38,10 @@ from ._models import ErrorMessage from ._models import LanguagePackInfo from ._models import MessageTimeMetadata -from ._models import MetricsMessage from ._models import SegmentMessage from ._models import SegmentMessageSegment from ._models import SegmentMessageSegmentFragment +from ._models import SessionMetricsMessage from ._models import SessionSpeaker from ._models import SpeakerFocusConfig from ._models import SpeakerFocusMode @@ -51,7 +51,6 @@ from ._models import SpeechFragment from ._models import SpeechSegmentEmitMode from ._models import TranscriptionUpdatePreset -from ._models import TTFBMetricsMessage from ._models import TurnPredictionMessage from ._models import TurnPredictionMetadata from ._models import TurnStartEndMessage @@ -185,8 +184,7 @@ def __init__( self._total_time: float = 0 self._total_bytes: int = 0 - # TTFB metrics - self._last_ttfb_time: Optional[float] = None + # Latency metrics self._last_ttfb: float = 0 # Time to disregard speech fragments before @@ -245,7 +243,7 @@ def __init__( self._dz_config = self._config.speaker_config # Metrics emitter task - self._metrics_emitter_interval: float = 10.0 + self._metrics_emitter_interval: float = 5.0 self._metrics_emitter_task: Optional[asyncio.Task] = None # Audio sampling info @@ -741,27 +739,44 @@ def _start_metrics_task(self) -> None: # Task to send metrics async def emit_metrics() -> None: + # Tracker + last_emission_time = self._total_time + + # Emit metrics while True: - # Interval between emitting metrics - await asyncio.sleep(self._metrics_emitter_interval) + # Calculate when the next emission should occur + next_emission_time = ( + last_emission_time // self._metrics_emitter_interval + 1 + ) * self._metrics_emitter_interval # Check if there are any listeners for AgentServerMessageType.METRICS - if not self.listeners(AgentServerMessageType.METRICS): - break + if not self.listeners(AgentServerMessageType.SESSION_METRICS): + await asyncio.sleep(self._metrics_emitter_interval) + last_emission_time = self._total_time + continue + + # Wait until we've actually reached that time + while self._total_time < next_emission_time: + time_to_wait = next_emission_time - self._total_time + await asyncio.sleep(min(0.25, time_to_wait)) # Calculations - time_s = round(self._total_time, 3) + total_time = self._total_time + total_bytes = self._total_bytes # Emit metrics self._emit_message( - MetricsMessage( - total_time=time_s, - total_time_str=time.strftime("%H:%M:%S", time.gmtime(time_s)), - total_bytes=self._total_bytes, - last_ttfb=int(self._last_ttfb), + SessionMetricsMessage( + total_time=round(total_time, 1), + total_time_str=time.strftime("%H:%M:%S", time.gmtime(total_time)), + total_bytes=total_bytes, + processing_time=round(self._last_ttfb, 3), ) ) + # Update tracker + last_emission_time = total_time + # Trigger the task self._metrics_emitter_task = asyncio.create_task(emit_metrics()) @@ -782,23 +797,8 @@ def _calculate_ttfb(self, end_time: float) -> None: end_time: The end time of the payload from the STT engine. """ - # Skip if not enabled - if not (self.listeners(AgentServerMessageType.TTFB_METRICS) or self.listeners(AgentServerMessageType.METRICS)): - return - - # Skip if no fragments are words - if len(self._speech_fragments) == 0 or all(f.type_ != "word" for f in self._speech_fragments): - return - - # Get start of the first fragment - fragments_start_time = self._speech_fragments[0].start_time - - # Skip if no partial word or if we have already calculated the TTFB for this word - if self._last_ttfb_time and fragments_start_time <= self._last_ttfb_time: - return - # Calculate the time difference (convert to ms) - ttfb = (self._total_time - end_time) * 1000.0 + ttfb = self._total_time - end_time # Skip if zero or less if ttfb <= 0: @@ -806,12 +806,6 @@ def _calculate_ttfb(self, end_time: float) -> None: # Save TTFB and end time self._last_ttfb = ttfb - self._last_ttfb_time = end_time - - # Emit the TTFB - self._emit_message( - TTFBMetricsMessage(ttfb=int(self._last_ttfb)), - ) def calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> None: """Calculate the speaker metrics. @@ -1342,7 +1336,7 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: clamped_delay = min(delay, self._config.end_of_utterance_max_delay) # Adjust time and make sure no less than 25ms - finalize_delay = max(clamped_delay - (self._last_ttfb / 1000), 0.025) + finalize_delay = max(clamped_delay - self._last_ttfb, 0.025) # Emit prediction if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index c07af4e8..96d9b37e 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -225,8 +225,7 @@ class AgentServerMessageType(str, Enum): SPEAKERS_RESULT = "SpeakersResult" # Metrics - METRICS = "Metrics" - TTFB_METRICS = "TTFBMetrics" + SESSION_METRICS = "SessionMetrics" SPEAKER_METRICS = "SpeakerMetrics" @@ -947,7 +946,7 @@ class ErrorMessage(BaseMessage): reason: str -class MetricsMessage(BaseMessage): +class SessionMetricsMessage(BaseMessage): """Emitted when metrics are calculated. Parameters: @@ -955,30 +954,14 @@ class MetricsMessage(BaseMessage): total_time: The total time in seconds. total_time_str: The total time in HH:MM:SS format. total_bytes: The total bytes sent to the STT engine. - last_ttfb: The last time to first text in seconds. + processing_time: The latest processing time in seconds. """ - message: AgentServerMessageType = AgentServerMessageType.METRICS + message: AgentServerMessageType = AgentServerMessageType.SESSION_METRICS total_time: float total_time_str: str total_bytes: int - last_ttfb: int - - -class TTFBMetricsMessage(BaseMessage): - """Emitted when the time to first text is calculated. - - TTFB is calculated as the time for the audio to be processed by the STT - engine and is calculated periodically during the session. This can be - used to measure the performance of the STT engine. - - Parameters: - message: The message type. - ttfb: The time to first text in seconds. - """ - - message: AgentServerMessageType = AgentServerMessageType.TTFB_METRICS - ttfb: int + processing_time: float class VADStatusMessage(BaseMessage): From f2a6eb3f5e3003fc864cd5f3eda03387da230f7d Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 00:46:05 +0000 Subject: [PATCH 175/215] Use new RT package messages. --- examples/voice/cli/cli.py | 4 +-- sdk/voice/speechmatics/voice/__init__.py | 3 --- sdk/voice/speechmatics/voice/_client.py | 3 +-- sdk/voice/speechmatics/voice/_models.py | 31 ------------------------ tests/voice/test_09_speaker_id.py | 4 +-- 5 files changed, 5 insertions(+), 40 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 89916141..1072ddf5 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -16,9 +16,9 @@ from utils import select_audio_device from utils import select_audio_output_device +from speechmatics.rt import ClientMessageType from speechmatics.rt import Microphone from speechmatics.voice import AdditionalVocabEntry -from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerFocusConfig @@ -143,7 +143,7 @@ async def main() -> None: # Request speaker IDs at the end of the session (if enrolling) if args.enrol: - await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS, "final": True}) + await client.send_message({"message": ClientMessageType.GET_SPEAKERS, "final": True}) # Stream audio try: diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index e1a687cb..fd808983 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -21,7 +21,6 @@ from ._client import VoiceAgentClient from ._models import AdditionalVocabEntry -from ._models import AgentClientMessageType from ._models import AgentServerMessageType from ._models import EndOfUtteranceMode from ._models import SegmentMessage @@ -57,8 +56,6 @@ "SpeechSegmentEmitMode", "VoiceAgentConfig", "VoiceAgentConfigPreset", - # Client messages - "AgentClientMessageType", # Server messages "AgentServerMessageType", "SegmentMessage", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 85218ada..edbbd822 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -28,7 +28,6 @@ from . import __version__ from ._audio import AudioBuffer from ._logging import get_logger -from ._models import AgentClientMessageType from ._models import AgentServerMessageType from ._models import AnnotationFlags from ._models import AnnotationResult @@ -608,7 +607,7 @@ def _on_eou(message: dict[str, Any]) -> None: ) # Emit the message - await self.send_message({"message": AgentClientMessageType.FORCE_END_OF_UTTERANCE}) + await self.force_end_of_utterance() else: """Standard emit segments and finalize.""" diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 84303cf7..193d7008 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -118,37 +118,6 @@ class SpeakerFocusMode(str, Enum): IGNORE = "ignore" -class AgentClientMessageType(str, Enum): - """Message types that can be sent from client to server. - - These enum values represent the different types of messages that the - client can send to the Speechmatics RT API during a transcription session. - - Attributes: - ForceEndOfUtterance: Force the finalization of the STT engine. - EndOfStream: Signals that no more audio data will be sent. - GetSpeakers: Internal, Speechmatics only message. Allows the client to request speaker data. - - Examples: - >>> # Finalizing the current turn - >>> finalize_turn_message = { - ... "message": AgentClientMessageType.FORCE_END_OF_UTTERANCE - ... } - >>> await client.send_message(finalize_turn_message) - >>> - >>> # Ending the session - >>> end_message = { - ... "message": AgentClientMessageType.END_OF_STREAM, - ... "last_seq_no": sequence_number - ... } - >>> await client.send_message(end_message) - """ - - FORCE_END_OF_UTTERANCE = "ForceEndOfUtterance" - END_OF_STREAM = "EndOfStream" - GET_SPEAKERS = "GetSpeakers" - - class AgentServerMessageType(str, Enum): """Message types that can be received from the server / agent. diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 07f38ec8..9492cca7 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -8,8 +8,8 @@ from _utils import get_client from _utils import send_audio_file +from speechmatics.rt import ClientMessageType from speechmatics.voice import AdditionalVocabEntry -from speechmatics.voice import AgentClientMessageType from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerIdentifier @@ -133,7 +133,7 @@ def save_speakers_result(message): await send_audio_file(client, "./assets/audio_02_8kHz.wav", sample_rate=8000, progress_callback=log_bytes_sent) # Request the speakers result - await client.send_message({"message": AgentClientMessageType.GET_SPEAKERS}) + await client.send_message({"message": ClientMessageType.GET_SPEAKERS}) # Wait for the callback with timeout try: From 1cb8437cde21db851c629c4d07ea19cecb3e9ae0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 00:56:17 +0000 Subject: [PATCH 176/215] fix to language test --- tests/voice/test_07_languages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 8e31ad04..2a3f4175 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -143,7 +143,7 @@ def log_message(message): last_message = message ts = (datetime.datetime.now() - start_time).total_seconds() audio_ts = bytes_sent / 16000 / 2 - log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message.model_dump()}) + log = json.dumps({"ts": round(ts, 3), "audio_ts": round(audio_ts, 2), "payload": message}) messages.append(log) # Log a segment From bf1edd2fdb62824791cee6142bc48170d170b27e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 11:44:19 +0000 Subject: [PATCH 177/215] support processing time in payloads (as well as metric messages). --- sdk/voice/speechmatics/voice/__init__.py | 3 +++ sdk/voice/speechmatics/voice/_client.py | 2 ++ sdk/voice/speechmatics/voice/_models.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index fd808983..ad51aa4c 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -15,6 +15,7 @@ from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat +from speechmatics.rt import ClientMessageType as AgentClientMessageType from speechmatics.rt import OperatingPoint from speechmatics.rt import SpeakerDiarizationConfig from speechmatics.rt import SpeakerIdentifier @@ -56,6 +57,8 @@ "SpeechSegmentEmitMode", "VoiceAgentConfig", "VoiceAgentConfigPreset", + # Client messages + "AgentClientMessageType", # Server messages "AgentServerMessageType", "SegmentMessage", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index edbbd822..0c6ddccb 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1102,6 +1102,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False final_metadata = MessageTimeMetadata( start_time=final_segments[0].start_time, end_time=final_segments[-1].end_time, + processing_time=self._last_ttfb, ) # Ensure final segment ends with EOS @@ -1166,6 +1167,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False partial_metadata = MessageTimeMetadata( start_time=partial_segments[0].start_time, end_time=partial_segments[-1].end_time, + processing_time=self._last_ttfb, ) # Emit segments diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 193d7008..76b956eb 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -958,10 +958,12 @@ class MessageTimeMetadata(BaseMessageModel): Parameters: start_time: The start time of the segment. end_time: The end time of the segment. + processing_time: The processing time of the segment. """ start_time: Optional[float] = None end_time: Optional[float] = None + processing_time: Optional[float] = None class TurnStartEndMessage(BaseMessage): From a511424fd797650f9416b89d70e463f61a15ae31 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 15:31:48 +0000 Subject: [PATCH 178/215] support engine config messages (internal servers). --- sdk/voice/speechmatics/voice/_client.py | 25 +++---------------------- sdk/voice/speechmatics/voice/_models.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 0c6ddccb..91b84943 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -291,6 +291,7 @@ def _prepare_config( diarization="speaker" if config.enable_diarization else None, enable_partials=True, max_delay=config.max_delay, + ctrl=config.advanced_engine_control, ) # Additional vocab @@ -1133,17 +1134,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False text=s.text, annotation=s.annotation, fragments=( - [ - SegmentMessageSegmentFragment( - start_time=f.start_time, - end_time=f.end_time, - language=f.language, - direction=f.direction, - type=f.type_, - content=f.content, - ) - for f in s.fragments - ] + [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] if self._config.include_results else None ), @@ -1183,17 +1174,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False text=s.text, annotation=s.annotation, fragments=( - [ - SegmentMessageSegmentFragment( - start_time=f.start_time, - end_time=f.end_time, - language=f.language, - direction=f.direction, - type=f.type_, - content=f.content, - ) - for f in s.fragments - ] + [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] if self._config.include_results else None ), diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 76b956eb..350694ab 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -469,6 +469,8 @@ class VoiceAgentConfig(BaseModel): smart_turn_config: Smart turn configuration for the Speechmatics Voice Agent. speech_segment_config: Speech segment configuration for the Speechmatics Voice Agent. + advanced_engine_control: Internal use only. + sample_rate: Audio sample rate for streaming. Defaults to `16000`. audio_encoding: Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. @@ -567,6 +569,9 @@ class VoiceAgentConfig(BaseModel): smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) speech_segment_config: SpeechSegmentConfig = Field(default_factory=SpeechSegmentConfig) + # Advanced engine configuration + advanced_engine_control: Optional[dict[str, Any]] = None + # Audio sample_rate: int = 16000 audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE @@ -1020,14 +1025,18 @@ class SegmentMessageSegmentFragment(BaseMessageModel): direction: The direction of the fragment. type_: The type of the fragment. content: The content of the fragment. + attaches_to: The ID of the fragment that this fragment attaches to. """ start_time: float end_time: float language: str = "en" direction: str = "ltr" - type: str = "word" + type: str = Field(default="word", alias="type_") content: str = "" + attaches_to: str = "" + + model_config = ConfigDict(extra="ignore") class SegmentMessageSegment(BaseMessageModel): From ba20f17ee238677dca45b947dd4fa3bafc321797 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 29 Oct 2025 19:38:43 +0000 Subject: [PATCH 179/215] emit EndOfTurnReset when user(s) speak during waiting for turn to end; configurable end of turn penalties --- examples/voice/cli/cli.py | 2 + sdk/voice/README.md | 4 +- sdk/voice/speechmatics/voice/__init__.py | 8 +- sdk/voice/speechmatics/voice/_client.py | 110 +++++++++++------------ sdk/voice/speechmatics/voice/_models.py | 73 +++++++++++++-- 5 files changed, 132 insertions(+), 65 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 1072ddf5..0cd2a73f 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -46,6 +46,7 @@ # End of turn "StartOfTurn": "\033[91m", "EndOfTurnPrediction": "\033[95m", + "EndOfTurnReset": "\033[95m", "EndOfTurn": "\033[1;91m", # Transcript events "AddPartialTranscript": "\033[90m", @@ -386,6 +387,7 @@ def log_message(message) -> None: # Verbose turn prediction if args.verbose >= 2: client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) + client.on(AgentServerMessageType.END_OF_TURN_RESET, log_message) # Metrics if args.verbose >= 4: diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 2c0758e4..6e4e76e6 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -204,7 +204,7 @@ advanced_config = VoiceAgentConfig( transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, smart_turn_config=SmartTurnConfig( audio_buffer_length=8.0, - smart_turn_threshold=0.78, + smart_turn_threshold=0.5, slice_margin=0.1, ), speech_segment_config=SpeechSegmentConfig(), @@ -218,7 +218,7 @@ smart_turn_enabled_config = VoiceAgentConfig( end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, smart_turn_config=SmartTurnConfig( audio_buffer_length=10.0, - smart_turn_threshold=0.75, + smart_turn_threshold=0.5, slice_margin=0.05, ) ) diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index ad51aa4c..7fcdec52 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -23,6 +23,8 @@ from ._client import VoiceAgentClient from ._models import AdditionalVocabEntry from ._models import AgentServerMessageType +from ._models import EndOfTurnConfig +from ._models import EndOfTurnPenaltyItem from ._models import EndOfUtteranceMode from ._models import SegmentMessage from ._models import SessionMetricsMessage @@ -33,7 +35,7 @@ from ._models import SpeechSegmentConfig from ._models import SpeechSegmentEmitMode from ._models import TurnPredictionMessage -from ._models import TurnStartEndMessage +from ._models import TurnStartEndResetMessage from ._models import VADStatusMessage from ._models import VoiceAgentConfig from ._presets import VoiceAgentConfigPreset @@ -46,6 +48,8 @@ "AdditionalVocabEntry", "AudioEncoding", "AudioFormat", + "EndOfTurnConfig", + "EndOfTurnPenaltyItem", "EndOfUtteranceMode", "OperatingPoint", "SpeakerDiarizationConfig", @@ -65,6 +69,6 @@ "SessionMetricsMessage", "SpeakerMetricsMessage", "TurnPredictionMessage", - "TurnStartEndMessage", + "TurnStartEndResetMessage", "VADStatusMessage", ] diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 91b84943..dfcadace 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -52,7 +52,7 @@ from ._models import TranscriptionUpdatePreset from ._models import TurnPredictionMessage from ._models import TurnPredictionMetadata -from ._models import TurnStartEndMessage +from ._models import TurnStartEndResetMessage from ._models import VADStatusMessage from ._models import VoiceAgentConfig from ._smart_turn import SMART_TURN_INSTALL_HINT @@ -1073,16 +1073,21 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Lock the speech fragments if self._current_view and self._current_view.segment_count > 0: async with self._speech_fragments_lock: + # Keep until end of turn (`ON_END_OF_TURN`) + if ( + self._config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN + and not end_of_turn + ): + final_segments = [] + partial_segments = self._current_view.segments + # Force finalize - if finalize: + elif finalize: final_segments = self._current_view.segments partial_segments = [] # Split between finals and interim segments (`ON_FINALIZED_SENTENCE` or `ON_SPEAKER_ENDED`) - elif self._config.speech_segment_config.emit_mode in [ - SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE, - SpeechSegmentEmitMode.ON_SPEAKER_ENDED, - ]: + else: final_segments = [ s for s in self._current_view.segments @@ -1090,11 +1095,6 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ] partial_segments = [s for s in self._current_view.segments if s not in final_segments] - # Keep until end of turn (`ON_END_OF_TURN`) - else: - final_segments = [] - partial_segments = self._current_view.segments - # Emit finals first if final_segments: """Final segments are checked for end of sentence.""" @@ -1204,7 +1204,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Emit self._emit_message( - TurnStartEndMessage( + TurnStartEndResetMessage( message=AgentServerMessageType.END_OF_TURN, turn_id=self._turn_id, metadata=metadata, @@ -1275,41 +1275,28 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: - ends with finalizes end of sentence """ - # Very speaking - if last_active_segment.annotation.has(AnnotationFlags.VERY_SLOW_SPEAKER): - add_multipler_reason(3.0, "very_slow_speaker") - - # Slow speaking - if last_active_segment.annotation.has(AnnotationFlags.SLOW_SPEAKER): - add_multipler_reason(2.0, "slow_speaker") - - # Disfluencies - if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_DISFLUENCY): - add_multipler_reason(2.5, "ends_with_disfluency") - elif last_active_segment.annotation.has(AnnotationFlags.HAS_DISFLUENCY): - add_multipler_reason(0.25, "has_disfluency") - - # Ends with an end of sentence - if last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL): - add_multipler_reason(-0.3, "ends_with_eos_and_final") - - # Does NOT end with end of sentence - if not last_active_segment.annotation.has(AnnotationFlags.ENDS_WITH_EOS): - add_multipler_reason(1.0, "does_not_end_with_eos") - - # If no segments - else: - add_multipler_reason(0, "no_segments") + # Iterate over the penalties + for penalty in self._config.end_of_turn_config.penalties: + description = "+".join(penalty.annotation) + if not penalty.is_not: + if last_active_segment.annotation.has(*penalty.annotation): + add_multipler_reason(penalty.penalty, description) + else: + if not last_active_segment.annotation.has(*penalty.annotation): + add_multipler_reason(penalty.penalty, description) # Smart turn prediction if smart_turn_prediction: if smart_turn_prediction.prediction: - add_multipler_reason(-1.0, "smart_turn_true") + add_multipler_reason(self._config.smart_turn_config.positive_penalty, "smart_turn_true") else: - add_multipler_reason(2.5, "smart_turn_false") + add_multipler_reason(self._config.smart_turn_config.negative_penalty, "smart_turn_false") # Calculate multiplier - multiplier = 1.0 + sum(m for m, _ in reasons) + multiplier = ( + self._config.end_of_turn_config.base_multiplier + + sum(m for m, _ in reasons) * self._config.end_of_turn_config.end_of_turn_adjustment_factor + ) # Minimum delay (50ms as a minimum) delay = round(max(self._config.end_of_utterance_silence_trigger, 0.05) * multiplier, 2) @@ -1318,7 +1305,7 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: clamped_delay = min(delay, self._config.end_of_utterance_max_delay) # Adjust time and make sure no less than 25ms - finalize_delay = max(clamped_delay - self._last_ttfb, 0.025) + finalize_delay = max(clamped_delay - self._last_ttfb, self._config.end_of_turn_config.min_end_of_turn_delay) # Emit prediction if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): @@ -1459,22 +1446,33 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: event_time = speaker_start_time if self._is_speaking else speaker_end_time # Emit start of turn (not when using EXTERNAL) - if ( - not self._end_of_turn_handler.handler_active - and self._is_speaking - and self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL - ): - self._end_of_turn_handler.start_handler() - self._turn_id = self._end_of_turn_handler.handler_id - self._emit_message( - TurnStartEndMessage( - message=AgentServerMessageType.START_OF_TURN, - turn_id=self._turn_id, - metadata=MessageTimeMetadata( - start_time=event_time, + if self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL: + # New turn started + if not self._end_of_turn_handler.handler_active and self._is_speaking: + self._end_of_turn_handler.start_handler() + self._turn_id = self._end_of_turn_handler.handler_id + self._emit_message( + TurnStartEndResetMessage( + message=AgentServerMessageType.START_OF_TURN, + turn_id=self._turn_id, + metadata=MessageTimeMetadata( + start_time=event_time, + ), ), - ), - ) + ) + + # Emit end of turn prediction wrong + elif self._end_of_turn_handler.handler_active and self._is_speaking: + self._end_of_turn_handler.reset() + self._emit_message( + TurnStartEndResetMessage( + message=AgentServerMessageType.END_OF_TURN_RESET, + turn_id=self._turn_id, + metadata=MessageTimeMetadata( + time=event_time, + ), + ), + ) # Emit the event self._emit_message( diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 350694ab..df1defb6 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -188,6 +188,7 @@ class AgentServerMessageType(str, Enum): # End of turn messages START_OF_TURN = "StartOfTurn" END_OF_TURN_PREDICTION = "EndOfTurnPrediction" + END_OF_TURN_RESET = "EndOfTurnReset" END_OF_TURN = "EndOfTurn" # Speaker messages @@ -345,6 +346,52 @@ class SpeechSegmentConfig(BaseModel): emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_SPEAKER_ENDED +class EndOfTurnPenaltyItem(BaseModel): + """End of turn penalty item. + + Parameters: + penalty: Penalty value. + annotation: List of annotations to apply the penalty to. + is_not: Whether the penalty should be applied when the annotation is not present. + """ + + penalty: float + annotation: list[AnnotationFlags] + is_not: bool = False + + +class EndOfTurnConfig(BaseModel): + """Configuration for end of turn. + + Parameters: + base_multiplier: Base multiplier for end of turn delay. + min_end_of_turn_delay: Minimum end of turn delay. + end_of_turn_adjustment_factor: End of turn adjustment factor. + penalties: List of end of turn penalty items. + """ + + base_multiplier: float = 1.75 + min_end_of_turn_delay: float = 0.025 + end_of_turn_adjustment_factor: float = 1.0 + penalties: list[EndOfTurnPenaltyItem] = Field( + default_factory=lambda: [ + EndOfTurnPenaltyItem(penalty=3.0, annotation=[AnnotationFlags.VERY_SLOW_SPEAKER]), + EndOfTurnPenaltyItem(penalty=2.0, annotation=[AnnotationFlags.SLOW_SPEAKER]), + EndOfTurnPenaltyItem(penalty=2.5, annotation=[AnnotationFlags.ENDS_WITH_DISFLUENCY]), + EndOfTurnPenaltyItem(penalty=0.25, annotation=[AnnotationFlags.HAS_DISFLUENCY]), + EndOfTurnPenaltyItem( + penalty=1.0, + annotation=[AnnotationFlags.ENDS_WITH_EOS], + is_not=True, + ), + EndOfTurnPenaltyItem( + penalty=-0.1, + annotation=[AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL], + ), + ] + ) + + class SmartTurnConfig(BaseModel): """Smart turn configuration for the Speechmatics Voice Agent. @@ -361,10 +408,14 @@ class SmartTurnConfig(BaseModel): slice_margin: Margin to add to the audio buffer to ensure that the end of thought models have enough audio to work with. Defaults to 0.05 seconds. + positive_penalty: Positive penalty for smart turn. Defaults to -1.0. + + negative_penalty: Negative penalty for smart turn. Defaults to 2.5. + Examples: >>> config = SmartTurnConfig( ... audio_buffer_length=0.5, - ... smart_turn_threshold=0.75, + ... smart_turn_threshold=0.5, ... slice_margin=0.05 ... ) """ @@ -372,6 +423,8 @@ class SmartTurnConfig(BaseModel): audio_buffer_length: float = 0.0 smart_turn_threshold: float = 0.5 slice_margin: float = 0.05 + positive_penalty: float = -1.0 + negative_penalty: float = 2.5 class VoiceAgentConfig(BaseModel): @@ -466,7 +519,10 @@ class VoiceAgentConfig(BaseModel): and word timings), and `TIMING` (emit on changes to word timings, not recommended). Defaults to `TranscriptionUpdatePreset.COMPLETE`. + end_of_turn_config: End of turn configuration for the Speechmatics Voice Agent. + smart_turn_config: Smart turn configuration for the Speechmatics Voice Agent. + speech_segment_config: Speech segment configuration for the Speechmatics Voice Agent. advanced_engine_control: Internal use only. @@ -528,7 +584,7 @@ class VoiceAgentConfig(BaseModel): ... max_speakers=3, ... end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, ... smart_turn_config=SmartTurnConfig( - ... smart_turn_threshold=0.55 + ... smart_turn_threshold=0.5 ... ), ... additional_vocab=[ ... AdditionalVocabEntry(content="API"), @@ -566,6 +622,7 @@ class VoiceAgentConfig(BaseModel): include_results: bool = False enable_preview_features: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE + end_of_turn_config: EndOfTurnConfig = Field(default_factory=EndOfTurnConfig) smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) speech_segment_config: SpeechSegmentConfig = Field(default_factory=SpeechSegmentConfig) @@ -961,25 +1018,31 @@ class MessageTimeMetadata(BaseMessageModel): """Metadata for segment messages. Parameters: + time: The time of the event. start_time: The start time of the segment. end_time: The end time of the segment. processing_time: The processing time of the segment. """ + time: Optional[float] = None start_time: Optional[float] = None end_time: Optional[float] = None processing_time: Optional[float] = None -class TurnStartEndMessage(BaseMessage): - """Emitted when a turn starts or ends. +class TurnStartEndResetMessage(BaseMessage): + """Emitted when a turn starts, ends or is reset. Parameters: turn_id: The ID of the turn. is_active: Whether the turn is active. """ - message: Literal[AgentServerMessageType.START_OF_TURN, AgentServerMessageType.END_OF_TURN] + message: Literal[ + AgentServerMessageType.START_OF_TURN, + AgentServerMessageType.END_OF_TURN, + AgentServerMessageType.END_OF_TURN_RESET, + ] turn_id: int metadata: MessageTimeMetadata From 6a79ce386a4d85dd0bd6c276e06f63a10ce27694 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 01:36:01 +0000 Subject: [PATCH 180/215] remove DEBUG_MORE and update RT dependency --- examples/voice/cli/cli.py | 2 +- sdk/voice/README.md | 1 - sdk/voice/pyproject.toml | 2 +- sdk/voice/speechmatics/voice/_client.py | 26 ------------------------- 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 0cd2a73f..c5ed15ed 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -133,7 +133,7 @@ async def main() -> None: # Debug the config if args.verbose >= 1: - print(f"Configuration used:\n{config.model_dump_json(exclude_none=True)}\n") + print(f"Configuration used:\n{config.model_dump_json(exclude_none=True, exclude_unset=True)}\n") # Connect to the Voice Agent service try: diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 6e4e76e6..368c510a 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -363,7 +363,6 @@ When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. - `SPEECHMATICS_API_KEY` - Your Speechmatics API key - `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) -- `SPEECHMATICS_DEBUG_MORE` - Enable verbose debugging (optional) - `SMART_TURN_MODEL_PATH` - Path for the SMART_TURN ONNX model (optional) - `SMART_TURN_HF_URL` - Override download URL for the SMART_TURN ONNX model (optional) diff --git a/sdk/voice/pyproject.toml b/sdk/voice/pyproject.toml index fc093666..239d3958 100644 --- a/sdk/voice/pyproject.toml +++ b/sdk/voice/pyproject.toml @@ -11,7 +11,7 @@ authors = [{ name = "Speechmatics", email = "support@speechmatics.com" }] license = "MIT" requires-python = ">=3.9" dependencies = [ - "speechmatics-rt>=0.5.0", + "speechmatics-rt>=0.5.1", "pydantic>=2.10.6,<3", "numpy>=1.26.4,<3" ] diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index dfcadace..c9408cf6 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -61,12 +61,6 @@ from ._turn import TurnTaskProcessor from ._utils import FragmentUtils -DEBUG_MORE = os.getenv("SPEECHMATICS_DEBUG_MORE", "0").lower() in ["1", "true"] - - -if DEBUG_MORE: - import json - class VoiceAgentClient(AsyncClient): """Voice Agent client. @@ -656,8 +650,6 @@ def _register_event_handlers(self) -> None: # Recognition started event @self.once(ServerMessageType.RECOGNITION_STARTED) # type: ignore[misc] def _evt_on_recognition_started(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) self._is_ready_for_audio = True self._client_session = ClientSessionInfo( config=self._config, @@ -669,15 +661,11 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) # type: ignore[misc] def _evt_on_partial_transcript(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=False)) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] def _evt_on_final_transcript(message: dict[str, Any]) -> None: - if DEBUG_MORE: - self._logger.debug(json.dumps(message)) self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=True)) def _emit_message(self, message: BaseMessage) -> None: @@ -902,7 +890,6 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Metadata metadata = message.get("metadata", {}) - payload_start_time = metadata.get("start_time", 0) payload_end_time = metadata.get("end_time", 0) # Iterate over the results in the payload @@ -978,19 +965,6 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = if not is_final: self._vad_evaluation(fragments) - # Debug the fragments - if DEBUG_MORE: - debug_payload = { - "final": is_final, - "start_time": payload_start_time, - "end_time": payload_end_time, - "keeping": [f.content for f in retained_fragments], - "adding": [f.content for f in fragments], - "transcript": metadata.get("transcript", ""), - "full": [[f.content, f.start_time, f.end_time, f.is_final] for f in self._speech_fragments], - } - self._logger.debug(json.dumps(debug_payload)) - # Update TTFB (only if there are listeners) if not is_final: self._calculate_ttfb(end_time=payload_end_time) From 97afd86b30ed5d626f8e7845a8ba39d4192b7547 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 01:39:45 +0000 Subject: [PATCH 181/215] support for presets and update tests --- sdk/voice/speechmatics/voice/_presets.py | 71 +++++++++++++++++------- tests/voice/test_04_models.py | 33 +++++++++++ 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 3a64bb89..261f41f0 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -14,23 +14,26 @@ class VoiceAgentConfigPreset: """Set of preset configurations for the Voice Agent SDK.""" @staticmethod - def LOW_LATENCY() -> VoiceAgentConfig: # noqa: N802 + def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for low latency situations. This configuration will emit the end of turn as soon as possible, with minimal delay to finalizing the spoken sentences. It is not recommended for conversation, as it will not account for pauses, slow speech or disfluencies. """ - return VoiceAgentConfig( - enable_diarization=True, - max_delay=0.7, - end_of_utterance_silence_trigger=0.2, - end_of_utterance_mode=EndOfUtteranceMode.FIXED, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + return VoiceAgentConfigPreset._merge_configs( + VoiceAgentConfig( + enable_diarization=True, + max_delay=0.7, + end_of_utterance_silence_trigger=0.2, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + ), + overlay, ) @staticmethod - def CONVERSATION_ADAPTIVE() -> VoiceAgentConfig: # noqa: N802 + def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for general conversational use cases. For conversation, there is a balance between accuracy, speed and the rate at @@ -38,15 +41,18 @@ def CONVERSATION_ADAPTIVE() -> VoiceAgentConfig: # noqa: N802 finalizing the spoken sentences will be adjusted based on the words and whether there are any pauses, slow speech or disfluencies. """ - return VoiceAgentConfig( - enable_diarization=True, - max_delay=0.7, - end_of_utterance_silence_trigger=0.5, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + return VoiceAgentConfigPreset._merge_configs( + VoiceAgentConfig( + enable_diarization=True, + max_delay=0.7, + end_of_utterance_silence_trigger=0.5, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + ), + overlay, ) @staticmethod - def CONVERSATION_SMART_TURN() -> VoiceAgentConfig: # noqa: N802 + def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for complex conversational use cases. For conversation, there is a balance between accuracy, speed and the rate at @@ -60,9 +66,36 @@ def CONVERSATION_SMART_TURN() -> VoiceAgentConfig: # noqa: N802 Use of this will requite `pip install speechmatics-voice[smart]` and may not be suited to low-power devices. """ - return VoiceAgentConfig( - enable_diarization=True, - max_delay=0.85, - end_of_utterance_silence_trigger=0.5, - end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + return VoiceAgentConfigPreset._merge_configs( + VoiceAgentConfig( + enable_diarization=True, + max_delay=0.85, + end_of_utterance_silence_trigger=0.5, + end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + ), + overlay, ) + + @staticmethod + def _merge_configs(base: VoiceAgentConfig, overlay: VoiceAgentConfig | None) -> VoiceAgentConfig: + """Merge two VoiceAgentConfig objects. + + Simply merge any overrides from the overlay into the base config. This makes creating + custom configs from presets easier. + + Args: + base: Base config to merge into. + overlay: Overlay config to merge from. + + Returns: + Merged config. + + """ + + # No overlay required + if overlay is None: + return base + + # Merge overlay into base - use model_validate to properly reconstruct nested models + merged_dict = {**base.model_dump(), **overlay.model_dump(exclude_unset=True)} + return VoiceAgentConfig.model_validate(merged_dict) # type: ignore[no-any-return] diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 402315f5..43eda7f4 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -11,6 +11,9 @@ from speechmatics.voice._models import SpeakerIdentifier from speechmatics.voice._models import SpeakerSegment from speechmatics.voice._models import SpeechFragment +from speechmatics.voice._models import SpeechSegmentConfig +from speechmatics.voice._models import SpeechSegmentEmitMode +from speechmatics.voice._presets import VoiceAgentConfigPreset @pytest.mark.asyncio @@ -245,3 +248,33 @@ async def test_speaker_segment(): assert "results" in dict_data_results assert "fragments" not in dict_data_results assert len(dict_data_results["results"]) == 2 + + +@pytest.mark.asyncio +async def test_presets(): + """Test VoiceAgentConfigPreset presets.""" + + # Create a preset + low_latency: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY() + assert low_latency is not None + assert low_latency.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE + + # Overlay #1 + low_latency_one: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( + VoiceAgentConfig(max_delay=12.34, enable_diarization=False) + ) + assert low_latency_one is not None + assert low_latency_one.max_delay == 12.34 + assert low_latency_one.enable_diarization is False + + print(low_latency_one.model_dump_json()) + + # Overlay #2 + low_latency_two: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( + VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_SPEAKER_ENDED)) + ) + assert low_latency_two is not None + assert low_latency_two.enable_diarization is True + assert low_latency_two.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_SPEAKER_ENDED + + print(low_latency_two.model_dump_json()) From 9e012dafbfb6b42d799ffb5b8c7e0b5f1d0c83b8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 01:40:05 +0000 Subject: [PATCH 182/215] update attribute name in __str__ --- sdk/voice/speechmatics/voice/_turn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index 3f8b9ca1..921d6df5 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -158,4 +158,4 @@ def __str__(self) -> str: Returns: String representation of the processor state. """ - return f"TurnTaskProcessor(name={self._name}, turn_id={self._handler_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" + return f"TurnTaskProcessor(name={self._name}, handler_id={self._handler_id}, tasks={self._tasks.keys()}, pending={self.has_pending_tasks})" From e3af4dadc91123b6c36f97cd9c305698a8cb43ef Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 02:01:56 +0000 Subject: [PATCH 183/215] updated handling of end of turn; faster response with preview server; adjustable penalties for end of turn delay; new defaults --- sdk/voice/speechmatics/voice/_client.py | 180 ++++++++++++++---------- tests/voice/test_04_models.py | 4 - tests/voice/test_05_utterance.py | 6 +- 3 files changed, 111 insertions(+), 79 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c9408cf6..df947340 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -231,6 +231,31 @@ def __init__( self._logger.warning("Smart Turn model not available. Falling back to ADAPTIVE.") self._config.end_of_utterance_mode = EndOfUtteranceMode.ADAPTIVE + # EOU mode + self._eou_mode: EndOfUtteranceMode = self._config.end_of_utterance_mode + + # Uses fixed EndOfUtterance message + self._uses_fixed_eou: bool = self._eou_mode == EndOfUtteranceMode.FIXED + + # Uses ForceEndOfUtterance message + self._uses_forced_eou: bool = self._config.enable_preview_features and self._eou_mode in [ + EndOfUtteranceMode.ADAPTIVE, + EndOfUtteranceMode.SMART_TURN, + ] + + # EOT start / stop messages + self._uses_turn_start_end: bool = self._eou_mode in [ + EndOfUtteranceMode.FIXED, + EndOfUtteranceMode.ADAPTIVE, + EndOfUtteranceMode.SMART_TURN, + ] + + # EOT prediction + self._uses_eot_prediction: bool = self._eou_mode in [ + EndOfUtteranceMode.ADAPTIVE, + EndOfUtteranceMode.SMART_TURN, + ] + # Diarization / speaker focus self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config @@ -563,6 +588,10 @@ def update_diarization_config(self, config: SpeakerFocusConfig) -> None: """ self._dz_config = config + # ============================================================================ + # PUBLIC UTTERANCE / TURN MANAGEMENT + # ============================================================================ + def finalize(self, ttl: Optional[float] = None, end_of_turn: bool = False) -> None: """Finalize segments. @@ -590,17 +619,9 @@ async def emit() -> None: if ttl is not None and ttl > 0: await asyncio.sleep(ttl) - # Emit segments or finalize STT message - if self._config.enable_preview_features: + # Emit segments or finalize STT message (only for ADAPTIVE and SMART_TURN) + if self._uses_forced_eou and not end_of_turn: """Uses the forced end of utterance message to emit segments and finalize.""" - - # Listen for the `END_OF_UTTERANCE` event - @self.once(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] - def _on_eou(message: dict[str, Any]) -> None: - self._stt_message_queue.put_nowait( - lambda: self._emit_segments(finalize=True, end_of_turn=end_of_turn) - ) - # Emit the message await self.force_end_of_utterance() @@ -668,6 +689,16 @@ def _evt_on_partial_transcript(message: dict[str, Any]) -> None: def _evt_on_final_transcript(message: dict[str, Any]) -> None: self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=True)) + # End of Utterance (FIXED mode only) + if self._uses_fixed_eou: + + @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] + def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: + async def _trigger_end_of_turn() -> None: + self.end_of_turn() + + self._stt_message_queue.put_nowait(_trigger_end_of_turn) + def _emit_message(self, message: BaseMessage) -> None: """Emit a message to the client. @@ -1185,6 +1216,9 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ), ) + # Stop the EOT handler + self._end_of_turn_handler.complete_handler() + # Reset the previous view self._previous_view = None self._turn_start_time = None @@ -1203,9 +1237,6 @@ async def _calculate_finalize_delay( the segments to the client. Args: - view: The speaker fragment to evaluate. - view_changes: The annotation result to use for evaluation. - filter_flags: The annotation flags to use for evaluation. smart_turn_prediction: The smart turn prediction result to use for evaluation. Returns: @@ -1251,13 +1282,13 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: # Iterate over the penalties for penalty in self._config.end_of_turn_config.penalties: - description = "+".join(penalty.annotation) + description = "__".join(penalty.annotation) if not penalty.is_not: if last_active_segment.annotation.has(*penalty.annotation): add_multipler_reason(penalty.penalty, description) else: if not last_active_segment.annotation.has(*penalty.annotation): - add_multipler_reason(penalty.penalty, description) + add_multipler_reason(penalty.penalty, f"not__{description}") # Smart turn prediction if smart_turn_prediction: @@ -1282,16 +1313,15 @@ def add_multipler_reason(multiplier: float, reason: str) -> None: finalize_delay = max(clamped_delay - self._last_ttfb, self._config.end_of_turn_config.min_end_of_turn_delay) # Emit prediction - if self.listeners(AgentServerMessageType.END_OF_TURN_PREDICTION): - self._emit_message( - TurnPredictionMessage( - turn_id=self._turn_id, - metadata=TurnPredictionMetadata( - ttl=round(finalize_delay, 2), - reasons=[_reason for _, _reason in reasons], - ), + self._emit_message( + TurnPredictionMessage( + turn_id=self._turn_id, + metadata=TurnPredictionMetadata( + ttl=round(finalize_delay, 2), + reasons=[_reason for _, _reason in reasons], ), - ) + ), + ) # Return the time return finalize_delay @@ -1332,7 +1362,7 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn return prediction # ============================================================================ - # VAD (VOICE ACTIVITY DETECTION) + # VAD (VOICE ACTIVITY DETECTION) / SPEAKER DETECTION # ============================================================================ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: @@ -1420,30 +1450,32 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: event_time = speaker_start_time if self._is_speaking else speaker_end_time # Emit start of turn (not when using EXTERNAL) - if self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL: - # New turn started - if not self._end_of_turn_handler.handler_active and self._is_speaking: - self._end_of_turn_handler.start_handler() - self._turn_id = self._end_of_turn_handler.handler_id + if self._uses_turn_start_end: + """Trigger a start of turn message and also reset pending end of turn prediction.""" + + # Emit end of turn prediction was wrong (turn continues) + if self._uses_eot_prediction and self._end_of_turn_handler.handler_active and self._is_speaking: + self._end_of_turn_handler.reset() self._emit_message( TurnStartEndResetMessage( - message=AgentServerMessageType.START_OF_TURN, + message=AgentServerMessageType.END_OF_TURN_RESET, turn_id=self._turn_id, metadata=MessageTimeMetadata( - start_time=event_time, + time=event_time, ), ), ) - # Emit end of turn prediction wrong - elif self._end_of_turn_handler.handler_active and self._is_speaking: - self._end_of_turn_handler.reset() + # New turn started + elif self._is_speaking and not self._end_of_turn_handler.handler_active: + self._end_of_turn_handler.start_handler() + self._turn_id = self._end_of_turn_handler.handler_id self._emit_message( TurnStartEndResetMessage( - message=AgentServerMessageType.END_OF_TURN_RESET, + message=AgentServerMessageType.START_OF_TURN, turn_id=self._turn_id, metadata=MessageTimeMetadata( - time=event_time, + start_time=event_time, ), ), ) @@ -1462,51 +1494,57 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: ), ) + # Speaking has started + if self._is_speaking: + self._handle_speaker_started() + # Speaking has stopped - if not self._is_speaking: - """Reset the current speaker and do smart turn detection (if enabled).""" + else: + self._handle_speaker_stopped(speaker_end_time) - # Reset current speaker - self._current_speaker = None + def _handle_speaker_started(self) -> None: + """Reset timers when a new speaker starts speaking after silence.""" - # Add task for end of utterance - self._end_of_utterance_handler.update_timer(self._config.end_of_utterance_silence_trigger) + # Reset the handlers + self._end_of_utterance_handler.reset() + self._end_of_turn_handler.reset() - # For ADAPTIVE and SMART_TURN only - if self._config.end_of_utterance_mode is not EndOfUtteranceMode.EXTERNAL: - """When not EXTERNAL, we need to do EOT detection / prediction.""" + def _handle_speaker_stopped(self, speaker_end_time: float) -> None: + """Reset the current speaker and do smart turn detection (if enabled).""" - # Callback - async def do_eot_detection(end_time: float, language: str) -> None: - try: - # Wait for Smart Turn result - if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: - result = await self._predict_smart_turn(end_time, language) - else: - result = None + # Reset current speaker + self._current_speaker = None - # Create a new task to evaluate the finalize delay - delay = await self._calculate_finalize_delay(smart_turn_prediction=result) + # Add task for end of utterance + self._end_of_utterance_handler.update_timer(self._config.end_of_utterance_silence_trigger) - # Set the finalize timer (go now if no delay) - self._end_of_turn_handler.update_timer(delay or 0.01) + # For ADAPTIVE and SMART_TURN only + if self._uses_eot_prediction: + """When not EXTERNAL, we need to do EOT detection / prediction.""" - except asyncio.CancelledError: - pass + # Callback + async def do_eot_detection(end_time: float, language: str) -> None: + try: + # Wait for Smart Turn result + if self._eou_mode == EndOfUtteranceMode.SMART_TURN: + result = await self._predict_smart_turn(end_time, language) + else: + result = None - # Add task - self._end_of_turn_handler.add_task( - asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), - self._config.end_of_utterance_mode.value, - ) + # Create a new task to evaluate the finalize delay + delay = await self._calculate_finalize_delay(smart_turn_prediction=result) - # Speaking has started - else: - """When speaking has started, reset speaking-related variables.""" + # Set the finalize timer (go now if no delay) + self._end_of_turn_handler.update_timer(delay or 0.01) + + except asyncio.CancelledError: + pass - # Reset the handlers - self._end_of_utterance_handler.reset() - self._end_of_turn_handler.reset() + # Add task + self._end_of_turn_handler.add_task( + asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), + self._eou_mode.value, + ) # ============================================================================ # HELPER METHODS diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 43eda7f4..ecd551ba 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -267,8 +267,6 @@ async def test_presets(): assert low_latency_one.max_delay == 12.34 assert low_latency_one.enable_diarization is False - print(low_latency_one.model_dump_json()) - # Overlay #2 low_latency_two: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_SPEAKER_ENDED)) @@ -276,5 +274,3 @@ async def test_presets(): assert low_latency_two is not None assert low_latency_two.enable_diarization is True assert low_latency_two.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_SPEAKER_ENDED - - print(low_latency_two.model_dump_json()) diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index bc27dadc..a652f87b 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -310,9 +310,7 @@ async def test_end_of_utterance_adaptive_vad(): # Test conversation log = ConversationLog(os.path.join(os.path.dirname(__file__), "./assets/chat2.jsonl")) - chat = log.get_conversation( - ["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript", "EndOfUtterance"] - ) + chat = log.get_conversation(["Info", "RecognitionStarted", "AddPartialTranscript", "AddTranscript"]) # Start time start_time = datetime.datetime.now() @@ -393,7 +391,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): assert last_final_time is not None # Timing info - timeout = adaptive_timeout * 1.5 + timeout = adaptive_timeout * 2.0 # Wait for EndOfUtterance try: From 1e79f297a72a933c0ca10a8107048b62f7bc6712 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 10:44:15 +0000 Subject: [PATCH 184/215] ambient scribe example --- examples/voice/scribe/README.md | 69 ++++++++++ examples/voice/scribe/scribe.py | 162 +++++++++++++++++++++++ examples/voice/scribe/vocab.json | 16 +++ examples/voice/simple/simple.py | 2 +- sdk/voice/speechmatics/voice/_presets.py | 18 +++ 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 examples/voice/scribe/README.md create mode 100644 examples/voice/scribe/scribe.py create mode 100644 examples/voice/scribe/vocab.json diff --git a/examples/voice/scribe/README.md b/examples/voice/scribe/README.md new file mode 100644 index 00000000..d2a1aebb --- /dev/null +++ b/examples/voice/scribe/README.md @@ -0,0 +1,69 @@ +# Ambient Scribe / Note-Taking + +Real-time ambient transcription for note-taking and documentation. Designed for continuous background transcription where the output feeds into another system. + +## Quick Start + +```bash +export SPEECHMATICS_API_KEY=your_api_key +python scribe.py +``` + +Press `CTRL+C` to stop. + +## What It Does + +- Transcribes conversations in real-time for note-taking +- Uses the SCRIBE preset optimized for ambient transcription +- Shows partial results (yellow) that update as speech continues +- Shows final results (green) with timestamps when sentences complete +- Identifies different speakers (S1, S2, etc.) +- Ideal for meeting notes, interview transcription, or ambient documentation + +## Output Example + +``` +Microphone ready - speak now... (Press CTRL+C to stop) + +00:00:03 - S1: Hello, how are you today? +00:00:07 - S2: I'm doing great, thanks for asking. +00:00:12 - S1: That's wonderful to hear. + listening ... +``` + +Partial results appear in yellow and update on the same line. Once finalized, they turn green with a timestamp showing elapsed time since session start. + +## Requirements + +- Speechmatics API key from the [portal](https://portal.speechmatics.com/) +- PyAudio: `pip install pyaudio` +- Install SDK dependencies: see [examples README](../README.md) + +## Configuration + +This example uses the SCRIBE preset: + +- Fixed end of utterance detection +- 1.2s max delay for smooth note-taking +- Emits finalized sentences as they complete +- Speaker diarization enabled + +### Custom Vocabulary + +The example automatically loads custom vocabulary from `vocab.json` if present. This helps improve transcription accuracy for domain-specific terms, brand names, or technical jargon. + +**Example `vocab.json`:** + +```json +[ + { + "content": "Speechmatics", + "sounds_like": ["speech matics", "speech mattics"] + }, + { + "content": "API" + } +] +``` + +Edit `vocab.json` to add your own terms without modifying the Python code. diff --git a/examples/voice/scribe/scribe.py b/examples/voice/scribe/scribe.py new file mode 100644 index 00000000..99b76cca --- /dev/null +++ b/examples/voice/scribe/scribe.py @@ -0,0 +1,162 @@ +"""Simple microphone transcription example. + +This example demonstrates basic real-time transcription with speaker diarization +using the default microphone. It prints partial segments, final segments, and +end-of-turn events. +""" + +import asyncio +import json +import os +from enum import Enum +from pathlib import Path + +from speechmatics.rt import Microphone +from speechmatics.voice import AdditionalVocabEntry +from speechmatics.voice import AgentServerMessageType +from speechmatics.voice import VoiceAgentClient +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice import VoiceAgentConfigPreset + + +class Color(Enum): + PARTIAL = "\033[93m" + FINAL = "\033[1;92m" + WAITING = "\033[95m" + RESET = "\033[0m" + + +async def main() -> None: + """Run simple microphone transcription.""" + + # Get API key from environment + api_key = os.getenv("SPEECHMATICS_API_KEY") + if not api_key: + print("Error: SPEECHMATICS_API_KEY environment variable not set") + return + + # Setup microphone with default device + mic = Microphone(sample_rate=16000, chunk_size=320) + if not mic.start(): + print("Error: PyAudio not available - install with: pip install pyaudio") + return + + # Load additional vocabulary from vocab.json if it exists + vocab_file = Path(__file__).parent / "vocab.json" + additional_vocab = [] + if vocab_file.exists(): + with open(vocab_file) as f: + vocab_data = json.load(f) + additional_vocab = [ + AdditionalVocabEntry(content=entry["content"], sounds_like=entry.get("sounds_like", [])) + for entry in vocab_data + ] + + # Use the SCRIBE preset with additional vocabulary + config = VoiceAgentConfigPreset.SCRIBE(VoiceAgentConfig(language="en", additional_vocab=additional_vocab)) + + # Create client + client = VoiceAgentClient(api_key=api_key, config=config) + + # Track waiting state + waiting_displayed = False + + # Show listening message + def show_listening(): + """Show listening for audio.""" + nonlocal waiting_displayed + if not waiting_displayed: + print(f"\r\033[K{Color.WAITING.value} listening ... {Color.RESET.value}", end="", flush=True) + waiting_displayed = True + + # Format timestamp from start_time (seconds since session start) + def format_time(seconds: float) -> str: + """Format seconds as HH:MM:SS timestamp.""" + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + secs = int(seconds % 60) + return f"{hours:02d}:{minutes:02d}:{secs:02d}" + + # Handle partial segments (interim results) + def on_partial_segment(message): + """Print partial segment(s) as yellow.""" + + # Clear waiting message + nonlocal waiting_displayed + waiting_displayed = False + + # Segments + segments = message.get("segments", []) + if not segments: + return + + # Get metadata start_time + metadata = message.get("metadata", {}) + start_time = metadata.get("start_time", 0) + timestamp = format_time(start_time) + + # Move to beginning of line, clear it, and print yellow partial with timestamp + for segment in segments: + print( + f"\r\033[K{Color.PARTIAL.value}{timestamp} - {segment['speaker_id']}: {segment['text']}{Color.RESET.value}", + end="", + flush=True, + ) + + # Handle final segments + def on_segment(message): + """Print final segment(s) as green.""" + + # Segments + segments = message.get("segments", []) + if not segments: + return + + # Get metadata start_time + metadata = message.get("metadata", {}) + start_time = metadata.get("start_time", 0) + timestamp = format_time(start_time) + + # Clear line, print green final with timestamp, then newline + for segment in segments: + print( + f"\r\033[K{Color.FINAL.value}{timestamp} - {segment['speaker_id']}: {segment['text']}{Color.RESET.value}", + flush=True, + ) + + # Show listening message + show_listening() + + # Register event handlers + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, on_partial_segment) + client.on(AgentServerMessageType.ADD_SEGMENT, on_segment) + + # Instructions + print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") + + # Connect to the service + await client.connect() + + # Show initial listening message + show_listening() + + # Stream audio from microphone + async def stream_audio(): + while True: + audio_chunk = await mic.read(160) + await client.send_audio(audio_chunk) + + # Run until interrupted + try: + await stream_audio() + except KeyboardInterrupt: + print("\n\nStopping...") + except asyncio.CancelledError: + pass + + # Disconnect + await client.disconnect() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/voice/scribe/vocab.json b/examples/voice/scribe/vocab.json new file mode 100644 index 00000000..aca0da4f --- /dev/null +++ b/examples/voice/scribe/vocab.json @@ -0,0 +1,16 @@ +[ + { + "content": "Speechmatics", + "sounds_like": ["speech matics", "speech mattics"] + }, + { + "content": "API" + }, + { + "content": "WebSocket" + }, + { + "content": "OAuth", + "sounds_like": ["oh auth", "o auth"] + } +] diff --git a/examples/voice/simple/simple.py b/examples/voice/simple/simple.py index 13ed9163..8b7f1b52 100644 --- a/examples/voice/simple/simple.py +++ b/examples/voice/simple/simple.py @@ -73,7 +73,7 @@ def on_end_of_turn(message): # Stream audio from microphone async def stream_audio(): while True: - audio_chunk = await mic.read(320) + audio_chunk = await mic.read(160) await client.send_audio(audio_chunk) # Run until interrupted diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 261f41f0..c016815c 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -76,6 +76,24 @@ def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAge overlay, ) + @staticmethod + def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 + """Best suited for note-taking and scribes. + + This mode will emit partial and final segments as they become available. The end of + utterance is set to fixed. End of turn is not required for note-taking. + """ + return VoiceAgentConfigPreset._merge_configs( + VoiceAgentConfig( + enable_diarization=True, + max_delay=1.2, + end_of_utterance_silence_trigger=1.0, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + ), + overlay, + ) + @staticmethod def _merge_configs(base: VoiceAgentConfig, overlay: VoiceAgentConfig | None) -> VoiceAgentConfig: """Merge two VoiceAgentConfig objects. From afe6ac5b1e5b6a11d91decef713882254d381380 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 30 Oct 2025 10:47:31 +0000 Subject: [PATCH 185/215] updated examples --- examples/voice/scribe/scribe.py | 2 +- examples/voice/simple/simple.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/voice/scribe/scribe.py b/examples/voice/scribe/scribe.py index 99b76cca..2b6822cc 100644 --- a/examples/voice/scribe/scribe.py +++ b/examples/voice/scribe/scribe.py @@ -143,7 +143,7 @@ def on_segment(message): # Stream audio from microphone async def stream_audio(): while True: - audio_chunk = await mic.read(160) + audio_chunk = await mic.read(320) await client.send_audio(audio_chunk) # Run until interrupted diff --git a/examples/voice/simple/simple.py b/examples/voice/simple/simple.py index 8b7f1b52..13ed9163 100644 --- a/examples/voice/simple/simple.py +++ b/examples/voice/simple/simple.py @@ -73,7 +73,7 @@ def on_end_of_turn(message): # Stream audio from microphone async def stream_audio(): while True: - audio_chunk = await mic.read(160) + audio_chunk = await mic.read(320) await client.send_audio(audio_chunk) # Run until interrupted From 19f1d9f674cac01f71fca1ebe8990b33b981cf0e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 1 Nov 2025 16:54:31 +0000 Subject: [PATCH 186/215] split processing functions within client. support excluding partial fragments from results. --- examples/voice/cli/cli.py | 21 ++- examples/voice/scribe/README.md | 3 +- sdk/voice/speechmatics/voice/_client.py | 162 ++++++++++++++--------- sdk/voice/speechmatics/voice/_models.py | 11 ++ sdk/voice/speechmatics/voice/_presets.py | 5 +- sdk/voice/speechmatics/voice/_utils.py | 34 +++-- 6 files changed, 150 insertions(+), 86 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index c5ed15ed..5c42c4fc 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -11,6 +11,7 @@ import os import wave from pathlib import Path +from typing import Any from utils import AudioPlayer from utils import select_audio_device @@ -329,22 +330,22 @@ def register_event_handlers(client: VoiceAgentClient, args, start_time: datetime start_time: Start time for timestamp calculation """ - def console_print(message) -> None: + def console_print(ts: datetime.datetime, message: dict) -> None: """Print message to console with optional formatting.""" if not args.pretty: print(json.dumps(message)) return # Extract common data - ts = message["ts"] + ts_str = ts.strftime("%H:%M:%S") + f".{ts.microsecond // 1000:03d}" msg_type = message["message"] color = COLORS.get(msg_type, "") - payload = {k: v for k, v in message.items() if k not in ("ts", "message")} + payload = message # Handle segment messages if msg_type in ("AddPartialSegment", "AddSegment"): _segs = [] - for segment in payload["segments"]: + for segment in message["segments"]: suffix = "" if segment["is_active"] else " (background)" if args.verbose >= 3: _segs.append(f"@{segment['speaker_id']}{suffix}: `{segment['text']}` {segment['annotation']}") @@ -353,17 +354,15 @@ def console_print(message) -> None: payload = {"segments": _segs} # Print to console - print(f"{color}{ts:7.3f} {msg_type:<24} {json.dumps(payload)}\033[0m") + print(f"{color}{ts_str} {msg_type:<24} {json.dumps(payload)}\033[0m") - def log_message(message) -> None: + def log_message(message: dict[str, Any]) -> None: """Log message to console and optional JSONL file.""" - ts = (datetime.datetime.now() - start_time).total_seconds() - message = {"ts": round(ts, 3), **message} - console_print(message) - + now = datetime.datetime.now() + console_print(now, message) if args.output_file: with open(args.output_file, "a") as f: - f.write(json.dumps(message) + "\n") + f.write(json.dumps({"ts": now, **message}) + "\n") # Register standard handlers client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) diff --git a/examples/voice/scribe/README.md b/examples/voice/scribe/README.md index d2a1aebb..b33bc5f1 100644 --- a/examples/voice/scribe/README.md +++ b/examples/voice/scribe/README.md @@ -5,7 +5,8 @@ Real-time ambient transcription for note-taking and documentation. Designed for ## Quick Start ```bash -export SPEECHMATICS_API_KEY=your_api_key +export SPEECHMATICS_API_KEY=your_api_key # Required +export SPEECHMATICS_RT_URL=endpoint_url # Optional python scribe.py ``` diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index df947340..1412870a 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -129,6 +129,10 @@ def __init__( # Logger self._logger = get_logger(__name__) + # ------------------------------------- + # Client Configuration + # ------------------------------------- + # Process the config self._config, self._transcription_config, self._audio_format = self._prepare_config(config) @@ -144,11 +148,15 @@ def __init__( language_pack_info=LanguagePackInfo.model_validate({}), ) + # ------------------------------------- + # Transcription Change Filter + # ------------------------------------- + # Change filter to emit segments self._change_filter: list[AnnotationFlags] = [ AnnotationFlags.NEW, - AnnotationFlags.UPDATED_PARTIALS, - AnnotationFlags.UPDATED_FINALS, + # AnnotationFlags.UPDATED_PARTIALS, + # AnnotationFlags.UPDATED_FINALS, ] # Full text has changed @@ -173,46 +181,39 @@ def __init__( self._stt_message_queue: asyncio.Queue[Callable[[], Awaitable[None]]] = asyncio.Queue() self._stt_queue_task: Optional[asyncio.Task] = None - # Timing info + # ------------------------------------- + # Session Timing + # ------------------------------------- + self._total_time: float = 0 self._total_bytes: int = 0 - - # Latency metrics self._last_ttfb: float = 0 - # Time to disregard speech fragments before - self._trim_before_time: float = 0 + # ------------------------------------- + # Segment Tracking + # ------------------------------------- - # Current utterance speech data + self._trim_before_time: float = 0 self._fragment_idx: int = 0 + self._last_fragment_end_time: float = 0 self._speech_fragments: list[SpeechFragment] = [] self._speech_fragments_lock: asyncio.Lock = asyncio.Lock() self._current_view: Optional[SpeakerSegmentView] = None self._previous_view: Optional[SpeakerSegmentView] = None + # ------------------------------------- + # EOU / EOT + # ------------------------------------- + + # Handlers + self._eou_handler: TurnTaskProcessor = TurnTaskProcessor(name="eou_handler", done_callback=self.finalize) + self._eot_handler: TurnTaskProcessor = TurnTaskProcessor(name="eot_handler", done_callback=self.end_of_turn) + self._turn_detector: Optional[SmartTurnDetector] = None + # Current turn self._turn_id: int = 0 self._turn_start_time: Optional[float] = None - # Speaking states - self._session_speakers: dict[str, SessionSpeaker] = {} - self._is_speaking: bool = False - self._current_speaker: Optional[str] = None - self._last_fragment_end_time: float = 0 - - # End of utterance handler - self._end_of_utterance_handler: TurnTaskProcessor = TurnTaskProcessor( - name="eou_handler", done_callback=self.finalize - ) - - # End of turn handler - self._end_of_turn_handler: TurnTaskProcessor = TurnTaskProcessor( - name="eot_handler", done_callback=self.end_of_turn - ) - - # Turn detector - self._turn_detector: Optional[SmartTurnDetector] = None - # Start turn detector if SMART_TURN requested if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: eou_mode_ok: bool = False @@ -256,14 +257,28 @@ def __init__( EndOfUtteranceMode.SMART_TURN, ] - # Diarization / speaker focus + # ------------------------------------- + # Diarization / Speakers + # ------------------------------------- + + self._session_speakers: dict[str, SessionSpeaker] = {} + self._is_speaking: bool = False + self._current_speaker: Optional[str] = None + self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config - # Metrics emitter task + # ------------------------------------- + # Metrics + # ------------------------------------- + self._metrics_emitter_interval: float = 5.0 self._metrics_emitter_task: Optional[asyncio.Task] = None + # ------------------------------------- + # Audio + # ------------------------------------- + # Audio sampling info self._audio_sample_rate: int = self._audio_format.sample_rate self._audio_sample_width: int = { @@ -469,6 +484,10 @@ async def disconnect(self) -> None: if not self._is_connected: return + # Emit final metrics + self._emit_speaker_metrics() + self._emit_metrics() + # Stop audio and metrics tasks self._is_ready_for_audio = False self._stop_metrics_task() @@ -482,8 +501,8 @@ async def disconnect(self) -> None: self._is_connected = False # Stop end of turn-related tasks - self._end_of_utterance_handler.cancel_tasks() - self._end_of_turn_handler.cancel_tasks() + self._eou_handler.cancel_tasks() + self._eot_handler.cancel_tasks() # Stop the STT queue task if self._stt_queue_task: @@ -630,7 +649,7 @@ async def emit() -> None: await self._emit_segments(finalize=True, end_of_turn=end_of_turn) # Cancel any pending tasks for end of utterance (as end of turn will emit segments) - self._end_of_utterance_handler.complete_handler() + self._eou_handler.complete_handler() # Add to queue self._stt_message_queue.put_nowait(emit) @@ -779,26 +798,26 @@ async def emit_metrics() -> None: time_to_wait = next_emission_time - self._total_time await asyncio.sleep(min(0.25, time_to_wait)) - # Calculations - total_time = self._total_time - total_bytes = self._total_bytes + # Update tracker + last_emission_time = self._total_time # Emit metrics - self._emit_message( - SessionMetricsMessage( - total_time=round(total_time, 1), - total_time_str=time.strftime("%H:%M:%S", time.gmtime(total_time)), - total_bytes=total_bytes, - processing_time=round(self._last_ttfb, 3), - ) - ) - - # Update tracker - last_emission_time = total_time + self._emit_metrics() # Trigger the task self._metrics_emitter_task = asyncio.create_task(emit_metrics()) + def _emit_metrics(self) -> None: + """Emit metrics.""" + self._emit_message( + SessionMetricsMessage( + total_time=round(self._total_time, 1), + total_time_str=time.strftime("%H:%M:%S", time.gmtime(self._total_time)), + total_bytes=self._total_bytes, + processing_time=round(self._last_ttfb, 3), + ) + ) + def _stop_metrics_task(self) -> None: """Stop the metrics task.""" if self._metrics_emitter_task: @@ -826,7 +845,7 @@ def _calculate_ttfb(self, end_time: float) -> None: # Save TTFB and end time self._last_ttfb = ttfb - def calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> None: + def _calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> None: """Calculate the speaker metrics. Used to track the number of final words per speaker. Only valid speakers are @@ -862,11 +881,15 @@ def calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> Non self._session_speakers[frag.speaker].last_heard = frag.end_time # Emit - self._emit_message( - SpeakerMetricsMessage( - speakers=list(self._session_speakers.values()), - ), - ) + self._emit_speaker_metrics() + + def _emit_speaker_metrics(self) -> None: + """Emit speaker metrics.""" + self._emit_message( + SpeakerMetricsMessage( + speakers=list(self._session_speakers.values()), + ), + ) # ============================================================================ # TRANSCRIPT PROCESSING @@ -1100,6 +1123,10 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ] partial_segments = [s for s in self._current_view.segments if s not in final_segments] + # Remove partial segments that have no final fragments + if not self._config.include_partials: + partial_segments = [s for s in partial_segments if s.annotation.has(AnnotationFlags.HAS_FINAL)] + # Emit finals first if final_segments: """Final segments are checked for end of sentence.""" @@ -1200,7 +1227,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Send updated speaker metrics if self._dz_enabled: - self.calculate_speaker_metrics(final_segments) + self._calculate_speaker_metrics(final_segments) # Emit END_OF_TURN if end_of_turn and self._previous_view: @@ -1217,7 +1244,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False ) # Stop the EOT handler - self._end_of_turn_handler.complete_handler() + self._eot_handler.complete_handler() # Reset the previous view self._previous_view = None @@ -1454,8 +1481,8 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Trigger a start of turn message and also reset pending end of turn prediction.""" # Emit end of turn prediction was wrong (turn continues) - if self._uses_eot_prediction and self._end_of_turn_handler.handler_active and self._is_speaking: - self._end_of_turn_handler.reset() + if self._uses_eot_prediction and self._eot_handler.handler_active and self._is_speaking: + self._eot_handler.reset() self._emit_message( TurnStartEndResetMessage( message=AgentServerMessageType.END_OF_TURN_RESET, @@ -1467,9 +1494,9 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: ) # New turn started - elif self._is_speaking and not self._end_of_turn_handler.handler_active: - self._end_of_turn_handler.start_handler() - self._turn_id = self._end_of_turn_handler.handler_id + elif self._is_speaking and not self._eot_handler.handler_active: + self._eot_handler.start_handler() + self._turn_id = self._eot_handler.handler_id self._emit_message( TurnStartEndResetMessage( message=AgentServerMessageType.START_OF_TURN, @@ -1506,8 +1533,8 @@ def _handle_speaker_started(self) -> None: """Reset timers when a new speaker starts speaking after silence.""" # Reset the handlers - self._end_of_utterance_handler.reset() - self._end_of_turn_handler.reset() + self._eou_handler.reset() + self._eot_handler.reset() def _handle_speaker_stopped(self, speaker_end_time: float) -> None: """Reset the current speaker and do smart turn detection (if enabled).""" @@ -1516,11 +1543,16 @@ def _handle_speaker_stopped(self, speaker_end_time: float) -> None: self._current_speaker = None # Add task for end of utterance - self._end_of_utterance_handler.update_timer(self._config.end_of_utterance_silence_trigger) + if self._uses_forced_eou: + self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger) + + # Fallback for fixed mode (e.g. when other speaker is speaking) + elif self._uses_fixed_eou: + self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger * 2) # For ADAPTIVE and SMART_TURN only if self._uses_eot_prediction: - """When not EXTERNAL, we need to do EOT detection / prediction.""" + """EOT detection / prediction.""" # Callback async def do_eot_detection(end_time: float, language: str) -> None: @@ -1535,13 +1567,13 @@ async def do_eot_detection(end_time: float, language: str) -> None: delay = await self._calculate_finalize_delay(smart_turn_prediction=result) # Set the finalize timer (go now if no delay) - self._end_of_turn_handler.update_timer(delay or 0.01) + self._eot_handler.update_timer(delay or 0.01) except asyncio.CancelledError: pass # Add task - self._end_of_turn_handler.add_task( + self._eot_handler.add_task( asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), self._eou_mode.value, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index df1defb6..d057fe3e 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -481,6 +481,13 @@ class VoiceAgentConfig(BaseModel): parameter can be used to adjust the sensitivity of diarization. Defaults to `False`. + include_partials: Include partial segment fragments (words) in the output of + AddPartialSegment messages. Partial fragments from the STT will always be used for + speaker activity detection. If `include_results` is enabled, then partials will + always be included in the segment fragment list. This setting is used only for + the formatted text output of individual segments. + Defaults to `True`. + speaker_sensitivity: Diarization sensitivity. A higher value increases the sensitivity of diarization and helps when two or more speakers have similar voices. Defaults to `0.5`. @@ -612,6 +619,7 @@ class VoiceAgentConfig(BaseModel): # Diarization enable_diarization: bool = False + include_partials: bool = True speaker_sensitivity: float = 0.5 max_speakers: Optional[int] = None prefer_current_speaker: bool = False @@ -896,6 +904,7 @@ def format_view_text( format: str = "|{speaker_id}|{text}|", separator: str = "", words_only: bool = False, + include_partials: bool = True, ) -> str: """Format each segment into a single string. @@ -903,6 +912,7 @@ def format_view_text( format: Format string. separator: Separator string. words_only: Whether to include only word fragments. + include_partials: Whether to include partial fragments in the output. Returns: str: The formatted text. @@ -916,6 +926,7 @@ def format_view_text( segment=segment, format=format, words_only=words_only, + include_partials=include_partials, ) for segment in self.segments ) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index c016815c..df7b6904 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -115,5 +115,8 @@ def _merge_configs(base: VoiceAgentConfig, overlay: VoiceAgentConfig | None) -> return base # Merge overlay into base - use model_validate to properly reconstruct nested models - merged_dict = {**base.model_dump(), **overlay.model_dump(exclude_unset=True)} + merged_dict = { + **base.model_dump(exclude_unset=True, exclude_none=True), + **overlay.model_dump(exclude_unset=True, exclude_none=True), + } return VoiceAgentConfig.model_validate(merged_dict) # type: ignore[no-any-return] diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index 761d2e11..93a72973 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -23,7 +23,11 @@ class FragmentUtils: @staticmethod def format_segment_text( - session: ClientSessionInfo, segment: SpeakerSegment, format: str = "{text}", words_only: bool = False + session: ClientSessionInfo, + segment: SpeakerSegment, + format: str = "{text}", + words_only: bool = False, + include_partials: bool = True, ) -> str: """Format a segment's text based on the language pack info. @@ -32,6 +36,7 @@ def format_segment_text( segment: SpeakerSegment object. format: Format string. words_only: Whether to include only word fragments. + include_partials: Whether to include partial fragments. Returns: str: The formatted text. @@ -46,6 +51,10 @@ def format_segment_text( else: fragments = segment.fragments + # Filter out partials if requested + if not include_partials: + fragments = [frag for frag in fragments if frag.is_final] + # Assemble the text previous_frag: Optional[SpeechFragment] = None for frag in fragments: @@ -131,7 +140,9 @@ def segment_list_from_fragments( annotate=annotate_segments, ) if segment: - segment.text = FragmentUtils.format_segment_text(session=session, segment=segment) + segment.text = FragmentUtils.format_segment_text( + session=session, segment=segment, include_partials=session.config.include_partials + ) segments.append(segment) # Return the grouped SpeakerFragments objects @@ -300,27 +311,34 @@ def compare_views( # Result result = AnnotationResult() + # Flag to include partials + include_partials = session.config.include_partials + # If we have a previous view, compare it if view2 and view2.segment_count > 0: # Compare full string - view1_full_str: str = view1.format_view_text() - view2_full_str: str = view2.format_view_text() + view1_full_str: str = view1.format_view_text(include_partials=include_partials) + view2_full_str: str = view2.format_view_text(include_partials=include_partials) if view1_full_str != view2_full_str: result.add(AnnotationFlags.UPDATED_FULL) if view1_full_str.lower() != view2_full_str.lower(): result.add(AnnotationFlags.UPDATED_FULL_LCASE) # Stripped string (without punctuation) - view1_stripped_str: str = view1.format_view_text(words_only=True) - view2_stripped_str: str = view2.format_view_text(words_only=True) + view1_stripped_str: str = view1.format_view_text(include_partials=include_partials, words_only=True) + view2_stripped_str: str = view2.format_view_text(include_partials=include_partials, words_only=True) if view1_stripped_str != view2_stripped_str: result.add(AnnotationFlags.UPDATED_STRIPPED) if view1_stripped_str.lower() != view2_stripped_str.lower(): result.add(AnnotationFlags.UPDATED_STRIPPED_LCASE) # Word timings - view1_timings_str: str = view1.format_view_text(format="|{start_time}-{end_time}|", words_only=True) - view2_timings_str: str = view2.format_view_text(format="|{start_time}-{end_time}|", words_only=True) + view1_timings_str: str = view1.format_view_text( + format="|{start_time}-{end_time}|", words_only=True, include_partials=include_partials + ) + view2_timings_str: str = view2.format_view_text( + format="|{start_time}-{end_time}|", words_only=True, include_partials=include_partials + ) if view1_timings_str != view2_timings_str: result.add(AnnotationFlags.UPDATED_WORD_TIMINGS) From 3d4b7db103ffebe0bc7891a77293dff6ed7ebd40 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sat, 1 Nov 2025 17:19:26 +0000 Subject: [PATCH 187/215] Update release-voice.yaml --- .github/workflows/release-voice.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-voice.yaml b/.github/workflows/release-voice.yaml index c80b36c4..6cd92ac5 100644 --- a/.github/workflows/release-voice.yaml +++ b/.github/workflows/release-voice.yaml @@ -49,7 +49,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: "3.13" - name: Update package version in sdk/voice/speechmatics/voice/__init__.py run: | From 8124aef6f44bc8f965227c4457a1a5d4aed7a942 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 3 Nov 2025 09:19:42 +0000 Subject: [PATCH 188/215] updated tests --- tests/voice/test_07_languages.py | 2 ++ tests/voice/test_09_speaker_id.py | 2 +- tests/voice/test_10_finalize.py | 5 ----- tests/voice/test_13_smart_turn_transcribe.py | 11 ++++------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 2a3f4175..11616eb2 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -22,6 +22,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") +URL: Optional[str] = "wss://eu2.rt.speechmatics.com/v2" @dataclass @@ -111,6 +112,7 @@ async def test_transcribe_languages(sample: AudioSample): # Client client = await get_client( api_key=API_KEY, + url=URL, connect=False, config=VoiceAgentConfig( max_delay=1.2, diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 9492cca7..2b76dc8d 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -23,7 +23,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = os.getenv("SPEECHMATICS_RT_URL", "wss://preview.rt.speechmatics.com/v2") +URL: Optional[str] = "wss://eu2.rt.speechmatics.com/v2" SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] # List of know speakers during tests diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 26885d66..e7437212 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -18,7 +18,6 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = "wss://jamesw.lab.speechmatics.io/v2" SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] AUDIO_FILE = "./assets/audio_03_16kHz.wav" @@ -41,7 +40,6 @@ async def test_finalize(): # Client client = await get_client( api_key=api_key, - url=URL, connect=False, config=VoiceAgentConfig( end_of_utterance_silence_trigger=0.7, @@ -142,9 +140,6 @@ def eot_received_callback(message): print() print() - # Debug result - print(f"{finalize_latency:.2f}ms") - # Make sure latency is within bounds assert finalize_latency < 500 diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 966c5c2e..6d21b516 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -24,7 +24,6 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = os.getenv("SPEECHMATICS_RT_URL", "wss://jamesw.lab.speechmatics.io/v2") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] @@ -94,7 +93,6 @@ async def test_prediction(sample: TranscriptionTest): # Client client = await get_client( api_key=api_key, - url=URL, connect=False, config=VoiceAgentConfig( max_delay=0.7, @@ -162,10 +160,9 @@ def log_message(message): await client.disconnect() assert not client._is_connected - print(eot_count) - - return + # Debug count + # print(eot_count) # Validate (if we have expected results) - if sample.eot_count: - assert eot_count == sample.eot_count + # if sample.eot_count: + # assert eot_count == sample.eot_count From 7e69eac5c5f6e25bad113c304fc8a2cae96e712c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 3 Nov 2025 23:23:33 +0000 Subject: [PATCH 189/215] increment word count with partials --- sdk/voice/speechmatics/voice/_client.py | 53 ++++++++++++++++++++++--- sdk/voice/speechmatics/voice/_models.py | 11 +++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 1412870a..2be3f0c6 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -845,13 +845,18 @@ def _calculate_ttfb(self, end_time: float) -> None: # Save TTFB and end time self._last_ttfb = ttfb - def _calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> None: + def _calculate_speaker_metrics( + self, partial_segments: list[SpeakerSegment], final_segments: list[SpeakerSegment] + ) -> None: """Calculate the speaker metrics. - Used to track the number of final words per speaker. Only valid speakers are - considered. Ignored speakers will be excluded. + Used to track the number of words per speaker. Only valid speakers are + considered. Ignored speakers will be excluded. Total is past finals + + new partials. The number _may_ go down if partials are removed or + re-attribute to a different speaker. Args: + partial_segments: The partial segments to calculate the speaker metrics for. final_segments: The final segments to calculate the speaker metrics for. """ @@ -859,6 +864,9 @@ def _calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> No if not self.listeners(AgentServerMessageType.SPEAKER_METRICS): return + # Changes detected + changes_detected = False + # Finalized words final_words = [ f for seg in final_segments for f in seg.fragments if f.type_ == "word" and f.speaker is not None @@ -876,11 +884,46 @@ def _calculate_speaker_metrics(self, final_segments: list[SpeakerSegment]) -> No if frag.speaker not in self._session_speakers: self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + # Update metrics + self._session_speakers[frag.speaker].final_word_count += 1 + self._session_speakers[frag.speaker].last_heard = frag.end_time + + # Update the total finals + self._session_speakers[frag.speaker].word_count = self._session_speakers[frag.speaker].final_word_count + + # Changes detected + changes_detected = True + + # Partial words + partial_words = [ + f for seg in partial_segments for f in seg.fragments if f.type_ == "word" and f.speaker is not None + ] + + # Rebase partials + for s in self._session_speakers.values(): + s.word_count = s.final_word_count + + # Only process if we have words + if partial_words: + # Update the metrics of the speakers in the session + for frag in partial_words: + # Check we have a speaker + if frag.speaker is None: + continue + + # Create new speaker + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + # Update metrics self._session_speakers[frag.speaker].word_count += 1 self._session_speakers[frag.speaker].last_heard = frag.end_time - # Emit + # Changes detected + changes_detected = True + + # Emit on changes + if changes_detected: self._emit_speaker_metrics() def _emit_speaker_metrics(self) -> None: @@ -1227,7 +1270,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False # Send updated speaker metrics if self._dz_enabled: - self._calculate_speaker_metrics(final_segments) + self._calculate_speaker_metrics(partial_segments, final_segments) # Emit END_OF_TURN if end_of_turn and self._previous_view: diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index d057fe3e..5566acb6 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -693,6 +693,17 @@ class SessionSpeaker(BaseModel): speaker_id: str word_count: int = 0 last_heard: float = 0 + final_word_count: int = Field(default=0, exclude=True) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SessionSpeaker): + return False + return ( + self.speaker_id == other.speaker_id + and self.word_count == other.word_count + and self.last_heard == other.last_heard + and self.final_word_count == other.final_word_count + ) class AnnotationResult(list): From 4cc1bbf9ebe8218cfd591ad58bdcd75ee1a9969f Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 3 Nov 2025 23:24:07 +0000 Subject: [PATCH 190/215] CLI example update --- .gitignore | 1 + examples/voice/cli/cli.py | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index f9e713da..bccfbb1b 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ tmp/ .cache/ *.onnx *.pkf +.claude diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 5c42c4fc..a48154fd 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -115,6 +115,12 @@ async def main() -> None: include_results=args.results, ) + # Display instructions + if audio_source["type"] == "file": + print("\nStreaming audio file... (Press CTRL+C to stop)\n") + else: + print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") + # Set common items config.enable_diarization = True config.sample_rate = audio_source["sample_rate"] @@ -126,16 +132,6 @@ async def main() -> None: start_time = datetime.datetime.now() register_event_handlers(client, args, start_time) - # Display instructions - if audio_source["type"] == "file": - print("\nStreaming audio file... (Press CTRL+C to stop)\n") - else: - print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") - - # Debug the config - if args.verbose >= 1: - print(f"Configuration used:\n{config.model_dump_json(exclude_none=True, exclude_unset=True)}\n") - # Connect to the Voice Agent service try: await client.connect() @@ -361,8 +357,9 @@ def log_message(message: dict[str, Any]) -> None: now = datetime.datetime.now() console_print(now, message) if args.output_file: + ts_str = now.strftime("%Y-%m-%d %H:%M:%S") + f".{now.microsecond // 1000:03d}" with open(args.output_file, "a") as f: - f.write(json.dumps({"ts": now, **message}) + "\n") + f.write(json.dumps({"ts": ts_str, **message}) + "\n") # Register standard handlers client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) @@ -404,6 +401,10 @@ def log_message(message: dict[str, Any]) -> None: client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) + # Log the config + if args.verbose >= 1: + log_message({"message": "VoiceAgentClientConfig", "config": client._config.model_dump(exclude_none=True, exclude_unset=True)}) + # ============================================================================== # AUDIO STREAMING From 1654d94e9bb0dd8b31f5ec8a7ba0e1c28ef573c8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 4 Nov 2025 10:44:59 +0000 Subject: [PATCH 191/215] add volume to partials / finals --- sdk/voice/speechmatics/voice/_client.py | 86 ++++++++++++------------- sdk/voice/speechmatics/voice/_models.py | 21 +++++- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 2be3f0c6..153366c1 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -326,6 +326,9 @@ def _prepare_config( enable_partials=True, max_delay=config.max_delay, ctrl=config.advanced_engine_control, + audio_filtering_config={ + "volume_threshold": 0.0, + }, ) # Additional vocab @@ -864,65 +867,55 @@ def _calculate_speaker_metrics( if not self.listeners(AgentServerMessageType.SPEAKER_METRICS): return - # Changes detected changes_detected = False - # Finalized words - final_words = [ - f for seg in final_segments for f in seg.fragments if f.type_ == "word" and f.speaker is not None - ] + # Process finalized words + for seg in final_segments: + for frag in seg.fragments: + if frag.type_ == "word" and frag.speaker is not None: + # Initialize speaker if not exists + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) - # Only process if we have words - if final_words: - # Update the metrics of the speakers in the session - for frag in final_words: - # Check we have a speaker - if frag.speaker is None: - continue + speaker = self._session_speakers[frag.speaker] - # Create new speaker - if frag.speaker not in self._session_speakers: - self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + # Update final word count + speaker.final_word_count += 1 + speaker.last_heard = frag.end_time - # Update metrics - self._session_speakers[frag.speaker].final_word_count += 1 - self._session_speakers[frag.speaker].last_heard = frag.end_time + # Update volume + if frag.volume is not None: + speaker.update_volume(frag.volume) - # Update the total finals - self._session_speakers[frag.speaker].word_count = self._session_speakers[frag.speaker].final_word_count + changes_detected = True - # Changes detected - changes_detected = True + # Reset word count to final count for all speakers before reprocessing partials + for speaker in self._session_speakers.values(): + speaker.word_count = speaker.final_word_count - # Partial words - partial_words = [ - f for seg in partial_segments for f in seg.fragments if f.type_ == "word" and f.speaker is not None - ] + # Process partial words (adds to the base final count) + for seg in partial_segments: + for frag in seg.fragments: + if frag.type_ == "word" and frag.speaker is not None: + # Initialize speaker if not exists + if frag.speaker not in self._session_speakers: + self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + # Set baseline for new speaker from partials + self._session_speakers[frag.speaker].word_count = 0 - # Rebase partials - for s in self._session_speakers.values(): - s.word_count = s.final_word_count - - # Only process if we have words - if partial_words: - # Update the metrics of the speakers in the session - for frag in partial_words: - # Check we have a speaker - if frag.speaker is None: - continue + speaker = self._session_speakers[frag.speaker] - # Create new speaker - if frag.speaker not in self._session_speakers: - self._session_speakers[frag.speaker] = SessionSpeaker(speaker_id=frag.speaker) + # Increment total word count + speaker.word_count += 1 + speaker.last_heard = frag.end_time - # Update metrics - self._session_speakers[frag.speaker].word_count += 1 - self._session_speakers[frag.speaker].last_heard = frag.end_time + # Update volume + if frag.volume is not None: + speaker.update_volume(frag.volume) - # Changes detected - changes_detected = True + changes_detected = True - # Emit on changes + # Emit metrics if any changes occurred if changes_detected: self._emit_speaker_metrics() @@ -1009,6 +1002,7 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = content=alt.get("content", ""), speaker=alt.get("speaker", None), confidence=alt.get("confidence", 1.0), + volume=result.get("volume", None), result={"final": is_final, **result}, ) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 5566acb6..27cdd06d 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -688,12 +688,29 @@ class SessionSpeaker(BaseModel): speaker_id (str): The speaker ID. word_count (int): The word count for the speaker. last_heard (float): The last time the speaker was heard. + volume (Optional[float]): The average volume of the speaker (mean of last 50 values). """ speaker_id: str word_count: int = 0 last_heard: float = 0 + volume: Optional[float] = None final_word_count: int = Field(default=0, exclude=True) + volume_history: list[float] = Field(default_factory=list, exclude=True) + + def update_volume(self, new_volume: float) -> None: + """Update volume with average from last 50 values. + + Args: + new_volume: The new volume value to add. + """ + # Track volume history (last 50 values) + self.volume_history.append(new_volume) + while len(self.volume_history) > 50: + self.volume_history.pop(0) + + # Calculate average from history + self.volume = round(sum(self.volume_history) / len(self.volume_history), 1) def __eq__(self, other: object) -> bool: if not isinstance(other, SessionSpeaker): @@ -702,7 +719,7 @@ def __eq__(self, other: object) -> bool: self.speaker_id == other.speaker_id and self.word_count == other.word_count and self.last_heard == other.last_heard - and self.final_word_count == other.final_word_count + and self.volume == other.volume ) @@ -769,6 +786,7 @@ class SpeechFragment(BaseModel): content: Content of the fragment. Defaults to empty string. speaker: Speaker of the fragment (if diarization is enabled). Defaults to `None`. confidence: Confidence of the fragment (0.0 to 1.0). Defaults to `1.0`. + volume: Volume of the fragment (0.0 to 100.0). Defaults to `None`. result: Raw result of the fragment from the TTS. annotation: Annotation for the fragment. """ @@ -787,6 +805,7 @@ class SpeechFragment(BaseModel): content: str = "" speaker: Optional[str] = None confidence: float = 1.0 + volume: Optional[float] = None result: Optional[Any] = None annotation: Optional[AnnotationResult] = None From f0fc30210a477dee3aa14874d3938f04ad78c20b Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 5 Nov 2025 11:57:43 +0000 Subject: [PATCH 192/215] updated end of turn triggers --- examples/voice/cli/cli.py | 17 +- sdk/voice/speechmatics/voice/_client.py | 495 ++++++++++++------------ sdk/voice/speechmatics/voice/_models.py | 39 +- tests/voice/test_04_models.py | 4 +- 4 files changed, 288 insertions(+), 267 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index a48154fd..2b409ba4 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -47,7 +47,6 @@ # End of turn "StartOfTurn": "\033[91m", "EndOfTurnPrediction": "\033[95m", - "EndOfTurnReset": "\033[95m", "EndOfTurn": "\033[1;91m", # Transcript events "AddPartialTranscript": "\033[90m", @@ -109,7 +108,7 @@ async def main() -> None: ], known_speakers=known_speakers, speech_segment_config=SpeechSegmentConfig( - emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_SPEAKER_ENDED + emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_END_OF_TURN ), transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, include_results=args.results, @@ -378,16 +377,15 @@ def log_message(message: dict[str, Any]) -> None: if args.verbose >= 1: client.on(AgentServerMessageType.SPEAKER_STARTED, log_message) client.on(AgentServerMessageType.SPEAKER_ENDED, log_message) - client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) # Verbose turn prediction if args.verbose >= 2: client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, log_message) - client.on(AgentServerMessageType.END_OF_TURN_RESET, log_message) # Metrics if args.verbose >= 4: client.on(AgentServerMessageType.SESSION_METRICS, log_message) + client.on(AgentServerMessageType.SPEAKER_METRICS, log_message) # Verbose STT events if args.verbose >= 5: @@ -403,7 +401,12 @@ def log_message(message: dict[str, Any]) -> None: # Log the config if args.verbose >= 1: - log_message({"message": "VoiceAgentClientConfig", "config": client._config.model_dump(exclude_none=True, exclude_unset=True)}) + log_message( + { + "message": "VoiceAgentClientConfig", + "config": client._config.model_dump(exclude_none=True, exclude_unset=True), + } + ) # ============================================================================== @@ -676,8 +679,8 @@ def parse_args(): "-e", "--emit-mode", type=lambda s: s.upper(), - choices=["ON_SPEAKER_ENDED", "ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], - help="End of segment emit mode (default: ON_SPEAKER_ENDED)", + choices=["ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], + help="End of segment emit mode (default: ON_END_OF_TURN)", ) # ============================================================================== diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 153366c1..4b222d4f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -208,7 +208,7 @@ def __init__( # Handlers self._eou_handler: TurnTaskProcessor = TurnTaskProcessor(name="eou_handler", done_callback=self.finalize) self._eot_handler: TurnTaskProcessor = TurnTaskProcessor(name="eot_handler", done_callback=self.end_of_turn) - self._turn_detector: Optional[SmartTurnDetector] = None + self._smart_turn_detector: Optional[SmartTurnDetector] = None # Current turn self._turn_id: int = 0 @@ -225,7 +225,7 @@ def __init__( threshold=self._config.smart_turn_config.smart_turn_threshold, ) if detector.model_exists(): - self._turn_detector = detector + self._smart_turn_detector = detector self._config.smart_turn_config.audio_buffer_length = 10.0 eou_mode_ok = True if not eou_mode_ok: @@ -325,12 +325,16 @@ def _prepare_config( diarization="speaker" if config.enable_diarization else None, enable_partials=True, max_delay=config.max_delay, - ctrl=config.advanced_engine_control, audio_filtering_config={ "volume_threshold": 0.0, }, ) + # Merge in overrides + if config.advanced_engine_control: + for key, value in config.advanced_engine_control.items(): + setattr(transcription_config, key, value) + # Additional vocab if config.additional_vocab: transcription_config.additional_vocab = [ @@ -487,6 +491,9 @@ async def disconnect(self) -> None: if not self._is_connected: return + # Emit final segments + await self._emit_segments(finalize=True, end_of_turn=True) + # Emit final metrics self._emit_speaker_metrics() self._emit_metrics() @@ -635,21 +642,29 @@ def finalize(self, ttl: Optional[float] = None, end_of_turn: bool = False) -> No >>> client.finalize(ttl=0.5) """ + # Current turn + _turn_id = self._turn_id + # Emit the finalize or use EndOfTurn on demand preview async def emit() -> None: # Yield for the delay if ttl is not None and ttl > 0: await asyncio.sleep(ttl) + # Check if the turn has changed + if self._turn_id != _turn_id: + return + # Emit segments or finalize STT message (only for ADAPTIVE and SMART_TURN) - if self._uses_forced_eou and not end_of_turn: - """Uses the forced end of utterance message to emit segments and finalize.""" - # Emit the message - await self.force_end_of_utterance() + if self._uses_forced_eou and end_of_turn: + await self._await_forced_eou() - else: - """Standard emit segments and finalize.""" - await self._emit_segments(finalize=True, end_of_turn=end_of_turn) + # Check if the turn has changed + if self._turn_id != _turn_id: + return + + # Emit the segments + await self._emit_segments(finalize=True, end_of_turn=end_of_turn) # Cancel any pending tasks for end of utterance (as end of turn will emit segments) self._eou_handler.complete_handler() @@ -657,27 +672,20 @@ async def emit() -> None: # Add to queue self._stt_message_queue.put_nowait(emit) - def end_of_turn(self, ttl: Optional[float] = None) -> None: + def end_of_turn(self) -> None: """Trigger end of turn. This function will force an end of turn and finalize segments, if required. Use as a helper function for finalize() with end_of_turn=True. - Args: - ttl: Optional delay before finalizing partial segments. - Examples: Immediate end of turn: >>> # Force end of turn of current segments - >>> client.end_of_turn(ttl=0) - - Delayed end of turn: - >>> # Wait 0.5 seconds before end of turn - >>> client.end_of_turn(ttl=0.5) + >>> client.end_of_turn() """ # Emit the finalize or use EndOfTurn on demand preview - self.finalize(ttl=ttl, end_of_turn=True) + self.finalize(end_of_turn=True) # ============================================================================ # EVENT REGISTRATION & HANDLERS @@ -1123,6 +1131,10 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio # Emit the segments await self._emit_segments() + # For ADAPTIVE and SMART_TURN only + if self._uses_eot_prediction: + self._eot_prediction() + async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: """Emit segments to listeners. @@ -1136,156 +1148,167 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False """ # Lock the speech fragments - if self._current_view and self._current_view.segment_count > 0: - async with self._speech_fragments_lock: - # Keep until end of turn (`ON_END_OF_TURN`) - if ( - self._config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN - and not end_of_turn - ): - final_segments = [] - partial_segments = self._current_view.segments - - # Force finalize - elif finalize: - final_segments = self._current_view.segments - partial_segments = [] - - # Split between finals and interim segments (`ON_FINALIZED_SENTENCE` or `ON_SPEAKER_ENDED`) - else: - final_segments = [ - s - for s in self._current_view.segments - if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) - ] - partial_segments = [s for s in self._current_view.segments if s not in final_segments] + if self._current_view and self._current_view.segment_count == 0: + return - # Remove partial segments that have no final fragments - if not self._config.include_partials: - partial_segments = [s for s in partial_segments if s.annotation.has(AnnotationFlags.HAS_FINAL)] + # Lock the speech fragments + async with self._speech_fragments_lock: + # Segments to emit + final_segments: list[SpeakerSegment] = [] + partial_segments: list[SpeakerSegment] = [] - # Emit finals first - if final_segments: - """Final segments are checked for end of sentence.""" + # Keep until end of turn (`ON_END_OF_TURN`) + if self._config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN and not end_of_turn: + partial_segments = self._current_view.segments if self._current_view else [] - # Metadata for final segments uses actual start/end times of the segments being emitted - final_metadata = MessageTimeMetadata( - start_time=final_segments[0].start_time, - end_time=final_segments[-1].end_time, - processing_time=self._last_ttfb, - ) + # Force finalize + elif finalize: + final_segments = self._current_view.segments if self._current_view else [] - # Ensure final segment ends with EOS - if self._config.speech_segment_config.add_trailing_eos: - last_segment = final_segments[-1] - last_fragment = last_segment.fragments[-1] - if not last_fragment.is_eos: - last_segment.fragments.append( - SpeechFragment( - idx=self._next_fragment_id(), - start_time=last_fragment.end_time, - end_time=last_fragment.end_time, - content=".", - is_eos=True, - ) + # Split between finals and interim segments (`ON_FINALIZED_SENTENCE`) + else: + final_segments = [ + s + for s in (self._current_view.segments if self._current_view else []) + if s.annotation.has(AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS) + ] + partial_segments = [ + s for s in (self._current_view.segments if self._current_view else []) if s not in final_segments + ] + + # Remove partial segments that have no final fragments + if not self._config.include_partials: + partial_segments = [s for s in partial_segments if s.annotation.has(AnnotationFlags.HAS_FINAL)] + + # Emit finals first + if final_segments: + """Final segments are checked for end of sentence.""" + + # Metadata for final segments uses actual start/end times of the segments being emitted + final_metadata = MessageTimeMetadata( + start_time=final_segments[0].start_time, + end_time=final_segments[-1].end_time, + processing_time=self._last_ttfb, + ) + + # Ensure final segment ends with EOS + if self._config.speech_segment_config.add_trailing_eos: + last_segment = final_segments[-1] + last_fragment = last_segment.fragments[-1] + if not last_fragment.is_eos: + last_segment.fragments.append( + SpeechFragment( + idx=self._next_fragment_id(), + start_time=last_fragment.end_time, + end_time=last_fragment.end_time, + content=".", + is_eos=True, ) + ) - # Emit segments - self._emit_message( - SegmentMessage( - message=AgentServerMessageType.ADD_SEGMENT, - segments=[ - SegmentMessageSegment( - speaker_id=s.speaker_id, - is_active=s.is_active, - timestamp=s.timestamp, - language=s.language, - text=s.text, - annotation=s.annotation, - fragments=( - [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] - if self._config.include_results - else None - ), - metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), - ) - for s in final_segments - ], - metadata=final_metadata, - ), - ) - self._trim_before_time = final_segments[-1].end_time - self._speech_fragments = [ - f for f in self._speech_fragments if f.start_time >= self._trim_before_time - ] + # Emit segments + self._emit_message( + SegmentMessage( + message=AgentServerMessageType.ADD_SEGMENT, + segments=[ + SegmentMessageSegment( + speaker_id=s.speaker_id, + is_active=s.is_active, + timestamp=s.timestamp, + language=s.language, + text=s.text, + annotation=s.annotation, + fragments=( + [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] + if self._config.include_results + else None + ), + metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), + ) + for s in final_segments + ], + metadata=final_metadata, + ), + ) + self._trim_before_time = final_segments[-1].end_time + self._speech_fragments = [f for f in self._speech_fragments if f.start_time >= self._trim_before_time] + + # Emit interim segments + if partial_segments: + """Partial segments are emitted as is.""" + + # Metadata for partial segments uses actual start/end times of the segments being emitted + partial_metadata = MessageTimeMetadata( + start_time=partial_segments[0].start_time, + end_time=partial_segments[-1].end_time, + processing_time=self._last_ttfb, + ) - # Emit interim segments - if partial_segments: - """Partial segments are emitted as is.""" + # Emit segments + self._emit_message( + SegmentMessage( + message=AgentServerMessageType.ADD_PARTIAL_SEGMENT, + segments=[ + SegmentMessageSegment( + speaker_id=s.speaker_id, + is_active=s.is_active, + timestamp=s.timestamp, + language=s.language, + text=s.text, + annotation=s.annotation, + fragments=( + [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] + if self._config.include_results + else None + ), + metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), + ) + for s in partial_segments + ], + metadata=partial_metadata, + ), + ) - # Metadata for partial segments uses actual start/end times of the segments being emitted - partial_metadata = MessageTimeMetadata( - start_time=partial_segments[0].start_time, - end_time=partial_segments[-1].end_time, - processing_time=self._last_ttfb, - ) + # Update the current view + self._update_current_view() - # Emit segments - self._emit_message( - SegmentMessage( - message=AgentServerMessageType.ADD_PARTIAL_SEGMENT, - segments=[ - SegmentMessageSegment( - speaker_id=s.speaker_id, - is_active=s.is_active, - timestamp=s.timestamp, - language=s.language, - text=s.text, - annotation=s.annotation, - fragments=( - [SegmentMessageSegmentFragment(**f.__dict__) for f in s.fragments] - if self._config.include_results - else None - ), - metadata=MessageTimeMetadata(start_time=s.start_time, end_time=s.end_time), - ) - for s in partial_segments - ], - metadata=partial_metadata, - ), - ) + # Reset the turn start time + if not self._turn_start_time and self._current_view: + self._turn_start_time = self._current_view.start_time - # Update the current view - self._update_current_view() + # Send updated speaker metrics + if self._dz_enabled: + self._calculate_speaker_metrics(partial_segments, final_segments) - # Reset the turn start time - if not self._turn_start_time: - self._turn_start_time = self._current_view.start_time + # Emit end of turn + if end_of_turn: + await self._emit_end_of_turn() - # Send updated speaker metrics - if self._dz_enabled: - self._calculate_speaker_metrics(partial_segments, final_segments) + async def _emit_end_of_turn(self) -> None: + """Emit the end of turn message.""" - # Emit END_OF_TURN - if end_of_turn and self._previous_view: - # Metadata (for LAST view) - metadata = MessageTimeMetadata(start_time=self._turn_start_time, end_time=self._previous_view.end_time) + # Check if we have a previous view + if not self._previous_view: + return - # Emit - self._emit_message( - TurnStartEndResetMessage( - message=AgentServerMessageType.END_OF_TURN, - turn_id=self._turn_id, - metadata=metadata, - ), - ) + # Metadata (for LAST view) + metadata = MessageTimeMetadata(start_time=self._turn_start_time, end_time=self._previous_view.end_time) - # Stop the EOT handler - self._eot_handler.complete_handler() + # Emit + self._emit_message( + TurnStartEndResetMessage( + message=AgentServerMessageType.END_OF_TURN, + turn_id=self._turn_id, + metadata=metadata, + ), + ) - # Reset the previous view - self._previous_view = None - self._turn_start_time = None + # Stop the EOT handler + self._eot_handler.complete_handler() + + # Reset the previous view + self._previous_view = None + self._turn_start_time = None # ============================================================================ # TURN DETECTION & FINALIZATION @@ -1298,7 +1321,8 @@ async def _calculate_finalize_delay( """Calculate the delay before finalizing / end of turn. Process the most recent segment and view to determine how long to delay before finalizing - the segments to the client. + the segments to the client. Checks for disfluencies, speech speed, end of sentence markers, + and smart turn predictions to calculate appropriate delay. Args: smart_turn_prediction: The smart turn prediction result to use for evaluation. @@ -1307,90 +1331,99 @@ async def _calculate_finalize_delay( Optional[float]: The delay before finalizing / end of turn. """ - # Get the current view -or- previous view + # Get the current view or previous view with active segments view = ( self._current_view if self._current_view and self._current_view.last_active_segment_index > -1 else self._previous_view ) - # Exit if none if not view: return None - # Last active segment + # Get last active segment last_active_segment_index = view.last_active_segment_index last_active_segment = view.segments[last_active_segment_index] if last_active_segment_index > -1 else None - # Calculations - clamped_delay: float = self._config.end_of_utterance_max_delay - finalize_delay: Optional[float] = None - - # Reasons for the calculation + # Track penalty multipliers and reasons reasons: list[tuple[float, str]] = [] - # Add multiplier - def add_multipler_reason(multiplier: float, reason: str) -> None: - reasons.append((multiplier, reason)) - - # If the last segment is for an active speaker + # Apply penalties based on last active segment annotations if last_active_segment: - """Check the contents of the last segment." - - Check for: - - has any disfluencies - - ends with a disfluency - - speed of speech - - ends with finalizes end of sentence - """ + for p in self._config.end_of_turn_config.penalties: + description = "__".join(p.annotation) + has_annotation = last_active_segment.annotation.has(*p.annotation) - # Iterate over the penalties - for penalty in self._config.end_of_turn_config.penalties: - description = "__".join(penalty.annotation) - if not penalty.is_not: - if last_active_segment.annotation.has(*penalty.annotation): - add_multipler_reason(penalty.penalty, description) - else: - if not last_active_segment.annotation.has(*penalty.annotation): - add_multipler_reason(penalty.penalty, f"not__{description}") + if (not p.is_not and has_annotation) or (p.is_not and not has_annotation): + reason = f"not__{description}" if p.is_not else description + reasons.append((p.penalty, reason)) - # Smart turn prediction + # Apply smart turn prediction penalty if smart_turn_prediction: if smart_turn_prediction.prediction: - add_multipler_reason(self._config.smart_turn_config.positive_penalty, "smart_turn_true") + reasons.append((self._config.smart_turn_config.positive_penalty, "smart_turn_true")) else: - add_multipler_reason(self._config.smart_turn_config.negative_penalty, "smart_turn_false") + reasons.append((self._config.smart_turn_config.negative_penalty, "smart_turn_false")) - # Calculate multiplier + # Calculate final multiplier (compound multiplication) multiplier = ( self._config.end_of_turn_config.base_multiplier - + sum(m for m, _ in reasons) * self._config.end_of_turn_config.end_of_turn_adjustment_factor + * self._config.end_of_turn_config.end_of_turn_adjustment_factor ) + for penalty, _ in reasons: + multiplier *= penalty - # Minimum delay (50ms as a minimum) - delay = round(max(self._config.end_of_utterance_silence_trigger, 0.05) * multiplier, 2) + # Calculate delay with minimum of 25ms + delay = round(max(self._config.end_of_utterance_silence_trigger, 0.025) * multiplier, 3) - # Clamp to max delay + # Clamp to max delay and adjust for TTFB clamped_delay = min(delay, self._config.end_of_utterance_max_delay) - - # Adjust time and make sure no less than 25ms finalize_delay = max(clamped_delay - self._last_ttfb, self._config.end_of_turn_config.min_end_of_turn_delay) - # Emit prediction + # Emit prediction message self._emit_message( TurnPredictionMessage( turn_id=self._turn_id, metadata=TurnPredictionMetadata( ttl=round(finalize_delay, 2), - reasons=[_reason for _, _reason in reasons], + reasons=[reason for _, reason in reasons], ), ), ) - # Return the time return finalize_delay - async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurnPredictionResult: + def _eot_prediction(self, base_time: Optional[float] = None) -> None: + """Handle end of turn prediction.""" + + # Reset handler + self._eot_handler.reset() + + # Callback + async def do_eot_detection(end_time: Optional[float], language: str) -> None: + try: + # Wait for Smart Turn result + if self._eou_mode == EndOfUtteranceMode.SMART_TURN and end_time is not None: + result = await self._smart_turn_prediction(end_time, language) + else: + result = None + + # Create a new task to evaluate the finalize delay + delay = await self._calculate_finalize_delay(smart_turn_prediction=result) + + # Set the finalize timer (go now if no delay) + self._eot_handler.update_timer(delay or 0.005) + + except asyncio.CancelledError: + pass + + # Add task + self._eot_handler.add_task( + asyncio.create_task(do_eot_detection(base_time, self._config.language)), + self._eou_mode.value, + ) + + async def _smart_turn_prediction(self, end_time: float, language: str) -> SmartTurnPredictionResult: """Predict when to emit the end of turn. This will give an acoustic prediction of when the turn has completed using @@ -1405,7 +1438,7 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn """ # Check we have smart turn enabled - if not self._turn_detector: + if not self._smart_turn_detector: return SmartTurnPredictionResult(error="Smart turn is not enabled") # Get audio slice (add small margin of 100ms to the end of the audio) @@ -1415,7 +1448,7 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn ) # Evaluate - prediction = await self._turn_detector.predict( + prediction = await self._smart_turn_detector.predict( segment_audio, language=language, sample_rate=self._audio_sample_rate, @@ -1425,6 +1458,27 @@ async def _predict_smart_turn(self, end_time: float, language: str) -> SmartTurn # Return the prediction return prediction + async def _await_forced_eou(self, timeout: float = 2.0) -> bool: + """Await the forced end of utterance.""" + + # Received EOU + eou_received: asyncio.Event = asyncio.Event() + + # Add listener + self.once(AgentServerMessageType.END_OF_UTTERANCE, lambda message: eou_received.set()) + + # Trigger EOU message + await self.force_end_of_utterance() + + # Wait for EOU + try: + await asyncio.wait_for(eou_received.wait(), timeout=timeout) + except asyncio.TimeoutError: + return False + + # Return True after EOU is received + return True + # ============================================================================ # VAD (VOICE ACTIVITY DETECTION) / SPEAKER DETECTION # ============================================================================ @@ -1520,15 +1574,6 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Emit end of turn prediction was wrong (turn continues) if self._uses_eot_prediction and self._eot_handler.handler_active and self._is_speaking: self._eot_handler.reset() - self._emit_message( - TurnStartEndResetMessage( - message=AgentServerMessageType.END_OF_TURN_RESET, - turn_id=self._turn_id, - metadata=MessageTimeMetadata( - time=event_time, - ), - ), - ) # New turn started elif self._is_speaking and not self._eot_handler.handler_active: @@ -1587,33 +1632,9 @@ def _handle_speaker_stopped(self, speaker_end_time: float) -> None: elif self._uses_fixed_eou: self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger * 2) - # For ADAPTIVE and SMART_TURN only - if self._uses_eot_prediction: - """EOT detection / prediction.""" - - # Callback - async def do_eot_detection(end_time: float, language: str) -> None: - try: - # Wait for Smart Turn result - if self._eou_mode == EndOfUtteranceMode.SMART_TURN: - result = await self._predict_smart_turn(end_time, language) - else: - result = None - - # Create a new task to evaluate the finalize delay - delay = await self._calculate_finalize_delay(smart_turn_prediction=result) - - # Set the finalize timer (go now if no delay) - self._eot_handler.update_timer(delay or 0.01) - - except asyncio.CancelledError: - pass - - # Add task - self._eot_handler.add_task( - asyncio.create_task(do_eot_detection(speaker_end_time, self._config.language)), - self._eou_mode.value, - ) + # # For ADAPTIVE and SMART_TURN only + # if self._uses_eot_prediction: + # self._eot_prediction(speaker_end_time) # ============================================================================ # HELPER METHODS diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 27cdd06d..a487a23a 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -188,7 +188,6 @@ class AgentServerMessageType(str, Enum): # End of turn messages START_OF_TURN = "StartOfTurn" END_OF_TURN_PREDICTION = "EndOfTurnPrediction" - END_OF_TURN_RESET = "EndOfTurnReset" END_OF_TURN = "EndOfTurn" # Speaker messages @@ -312,14 +311,11 @@ class SpeakerFocusConfig(BaseModel): class SpeechSegmentEmitMode(str, Enum): - """EMode for when to emit finalized segments. + """Mode for when to emit finalized segments. As transcription is processed, segments are emitted based on the selected emit mode. This gives the client more control in how to process incomplete and complete segments of speech. - - `ON_SPEAKER_ENDED`: Emit segments when a speaker has ended. If a speaker change is detected - during a turn, then multiple finalized segments may be emitted in sequence. - - `ON_FINALIZED_SENTENCE`: Emit segments when a sentence has ended. A finalized segment is emitted as soon as a finalized end of sentence is detected. If a speaker continues to speak during a turn, then multiple finalized segments may be emitted during the turn. @@ -327,7 +323,6 @@ class SpeechSegmentEmitMode(str, Enum): - `ON_END_OF_TURN`: No finalized segments will be emitted until the turn has been completed. """ - ON_SPEAKER_ENDED = "on_speaker_ended" ON_FINALIZED_SENTENCE = "on_finalized_sentence" ON_END_OF_TURN = "on_end_of_turn" @@ -339,11 +334,11 @@ class SpeechSegmentConfig(BaseModel): add_trailing_eos: Add trailing end of sentence to segments. When enabled, segments are emitted with missing trailing end of sentence added. Defaults to False. - emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_SPEAKER_ENDED`. + emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_END_OF_TURN`. """ add_trailing_eos: bool = False - emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_SPEAKER_ENDED + emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_END_OF_TURN class EndOfTurnPenaltyItem(BaseModel): @@ -379,15 +374,15 @@ class EndOfTurnConfig(BaseModel): EndOfTurnPenaltyItem(penalty=2.0, annotation=[AnnotationFlags.SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.5, annotation=[AnnotationFlags.ENDS_WITH_DISFLUENCY]), EndOfTurnPenaltyItem(penalty=0.25, annotation=[AnnotationFlags.HAS_DISFLUENCY]), - EndOfTurnPenaltyItem( - penalty=1.0, - annotation=[AnnotationFlags.ENDS_WITH_EOS], - is_not=True, - ), - EndOfTurnPenaltyItem( - penalty=-0.1, - annotation=[AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL], - ), + # EndOfTurnPenaltyItem( + # penalty=1.0, + # annotation=[AnnotationFlags.ENDS_WITH_EOS], + # is_not=True, + # ), + # EndOfTurnPenaltyItem( + # penalty=-0.5, + # annotation=[AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL], + # ), ] ) @@ -699,14 +694,14 @@ class SessionSpeaker(BaseModel): volume_history: list[float] = Field(default_factory=list, exclude=True) def update_volume(self, new_volume: float) -> None: - """Update volume with average from last 50 values. + """Update volume with average from last N values. Args: new_volume: The new volume value to add. """ - # Track volume history (last 50 values) + # Track volume history (last N values) self.volume_history.append(new_volume) - while len(self.volume_history) > 50: + while len(self.volume_history) > 10: self.volume_history.pop(0) # Calculate average from history @@ -929,6 +924,9 @@ def last_active_segment_index(self) -> int: return -1 return len(self.segments) - idx - 1 + def has_no_active_segments_remaining(self) -> bool: + return self.last_active_segment_index == -1 + def format_view_text( self, format: str = "|{speaker_id}|{text}|", @@ -1082,7 +1080,6 @@ class TurnStartEndResetMessage(BaseMessage): message: Literal[ AgentServerMessageType.START_OF_TURN, AgentServerMessageType.END_OF_TURN, - AgentServerMessageType.END_OF_TURN_RESET, ] turn_id: int metadata: MessageTimeMetadata diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index ecd551ba..401c3013 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -269,8 +269,8 @@ async def test_presets(): # Overlay #2 low_latency_two: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( - VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_SPEAKER_ENDED)) + VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN)) ) assert low_latency_two is not None assert low_latency_two.enable_diarization is True - assert low_latency_two.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_SPEAKER_ENDED + assert low_latency_two.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN From 65379300eee80b94ecc69cfb482db71ff71888da Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 5 Nov 2025 22:10:10 +0000 Subject: [PATCH 193/215] Removal of `ON_END_OF_TURN` for segment emit control. --- examples/voice/cli/cli.py | 4 +-- sdk/voice/speechmatics/voice/_models.py | 22 +++++++-------- tests/voice/test_05_utterance.py | 36 ++++++++++++++++--------- tests/voice/test_07_languages.py | 2 +- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 2b409ba4..e55f1bb4 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -108,7 +108,7 @@ async def main() -> None: ], known_speakers=known_speakers, speech_segment_config=SpeechSegmentConfig( - emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_END_OF_TURN + emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE ), transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, include_results=args.results, @@ -680,7 +680,7 @@ def parse_args(): "--emit-mode", type=lambda s: s.upper(), choices=["ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], - help="End of segment emit mode (default: ON_END_OF_TURN)", + help="End of segment emit mode (default: ON_FINALIZED_SENTENCE)", ) # ============================================================================== diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index a487a23a..eb910e8f 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -334,11 +334,11 @@ class SpeechSegmentConfig(BaseModel): add_trailing_eos: Add trailing end of sentence to segments. When enabled, segments are emitted with missing trailing end of sentence added. Defaults to False. - emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_END_OF_TURN`. + emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE`. """ add_trailing_eos: bool = False - emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_END_OF_TURN + emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE class EndOfTurnPenaltyItem(BaseModel): @@ -365,7 +365,7 @@ class EndOfTurnConfig(BaseModel): penalties: List of end of turn penalty items. """ - base_multiplier: float = 1.75 + base_multiplier: float = 1.15 min_end_of_turn_delay: float = 0.025 end_of_turn_adjustment_factor: float = 1.0 penalties: list[EndOfTurnPenaltyItem] = Field( @@ -373,16 +373,12 @@ class EndOfTurnConfig(BaseModel): EndOfTurnPenaltyItem(penalty=3.0, annotation=[AnnotationFlags.VERY_SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.0, annotation=[AnnotationFlags.SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.5, annotation=[AnnotationFlags.ENDS_WITH_DISFLUENCY]), - EndOfTurnPenaltyItem(penalty=0.25, annotation=[AnnotationFlags.HAS_DISFLUENCY]), - # EndOfTurnPenaltyItem( - # penalty=1.0, - # annotation=[AnnotationFlags.ENDS_WITH_EOS], - # is_not=True, - # ), - # EndOfTurnPenaltyItem( - # penalty=-0.5, - # annotation=[AnnotationFlags.ENDS_WITH_EOS, AnnotationFlags.ENDS_WITH_FINAL], - # ), + EndOfTurnPenaltyItem(penalty=1.25, annotation=[AnnotationFlags.HAS_DISFLUENCY]), + EndOfTurnPenaltyItem( + penalty=2.0, + annotation=[AnnotationFlags.ENDS_WITH_EOS], + is_not=True, + ), ] ) diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index a652f87b..26332096 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -14,6 +14,8 @@ from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig +SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] + @pytest.mark.asyncio async def test_speech_fragments(): @@ -145,15 +147,21 @@ async def test_end_of_utterance_fixed(): start_time = datetime.datetime.now() # Create a client - client = await get_client(api_key="NONE", connect=False) + client = await get_client( + api_key="NONE", + connect=False, + config=VoiceAgentConfig(end_of_utterance_silence_trigger=0.5, end_of_utterance_mode=EndOfUtteranceMode.FIXED), + ) assert client is not None # Debug - # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) - # client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) - # client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) - # client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) - # client.on(AgentServerMessageType.END_OF_UTTERANCE, lambda message: print(message)) + if SHOW_LOG: + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) + client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) + client.on(AgentServerMessageType.START_OF_TURN, lambda message: print(message)) + client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) + client.on(AgentServerMessageType.END_OF_UTTERANCE, lambda message: print(message)) # Start the queue client._start_stt_queue() @@ -186,7 +194,7 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.emit(message["payload"]["message"], message["payload"]) # Add listener for first interim segment - client.once(AgentServerMessageType.END_OF_TURN, message_rx) + client.once(AgentServerMessageType.ADD_SEGMENT, message_rx) # Inject conversation await send_message(0, count=14, use_ttl=False) @@ -196,10 +204,10 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): await asyncio.wait_for(event_rx.wait(), timeout=5.0) assert last_message is not None except asyncio.TimeoutError: - pytest.fail("END_OF_TURN event was not received within 5 seconds") + pytest.fail("ADD_SEGMENT event was not received within 5 seconds") # Check the right message was received - assert last_message.get("message") == AgentServerMessageType.END_OF_TURN + assert last_message.get("message") == AgentServerMessageType.ADD_SEGMENT @pytest.mark.asyncio @@ -379,10 +387,12 @@ async def send_message(idx: int, count: int = 1, use_ttl: bool = True): client.once(AgentServerMessageType.END_OF_TURN, eot_rx) # Debug - # client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) - # client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) - # client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) - # client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) + if SHOW_LOG: + client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, lambda message: print(message)) + client.on(AgentServerMessageType.ADD_SEGMENT, lambda message: print(message)) + client.on(AgentServerMessageType.START_OF_TURN, lambda message: print(message)) + client.on(AgentServerMessageType.END_OF_TURN_PREDICTION, lambda message: print(message)) + client.on(AgentServerMessageType.END_OF_TURN, lambda message: print(message)) # Inject conversation up to the penultimate final from the STT await send_message(0, count=12, use_ttl=True) diff --git a/tests/voice/test_07_languages.py b/tests/voice/test_07_languages.py index 11616eb2..759e67b7 100644 --- a/tests/voice/test_07_languages.py +++ b/tests/voice/test_07_languages.py @@ -22,7 +22,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") -URL: Optional[str] = "wss://eu2.rt.speechmatics.com/v2" +URL = "wss://eu2.rt.speechmatics.com/v2" @dataclass From 862e110ec71f98ec609ce1f59e2a0c07d2575665 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 5 Nov 2025 22:10:40 +0000 Subject: [PATCH 194/215] change `emit_mode` to `emit_sentences` boolean --- examples/voice/cli/cli.py | 10 ++++----- sdk/voice/speechmatics/voice/__init__.py | 2 -- sdk/voice/speechmatics/voice/_client.py | 3 +-- sdk/voice/speechmatics/voice/_models.py | 23 ++++---------------- sdk/voice/speechmatics/voice/_presets.py | 7 +++--- sdk/voice/speechmatics/voice/_utils.py | 3 +-- tests/voice/test_04_models.py | 7 +++--- tests/voice/test_05_utterance.py | 3 +-- tests/voice/test_08_multiple_speakers.py | 3 +-- tests/voice/test_09_speaker_id.py | 5 ++--- tests/voice/test_10_finalize.py | 3 +-- tests/voice/test_13_smart_turn_transcribe.py | 6 ++--- 12 files changed, 24 insertions(+), 51 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index e55f1bb4..1b71848f 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -26,7 +26,6 @@ from speechmatics.voice import SpeakerFocusMode from speechmatics.voice import SpeakerIdentifier from speechmatics.voice import SpeechSegmentConfig -from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentClient from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import TranscriptionUpdatePreset @@ -108,7 +107,7 @@ async def main() -> None: ], known_speakers=known_speakers, speech_segment_config=SpeechSegmentConfig( - emit_mode=args.emit_mode.lower() if args.emit_mode else SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE + emit_sentences=args.emit_sentences, ), transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, include_results=args.results, @@ -677,10 +676,9 @@ def parse_args(): ) parser.add_argument( "-e", - "--emit-mode", - type=lambda s: s.upper(), - choices=["ON_FINALIZED_SENTENCE", "ON_END_OF_TURN"], - help="End of segment emit mode (default: ON_FINALIZED_SENTENCE)", + "--emit-sentences", + action="store_true", + help="Emit sentences (default: False)", ) # ============================================================================== diff --git a/sdk/voice/speechmatics/voice/__init__.py b/sdk/voice/speechmatics/voice/__init__.py index 7fcdec52..6cb66fd6 100644 --- a/sdk/voice/speechmatics/voice/__init__.py +++ b/sdk/voice/speechmatics/voice/__init__.py @@ -33,7 +33,6 @@ from ._models import SpeakerFocusMode from ._models import SpeakerMetricsMessage from ._models import SpeechSegmentConfig -from ._models import SpeechSegmentEmitMode from ._models import TurnPredictionMessage from ._models import TurnStartEndResetMessage from ._models import VADStatusMessage @@ -58,7 +57,6 @@ "SpeakerIdentifier", "SmartTurnConfig", "SpeechSegmentConfig", - "SpeechSegmentEmitMode", "VoiceAgentConfig", "VoiceAgentConfigPreset", # Client messages diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 4b222d4f..d39c31e2 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -48,7 +48,6 @@ from ._models import SpeakerSegment from ._models import SpeakerSegmentView from ._models import SpeechFragment -from ._models import SpeechSegmentEmitMode from ._models import TranscriptionUpdatePreset from ._models import TurnPredictionMessage from ._models import TurnPredictionMetadata @@ -1158,7 +1157,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False partial_segments: list[SpeakerSegment] = [] # Keep until end of turn (`ON_END_OF_TURN`) - if self._config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN and not end_of_turn: + if not self._config.speech_segment_config.emit_sentences and not end_of_turn: partial_segments = self._current_view.segments if self._current_view else [] # Force finalize diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index eb910e8f..e09a1137 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -310,23 +310,6 @@ class SpeakerFocusConfig(BaseModel): focus_mode: SpeakerFocusMode = SpeakerFocusMode.RETAIN -class SpeechSegmentEmitMode(str, Enum): - """Mode for when to emit finalized segments. - - As transcription is processed, segments are emitted based on the selected emit mode. This gives - the client more control in how to process incomplete and complete segments of speech. - - - `ON_FINALIZED_SENTENCE`: Emit segments when a sentence has ended. A finalized segment is emitted - as soon as a finalized end of sentence is detected. If a speaker continues to speak during - a turn, then multiple finalized segments may be emitted during the turn. - - - `ON_END_OF_TURN`: No finalized segments will be emitted until the turn has been completed. - """ - - ON_FINALIZED_SENTENCE = "on_finalized_sentence" - ON_END_OF_TURN = "on_end_of_turn" - - class SpeechSegmentConfig(BaseModel): """Configuration on how segments are emitted. @@ -334,11 +317,13 @@ class SpeechSegmentConfig(BaseModel): add_trailing_eos: Add trailing end of sentence to segments. When enabled, segments are emitted with missing trailing end of sentence added. Defaults to False. - emit_mode: How to emit segments. Defaults to `SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE`. + emit_sentences: Emit segments when a sentence has ended. A finalized segment is emitted + as soon as a finalized end of sentence is detected. If a speaker continues to speak during + a turn, then multiple finalized segments may be emitted during the turn. """ add_trailing_eos: bool = False - emit_mode: SpeechSegmentEmitMode = SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE + emit_sentences: bool = True class EndOfTurnPenaltyItem(BaseModel): diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index df7b6904..62f153a4 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -6,7 +6,6 @@ from ._models import EndOfUtteranceMode from ._models import SpeechSegmentConfig -from ._models import SpeechSegmentEmitMode from ._models import VoiceAgentConfig @@ -27,7 +26,7 @@ def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # max_delay=0.7, end_of_utterance_silence_trigger=0.2, end_of_utterance_mode=EndOfUtteranceMode.FIXED, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + speech_segment_config=SpeechSegmentConfig(emit_sentences=True), ), overlay, ) @@ -47,6 +46,7 @@ def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgent max_delay=0.7, end_of_utterance_silence_trigger=0.5, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), overlay, ) @@ -72,6 +72,7 @@ def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAge max_delay=0.85, end_of_utterance_silence_trigger=0.5, end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), overlay, ) @@ -89,7 +90,7 @@ def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa max_delay=1.2, end_of_utterance_silence_trigger=1.0, end_of_utterance_mode=EndOfUtteranceMode.FIXED, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE), + speech_segment_config=SpeechSegmentConfig(emit_sentences=True), ), overlay, ) diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index 93a72973..8684991d 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -15,7 +15,6 @@ from ._models import SpeakerSegment from ._models import SpeakerSegmentView from ._models import SpeechFragment -from ._models import SpeechSegmentEmitMode class FragmentUtils: @@ -118,7 +117,7 @@ def segment_list_from_fragments( continue # Split group into sub-groups by end-of-sentence markers (finals only) - if session.config.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE: + if session.config.speech_segment_config.emit_sentences: subgroup: list[SpeechFragment] = [] subgroups: list[list[SpeechFragment]] = [] for frag in group: diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 401c3013..ac4fe737 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -12,7 +12,6 @@ from speechmatics.voice._models import SpeakerSegment from speechmatics.voice._models import SpeechFragment from speechmatics.voice._models import SpeechSegmentConfig -from speechmatics.voice._models import SpeechSegmentEmitMode from speechmatics.voice._presets import VoiceAgentConfigPreset @@ -257,7 +256,7 @@ async def test_presets(): # Create a preset low_latency: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY() assert low_latency is not None - assert low_latency.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_FINALIZED_SENTENCE + assert low_latency.speech_segment_config.emit_sentences is True # Overlay #1 low_latency_one: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( @@ -269,8 +268,8 @@ async def test_presets(): # Overlay #2 low_latency_two: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( - VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN)) + VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_sentences=False)) ) assert low_latency_two is not None assert low_latency_two.enable_diarization is True - assert low_latency_two.speech_segment_config.emit_mode == SpeechSegmentEmitMode.ON_END_OF_TURN + assert low_latency_two.speech_segment_config.emit_sentences is False diff --git a/tests/voice/test_05_utterance.py b/tests/voice/test_05_utterance.py index 26332096..cf30453d 100644 --- a/tests/voice/test_05_utterance.py +++ b/tests/voice/test_05_utterance.py @@ -11,7 +11,6 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeechSegmentConfig -from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] @@ -333,7 +332,7 @@ async def test_end_of_utterance_adaptive_vad(): config=VoiceAgentConfig( end_of_utterance_silence_trigger=adaptive_timeout, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), ) assert client is not None diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index e6651271..941481fa 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -16,7 +16,6 @@ from speechmatics.voice import SpeakerFocusConfig from speechmatics.voice import SpeakerFocusMode from speechmatics.voice import SpeechSegmentConfig -from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -104,7 +103,7 @@ async def test_multiple_speakers(sample: SpeakerTest): additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ) # Diarization options diff --git a/tests/voice/test_09_speaker_id.py b/tests/voice/test_09_speaker_id.py index 2b76dc8d..95929848 100644 --- a/tests/voice/test_09_speaker_id.py +++ b/tests/voice/test_09_speaker_id.py @@ -14,7 +14,6 @@ from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeakerIdentifier from speechmatics.voice import SpeechSegmentConfig -from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._models import SpeakerSegment @@ -187,7 +186,7 @@ async def test_known_speakers(): enable_diarization=True, sample_rate=8000, known_speakers=known_speakers, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], @@ -264,7 +263,7 @@ async def test_ignoring_assistant(): enable_diarization=True, sample_rate=8000, known_speakers=known_speakers, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index e7437212..b1c6532a 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -2,7 +2,6 @@ import datetime import json import os -from typing import Optional import pytest from _utils import get_client @@ -100,7 +99,7 @@ def eot_received_callback(message): try: await client.connect() except Exception: - pytest.skip(f"Failed to connect to server: {URL}") + pytest.skip("Failed to connect to server") # Check we are connected assert client._is_connected diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 6d21b516..fc1b6f0f 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -2,7 +2,6 @@ import json import os import shutil -from typing import Optional import pytest from _utils import get_client @@ -14,7 +13,6 @@ from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode from speechmatics.voice import SpeechSegmentConfig -from speechmatics.voice import SpeechSegmentEmitMode from speechmatics.voice import VoiceAgentConfig from speechmatics.voice._smart_turn import SmartTurnDetector @@ -102,7 +100,7 @@ async def test_prediction(sample: TranscriptionTest): sample_rate=sample.sample_rate, additional_vocab=sample.additional_vocab, enable_preview_features=True, - speech_segment_config=SpeechSegmentConfig(emit_mode=SpeechSegmentEmitMode.ON_END_OF_TURN), + speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), ) @@ -142,7 +140,7 @@ def log_message(message): try: await client.connect() except Exception: - pytest.skip(f"Failed to connect to server: {URL}") + pytest.skip("Failed to connect to server") # Check we are connected assert client._is_connected From 900a5736ff66465f9af517f8435dbcb90bb0d289 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Thu, 6 Nov 2025 17:12:14 +0000 Subject: [PATCH 195/215] forced eou changes --- examples/voice/cli/cli.py | 10 +++--- sdk/voice/speechmatics/voice/_client.py | 34 ++++++++++++++------ sdk/voice/speechmatics/voice/_models.py | 8 ++--- sdk/voice/speechmatics/voice/_presets.py | 19 +++++++---- sdk/voice/speechmatics/voice/_smart_turn.py | 2 ++ tests/voice/test_10_finalize.py | 2 +- tests/voice/test_13_smart_turn_transcribe.py | 2 +- 7 files changed, 51 insertions(+), 26 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 1b71848f..17021388 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -101,7 +101,7 @@ async def main() -> None: args.end_of_utterance_mode.lower() if args.end_of_utterance_mode else EndOfUtteranceMode.ADAPTIVE ), speaker_config=speaker_config, - enable_preview_features=args.preview, + use_forced_eou=args.forced_eou, additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], @@ -360,6 +360,7 @@ def log_message(message: dict[str, Any]) -> None: f.write(json.dumps({"ts": ts_str, **message}) + "\n") # Register standard handlers + client.on(AgentServerMessageType.INFO, log_message) client.on(AgentServerMessageType.RECOGNITION_STARTED, log_message) client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) @@ -389,6 +390,7 @@ def log_message(message: dict[str, Any]) -> None: # Verbose STT events if args.verbose >= 5: client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on("ForcedEndOfUtterance", log_message) client.on(AgentServerMessageType.ADD_PARTIAL_TRANSCRIPT, log_message) client.on(AgentServerMessageType.ADD_TRANSCRIPT, log_message) @@ -721,9 +723,9 @@ def parse_args(): help="Known speakers as JSON string or path to JSON file (default: None)", ) parser.add_argument( - "--preview", + "--forced-eou", action="store_true", - help="Use preview features (default: False)", + help="Use forced end of utterance (default: False)", ) # ============================================================================== @@ -733,7 +735,7 @@ def parse_args(): args = parser.parse_args() mutually_excludive = [ - "emit-mode", + "emit-sentences", "end-of-utterance-mode", "end-of-utterance-silence-trigger", "focus-speakers", diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index d39c31e2..6f517319 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -238,7 +238,7 @@ def __init__( self._uses_fixed_eou: bool = self._eou_mode == EndOfUtteranceMode.FIXED # Uses ForceEndOfUtterance message - self._uses_forced_eou: bool = self._config.enable_preview_features and self._eou_mode in [ + self._uses_forced_eou: bool = self._config.use_forced_eou and self._eou_mode in [ EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN, ] @@ -263,7 +263,6 @@ def __init__( self._session_speakers: dict[str, SessionSpeaker] = {} self._is_speaking: bool = False self._current_speaker: Optional[str] = None - self._dz_enabled: bool = self._config.enable_diarization self._dz_config = self._config.speaker_config @@ -683,6 +682,8 @@ def end_of_turn(self) -> None: >>> client.end_of_turn() """ + print("*** EOT called ***") + # Emit the finalize or use EndOfTurn on demand preview self.finalize(end_of_turn=True) @@ -741,6 +742,12 @@ def _emit_message(self, message: BaseMessage) -> None: # Forward to the emit() method self.emit(message.message, message.model_dump()) + def _emit_info_message(self, message: str | dict[str, Any]) -> None: + """Emit an info message to the client.""" + if isinstance(message, str): + message = {"msg": message} + self.emit(AgentServerMessageType.INFO, {"message": AgentServerMessageType.INFO.value, **message}) + # ============================================================================ # QUEUE PROCESSING # ============================================================================ @@ -1195,15 +1202,23 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False last_segment = final_segments[-1] last_fragment = last_segment.fragments[-1] if not last_fragment.is_eos: + # Add new fragment last_segment.fragments.append( SpeechFragment( idx=self._next_fragment_id(), start_time=last_fragment.end_time, end_time=last_fragment.end_time, content=".", + attaches_to="previous", is_eos=True, ) ) + # Update text + last_segment.text = FragmentUtils.format_segment_text( + session=self._client_session, + segment=last_segment, + include_partials=True, + ) # Emit segments self._emit_message( @@ -1280,7 +1295,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._calculate_speaker_metrics(partial_segments, final_segments) # Emit end of turn - if end_of_turn: + if self._uses_turn_start_end and end_of_turn: await self._emit_end_of_turn() async def _emit_end_of_turn(self) -> None: @@ -1373,7 +1388,7 @@ async def _calculate_finalize_delay( multiplier *= penalty # Calculate delay with minimum of 25ms - delay = round(max(self._config.end_of_utterance_silence_trigger, 0.025) * multiplier, 3) + delay = round(self._config.end_of_utterance_silence_trigger * multiplier, 3) # Clamp to max delay and adjust for TTFB clamped_delay = min(delay, self._config.end_of_utterance_max_delay) @@ -1392,7 +1407,7 @@ async def _calculate_finalize_delay( return finalize_delay - def _eot_prediction(self, base_time: Optional[float] = None) -> None: + def _eot_prediction(self, end_time: Optional[float] = None) -> None: """Handle end of turn prediction.""" # Reset handler @@ -1418,7 +1433,7 @@ async def do_eot_detection(end_time: Optional[float], language: str) -> None: # Add task self._eot_handler.add_task( - asyncio.create_task(do_eot_detection(base_time, self._config.language)), + asyncio.create_task(do_eot_detection(end_time, self._config.language)), self._eou_mode.value, ) @@ -1467,6 +1482,7 @@ async def _await_forced_eou(self, timeout: float = 2.0) -> bool: self.once(AgentServerMessageType.END_OF_UTTERANCE, lambda message: eou_received.set()) # Trigger EOU message + self._emit_info_message("ForceEndOfUtterance sent") await self.force_end_of_utterance() # Wait for EOU @@ -1631,9 +1647,9 @@ def _handle_speaker_stopped(self, speaker_end_time: float) -> None: elif self._uses_fixed_eou: self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger * 2) - # # For ADAPTIVE and SMART_TURN only - # if self._uses_eot_prediction: - # self._eot_prediction(speaker_end_time) + # For ADAPTIVE and SMART_TURN only + if self._uses_eot_prediction: + self._eot_prediction(speaker_end_time) # ============================================================================ # HELPER METHODS diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index e09a1137..4db6e1c8 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -399,7 +399,7 @@ class SmartTurnConfig(BaseModel): audio_buffer_length: float = 0.0 smart_turn_threshold: float = 0.5 slice_margin: float = 0.05 - positive_penalty: float = -1.0 + positive_penalty: float = 0.2 negative_penalty: float = 2.5 @@ -492,8 +492,8 @@ class VoiceAgentConfig(BaseModel): include_results: Include word data in the response. This is useful for debugging and understanding the STT engine's behavior. Defaults to False. - enable_preview_features: Enable preview features using a preview endpoint provided by - Speechmatics. Defaults to False. + use_forced_eou: Use forced end of utterance. This will force the STT engine to emit + end of utterance messages. Defaults to False. transcription_update_preset: Emit segments when the text content or word timings change. Options are: `COMPLETE` (emit on changes to text content), `COMPLETE_PLUS_TIMING` @@ -604,7 +604,7 @@ class VoiceAgentConfig(BaseModel): # Advanced features include_results: bool = False - enable_preview_features: bool = False + use_forced_eou: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE end_of_turn_config: EndOfTurnConfig = Field(default_factory=EndOfTurnConfig) smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 62f153a4..8d54f6ca 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -5,6 +5,7 @@ from __future__ import annotations from ._models import EndOfUtteranceMode +from ._models import OperatingPoint from ._models import SpeechSegmentConfig from ._models import VoiceAgentConfig @@ -22,8 +23,9 @@ def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - enable_diarization=True, - max_delay=0.7, + model=OperatingPoint.STANDARD, + enable_diarization=False, + max_delay=0.8, end_of_utterance_silence_trigger=0.2, end_of_utterance_mode=EndOfUtteranceMode.FIXED, speech_segment_config=SpeechSegmentConfig(emit_sentences=True), @@ -42,9 +44,10 @@ def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgent """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( + model=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=0.7, - end_of_utterance_silence_trigger=0.5, + max_delay=1.25, + end_of_utterance_silence_trigger=0.8, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), @@ -68,9 +71,10 @@ def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAge """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( + model=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=0.85, - end_of_utterance_silence_trigger=0.5, + max_delay=1.25, + end_of_utterance_silence_trigger=0.8, end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), @@ -86,8 +90,9 @@ def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( + model=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=1.2, + max_delay=1.4, end_of_utterance_silence_trigger=1.0, end_of_utterance_mode=EndOfUtteranceMode.FIXED, speech_segment_config=SpeechSegmentConfig(emit_sentences=True), diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 011318d8..20f66991 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -214,6 +214,8 @@ async def predict( # Record end time end_time = datetime.datetime.now() + print(prediction) + # Return the result return SmartTurnPredictionResult( prediction=prediction, diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index b1c6532a..81933951 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -44,7 +44,7 @@ async def test_finalize(): end_of_utterance_silence_trigger=0.7, max_delay=1.2, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, - enable_preview_features=True, + use_forced_eou=True, ), ) diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index fc1b6f0f..2a1646c0 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -99,7 +99,7 @@ async def test_prediction(sample: TranscriptionTest): enable_diarization=True, sample_rate=sample.sample_rate, additional_vocab=sample.additional_vocab, - enable_preview_features=True, + use_forced_eou=True, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), ) From ced28d85a3d87e2bc243043c03ca21154aee9ec0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Fri, 7 Nov 2025 12:48:46 +0000 Subject: [PATCH 196/215] guard against FEOU being called when no fragments are in the buffer --- sdk/voice/speechmatics/voice/_client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 6f517319..150240e9 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -323,6 +323,7 @@ def _prepare_config( diarization="speaker" if config.enable_diarization else None, enable_partials=True, max_delay=config.max_delay, + max_delay_mode="fixed", audio_filtering_config={ "volume_threshold": 0.0, }, @@ -653,8 +654,12 @@ async def emit() -> None: if self._turn_id != _turn_id: return + # Guard against EOT being called when no segments are in the buffer + if end_of_turn and self._current_view and self._current_view.has_no_active_segments_remaining(): + return + # Emit segments or finalize STT message (only for ADAPTIVE and SMART_TURN) - if self._uses_forced_eou and end_of_turn: + if end_of_turn and self._uses_forced_eou: await self._await_forced_eou() # Check if the turn has changed @@ -682,8 +687,6 @@ def end_of_turn(self) -> None: >>> client.end_of_turn() """ - print("*** EOT called ***") - # Emit the finalize or use EndOfTurn on demand preview self.finalize(end_of_turn=True) @@ -1657,7 +1660,7 @@ def _handle_speaker_stopped(self, speaker_end_time: float) -> None: def _next_fragment_id(self) -> int: """Return the next fragment ID.""" - self._fragment_idx += 1 + self._fragment_idx += 10 return self._fragment_idx def _get_endpoint_url(self, url: str, app: Optional[str] = None) -> str: From 8d64b1d0ca0a0b9f828ae58459554aa9a9912265 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Sun, 9 Nov 2025 19:50:28 +0000 Subject: [PATCH 197/215] fixed to end of utterance timer (reduced to one timer and not separate for end of turn) --- examples/voice/cli/cli.py | 2 +- sdk/voice/speechmatics/voice/_client.py | 290 ++++++++----------- sdk/voice/speechmatics/voice/_models.py | 13 +- sdk/voice/speechmatics/voice/_presets.py | 22 +- sdk/voice/speechmatics/voice/_turn.py | 2 +- tests/voice/test_08_multiple_speakers.py | 2 +- tests/voice/test_10_finalize.py | 6 +- tests/voice/test_13_smart_turn_transcribe.py | 2 +- 8 files changed, 143 insertions(+), 196 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 17021388..9e205381 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -101,7 +101,7 @@ async def main() -> None: args.end_of_utterance_mode.lower() if args.end_of_utterance_mode else EndOfUtteranceMode.ADAPTIVE ), speaker_config=speaker_config, - use_forced_eou=args.forced_eou, + use_forced_eou_message=args.forced_eou, additional_vocab=[ AdditionalVocabEntry(content="Speechmatics", sounds_like=["speech matics"]), ], diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 150240e9..2acf0668 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -13,6 +13,7 @@ from typing import Any from typing import Callable from typing import Optional +from typing import Union from urllib.parse import urlencode from speechmatics.rt import AsyncClient @@ -205,13 +206,12 @@ def __init__( # ------------------------------------- # Handlers - self._eou_handler: TurnTaskProcessor = TurnTaskProcessor(name="eou_handler", done_callback=self.finalize) - self._eot_handler: TurnTaskProcessor = TurnTaskProcessor(name="eot_handler", done_callback=self.end_of_turn) + self._turn_handler: TurnTaskProcessor = TurnTaskProcessor(name="turn_handler", done_callback=self.finalize) self._smart_turn_detector: Optional[SmartTurnDetector] = None # Current turn - self._turn_id: int = 0 self._turn_start_time: Optional[float] = None + self._turn_active: bool = False # Start turn detector if SMART_TURN requested if self._config.end_of_utterance_mode == EndOfUtteranceMode.SMART_TURN: @@ -238,23 +238,11 @@ def __init__( self._uses_fixed_eou: bool = self._eou_mode == EndOfUtteranceMode.FIXED # Uses ForceEndOfUtterance message - self._uses_forced_eou: bool = self._config.use_forced_eou and self._eou_mode in [ - EndOfUtteranceMode.ADAPTIVE, - EndOfUtteranceMode.SMART_TURN, - ] - - # EOT start / stop messages - self._uses_turn_start_end: bool = self._eou_mode in [ - EndOfUtteranceMode.FIXED, - EndOfUtteranceMode.ADAPTIVE, - EndOfUtteranceMode.SMART_TURN, - ] - - # EOT prediction - self._uses_eot_prediction: bool = self._eou_mode in [ + self._uses_forced_eou: bool = self._eou_mode in [ EndOfUtteranceMode.ADAPTIVE, EndOfUtteranceMode.SMART_TURN, ] + self._forced_eou_active: bool = False # ------------------------------------- # Diarization / Speakers @@ -491,7 +479,7 @@ async def disconnect(self) -> None: return # Emit final segments - await self._emit_segments(finalize=True, end_of_turn=True) + await self._emit_segments(finalize=True) # Emit final metrics self._emit_speaker_metrics() @@ -503,15 +491,14 @@ async def disconnect(self) -> None: # end session try: - await self.stop_session() + await asyncio.wait_for(self.stop_session(), timeout=5.0) except Exception as e: self._logger.error(f"Error closing session: {e}") finally: self._is_connected = False # Stop end of turn-related tasks - self._eou_handler.cancel_tasks() - self._eot_handler.cancel_tasks() + self._turn_handler.cancel_tasks() # Stop the STT queue task if self._stt_queue_task: @@ -620,75 +607,38 @@ def update_diarization_config(self, config: SpeakerFocusConfig) -> None: # PUBLIC UTTERANCE / TURN MANAGEMENT # ============================================================================ - def finalize(self, ttl: Optional[float] = None, end_of_turn: bool = False) -> None: + def finalize(self, end_of_turn: bool = False) -> None: """Finalize segments. This function will emit segments in the buffer without any further checks - on the contents of the segments. If the ttl is set to zero, then finalization - will be forced through without yielding for any remaining STT messages. + on the contents of the segments. Args: - ttl: Optional delay before finalizing partial segments. end_of_turn: Whether to emit an end of turn message. - - Examples: - Immediate finalization: - >>> # Force finalization of current segments - >>> client.finalize(ttl=0) - - Delayed finalization: - >>> # Wait 0.5 seconds before finalizing - >>> client.finalize(ttl=0.5) """ # Current turn - _turn_id = self._turn_id + _turn_id = self._turn_handler.handler_id # Emit the finalize or use EndOfTurn on demand preview async def emit() -> None: - # Yield for the delay - if ttl is not None and ttl > 0: - await asyncio.sleep(ttl) + """Wait for EndOfUtterance if needed, then emit segments.""" - # Check if the turn has changed - if self._turn_id != _turn_id: - return - - # Guard against EOT being called when no segments are in the buffer - if end_of_turn and self._current_view and self._current_view.has_no_active_segments_remaining(): - return - - # Emit segments or finalize STT message (only for ADAPTIVE and SMART_TURN) - if end_of_turn and self._uses_forced_eou: + # Forced end of utterance message + if (self._config.use_forced_eou_message and self._current_view) and not ( + self._current_view.fragments[-1].is_eos and self._current_view.fragments[-1].is_final + ): await self._await_forced_eou() # Check if the turn has changed - if self._turn_id != _turn_id: + if self._turn_handler.handler_id != _turn_id: return # Emit the segments - await self._emit_segments(finalize=True, end_of_turn=end_of_turn) + self._stt_message_queue.put_nowait(lambda: self._emit_segments(finalize=True)) - # Cancel any pending tasks for end of utterance (as end of turn will emit segments) - self._eou_handler.complete_handler() - - # Add to queue - self._stt_message_queue.put_nowait(emit) - - def end_of_turn(self) -> None: - """Trigger end of turn. - - This function will force an end of turn and finalize segments, if required. Use - as a helper function for finalize() with end_of_turn=True. - - Examples: - Immediate end of turn: - >>> # Force end of turn of current segments - >>> client.end_of_turn() - """ - - # Emit the finalize or use EndOfTurn on demand preview - self.finalize(end_of_turn=True) + # Call async task + asyncio.create_task(emit()) # ============================================================================ # EVENT REGISTRATION & HANDLERS @@ -728,7 +678,7 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: async def _trigger_end_of_turn() -> None: - self.end_of_turn() + self.finalize() self._stt_message_queue.put_nowait(_trigger_end_of_turn) @@ -745,7 +695,7 @@ def _emit_message(self, message: BaseMessage) -> None: # Forward to the emit() method self.emit(message.message, message.model_dump()) - def _emit_info_message(self, message: str | dict[str, Any]) -> None: + def _emit_info_message(self, message: Union[str, dict[str, Any]]) -> None: """Emit an info message to the client.""" if isinstance(message, str): message = {"msg": message} @@ -969,7 +919,7 @@ async def _handle_transcript(self, message: dict[str, Any], is_final: bool) -> N if not fragments_available: return - # Process (only done with non-finals) + # Process (only done with AddPartialTranscript, as they always immediately follow AddTranscript if not is_final: await self._process_speech_fragments(self._change_filter) @@ -1051,6 +1001,10 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = # Track the last fragment end time self._last_fragment_end_time = max(self._last_fragment_end_time, fragment.end_time) + # Evaluate for VAD (only done on partials) + if not is_final: + await self._vad_evaluation(fragments) + # Fragments to retain retained_fragments = [ frag for frag in self._speech_fragments if frag.is_final and frag.start_time >= self._trim_before_time @@ -1069,10 +1023,6 @@ async def _add_speech_fragments(self, message: dict[str, Any], is_final: bool = ): self._speech_fragments.pop(0) - # Evaluate for VAD (only done on partials) - if not is_final: - self._vad_evaluation(fragments) - # Update TTFB (only if there are listeners) if not is_final: self._calculate_ttfb(end_time=payload_end_time) @@ -1140,11 +1090,7 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio # Emit the segments await self._emit_segments() - # For ADAPTIVE and SMART_TURN only - if self._uses_eot_prediction: - self._eot_prediction() - - async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False) -> None: + async def _emit_segments(self, finalize: bool = False) -> None: """Emit segments to listeners. This function will emit segments in the view without any further checks @@ -1153,13 +1099,19 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False Args: finalize: Whether to finalize all segments. - end_of_turn: Whether to emit an end of turn event. """ - # Lock the speech fragments + # Only process if we have segments in the buffer if self._current_view and self._current_view.segment_count == 0: + if finalize: + await self._emit_end_of_turn() return + # For ADAPTIVE and SMART_TURN only + if not finalize and self._uses_forced_eou and not self._forced_eou_active: + ttl = await self._eot_prediction() + self._turn_handler.update_timer(ttl) + # Lock the speech fragments async with self._speech_fragments_lock: # Segments to emit @@ -1167,7 +1119,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False partial_segments: list[SpeakerSegment] = [] # Keep until end of turn (`ON_END_OF_TURN`) - if not self._config.speech_segment_config.emit_sentences and not end_of_turn: + if not finalize and not self._config.speech_segment_config.emit_sentences: partial_segments = self._current_view.segments if self._current_view else [] # Force finalize @@ -1197,7 +1149,7 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False final_metadata = MessageTimeMetadata( start_time=final_segments[0].start_time, end_time=final_segments[-1].end_time, - processing_time=self._last_ttfb, + processing_time=round(self._last_ttfb, 3), ) # Ensure final segment ends with EOS @@ -1250,15 +1202,15 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._trim_before_time = final_segments[-1].end_time self._speech_fragments = [f for f in self._speech_fragments if f.start_time >= self._trim_before_time] - # Emit interim segments - if partial_segments: + # Emit interim segments (suppress when forced EOU is active) + if partial_segments and not self._forced_eou_active: """Partial segments are emitted as is.""" # Metadata for partial segments uses actual start/end times of the segments being emitted partial_metadata = MessageTimeMetadata( start_time=partial_segments[0].start_time, end_time=partial_segments[-1].end_time, - processing_time=self._last_ttfb, + processing_time=round(self._last_ttfb, 3), ) # Emit segments @@ -1298,16 +1250,36 @@ async def _emit_segments(self, finalize: bool = False, end_of_turn: bool = False self._calculate_speaker_metrics(partial_segments, final_segments) # Emit end of turn - if self._uses_turn_start_end and end_of_turn: + if finalize: await self._emit_end_of_turn() + async def _emit_start_of_turn(self, event_time: float) -> None: + """Emit the start of turn message.""" + + # Flag as turn active + self._turn_active = True + + # Emit + self._emit_message( + TurnStartEndResetMessage( + message=AgentServerMessageType.START_OF_TURN, + turn_id=self._turn_handler.handler_id, + metadata=MessageTimeMetadata( + start_time=event_time, + ), + ), + ) + async def _emit_end_of_turn(self) -> None: """Emit the end of turn message.""" # Check if we have a previous view - if not self._previous_view: + if not self._previous_view or not self._turn_active: return + # Flag as turn active + self._turn_active = False + # Metadata (for LAST view) metadata = MessageTimeMetadata(start_time=self._turn_start_time, end_time=self._previous_view.end_time) @@ -1315,13 +1287,13 @@ async def _emit_end_of_turn(self) -> None: self._emit_message( TurnStartEndResetMessage( message=AgentServerMessageType.END_OF_TURN, - turn_id=self._turn_id, + turn_id=self._turn_handler.handler_id, metadata=metadata, ), ) # Stop the EOT handler - self._eot_handler.complete_handler() + self._turn_handler.complete_handler() # Reset the previous view self._previous_view = None @@ -1400,7 +1372,7 @@ async def _calculate_finalize_delay( # Emit prediction message self._emit_message( TurnPredictionMessage( - turn_id=self._turn_id, + turn_id=self._turn_handler.handler_id, metadata=TurnPredictionMetadata( ttl=round(finalize_delay, 2), reasons=[reason for _, reason in reasons], @@ -1410,35 +1382,20 @@ async def _calculate_finalize_delay( return finalize_delay - def _eot_prediction(self, end_time: Optional[float] = None) -> None: + async def _eot_prediction(self, end_time: Optional[float] = None) -> float: """Handle end of turn prediction.""" - # Reset handler - self._eot_handler.reset() - - # Callback - async def do_eot_detection(end_time: Optional[float], language: str) -> None: - try: - # Wait for Smart Turn result - if self._eou_mode == EndOfUtteranceMode.SMART_TURN and end_time is not None: - result = await self._smart_turn_prediction(end_time, language) - else: - result = None - - # Create a new task to evaluate the finalize delay - delay = await self._calculate_finalize_delay(smart_turn_prediction=result) + # Wait for Smart Turn result + if self._eou_mode == EndOfUtteranceMode.SMART_TURN and end_time is not None: + result = await self._smart_turn_prediction(end_time, self._config.language) + else: + result = None - # Set the finalize timer (go now if no delay) - self._eot_handler.update_timer(delay or 0.005) + # Create a new task to evaluate the finalize delay + delay = await self._calculate_finalize_delay(smart_turn_prediction=result) - except asyncio.CancelledError: - pass - - # Add task - self._eot_handler.add_task( - asyncio.create_task(do_eot_detection(end_time, self._config.language)), - self._eou_mode.value, - ) + # Return the result + return delay or 0.005 async def _smart_turn_prediction(self, end_time: float, language: str) -> SmartTurnPredictionResult: """Predict when to emit the end of turn. @@ -1464,6 +1421,8 @@ async def _smart_turn_prediction(self, end_time: float, language: str) -> SmartT end_time=end_time + self._config.smart_turn_config.slice_margin, ) + # TODO - Output audio (for client to use) + # Evaluate prediction = await self._smart_turn_detector.predict( segment_audio, @@ -1475,7 +1434,7 @@ async def _smart_turn_prediction(self, end_time: float, language: str) -> SmartT # Return the prediction return prediction - async def _await_forced_eou(self, timeout: float = 2.0) -> bool: + async def _await_forced_eou(self, timeout: float = 2.0) -> None: """Await the forced end of utterance.""" # Received EOU @@ -1490,18 +1449,18 @@ async def _await_forced_eou(self, timeout: float = 2.0) -> bool: # Wait for EOU try: + self._forced_eou_active = True await asyncio.wait_for(eou_received.wait(), timeout=timeout) except asyncio.TimeoutError: - return False - - # Return True after EOU is received - return True + pass + finally: + self._forced_eou_active = False # ============================================================================ # VAD (VOICE ACTIVITY DETECTION) / SPEAKER DETECTION # ============================================================================ - def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: + async def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: """Emit a VAD event. This will emit `SPEAKER_STARTED` and `SPEAKER_ENDED` events to the client and is @@ -1585,75 +1544,52 @@ def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Event time event_time = speaker_start_time if self._is_speaking else speaker_end_time - # Emit start of turn (not when using EXTERNAL) - if self._uses_turn_start_end: - """Trigger a start of turn message and also reset pending end of turn prediction.""" + # Skip if no event time + if event_time is None: + return - # Emit end of turn prediction was wrong (turn continues) - if self._uses_eot_prediction and self._eot_handler.handler_active and self._is_speaking: - self._eot_handler.reset() + # Speaker events + if self._is_speaking: + await self._handle_speaker_started(latest_speaker, event_time) + else: + await self._handle_speaker_stopped(latest_speaker, speaker_end_time) - # New turn started - elif self._is_speaking and not self._eot_handler.handler_active: - self._eot_handler.start_handler() - self._turn_id = self._eot_handler.handler_id - self._emit_message( - TurnStartEndResetMessage( - message=AgentServerMessageType.START_OF_TURN, - turn_id=self._turn_id, - metadata=MessageTimeMetadata( - start_time=event_time, - ), - ), - ) + async def _handle_speaker_started(self, speaker: Optional[str], event_time: float) -> None: + """Reset timers when a new speaker starts speaking after silence.""" + + # Emit start of turn (not when using EXTERNAL) + if self._is_speaking and not self._turn_active: + await self._emit_start_of_turn(event_time) # Emit the event self._emit_message( VADStatusMessage( - message=( - AgentServerMessageType.SPEAKER_STARTED - if self._is_speaking - else AgentServerMessageType.SPEAKER_ENDED - ), - speaker_id=latest_speaker, - is_active=self._is_speaking, + message=AgentServerMessageType.SPEAKER_STARTED, + speaker_id=speaker, + is_active=True, time=event_time, ), ) - # Speaking has started - if self._is_speaking: - self._handle_speaker_started() - - # Speaking has stopped - else: - self._handle_speaker_stopped(speaker_end_time) - - def _handle_speaker_started(self) -> None: - """Reset timers when a new speaker starts speaking after silence.""" - # Reset the handlers - self._eou_handler.reset() - self._eot_handler.reset() + self._turn_handler.reset() - def _handle_speaker_stopped(self, speaker_end_time: float) -> None: + async def _handle_speaker_stopped(self, speaker: Optional[str], event_time: float) -> None: """Reset the current speaker and do smart turn detection (if enabled).""" + # Emit the event + self._emit_message( + VADStatusMessage( + message=AgentServerMessageType.SPEAKER_ENDED, + speaker_id=speaker, + is_active=False, + time=event_time, + ), + ) + # Reset current speaker self._current_speaker = None - # Add task for end of utterance - if self._uses_forced_eou: - self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger) - - # Fallback for fixed mode (e.g. when other speaker is speaking) - elif self._uses_fixed_eou: - self._eou_handler.update_timer(self._config.end_of_utterance_silence_trigger * 2) - - # For ADAPTIVE and SMART_TURN only - if self._uses_eot_prediction: - self._eot_prediction(speaker_end_time) - # ============================================================================ # HELPER METHODS # ============================================================================ diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 4db6e1c8..2cb65ab8 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -185,10 +185,11 @@ class AgentServerMessageType(str, Enum): ADD_PARTIAL_SEGMENT = "AddPartialSegment" ADD_SEGMENT = "AddSegment" - # End of turn messages + # Turn messages START_OF_TURN = "StartOfTurn" END_OF_TURN_PREDICTION = "EndOfTurnPrediction" END_OF_TURN = "EndOfTurn" + SMART_TURN_AUDIO = "SmartTurnAudio" # Speaker messages SPEAKERS_RESULT = "SpeakersResult" @@ -320,10 +321,16 @@ class SpeechSegmentConfig(BaseModel): emit_sentences: Emit segments when a sentence has ended. A finalized segment is emitted as soon as a finalized end of sentence is detected. If a speaker continues to speak during a turn, then multiple finalized segments may be emitted during the turn. + + pause_mark: Add pause mark to segments. When set, a pause fragment will be added to the segment + when a pause is detected using the string provided. For example, `...` would add this text + into the formatted output for a segment as `Hello ... how are you?`. + Defaults to None. """ add_trailing_eos: bool = False emit_sentences: bool = True + pause_mark: Optional[str] = None class EndOfTurnPenaltyItem(BaseModel): @@ -492,7 +499,7 @@ class VoiceAgentConfig(BaseModel): include_results: Include word data in the response. This is useful for debugging and understanding the STT engine's behavior. Defaults to False. - use_forced_eou: Use forced end of utterance. This will force the STT engine to emit + use_forced_eou_message: Use forced end of utterance message. This will force the STT engine to emit end of utterance messages. Defaults to False. transcription_update_preset: Emit segments when the text content or word timings change. @@ -604,7 +611,7 @@ class VoiceAgentConfig(BaseModel): # Advanced features include_results: bool = False - use_forced_eou: bool = False + use_forced_eou_message: bool = False transcription_update_preset: TranscriptionUpdatePreset = TranscriptionUpdatePreset.COMPLETE end_of_turn_config: EndOfTurnConfig = Field(default_factory=EndOfTurnConfig) smart_turn_config: SmartTurnConfig = Field(default_factory=SmartTurnConfig) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 8d54f6ca..d35582ea 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import Optional + from ._models import EndOfUtteranceMode from ._models import OperatingPoint from ._models import SpeechSegmentConfig @@ -14,7 +16,7 @@ class VoiceAgentConfigPreset: """Set of preset configurations for the Voice Agent SDK.""" @staticmethod - def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 + def LOW_LATENCY(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for low latency situations. This configuration will emit the end of turn as soon as possible, with minimal @@ -23,8 +25,8 @@ def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - model=OperatingPoint.STANDARD, - enable_diarization=False, + operating_point=OperatingPoint.STANDARD, + enable_diarization=True, max_delay=0.8, end_of_utterance_silence_trigger=0.2, end_of_utterance_mode=EndOfUtteranceMode.FIXED, @@ -34,7 +36,7 @@ def LOW_LATENCY(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # ) @staticmethod - def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 + def CONVERSATION_ADAPTIVE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for general conversational use cases. For conversation, there is a balance between accuracy, speed and the rate at @@ -44,7 +46,7 @@ def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgent """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - model=OperatingPoint.ENHANCED, + operating_point=OperatingPoint.ENHANCED, enable_diarization=True, max_delay=1.25, end_of_utterance_silence_trigger=0.8, @@ -55,7 +57,7 @@ def CONVERSATION_ADAPTIVE(overlay: VoiceAgentConfig | None = None) -> VoiceAgent ) @staticmethod - def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 + def CONVERSATION_SMART_TURN(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for complex conversational use cases. For conversation, there is a balance between accuracy, speed and the rate at @@ -71,7 +73,7 @@ def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAge """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - model=OperatingPoint.ENHANCED, + operating_point=OperatingPoint.ENHANCED, enable_diarization=True, max_delay=1.25, end_of_utterance_silence_trigger=0.8, @@ -82,7 +84,7 @@ def CONVERSATION_SMART_TURN(overlay: VoiceAgentConfig | None = None) -> VoiceAge ) @staticmethod - def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa: N802 + def SCRIBE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noqa: N802 """Best suited for note-taking and scribes. This mode will emit partial and final segments as they become available. The end of @@ -90,7 +92,7 @@ def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - model=OperatingPoint.ENHANCED, + operating_point=OperatingPoint.ENHANCED, enable_diarization=True, max_delay=1.4, end_of_utterance_silence_trigger=1.0, @@ -101,7 +103,7 @@ def SCRIBE(overlay: VoiceAgentConfig | None = None) -> VoiceAgentConfig: # noqa ) @staticmethod - def _merge_configs(base: VoiceAgentConfig, overlay: VoiceAgentConfig | None) -> VoiceAgentConfig: + def _merge_configs(base: VoiceAgentConfig, overlay: Optional[VoiceAgentConfig]) -> VoiceAgentConfig: """Merge two VoiceAgentConfig objects. Simply merge any overrides from the overlay into the base config. This makes creating diff --git a/sdk/voice/speechmatics/voice/_turn.py b/sdk/voice/speechmatics/voice/_turn.py index 921d6df5..09af0546 100644 --- a/sdk/voice/speechmatics/voice/_turn.py +++ b/sdk/voice/speechmatics/voice/_turn.py @@ -125,7 +125,7 @@ async def _do_done_callback(self) -> None: pass # Complete the task - self.complete_handler() + # self.complete_handler() def cancel_tasks(self) -> None: """Cancel any pending tasks.""" diff --git a/tests/voice/test_08_multiple_speakers.py b/tests/voice/test_08_multiple_speakers.py index 941481fa..adbebd52 100644 --- a/tests/voice/test_08_multiple_speakers.py +++ b/tests/voice/test_08_multiple_speakers.py @@ -99,7 +99,7 @@ async def test_multiple_speakers(sample: SpeakerTest): config = sample.config or VoiceAgentConfig( end_of_utterance_silence_trigger=1.0, max_delay=2.0, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, additional_vocab=[ AdditionalVocabEntry(content="GeoRouter"), ], diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 81933951..2357ff04 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -44,7 +44,7 @@ async def test_finalize(): end_of_utterance_silence_trigger=0.7, max_delay=1.2, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, - use_forced_eou=True, + use_forced_eou_message=True, ), ) @@ -81,7 +81,9 @@ def eot_received_callback(message): client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) + client.on(AgentServerMessageType.START_OF_TURN, log_message) client.on(AgentServerMessageType.END_OF_TURN, log_message) + client.on(AgentServerMessageType.END_OF_TRANSCRIPT, log_message) # End of Turn client.once(AgentServerMessageType.END_OF_TURN, eot_received_callback) @@ -124,7 +126,7 @@ def eot_received_callback(message): # Request the speakers result finalize_trigger_time = datetime.datetime.now() - client.end_of_turn() + client.finalize() # Wait for the callback with timeout try: diff --git a/tests/voice/test_13_smart_turn_transcribe.py b/tests/voice/test_13_smart_turn_transcribe.py index 2a1646c0..9283c33e 100644 --- a/tests/voice/test_13_smart_turn_transcribe.py +++ b/tests/voice/test_13_smart_turn_transcribe.py @@ -99,7 +99,7 @@ async def test_prediction(sample: TranscriptionTest): enable_diarization=True, sample_rate=sample.sample_rate, additional_vocab=sample.additional_vocab, - use_forced_eou=True, + use_forced_eou_message=True, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), ) From b48cfbc9b4e52c552ebe81b585d414ddf48c6357 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 10 Nov 2025 14:51:28 +0000 Subject: [PATCH 198/215] tidy session close --- examples/voice/cli/cli.py | 6 ++++-- sdk/voice/speechmatics/voice/_client.py | 14 ++++++++++++++ sdk/voice/speechmatics/voice/_smart_turn.py | 2 -- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/voice/cli/cli.py b/examples/voice/cli/cli.py index 9e205381..a581c9c0 100644 --- a/examples/voice/cli/cli.py +++ b/examples/voice/cli/cli.py @@ -133,8 +133,8 @@ async def main() -> None: # Connect to the Voice Agent service try: await client.connect() - except Exception as e: - print(f"Error connecting to Voice Agent service: {e}") + except Exception: + print("Error connecting to Voice Agent service") return # Request speaker IDs at the end of the session (if enrolling) @@ -743,6 +743,8 @@ def parse_args(): "ignore-speakers", "language", "max-delay", + "forced-eou", + "speakers", ] if args.config is not None: diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 2acf0668..241c9a7f 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -139,6 +139,7 @@ def __init__( # Connection status self._is_connected: bool = False self._is_ready_for_audio: bool = False + self._closing_session: bool = False # Session info (updated on session created) self._client_session: ClientSessionInfo = ClientSessionInfo( @@ -414,6 +415,9 @@ async def connect(self) -> None: ) return + # Update the closing session flag + self._closing_session = False + # Start the processor task self._stt_queue_task = asyncio.create_task(self._run_stt_queue()) @@ -478,6 +482,9 @@ async def disconnect(self) -> None: if not self._is_connected: return + # Update the closing session flag + self._closing_session = True + # Emit final segments await self._emit_segments(finalize=True) @@ -665,11 +672,15 @@ def _evt_on_recognition_started(message: dict[str, Any]) -> None: # Partial transcript event @self.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT) # type: ignore[misc] def _evt_on_partial_transcript(message: dict[str, Any]) -> None: + if self._closing_session: + return self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=False)) # Final transcript event @self.on(ServerMessageType.ADD_TRANSCRIPT) # type: ignore[misc] def _evt_on_final_transcript(message: dict[str, Any]) -> None: + if self._closing_session: + return self._stt_message_queue.put_nowait(lambda: self._handle_transcript(message, is_final=True)) # End of Utterance (FIXED mode only) @@ -677,6 +688,9 @@ def _evt_on_final_transcript(message: dict[str, Any]) -> None: @self.on(ServerMessageType.END_OF_UTTERANCE) # type: ignore[misc] def _evt_on_end_of_utterance(message: dict[str, Any]) -> None: + if self._closing_session: + return + async def _trigger_end_of_turn() -> None: self.finalize() diff --git a/sdk/voice/speechmatics/voice/_smart_turn.py b/sdk/voice/speechmatics/voice/_smart_turn.py index 20f66991..011318d8 100644 --- a/sdk/voice/speechmatics/voice/_smart_turn.py +++ b/sdk/voice/speechmatics/voice/_smart_turn.py @@ -214,8 +214,6 @@ async def predict( # Record end time end_time = datetime.datetime.now() - print(prediction) - # Return the result return SmartTurnPredictionResult( prediction=prediction, From efe8ddd56b3caa6c9bbcb664f26e2208774a75bc Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Mon, 10 Nov 2025 16:32:39 +0000 Subject: [PATCH 199/215] initial support for ellipses for pauses --- sdk/voice/speechmatics/voice/_client.py | 6 ++- sdk/voice/speechmatics/voice/_utils.py | 49 +++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 241c9a7f..566801b9 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1101,6 +1101,9 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio if change_filter and not changes.any(*change_filter): return + # Check for gaps + # FragmentUtils.find_segment_pauses(self._client_session, self._current_view) + # Emit the segments await self._emit_segments() @@ -1183,10 +1186,9 @@ async def _emit_segments(self, finalize: bool = False) -> None: ) ) # Update text - last_segment.text = FragmentUtils.format_segment_text( + FragmentUtils.update_segment_text( session=self._client_session, segment=last_segment, - include_partials=True, ) # Emit segments diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index 8684991d..c81f16d1 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -139,14 +139,24 @@ def segment_list_from_fragments( annotate=annotate_segments, ) if segment: - segment.text = FragmentUtils.format_segment_text( - session=session, segment=segment, include_partials=session.config.include_partials - ) + FragmentUtils.update_segment_text(session=session, segment=segment) segments.append(segment) # Return the grouped SpeakerFragments objects return segments + @staticmethod + def update_segment_text(session: ClientSessionInfo, segment: SpeakerSegment) -> None: + """Update the text of a segment based on the language pack info. + + Args: + session: ClientSessionInfo object. + segment: SpeakerSegment object. + """ + segment.text = FragmentUtils.format_segment_text( + session=session, segment=segment, include_partials=session.config.include_partials + ) + @staticmethod def segment_from_fragments( session: ClientSessionInfo, @@ -364,6 +374,39 @@ def compare_views( # Return the result return result + @staticmethod + def find_segment_pauses(session: ClientSessionInfo, view: SpeakerSegmentView) -> None: + """Find gaps in the segments.""" + + # Find gaps in the view + for segment in view.segments: + # Strip out existing pauses + words = [f for f in segment.fragments if f.type_ != "pause"] + + # Find gaps between the end of one word and the start of the next + for i in range(len(words) - 1): + word = words[i] + next_word = words[i + 1] + gap_start = word.end_time + gap_end = next_word.start_time + if gap_end - gap_start > 0.1: + segment.fragments.append( + SpeechFragment( + idx=word.idx + 1, + type_="pause", + start_time=gap_start, + end_time=gap_end, + if_final=word.is_final, + content=session.config.speech_segment_config.pause_mark or "...", + ) + ) + + # Resort the fragments + segment.fragments.sort(key=lambda f: f.idx) + + # Re-process the text + FragmentUtils.update_segment_text(session, segment) + class TextUtils: """Set of string / text utilities.""" From ef6e968706eda493b077df3d1c5dd238097c01d0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 11:32:01 +0000 Subject: [PATCH 200/215] faster speaker end detection; forced EOU updates; test updates --- sdk/voice/speechmatics/voice/_client.py | 52 ++++++++++++++++++------- sdk/voice/speechmatics/voice/_utils.py | 26 +++++++++---- tests/voice/test_10_finalize.py | 18 ++------- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 566801b9..04fe0226 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -631,10 +631,12 @@ def finalize(self, end_of_turn: bool = False) -> None: async def emit() -> None: """Wait for EndOfUtterance if needed, then emit segments.""" - # Forced end of utterance message - if (self._config.use_forced_eou_message and self._current_view) and not ( - self._current_view.fragments[-1].is_eos and self._current_view.fragments[-1].is_final - ): + # Forced end of utterance message (only when no speaker is detected) + if ( + self._config.use_forced_eou_message + and self._current_view + and (self._eou_mode == EndOfUtteranceMode.EXTERNAL or not self._is_speaking) + ) and not (self._current_view.fragments[-1].is_eos and self._current_view.fragments[-1].is_final): await self._await_forced_eou() # Check if the turn has changed @@ -1124,11 +1126,6 @@ async def _emit_segments(self, finalize: bool = False) -> None: await self._emit_end_of_turn() return - # For ADAPTIVE and SMART_TURN only - if not finalize and self._uses_forced_eou and not self._forced_eou_active: - ttl = await self._eot_prediction() - self._turn_handler.update_timer(ttl) - # Lock the speech fragments async with self._speech_fragments_lock: # Segments to emit @@ -1491,29 +1488,47 @@ async def _vad_evaluation(self, fragments: list[SpeechFragment]) -> None: # Find the valid list of partial words if self._dz_enabled and self._dz_config.focus_speakers: - partial_words = [ + new_partials = [ frag for frag in fragments if frag.speaker in self._dz_config.focus_speakers and frag.type_ == "word" and not frag.is_final ] + pre_partials = [ + frag + for frag in self._speech_fragments + if frag.speaker in self._dz_config.focus_speakers and frag.type_ == "word" and not frag.is_final + ] else: - partial_words = [frag for frag in fragments if frag.type_ == "word" and not frag.is_final] + new_partials = [frag for frag in fragments if frag.type_ == "word" and not frag.is_final] + pre_partials = [frag for frag in self._speech_fragments if frag.type_ == "word" and not frag.is_final] + + # Check if last new partial matches the last pre partial + if len(pre_partials) > 0 and len(new_partials) > 0: + has_valid_partial = not all( + [ + pre_partials[-1].speaker == new_partials[-1].speaker, + pre_partials[-1].start_time == new_partials[-1].start_time, + pre_partials[-1].end_time == new_partials[-1].end_time, + pre_partials[-1].content == new_partials[-1].content, + ] + ) # Evaluate if any valid partial words exist - has_valid_partial = len(partial_words) > 0 + else: + has_valid_partial = len(new_partials) > 0 # Current states current_is_speaking = self._is_speaking current_speaker = self._current_speaker # Establish the speaker from latest partials - latest_speaker = partial_words[-1].speaker if has_valid_partial else current_speaker + latest_speaker = new_partials[-1].speaker if has_valid_partial else current_speaker # Determine if the speaker has changed (and we have a speaker) speaker_changed = latest_speaker != current_speaker and current_speaker is not None # Start / end times (earliest and latest) - speaker_start_time = partial_words[0].start_time if has_valid_partial else None + speaker_start_time = new_partials[0].start_time if has_valid_partial else None speaker_end_time = self._last_fragment_end_time # If diarization is enabled, indicate speaker switching @@ -1577,6 +1592,10 @@ async def _handle_speaker_started(self, speaker: Optional[str], event_time: floa if self._is_speaking and not self._turn_active: await self._emit_start_of_turn(event_time) + # Update the turn handler + if self._uses_forced_eou: + self._turn_handler.reset() + # Emit the event self._emit_message( VADStatusMessage( @@ -1593,6 +1612,11 @@ async def _handle_speaker_started(self, speaker: Optional[str], event_time: floa async def _handle_speaker_stopped(self, speaker: Optional[str], event_time: float) -> None: """Reset the current speaker and do smart turn detection (if enabled).""" + # Turn prediction + if self._uses_forced_eou: + ttl = await self._eot_prediction(event_time) + self._turn_handler.update_timer(ttl) + # Emit the event self._emit_message( VADStatusMessage( diff --git a/sdk/voice/speechmatics/voice/_utils.py b/sdk/voice/speechmatics/voice/_utils.py index c81f16d1..31e7e244 100644 --- a/sdk/voice/speechmatics/voice/_utils.py +++ b/sdk/voice/speechmatics/voice/_utils.py @@ -286,16 +286,17 @@ def _annotate_segment(segment: SpeakerSegment) -> AnnotationResult: # Rate of speech if len(words) > 1: - # Calculate the approximate words-per-minute (for last 5 words) - last_5_words = words[-5:] - wpm = len(last_5_words) / ((last_5_words[-1].end_time - last_5_words[0].start_time) / 60.0) + # Calculate the approximate words-per-minute (for last few words) + recent_words = words[-10:] + word_time_span = recent_words[-1].end_time - recent_words[0].start_time + wpm = (len(recent_words) / word_time_span) * 60 # Categorize the speaker - if wpm < 30: + if wpm < 80: result.add(AnnotationFlags.VERY_SLOW_SPEAKER) - elif wpm < 80: + elif wpm < 120: result.add(AnnotationFlags.SLOW_SPEAKER) - elif wpm > 350: + elif wpm > 250: result.add(AnnotationFlags.FAST_SPEAKER) # Return the annotation result @@ -376,7 +377,16 @@ def compare_views( @staticmethod def find_segment_pauses(session: ClientSessionInfo, view: SpeakerSegmentView) -> None: - """Find gaps in the segments.""" + """Find gaps in the segments. + + LLMs may find knowledge of when someone pauses between words of use when constructing + their reply. This utility adds in pause markers to the segments which can then be used + during the text formatting of the segment. + + Args: + session: ClientSessionInfo object. + view: The SpeakerSegmentView object to process. + """ # Find gaps in the view for segment in view.segments: @@ -396,7 +406,7 @@ def find_segment_pauses(session: ClientSessionInfo, view: SpeakerSegmentView) -> type_="pause", start_time=gap_start, end_time=gap_end, - if_final=word.is_final, + is_final=word.is_final, content=session.config.speech_segment_config.pause_mark or "...", ) ) diff --git a/tests/voice/test_10_finalize.py b/tests/voice/test_10_finalize.py index 2357ff04..247d46f4 100644 --- a/tests/voice/test_10_finalize.py +++ b/tests/voice/test_10_finalize.py @@ -6,7 +6,6 @@ import pytest from _utils import get_client from _utils import send_audio_file -from _utils import send_silence from speechmatics.voice import AgentServerMessageType from speechmatics.voice import EndOfUtteranceMode @@ -18,7 +17,7 @@ # Constants API_KEY = os.getenv("SPEECHMATICS_API_KEY") SHOW_LOG = os.getenv("SPEECHMATICS_SHOW_LOG", "0").lower() in ["1", "true"] -AUDIO_FILE = "./assets/audio_03_16kHz.wav" +AUDIO_FILE = "./assets/audio_05_16kHz.wav" @pytest.mark.asyncio @@ -78,6 +77,7 @@ def eot_received_callback(message): # Add listeners client.once(AgentServerMessageType.RECOGNITION_STARTED, log_message) + client.on(AgentServerMessageType.INFO, log_message) client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, log_message) client.on(AgentServerMessageType.ADD_SEGMENT, log_message) client.on(AgentServerMessageType.END_OF_UTTERANCE, log_message) @@ -109,20 +109,10 @@ def eot_received_callback(message): # Set chunk size chunk_size = 160 - # Send words twice with silence in-between - await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) - await send_silence(client, 0.5, chunk_size=chunk_size, progress_callback=log_bytes_sent) - await send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent) - - # Send silence in a thread - asyncio.create_task( - send_silence( - client, 10.0, chunk_size=chunk_size, progress_callback=log_bytes_sent, terminate_event=eot_received - ) - ) + asyncio.create_task(send_audio_file(client, AUDIO_FILE, chunk_size=chunk_size, progress_callback=log_bytes_sent)) # Wait for 0.25 seconds - await asyncio.sleep(0.25) + await asyncio.sleep(2) # Request the speakers result finalize_trigger_time = datetime.datetime.now() From 60815bae1dee7f46ecae3fe5ec92e2fc012c190c Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 21:03:55 +0000 Subject: [PATCH 201/215] preset updates and tests --- sdk/voice/speechmatics/voice/_presets.py | 34 +++++++++++++++++++++--- tests/voice/test_04_models.py | 18 ++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index d35582ea..d4406aa8 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -48,8 +48,8 @@ def CONVERSATION_ADAPTIVE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAg VoiceAgentConfig( operating_point=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=1.25, - end_of_utterance_silence_trigger=0.8, + max_delay=0.7, + end_of_utterance_silence_trigger=1.0, end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), @@ -75,8 +75,8 @@ def CONVERSATION_SMART_TURN(overlay: Optional[VoiceAgentConfig] = None) -> Voice VoiceAgentConfig( operating_point=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=1.25, - end_of_utterance_silence_trigger=0.8, + max_delay=0.7, + end_of_utterance_silence_trigger=1.0, end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, speech_segment_config=SpeechSegmentConfig(emit_sentences=False), ), @@ -102,6 +102,32 @@ def SCRIBE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # n overlay, ) + @staticmethod + def list_presets() -> list[str]: + """List available presets.""" + return [attr.lower() for attr in dir(VoiceAgentConfigPreset) if not attr.startswith("_") and attr.isupper()] + + @staticmethod + def get(preset: str, overlay_json: Optional[str] = None) -> VoiceAgentConfig: + """Get a preset configuration. + + Args: + preset: Preset to use. + overlay_json: Optional overlay JSON to apply to the preset. + + Returns: + VoiceAgentConfig: Preset configuration. + """ + try: + config: VoiceAgentConfig = getattr(VoiceAgentConfigPreset, preset.upper())() + if overlay_json is not None: + config = VoiceAgentConfig.model_validate_json(overlay_json) + return config + except ValueError: + raise ValueError(f"Invalid overlay JSON: {overlay_json}") + except AttributeError: + raise ValueError(f"Invalid preset: {preset}") + @staticmethod def _merge_configs(base: VoiceAgentConfig, overlay: Optional[VoiceAgentConfig]) -> VoiceAgentConfig: """Merge two VoiceAgentConfig objects. diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index ac4fe737..de4201cf 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -254,12 +254,12 @@ async def test_presets(): """Test VoiceAgentConfigPreset presets.""" # Create a preset - low_latency: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY() + low_latency: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY() assert low_latency is not None assert low_latency.speech_segment_config.emit_sentences is True # Overlay #1 - low_latency_one: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( + low_latency_one: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( VoiceAgentConfig(max_delay=12.34, enable_diarization=False) ) assert low_latency_one is not None @@ -267,9 +267,21 @@ async def test_presets(): assert low_latency_one.enable_diarization is False # Overlay #2 - low_latency_two: VoiceAgentConfig | None = VoiceAgentConfigPreset.LOW_LATENCY( + low_latency_two: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_sentences=False)) ) assert low_latency_two is not None assert low_latency_two.enable_diarization is True assert low_latency_two.speech_segment_config.emit_sentences is False + + # Preset names + presets = VoiceAgentConfigPreset.list_presets() + assert "low_latency" in presets + + # Get a preset by a name + low_latency: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency") + assert low_latency is not None + + # Check using incorrect preset name + with pytest.raises(ValueError): + VoiceAgentConfigPreset.get("invalid_preset") From 2fae2c74ce3389e3c0d99101e181c7ae3ea7112a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 21:05:11 +0000 Subject: [PATCH 202/215] updated changeset --- sdk/voice/speechmatics/voice/_client.py | 7 +++++++ sdk/voice/speechmatics/voice/_models.py | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 04fe0226..5a9c905c 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -1103,6 +1103,12 @@ async def _process_speech_fragments(self, change_filter: Optional[list[Annotatio if change_filter and not changes.any(*change_filter): return + # Turn prediction + if self._uses_forced_eou: + ttl = await self._calculate_finalize_delay() + if ttl: + self._turn_handler.update_timer(ttl) + # Check for gaps # FragmentUtils.find_segment_pauses(self._client_session, self._current_view) @@ -1340,6 +1346,7 @@ async def _calculate_finalize_delay( else self._previous_view ) + # Skip if view doesn't exist if not view: return None diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 2cb65ab8..9d272f80 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -357,11 +357,12 @@ class EndOfTurnConfig(BaseModel): penalties: List of end of turn penalty items. """ - base_multiplier: float = 1.15 - min_end_of_turn_delay: float = 0.025 + base_multiplier: float = 1.0 + min_end_of_turn_delay: float = 0.015 end_of_turn_adjustment_factor: float = 1.0 penalties: list[EndOfTurnPenaltyItem] = Field( default_factory=lambda: [ + # Increase delay EndOfTurnPenaltyItem(penalty=3.0, annotation=[AnnotationFlags.VERY_SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.0, annotation=[AnnotationFlags.SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.5, annotation=[AnnotationFlags.ENDS_WITH_DISFLUENCY]), @@ -371,6 +372,10 @@ class EndOfTurnConfig(BaseModel): annotation=[AnnotationFlags.ENDS_WITH_EOS], is_not=True, ), + # Decrease delay + EndOfTurnPenaltyItem( + penalty=0.25, annotation=[AnnotationFlags.ENDS_WITH_FINAL, AnnotationFlags.ENDS_WITH_EOS] + ), ] ) From 073035c1c93e1c566008629cb14acd3642c4f80e Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 21:28:24 +0000 Subject: [PATCH 203/215] Enable strict JSON parsing for models. --- sdk/voice/speechmatics/voice/_models.py | 26 ++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 9d272f80..78c1c745 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -248,7 +248,13 @@ class AnnotationFlags(str, Enum): # ============================================================================== -class AdditionalVocabEntry(BaseModel): +class BaseConfigModel(BaseModel): + """Base configuration model.""" + + model_config = ConfigDict(strict=True, extra="forbid") + + +class AdditionalVocabEntry(BaseConfigModel): """Additional vocabulary entry. Parameters: @@ -277,7 +283,7 @@ class AdditionalVocabEntry(BaseModel): sounds_like: list[str] = Field(default_factory=list) -class SpeakerFocusConfig(BaseModel): +class SpeakerFocusConfig(BaseConfigModel): """Speaker Focus Config. List of speakers to focus on, ignore and how to deal with speakers that are not @@ -311,7 +317,7 @@ class SpeakerFocusConfig(BaseModel): focus_mode: SpeakerFocusMode = SpeakerFocusMode.RETAIN -class SpeechSegmentConfig(BaseModel): +class SpeechSegmentConfig(BaseConfigModel): """Configuration on how segments are emitted. Parameters: @@ -333,7 +339,7 @@ class SpeechSegmentConfig(BaseModel): pause_mark: Optional[str] = None -class EndOfTurnPenaltyItem(BaseModel): +class EndOfTurnPenaltyItem(BaseConfigModel): """End of turn penalty item. Parameters: @@ -347,7 +353,7 @@ class EndOfTurnPenaltyItem(BaseModel): is_not: bool = False -class EndOfTurnConfig(BaseModel): +class EndOfTurnConfig(BaseConfigModel): """Configuration for end of turn. Parameters: @@ -380,7 +386,7 @@ class EndOfTurnConfig(BaseModel): ) -class SmartTurnConfig(BaseModel): +class SmartTurnConfig(BaseConfigModel): """Smart turn configuration for the Speechmatics Voice Agent. This configuration is used to determine when a turn has completed. It is used to @@ -415,7 +421,7 @@ class SmartTurnConfig(BaseModel): negative_penalty: float = 2.5 -class VoiceAgentConfig(BaseModel): +class VoiceAgentConfig(BaseConfigModel): """Voice Agent configuration. A framework-independent configuration object for the Speechmatics Voice Agent. This uses @@ -629,6 +635,12 @@ class VoiceAgentConfig(BaseModel): sample_rate: int = 16000 audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE + # Parse JSON + @classmethod + def from_json(cls, json_data: str) -> VoiceAgentConfig: + cfg: VoiceAgentConfig = cls.model_validate_json(json_data) + return cfg + # ============================================================================== # SESSION & INFO MODELS From 5461f7403aff2fc1b838e371d202c95814931e61 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 21:28:35 +0000 Subject: [PATCH 204/215] updated preset tests --- sdk/voice/speechmatics/voice/_models.py | 2 +- sdk/voice/speechmatics/voice/_presets.py | 3 +- tests/voice/test_04_models.py | 45 ++---------------- tests/voice/test_14_presets.py | 58 ++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 tests/voice/test_14_presets.py diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 78c1c745..75563747 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -251,7 +251,7 @@ class AnnotationFlags(str, Enum): class BaseConfigModel(BaseModel): """Base configuration model.""" - model_config = ConfigDict(strict=True, extra="forbid") + model_config = ConfigDict(extra="forbid") class AdditionalVocabEntry(BaseConfigModel): diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index d4406aa8..d8614bd2 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -121,7 +121,8 @@ def get(preset: str, overlay_json: Optional[str] = None) -> VoiceAgentConfig: try: config: VoiceAgentConfig = getattr(VoiceAgentConfigPreset, preset.upper())() if overlay_json is not None: - config = VoiceAgentConfig.model_validate_json(overlay_json) + overlay = VoiceAgentConfig.model_validate_json(overlay_json) + config = VoiceAgentConfigPreset._merge_configs(config, overlay) return config except ValueError: raise ValueError(f"Invalid overlay JSON: {overlay_json}") diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index de4201cf..32b0c1e0 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -6,13 +6,12 @@ from speechmatics.voice._models import AdditionalVocabEntry from speechmatics.voice._models import AnnotationFlags from speechmatics.voice._models import AnnotationResult +from speechmatics.voice._models import OperatingPoint from speechmatics.voice._models import SpeakerFocusConfig from speechmatics.voice._models import SpeakerFocusMode from speechmatics.voice._models import SpeakerIdentifier from speechmatics.voice._models import SpeakerSegment from speechmatics.voice._models import SpeechFragment -from speechmatics.voice._models import SpeechSegmentConfig -from speechmatics.voice._presets import VoiceAgentConfigPreset @pytest.mark.asyncio @@ -50,6 +49,10 @@ async def test_voice_agent_config(): assert len(config_from_json.known_speakers) == 1 assert config_from_json.known_speakers[0].label == "John" + # From JSON + preset: VoiceAgentConfig = VoiceAgentConfig.from_json('{"operating_point": "enhanced"}') + assert preset.operating_point == OperatingPoint.ENHANCED + @pytest.mark.asyncio async def test_annotation_result(): @@ -247,41 +250,3 @@ async def test_speaker_segment(): assert "results" in dict_data_results assert "fragments" not in dict_data_results assert len(dict_data_results["results"]) == 2 - - -@pytest.mark.asyncio -async def test_presets(): - """Test VoiceAgentConfigPreset presets.""" - - # Create a preset - low_latency: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY() - assert low_latency is not None - assert low_latency.speech_segment_config.emit_sentences is True - - # Overlay #1 - low_latency_one: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( - VoiceAgentConfig(max_delay=12.34, enable_diarization=False) - ) - assert low_latency_one is not None - assert low_latency_one.max_delay == 12.34 - assert low_latency_one.enable_diarization is False - - # Overlay #2 - low_latency_two: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( - VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_sentences=False)) - ) - assert low_latency_two is not None - assert low_latency_two.enable_diarization is True - assert low_latency_two.speech_segment_config.emit_sentences is False - - # Preset names - presets = VoiceAgentConfigPreset.list_presets() - assert "low_latency" in presets - - # Get a preset by a name - low_latency: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency") - assert low_latency is not None - - # Check using incorrect preset name - with pytest.raises(ValueError): - VoiceAgentConfigPreset.get("invalid_preset") diff --git a/tests/voice/test_14_presets.py b/tests/voice/test_14_presets.py new file mode 100644 index 00000000..f4a69c5d --- /dev/null +++ b/tests/voice/test_14_presets.py @@ -0,0 +1,58 @@ +import pytest + +from speechmatics.voice import VoiceAgentConfig +from speechmatics.voice._models import OperatingPoint +from speechmatics.voice._models import SpeechSegmentConfig +from speechmatics.voice._presets import VoiceAgentConfigPreset + + +@pytest.mark.asyncio +async def test_presets(): + """Test VoiceAgentConfigPreset presets.""" + + # Create a preset + preset: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY() + assert preset is not None + assert preset.speech_segment_config.emit_sentences is True + + # Overlay #1 + preset: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( + VoiceAgentConfig(max_delay=12.34, enable_diarization=False) + ) + assert preset is not None + assert preset.max_delay == 12.34 + assert preset.enable_diarization is False + + # Overlay #2 + preset: VoiceAgentConfig = VoiceAgentConfigPreset.LOW_LATENCY( + VoiceAgentConfig(speech_segment_config=SpeechSegmentConfig(emit_sentences=False)) + ) + assert preset is not None + assert preset.enable_diarization is True + assert preset.speech_segment_config.emit_sentences is False + + # Preset names + presets = VoiceAgentConfigPreset.list_presets() + assert "low_latency" in presets + + # Get a preset by a name + preset: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency") + assert preset is not None + + +@pytest.mark.asyncio +async def test_json_presets(): + """Test VoiceAgentConfigPreset JSON presets.""" + + # With a JSON string overlay + preset: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency", '{"operating_point": "enhanced"}') + assert preset is not None + assert preset.operating_point == OperatingPoint.ENHANCED + + # Check using incorrect preset name + with pytest.raises(ValueError): + VoiceAgentConfigPreset.get("invalid_preset") + + # Check with invalid overlay + with pytest.raises(ValueError): + VoiceAgentConfigPreset.get("low_latency", '{"invalid": "value"}') From 88285533a39512d12484091e719f2a851361edc0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 22:39:24 +0000 Subject: [PATCH 205/215] updated presets --- sdk/voice/speechmatics/voice/_presets.py | 31 ++++++++++++++++++++---- tests/voice/test_14_presets.py | 8 +++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index d8614bd2..2452d14f 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -27,8 +27,8 @@ def LOW_LATENCY(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: VoiceAgentConfig( operating_point=OperatingPoint.STANDARD, enable_diarization=True, - max_delay=0.8, - end_of_utterance_silence_trigger=0.2, + max_delay=0.7, + end_of_utterance_silence_trigger=0.5, end_of_utterance_mode=EndOfUtteranceMode.FIXED, speech_segment_config=SpeechSegmentConfig(emit_sentences=True), ), @@ -94,10 +94,31 @@ def SCRIBE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # n VoiceAgentConfig( operating_point=OperatingPoint.ENHANCED, enable_diarization=True, - max_delay=1.4, - end_of_utterance_silence_trigger=1.0, + max_delay=1.0, + end_of_utterance_silence_trigger=1.2, + end_of_utterance_mode=EndOfUtteranceMode.FIXED, + speech_segment_config=SpeechSegmentConfig(emit_sentences=True), + ), + overlay, + ) + + @staticmethod + def CAPTIONS(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noqa: N802 + """Best suited for captions. + + This mode will emit partial and final segments as they become available. The end of + utterance is set to fixed. End of turn is not required for captions. The segments + will only include finalized words. + """ + return VoiceAgentConfigPreset._merge_configs( + VoiceAgentConfig( + operating_point=OperatingPoint.ENHANCED, + enable_diarization=True, + max_delay=0.9, + end_of_utterance_silence_trigger=1.2, end_of_utterance_mode=EndOfUtteranceMode.FIXED, speech_segment_config=SpeechSegmentConfig(emit_sentences=True), + include_partials=False, ), overlay, ) @@ -108,7 +129,7 @@ def list_presets() -> list[str]: return [attr.lower() for attr in dir(VoiceAgentConfigPreset) if not attr.startswith("_") and attr.isupper()] @staticmethod - def get(preset: str, overlay_json: Optional[str] = None) -> VoiceAgentConfig: + def load(preset: str, overlay_json: Optional[str] = None) -> VoiceAgentConfig: """Get a preset configuration. Args: diff --git a/tests/voice/test_14_presets.py b/tests/voice/test_14_presets.py index f4a69c5d..16693925 100644 --- a/tests/voice/test_14_presets.py +++ b/tests/voice/test_14_presets.py @@ -36,7 +36,7 @@ async def test_presets(): assert "low_latency" in presets # Get a preset by a name - preset: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency") + preset: VoiceAgentConfig = VoiceAgentConfigPreset.load("low_latency") assert preset is not None @@ -45,14 +45,14 @@ async def test_json_presets(): """Test VoiceAgentConfigPreset JSON presets.""" # With a JSON string overlay - preset: VoiceAgentConfig = VoiceAgentConfigPreset.get("low_latency", '{"operating_point": "enhanced"}') + preset: VoiceAgentConfig = VoiceAgentConfigPreset.load("low_latency", '{"operating_point": "enhanced"}') assert preset is not None assert preset.operating_point == OperatingPoint.ENHANCED # Check using incorrect preset name with pytest.raises(ValueError): - VoiceAgentConfigPreset.get("invalid_preset") + VoiceAgentConfigPreset.load("invalid_preset") # Check with invalid overlay with pytest.raises(ValueError): - VoiceAgentConfigPreset.get("low_latency", '{"invalid": "value"}') + VoiceAgentConfigPreset.load("low_latency", '{"invalid": "value"}') From c3ee49781e2a2865b9220c9c71806b49bcaff630 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 22:39:50 +0000 Subject: [PATCH 206/215] Use a preset string to setup the client. --- sdk/voice/speechmatics/voice/_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 5a9c905c..8802544c 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -55,6 +55,7 @@ from ._models import TurnStartEndResetMessage from ._models import VADStatusMessage from ._models import VoiceAgentConfig +from ._presets import VoiceAgentConfigPreset from ._smart_turn import SMART_TURN_INSTALL_HINT from ._smart_turn import SmartTurnDetector from ._smart_turn import SmartTurnPredictionResult @@ -81,6 +82,7 @@ def __init__( url: Optional[str] = None, app: Optional[str] = None, config: Optional[VoiceAgentConfig] = None, + preset: Optional[str] = None, ): """Initialize the Voice Agent client. @@ -90,6 +92,7 @@ def __init__( or defaults to production endpoint. app: Optional application name to use in the endpoint URL. config: Optional voice agent configuration. + preset: Optional voice agent preset. Examples: Recommended - using context manager: @@ -99,6 +102,23 @@ def __init__( ... # Client automatically connects and disconnects ... await client.send_audio(audio_data) + Using a preset (named): + >>> from speechmatics.voice import VoiceAgentClient + >>> client = VoiceAgentClient( + ... api_key="your_api_key", + ... url="wss://custom.endpoint.com/v2", + ... preset="conversation_adaptive" + ... ) + + Using a preset (utility class): + >>> from speechmatics.voice import VoiceAgentClient, VoiceAgentConfigPreset + >>> config=VoiceAgentConfigPreset.CONVERSATION_ADAPTIVE() + >>> client = VoiceAgentClient( + ... api_key="your_api_key", + ... url="wss://custom.endpoint.com/v2", + ... config=config + ... ) + Manual connection management: >>> client = VoiceAgentClient(api_key="your_api_key", config=config) >>> await client.connect() @@ -133,6 +153,11 @@ def __init__( # Client Configuration # ------------------------------------- + # Check for preset + if preset: + preset_config = VoiceAgentConfigPreset.load(preset) + config = VoiceAgentConfigPreset._merge_configs(preset_config, config) + # Process the config self._config, self._transcription_config, self._audio_format = self._prepare_config(config) From 5ae37a6e655f08c9a0f76f73871e1cdac6a43a0a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 22:40:21 +0000 Subject: [PATCH 207/215] updated example --- examples/voice/simple/simple.py | 26 ++++++------------------- sdk/voice/speechmatics/voice/_models.py | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/examples/voice/simple/simple.py b/examples/voice/simple/simple.py index 13ed9163..aa0afe93 100644 --- a/examples/voice/simple/simple.py +++ b/examples/voice/simple/simple.py @@ -11,7 +11,6 @@ from speechmatics.rt import Microphone from speechmatics.voice import AgentServerMessageType from speechmatics.voice import VoiceAgentClient -from speechmatics.voice import VoiceAgentConfig async def main() -> None: @@ -29,41 +28,28 @@ async def main() -> None: print("Error: PyAudio not available - install with: pip install pyaudio") return - # Configure Voice Agent with adaptive turn detection - config = VoiceAgentConfig( - language="en", - enable_diarization=True, - end_of_utterance_mode="adaptive", - ) - # Create client - client = VoiceAgentClient(api_key=api_key, config=config) + client = VoiceAgentClient(api_key=api_key, preset="scribe") # Handle partial segments (interim results) + @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) def on_partial_segment(message): segments = message.get("segments", []) for segment in segments: - speaker = segment["speaker_id"] - text = segment["text"] - print(f"[PARTIAL] {speaker}: {text}") + print(f"[PARTIAL] {segment['speaker_id']}: {segment['text']}") # Handle final segments + @client.on(AgentServerMessageType.ADD_SEGMENT) def on_segment(message): segments = message.get("segments", []) for segment in segments: - speaker = segment["speaker_id"] - text = segment["text"] - print(f"[FINAL] {speaker}: {text}") + print(f"[FINAL] {segment['speaker_id']}: {segment['text']}") # Handle end of turn + @client.on(AgentServerMessageType.END_OF_TURN) def on_end_of_turn(message): print("[END OF TURN]") - # Register event handlers - client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT, on_partial_segment) - client.on(AgentServerMessageType.ADD_SEGMENT, on_segment) - client.on(AgentServerMessageType.END_OF_TURN, on_end_of_turn) - # Instructions print("\nMicrophone ready - speak now... (Press CTRL+C to stop)\n") diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 75563747..eff3fefb 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -372,7 +372,7 @@ class EndOfTurnConfig(BaseConfigModel): EndOfTurnPenaltyItem(penalty=3.0, annotation=[AnnotationFlags.VERY_SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.0, annotation=[AnnotationFlags.SLOW_SPEAKER]), EndOfTurnPenaltyItem(penalty=2.5, annotation=[AnnotationFlags.ENDS_WITH_DISFLUENCY]), - EndOfTurnPenaltyItem(penalty=1.25, annotation=[AnnotationFlags.HAS_DISFLUENCY]), + EndOfTurnPenaltyItem(penalty=1.2, annotation=[AnnotationFlags.HAS_DISFLUENCY]), EndOfTurnPenaltyItem( penalty=2.0, annotation=[AnnotationFlags.ENDS_WITH_EOS], From 66583e68a5caa114502d96e3575ece704bad2572 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 22:52:25 +0000 Subject: [PATCH 208/215] updated README for examples --- examples/voice/cli/README.md | 217 +++++++++----------------------- examples/voice/scribe/README.md | 55 +++----- examples/voice/simple/README.md | 67 ++++++---- 3 files changed, 122 insertions(+), 217 deletions(-) diff --git a/examples/voice/cli/README.md b/examples/voice/cli/README.md index c1573c7f..0a036a2f 100644 --- a/examples/voice/cli/README.md +++ b/examples/voice/cli/README.md @@ -1,17 +1,15 @@ # Transcription CLI with Speaker Diarization -Command-line tool for real-time transcription using the Speechmatics Voice SDK. Supports both microphone input and audio file streaming with speaker diarization. +Real-time transcription tool using the Speechmatics Voice SDK. Supports microphone input and audio file streaming with speaker diarization. ## Quick Start **Microphone:** - ```bash python cli.py -p -k YOUR_API_KEY ``` **Audio file:** - ```bash python cli.py -p -k YOUR_API_KEY -i audio.wav ``` @@ -23,207 +21,116 @@ Press `CTRL+C` to stop. - Speechmatics API key from the [portal](https://portal.speechmatics.com/) - Install dependencies: see [examples README](../README.md) -## Usage - -```bash -python cli.py [OPTIONS] -``` - -### Core Parameters - -**`-k, --api-key`** -Speechmatics API key. Defaults to `SPEECHMATICS_API_KEY` environment variable. - -**`-u, --url`** -Custom Speechmatics server URL. Also uses `SPEECHMATICS_RT_URL` environment variable, if not provided. Optional, defaults to production endpoint. - -**`-i, --input-file FILE`** -Path to input audio file (WAV format, mono 16-bit). If not provided, uses microphone. +## Options -**`-p, --pretty`** -Enable formatted console output with colours and emojis. +### Core -**`-o, --output-file FILE`** -Save all output to a JSONL file for later analysis. +- `-k, --api-key` - API key (defaults to `SPEECHMATICS_API_KEY` env var) +- `-u, --url` - Server URL (defaults to `SPEECHMATICS_RT_URL` env var) +- `-i, --input-file` - Audio file path (WAV, mono 16-bit). Uses microphone if not specified +- `-c, --config` - JSON config string or file path (overrides other Voice Agent options) -**`-v, --verbose`** -Increase logging verbosity. `-v` adds speaker start/end VAD events. `-vv` also emits `END_OF_TURN_PREDICTION`. `-vvv` shows the `annotation` field in segments. `-vvvv` adds metric messages.`-vvvvv` adds additional payloads: `END_OF_UTTERANCE`, `ADD_PARTIAL_TRANSCRIPT`, and `ADD_TRANSCRIPT`. Useful for debugging or detailed analysis. Default: `0`. +### Output -**`--results`** -Include the individual fragments in the segment payload. Default: `False`. +- `-p, --pretty` - Formatted console output with colors +- `-o, --output-file` - Save output to JSONL file +- `-v, --verbose` - Increase verbosity (can repeat: `-v`, `-vv`, `-vvv`, `-vvvv`, `-vvvvv`) + - `-v` - Add speaker VAD events + - `-vv` - Add turn predictions + - `-vvv` - Add segment annotations + - `-vvvv` - Add metrics + - `-vvvvv` - Add STT events +- `-L, --legacy` - Show only legacy transcript messages +- `--results` - Include word-level results in segments -### Audio Configuration +### Audio -**`--sample-rate`** -Audio sample rate in Hz. Default: `16000`. +- `--sample-rate` - Sample rate in Hz (default: 16000) +- `--chunk-size` - Chunk size in bytes (default: 320) +- `-M, --mute` - Mute audio playback for file input +- `-D, --default-device` - Use default audio device (skip selection) -**`--chunk-size`** -Audio chunk size in bytes. Default: `320`. +### Voice Agent Config -**`-M, --mute`** -Mute audio playback for file input. When enabled, audio files are transcribed without playing through speakers. Default: `False`. - -### Turn Detection - -**`-m, --end-of-utterance-mode`** -Controls how turn endings are detected. Options: - -- `FIXED` - Fixed silence threshold -- `ADAPTIVE` - Adjusts based on speaker characteristics -- `SMART_TURN` - ML-based turn detection -- `EXTERNAL` - Manual control via `finalize()` - -**`-t, --end-of-utterance-silence-trigger`** -Silence duration in seconds to trigger turn end. Default: `0.5`. - -**`-d, --max-delay`** -Maximum transcription delay in seconds. Default: `0.7`. +- `-l, --language` - Language code (default: en) +- `-d, --max-delay` - Max transcription delay in seconds (default: 0.7) +- `-t, --end-of-utterance-silence-trigger` - Silence duration for turn end (default: 0.5) +- `-m, --end-of-utterance-mode` - Turn detection mode: `FIXED`, `ADAPTIVE`, `SMART_TURN`, or `EXTERNAL` +- `-e, --emit-sentences` - Emit sentence-level segments +- `--forced-eou` - Enable forced end of utterance ### Speaker Management -**`--focus-speakers 'S1' 'S2' ...`** -Speakers to focus on. Only these speakers will be emitted as finalized frames. - -**`--ignore-speakers 'S1' 'S2' ...`** -Specific speakers to exclude from transcription. - -**`--ignore-mode`** -Use ignore mode instead of focus mode for `--focus-speakers`. +- `-f, --focus-speakers` - Speakers to focus on (e.g., `S1 S2`) +- `-I, --ignore-speakers` - Speakers to ignore (e.g., `S1 S2`) +- `-x, --ignore-mode` - Use ignore mode (instead of retain) for focus speakers ### Speaker Identification -**`-E, --enrol`** -Enrol speakers and output their identifiers at the end of the session. - -**`-s, --speakers JSON|FILE`** -Use known speakers from previous sessions. Provide as either: - -- A JSON string: `'[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]'` -- A path to a JSON file: `speakers.json` - -**`--preview`** -Enable preview features. +- `-E, --enrol` - Enrol speakers and output identifiers at end +- `-s, --speakers` - Known speakers JSON string or file path ## Examples -### Microphone - basic transcription - +**Basic microphone:** ```bash python cli.py -k YOUR_KEY -p ``` -### Audio file - basic transcription - +**Audio file:** ```bash python cli.py -k YOUR_KEY -i audio.wav -p ``` -### Audio file - muted (no playback) - +**Audio file (muted):** ```bash python cli.py -k YOUR_KEY -i audio.wav -Mp ``` -### With adaptive turn detection - -```bash -python cli.py -k YOUR_KEY -m ADAPTIVE -p -``` - -### Enrol speakers (microphone) - -```bash -python cli.py -k YOUR_KEY -ep -``` - -Press `CTRL+C` when done. Speaker identifiers will be displayed: - -```json -[{ "label": "S1", "speaker_identifiers": ["XX...XX"] }] -``` - -### Use known speakers (JSON string) - +**Save output:** ```bash -python cli.py -k YOUR_KEY -s '[{"label": "Alice", "speaker_identifiers": ["XX...XX"]}]' -p -``` - -### Use known speakers (JSON file) - -Create a `speakers.json` file: - -```json -[ - { - "label": "Alice", - "speaker_identifiers": ["XX...XX"] - }, - { - "label": "Bob", - "speaker_identifiers": ["YY...YY"] - } -] -``` - -Then run: - -```bash -python cli.py -k YOUR_KEY -s speakers.json -p -``` - -Output shows speaker labels: - -``` -@Alice: Hello, how are you? -@Bob: I'm doing well, thanks! +python cli.py -k YOUR_KEY -o output.jsonl -p ``` -### Focus on specific speakers - +**Verbose logging:** ```bash -python cli.py -k YOUR_KEY --focus-speakers S1 S2 -p +python cli.py -k YOUR_KEY -vv -p ``` -### Save to file - +**Focus on speakers:** ```bash -python cli.py -k YOUR_KEY -o output.jsonl -p +python cli.py -k YOUR_KEY -f S1 S2 -p ``` -### Verbose logging - -Show speaker start/end events: - +**Enrol speakers:** ```bash -python cli.py -k YOUR_KEY -i audio.wav -v -p +python cli.py -k YOUR_KEY -Ep ``` +Press `CTRL+C` when done to see speaker identifiers. -Include turn prediction events: - +**Use known speakers:** ```bash -python cli.py -k YOUR_KEY -i audio.wav -vv -p +python cli.py -k YOUR_KEY -s speakers.json -p ``` -Include additional payloads for debugging: - -```bash -python cli.py -k YOUR_KEY -i audio.wav -vvv -p +Example `speakers.json`: +```json +[ + {"label": "Alice", "speaker_identifiers": ["XX...XX"]}, + {"label": "Bob", "speaker_identifiers": ["YY...YY"]} +] ``` -### Audio file with speaker focus - +**Custom config:** ```bash -python cli.py -k YOUR_KEY -i audio.wav --focus-speakers S2 -p +python cli.py -k YOUR_KEY -c config.json -p ``` -## Speaker Identification - -Speaker identifiers are encrypted and unique to your API key. They enable the engine to recognise speakers across sessions. - -**Best practices:** +## Notes +- Speaker identifiers are encrypted and unique to your API key - Allow speakers to say at least 20 words before enrolling -- Avoid using labels `S1`, `S2` (reserved by engine) -- Labels in format `__XXX__` are automatically ignored +- Avoid labels `S1`, `S2` (reserved by engine) +- Labels like `__XXX__` are automatically ignored -For more details, see the [Speechmatics documentation](https://docs.speechmatics.com/speech-to-text/realtime/realtime-speaker-identification). +See the [Speechmatics documentation](https://docs.speechmatics.com/speech-to-text/realtime/realtime-speaker-identification) for more details. diff --git a/examples/voice/scribe/README.md b/examples/voice/scribe/README.md index b33bc5f1..2b0468f1 100644 --- a/examples/voice/scribe/README.md +++ b/examples/voice/scribe/README.md @@ -1,25 +1,31 @@ -# Ambient Scribe / Note-Taking +# Ambient Scribe -Real-time ambient transcription for note-taking and documentation. Designed for continuous background transcription where the output feeds into another system. +Real-time transcription for note-taking and documentation. Uses the default microphone and the SCRIBE preset. + +A custom dictionary can be used to improve accuracy for domain-specific terms. The example `vocab.json` is loaded automatically if present. ## Quick Start ```bash -export SPEECHMATICS_API_KEY=your_api_key # Required -export SPEECHMATICS_RT_URL=endpoint_url # Optional +export SPEECHMATICS_API_KEY=your_api_key python scribe.py ``` Press `CTRL+C` to stop. -## What It Does +## Features + +- Real-time transcription with speaker diarization +- Partial results (yellow) update as speech continues +- Final results (green) shown with timestamps +- Automatically loads custom vocabulary from `vocab.json` if present +- Uses SCRIBE preset (fixed EOU, 1s max delay, sentence emission) -- Transcribes conversations in real-time for note-taking -- Uses the SCRIBE preset optimized for ambient transcription -- Shows partial results (yellow) that update as speech continues -- Shows final results (green) with timestamps when sentences complete -- Identifies different speakers (S1, S2, etc.) -- Ideal for meeting notes, interview transcription, or ambient documentation +## Requirements + +- Speechmatics API key from the [portal](https://portal.speechmatics.com/) +- PyAudio: `pip install pyaudio` +- See [examples README](../README.md) for SDK dependencies ## Output Example @@ -32,39 +38,18 @@ Microphone ready - speak now... (Press CTRL+C to stop) listening ... ``` -Partial results appear in yellow and update on the same line. Once finalized, they turn green with a timestamp showing elapsed time since session start. +## Custom Vocabulary -## Requirements - -- Speechmatics API key from the [portal](https://portal.speechmatics.com/) -- PyAudio: `pip install pyaudio` -- Install SDK dependencies: see [examples README](../README.md) - -## Configuration - -This example uses the SCRIBE preset: - -- Fixed end of utterance detection -- 1.2s max delay for smooth note-taking -- Emits finalized sentences as they complete -- Speaker diarization enabled - -### Custom Vocabulary - -The example automatically loads custom vocabulary from `vocab.json` if present. This helps improve transcription accuracy for domain-specific terms, brand names, or technical jargon. - -**Example `vocab.json`:** +Create `vocab.json` to improve accuracy for domain-specific terms: ```json [ { "content": "Speechmatics", - "sounds_like": ["speech matics", "speech mattics"] + "sounds_like": ["speech matics"] }, { "content": "API" } ] ``` - -Edit `vocab.json` to add your own terms without modifying the Python code. diff --git a/examples/voice/simple/README.md b/examples/voice/simple/README.md index 8802bab6..3c1fee3f 100644 --- a/examples/voice/simple/README.md +++ b/examples/voice/simple/README.md @@ -1,24 +1,55 @@ # Simple Microphone Transcription -Basic real-time transcription example using your default microphone with speaker diarization. +Basic real-time transcription using the default microphone with speaker diarization. ## Quick Start ```bash export SPEECHMATICS_API_KEY=your_api_key -python app.py +python simple.py ``` Press `CTRL+C` to stop. -## What It Does +## Features -- Uses your default microphone -- Transcribes speech in real-time -- Identifies different speakers (S1, S2, etc.) -- Shows partial results as you speak -- Shows final results when complete -- Detects end of turn using adaptive mode +- Uses default microphone +- Real-time transcription with speaker diarization +- Shows partial and final results +- Detects end of turn +- Uses "scribe" preset + +## Requirements + +- Speechmatics API key from the [portal](https://portal.speechmatics.com/) +- PyAudio: `pip install pyaudio` +- See [examples README](../README.md) for SDK dependencies + +## Code Example + +```python +from speechmatics.rt import Microphone +from speechmatics.voice import VoiceAgentClient, AgentServerMessageType + +# Create client with preset +client = VoiceAgentClient(api_key="YOUR_KEY", preset="scribe") + +# Register event handlers +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + segments = message.get("segments", []) + for segment in segments: + print(f"{segment['speaker_id']}: {segment['text']}") + +# Connect and stream +await client.connect() +mic = Microphone(sample_rate=16000, chunk_size=320) +mic.start() + +while True: + audio_chunk = await mic.read(320) + await client.send_audio(audio_chunk) +``` ## Output Example @@ -35,21 +66,3 @@ Microphone ready - speak now... (Press CTRL+C to stop) [FINAL] S2: I'm good, thanks! [END OF TURN] ``` - -## Requirements - -- Speechmatics API key from the [portal](https://portal.speechmatics.com/) -- PyAudio: `pip install pyaudio` -- Install SDK dependencies: see [examples README](../README.md) - -## Configuration - -This example uses standard settings: - -- Language: English -- Turn detection: Adaptive (adjusts to speaker characteristics) -- Speaker diarization: Enabled -- Sample rate: 16kHz -- Chunk size: 320 bytes - -For more advanced options, see the [transcribe_mic](../transcribe_mic) example. From 1533fe1549555f238df44d7e85e97f2291179fe9 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:23:00 +0000 Subject: [PATCH 209/215] Updated README. --- sdk/voice/README.md | 792 +++++++++++++++++++++++++++++++------------- 1 file changed, 569 insertions(+), 223 deletions(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 368c510a..e6a27e15 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -1,287 +1,341 @@ -# Voice Agent Python client for Speechmatics Real-Time API +# Speechmatics Voice SDK [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/speechmatics/speechmatics-python-voice/blob/master/LICENSE) -An SDK for working with the Speechmatics Real-Time API optimised for use in voice agents or transcription services. +Python SDK for building voice-enabled applications with the Speechmatics Real-Time API. Optimized for conversational AI, voice agents, transcription services, and real-time captioning. -## Overview +## What is the Voice SDK? -The Voice Agent SDK is designed to be a set of helper classes that use the Real-Time AsyncClient to connect to the Speechmatics Real-Time API and process the transcription results from the STT engine and combine them into manageable segments of text. Taking advantage of speaker diarization, the transcription is grouped into individual speakers, with advanced options to focus on and/or ignore specific speakers. +The Voice SDK is a higher-level abstraction built on top of the Speechmatics Real-Time API (`speechmatics-rt`). While the Real-Time SDK provides raw transcription events (words and utterances), the Voice SDK adds: + +- **Intelligent Segmentation** - Groups words into meaningful speech segments per speaker +- **Turn Detection** - Automatically detects when speakers finish their turns using adaptive or ML-based methods +- **Speaker Management** - Focus on or ignore specific speakers in multi-speaker scenarios +- **Preset Configurations** - Ready-to-use configs for common use cases (conversation, note-taking, captions) +- **Simplified Event Handling** - Receive clean, structured segments instead of raw word-level events + +### When to Use Voice SDK vs Real-Time SDK + +**Use Voice SDK when:** + +- Building conversational AI or voice agents +- You need automatic turn detection +- You want speaker-focused transcription +- You need ready-to-use presets for common scenarios + +**Use Real-Time SDK when:** + +- You need raw word-level events +- Building custom segmentation logic +- You want fine-grained control over every event +- Processing batch files or custom workflows ## Installation ```bash -# Install with standard turn detection +# Standard installation pip install speechmatics-voice -# Install with SMART_TURN (includes ONNX runtime and transformers) +# With SMART_TURN (ML-based turn detection) pip install speechmatics-voice[smart] ``` -> **Note:** If the `smart` extras are not installed, selecting `EndOfUtteranceMode.SMART_TURN` will emit a warning and automatically fall back to `EndOfUtteranceMode.ADAPTIVE`. - -## Requirements - -You must have a valid Speechmatics API key to use this SDK. You can get one from the [Speechmatics Portal](https://portal.speechmatics.com/). - -Store this as `SPEECHMATICS_API_KEY` environment variable in your `.env` file or use `export SPEECHMATICS_API_KEY="your_api_key_here"` in your terminal. +> **Note:** `SMART_TURN` requires additional ML dependencies (ONNX runtime, transformers). If not installed, it automatically falls back to `ADAPTIVE` mode. ## Quick Start -Below is a basic example of how to use the SDK to transcribe audio from a microphone. +### Basic Example ```python import asyncio +import os from speechmatics.rt import Microphone -from speechmatics.voice import ( - VoiceAgentClient, - VoiceAgentConfig, - EndOfUtteranceMode, - AgentServerMessageType -) +from speechmatics.voice import VoiceAgentClient, AgentServerMessageType async def main(): - # Configure the voice agent - config = VoiceAgentConfig( - enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.FIXED, + # Create client with preset + client = VoiceAgentClient( + api_key=os.getenv("SPEECHMATICS_API_KEY"), + preset="scribe" ) - # Initialize microphone - mic = Microphone( - sample_rate=16000, - chunk_size=160, - ) + # Handle final segments + @client.on(AgentServerMessageType.ADD_SEGMENT) + def on_segment(message): + for segment in message["segments"]: + speaker = segment["speaker_id"] + text = segment["text"] + print(f"{speaker}: {text}") + # Setup microphone + mic = Microphone(sample_rate=16000, chunk_size=320) if not mic.start(): - print("Microphone not available") + print("Error: Microphone not available") return - # Create client and register event handlers - async with VoiceAgentClient(config=config) as client: - - # Handle interim transcription segments - @client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) - def handle_interim_segments(message): - segments = message["segments"] - for segment in segments: - print(f"[PARTIAL] Speaker {segment['speaker_id']}: {segment['text']}") - - # Handle finalized transcription segments - @client.on(AgentServerMessageType.ADD_SEGMENT) - def handle_final_segments(message): - segments = message["segments"] - for segment in segments: - print(f"[FINAL] Speaker {segment['speaker_id']}: {segment['text']}") - - # Handle user started speaking event - @client.on(AgentServerMessageType.SPEAKER_STARTED) - def handle_speech_started(message): - status = message["status"] - print(f"User started speaking: {status}") - - # Handle user stopped speaking event - @client.on(AgentServerMessageType.SPEAKER_ENDED) - def handle_speech_ended(message): - status = message["status"] - print(f"User stopped speaking: {status}") - - # End of turn / utterance(s) - @client.on(AgentServerMessageType.END_OF_TURN) - def handle_end_of_turn(message): - print("End of turn") - - # Connect and start processing audio - await client.connect() + # Connect and stream + await client.connect() + try: while True: - frame = await mic.read(160) - await client.send_audio(frame) + audio_chunk = await mic.read(320) + await client.send_audio(audio_chunk) + except KeyboardInterrupt: + pass + finally: + await client.disconnect() if __name__ == "__main__": asyncio.run(main()) ``` -## Examples +### Using Presets + +Presets provide optimized configurations for common use cases: + +```python +# Scribe preset - for note-taking +client = VoiceAgentClient(api_key=api_key, preset="scribe") + +# Low latency preset - for fast responses +client = VoiceAgentClient(api_key=api_key, preset="low_latency") + +# Conversation preset - for natural dialogue +client = VoiceAgentClient(api_key=api_key, preset="conversation_adaptive") + +# Advanced conversation with ML turn detection +client = VoiceAgentClient(api_key=api_key, preset="conversation_smart_turn") + +# Captions preset - for live captioning +client = VoiceAgentClient(api_key=api_key, preset="captions") +``` + +**Available Presets:** + +- **`low_latency`** - FIXED mode, 0.7s max delay. Fast responses with minimal delay. +- **`conversation_adaptive`** - ADAPTIVE mode, 0.7s max delay. Natural conversation with intelligent turn detection. +- **`conversation_smart_turn`** - SMART_TURN mode, 0.7s max delay. Complex conversation with ML-based turn detection. +- **`scribe`** - FIXED mode, 1.0s max delay. Optimized for note-taking and meeting transcription. +- **`captions`** - FIXED mode, 0.9s max delay. Live captioning with final segments only. + +### Custom Configuration + +```python +from speechmatics.voice import VoiceAgentClient, VoiceAgentConfig, EndOfUtteranceMode + +config = VoiceAgentConfig( + language="en", + enable_diarization=True, + max_delay=0.7, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, +) + +client = VoiceAgentClient(api_key=api_key, config=config) +``` -The `examples/` directory contains practical demonstrations of the Voice Agent SDK. See the README in the `examples/voice` directory for more information. +## Configuration -## Client Configuration +### Basic Parameters -The `VoiceAgentClient` can be configured with a number of options to control the behaviour of the client using the `VoiceAgentConfig` class. +**`language`** (str, default: `"en"`) +Language code for transcription (e.g., `"en"`, `"es"`, `"fr"`). -### VoiceAgentConfig Parameters +**`enable_diarization`** (bool, default: `False`) +Enable speaker diarization to identify and label different speakers. -#### Service Configuration +**`sample_rate`** (int, default: `16000`) +Audio sample rate in Hz. -- **`operating_point`** (`OperatingPoint`): Operating point for transcription accuracy vs. latency tradeoff. It is recommended to use `OperatingPoint.ENHANCED` for most use cases. Defaults to `OperatingPoint.ENHANCED`. -- **`domain`** (`Optional[str]`): Domain for Speechmatics API. Defaults to `None`. -- **`language`** (`str`): Language code for transcription. Defaults to `"en"`. -- **`output_locale`** (`Optional[str]`): Output locale for transcription, e.g. `"en-GB"`. Defaults to `None`. +**`audio_encoding`** (AudioEncoding, default: `PCM_S16LE`) +Audio encoding format. -#### Timing and Latency Features +### Turn Detection Parameters -- **`max_delay`** (`float`): Maximum delay in seconds for transcription. This forces the STT engine to speed up the processing of transcribed words and reduces the interval between partial and final results. Lower values can have an impact on accuracy. Defaults to `0.7`. -- **`end_of_utterance_silence_trigger`** (`float`): Maximum delay in seconds for end of utterance trigger. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be lower than `max_delay`. Defaults to `0.2`. -- **`end_of_utterance_max_delay`** (`float`): Maximum delay in seconds for end of utterance delay. The delay is used to wait for any further transcribed words before emitting the final word frames. The value must be greater than `end_of_utterance_silence_trigger`. Defaults to `10.0`. -- **`end_of_utterance_mode`** (`EndOfUtteranceMode`): End of utterance delay mode. When `ADAPTIVE` is used, the delay can be adjusted on the content of what the most recent speaker has said, such as rate of speech and whether they have any pauses or disfluencies. When `FIXED` is used, the delay is fixed to the value of `end_of_utterance_silence_trigger`. Use of `EXTERNAL` disables end of utterance detection and uses a fallback timer. `SMART_TURN` uses an acoustic model (requires installing `speechmatics-voice[smart]`) and automatically falls back to `ADAPTIVE` when the optional dependencies or model are unavailable. Defaults to `EndOfUtteranceMode.FIXED`. +**`end_of_utterance_mode`** (EndOfUtteranceMode, default: `FIXED`) +Controls how turn endings are detected: -#### Language and Vocabulary Features +- **`FIXED`** - Uses fixed silence threshold. Fast but may split slow speech. +- **`ADAPTIVE`** - Adjusts delay based on speech rate, pauses, and disfluencies. Best for natural conversation. +- **`SMART_TURN`** - Uses ML model to detect acoustic turn-taking cues. Requires `[smart]` extras. +- **`EXTERNAL`** - Manual control via `client.finalize()`. For custom turn logic. -- **`additional_vocab`** (`list[AdditionalVocabEntry]`): List of additional vocabulary entries. If you supply a list of additional vocabulary entries, this will increase the weight of the words in the vocabulary and help the STT engine to better transcribe the words. Defaults to `[]`. -- **`punctuation_overrides`** (`Optional[dict]`): Punctuation overrides. This allows you to override the punctuation in the STT engine. This is useful for languages that use different punctuation than English. See documentation for more information. Defaults to `None`. +**`end_of_utterance_silence_trigger`** (float, default: `0.2`) +Silence duration in seconds to trigger turn end. -#### Speaker Diarization +**`end_of_utterance_max_delay`** (float, default: `10.0`) +Maximum delay before forcing turn end. -- **`enable_diarization`** (`bool`): Enable speaker diarization. When enabled, the STT engine will determine and attribute words to unique speakers. The `speaker_sensitivity` parameter can be used to adjust the sensitivity of diarization. Defaults to `False`. -- **`speaker_sensitivity`** (`float`): Diarization sensitivity. A higher value increases the sensitivity of diarization and helps when two or more speakers have similar voices. Defaults to `0.5`. -- **`max_speakers`** (`Optional[int]`): Maximum number of speakers to detect. This forces the STT engine to cluster words into a fixed number of speakers. It should not be used to limit the number of speakers, unless it is clear that there will only be a known number of speakers. Defaults to `None`. -- **`prefer_current_speaker`** (`bool`): Prefer current speaker ID. When set to true, groups of words close together are given extra weight to be identified as the same speaker. Defaults to `False`. -- **`speaker_config`** (`SpeakerFocusConfig`): Configuration to specify speakers to focus on, ignore and how to deal with speakers that are not in focus. Defaults to `SpeakerFocusConfig()`. -- **`known_speakers`** (`list[SpeakerIdentifier]`): List of known speaker labels and identifiers. If you supply a list of labels and identifiers for speakers, then the STT engine will use them to attribute any spoken words to that speaker. This is useful when you want to attribute words to a specific speaker, such as the assistant or a specific user. Labels and identifiers can be obtained from a running STT session and then used in subsequent sessions. Identifiers are unique to each Speechmatics account and cannot be used across accounts. Defaults to `[]`. +**`max_delay`** (float, default: `0.7`) +Maximum transcription delay for word emission. -#### Advanced Features +### Speaker Configuration -- **`include_results`** (`bool`): Include word data in the response. This is useful for debugging and understanding the STT engine's behaviour. Defaults to `False`. -- **`enable_preview_features`** (`bool`): Enable preview features. Defaults to `False`. -- **`transcription_update_preset`** (`TranscriptionUpdatePreset`): Controls when transcription updates are emitted (e.g. only on finalized changes or when timings shift). Defaults to `TranscriptionUpdatePreset.COMPLETE`. -- **`smart_turn_config`** (`SmartTurnConfig`): Configures SMART_TURN behaviour, such as audio buffer length and probability threshold. Only applies when `end_of_utterance_mode` is `SMART_TURN`. -- **`speech_segment_config`** (`SpeechSegmentConfig`): Fine-tunes how speech segments are generated and post-processed before emission. +**`speaker_sensitivity`** (float, default: `0.5`) +Diarization sensitivity between 0.0 and 1.0. Higher values detect more speakers. -#### Audio Configuration +**`max_speakers`** (int, default: `None`) +Limit maximum number of speakers to detect. -- **`sample_rate`** (`int`): Audio sample rate for streaming. Defaults to `16000`. -- **`audio_encoding`** (`AudioEncoding`): Audio encoding format. Defaults to `AudioEncoding.PCM_S16LE`. +**`prefer_current_speaker`** (bool, default: `False`) +Give extra weight to current speaker for word grouping. -### Example Configuration +**`speaker_config`** (SpeakerFocusConfig, default: `SpeakerFocusConfig()`) +Configure speaker focus/ignore rules. ```python -from speechmatics.voice import ( - AdditionalVocabEntry, - AudioEncoding, - SpeakerFocusConfig, - SmartTurnConfig, - SpeechSegmentConfig, - SpeakerFocusMode, - EndOfUtteranceMode, - OperatingPoint, - TranscriptionUpdatePreset, - VoiceAgentConfig -) +from speechmatics.voice import SpeakerFocusConfig, SpeakerFocusMode -# Basic configuration +# Focus only on specific speakers config = VoiceAgentConfig( - language="en", enable_diarization=True, - end_of_utterance_mode=EndOfUtteranceMode.FIXED, + speaker_config=SpeakerFocusConfig( + focus_speakers=["S1", "S2"], + focus_mode=SpeakerFocusMode.RETAIN + ) ) -# Advanced configuration with custom settings -advanced_config = VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, - language="en", - output_locale="en-GB", - max_delay=0.7, - end_of_utterance_silence_trigger=0.25, - end_of_utterance_max_delay=8.0, - end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, +# Ignore specific speakers +config = VoiceAgentConfig( enable_diarization=True, - speaker_sensitivity=0.5, - max_speakers=6, - prefer_current_speaker=True, speaker_config=SpeakerFocusConfig( - focus_speakers=["S1", "S2"], ignore_speakers=["S3"], - focus_mode=SpeakerFocusMode.RETAIN, - ), + focus_mode=SpeakerFocusMode.IGNORE + ) +) +``` + +**`known_speakers`** (list[SpeakerIdentifier], default: `[]`) +Pre-enrolled speaker identifiers for speaker identification. + +```python +from speechmatics.voice import SpeakerIdentifier + +config = VoiceAgentConfig( + enable_diarization=True, + known_speakers=[ + SpeakerIdentifier(label="Alice", speaker_identifiers=["XX...XX"]), + SpeakerIdentifier(label="Bob", speaker_identifiers=["YY...YY"]) + ] +) +``` + +### Language & Vocabulary + +**`output_locale`** (str, default: `None`) +Output locale for formatting (e.g., `"en-GB"`, `"en-US"`). + +**`additional_vocab`** (list[AdditionalVocabEntry], default: `[]`) +Custom vocabulary for domain-specific terms. + +```python +from speechmatics.voice import AdditionalVocabEntry + +config = VoiceAgentConfig( + language="en", additional_vocab=[ AdditionalVocabEntry( content="Speechmatics", - sounds_like=["speech matters", "speech magic"] - ) - ], - include_results=True, - transcription_update_preset=TranscriptionUpdatePreset.COMPLETE_PLUS_TIMING, - smart_turn_config=SmartTurnConfig( - audio_buffer_length=8.0, - smart_turn_threshold=0.5, - slice_margin=0.1, - ), - speech_segment_config=SpeechSegmentConfig(), - sample_rate=16000, - audio_encoding=AudioEncoding.PCM_S16LE + sounds_like=["speech matters", "speech matics"] + ), + AdditionalVocabEntry(content="API"), + ] ) +``` -# SMART_TURN configuration (requires `pip install speechmatics-voice[smart]`) -smart_turn_enabled_config = VoiceAgentConfig( - language="en", - end_of_utterance_mode=EndOfUtteranceMode.SMART_TURN, - smart_turn_config=SmartTurnConfig( - audio_buffer_length=10.0, - smart_turn_threshold=0.5, - slice_margin=0.05, +**`punctuation_overrides`** (dict, default: `None`) +Custom punctuation rules. + +### Advanced Parameters + +**`operating_point`** (OperatingPoint, default: `ENHANCED`) +Balance accuracy vs latency. Options: `STANDARD` or `ENHANCED`. + +**`domain`** (str, default: `None`) +Domain-specific model (e.g., `"finance"`, `"medical"`). + +**`transcription_update_preset`** (TranscriptionUpdatePreset, default: `COMPLETE`) +Controls when to emit updates: `COMPLETE`, `COMPLETE_PLUS_TIMING`, `WORDS`, `WORDS_PLUS_TIMING`, or `TIMING`. + +**`speech_segment_config`** (SpeechSegmentConfig, default: `SpeechSegmentConfig()`) +Fine-tune segment generation and post-processing. + +**`smart_turn_config`** (SmartTurnConfig, default: `None`) +Configure SMART_TURN behavior (buffer length, threshold). + +**`include_results`** (bool, default: `False`) +Include word-level timing data in segments. + +**`include_partials`** (bool, default: `True`) +Emit partial segments. Set to `False` for final-only output. + +**`enable_preview_features`** (bool, default: `False`) +Enable experimental features. + +### Configuration with Overlays + +Use presets as a starting point and customize with overlays: + +```python +from speechmatics.voice import VoiceAgentConfigPreset, VoiceAgentConfig + +# Use preset with custom overrides +config = VoiceAgentConfigPreset.SCRIBE( + VoiceAgentConfig( + language="es", + max_delay=0.8 ) ) ``` -## Messages +## Event Messages + +The Voice SDK emits structured events via `AgentServerMessageType`. Register handlers using the `@client.on()` decorator or `client.on()` method. + +> **Note:** The payloads shown below are the actual message payloads from the Voice SDK. When using the CLI example with `--output-file`, messages also include a `ts` timestamp field (e.g., `"ts": "2025-11-11 23:18:35.909"`), which is added by the CLI for logging purposes and is not part of the SDK payload. + +### Core Events + +#### RECOGNITION_STARTED -The async client will return messages of type `AgentServerMessageType`. To register for a message, use the `on` method. Optionally, you can use the `once` method to register a callback that will be called once and then removed. Use the `off` method to remove a callback. +Emitted when transcription session starts. Contains session ID and language pack info. -### `RECOGNITION_STARTED` +```python +@client.on(AgentServerMessageType.RECOGNITION_STARTED) +def on_started(message): + session_id = message["id"] + language = message["language_pack_info"]["language_description"] + print(f"Session {session_id} started - Language: {language}") +``` -Emitted when the recognition has started and contains the session ID and base time for when transcription started. It also contains the language pack information for the model being used. +**Payload:** ```json { "message": "RecognitionStarted", - "orchestrator_version": "2025.08.29127+289170c022.HEAD", "id": "a8779b0b-a238-43de-8211-c70f5fcbe191", + "orchestrator_version": "2025.08.29127+289170c022.HEAD", "language_pack_info": { - "adapted": false, - "itn": true, "language_description": "English", "word_delimiter": " ", - "writing_direction": "left-to-right" - } -} -``` - -### `SPEAKER_STARTED` - -Emitted when a speaker starts speaking. Contains the speaker ID and the VAD status. If there are multiple speakers, this will be emitted each time a speaker starts speaking. There will only be one active speaker at any given time. As speakers switch during a turn, separate `SPEAKER_ENDED` and `SPEAKER_STARTED` events will be emitted. - -```json -{ - "message": "SpeakerStarted", - "status": { - "is_active": true, - "speaker_id": "S1" + "writing_direction": "left-to-right", + "itn": true, + "adapted": false } } ``` -### `SPEAKER_ENDED` +#### ADD_PARTIAL_SEGMENT -Emitted when a speaker stops speaking. Contains the speaker ID (if diarization is enabled) and the VAD status. If there are multiple speakers, this will be emitted each time a speaker stops speaking. +Emitted continuously as speech is being processed. Contains interim text that updates in real-time. -```json -{ - "message": "SpeakerEnded", - "status": { - "is_active": false, - "speaker_id": "S1" - } -} +```python +@client.on(AgentServerMessageType.ADD_PARTIAL_SEGMENT) +def on_partial(message): + for segment in message["segments"]: + print(f"[INTERIM] {segment['speaker_id']}: {segment['text']}") ``` -### `ADD_PARTIAL_SEGMENT` - -Emitted when a partial segment has been detected. Contains the speaker ID, if diarization is enabled. If there are multiple speakers, this will be emitted each time a speaker starts speaking. Words from different speakers will be grouped into segments. - -If diarization is enabled and the client has been configured to focus on a specific speaker, the `is_active` will indicate whether the contents are from focused speakers. Ignored speakers will not have their words emitted. - -The `metadata` contains the start and end time for the segment. Each time the segment is updated as new partials are received, whole segment will be emitted again with updated `metadata`. The `annotation` field contains additional information about the contents of the segment. +**Payload:** ```json { @@ -290,21 +344,50 @@ The `metadata` contains the start and end time for the segment. Each time the se { "speaker_id": "S1", "is_active": true, - "timestamp": "2025-09-15T19:47:29.096+00:00", + "timestamp": "2025-11-11T23:18:37.189+00:00", "language": "en", - "text": "Welcome", - "annotation": ["has_partial"] + "text": "Welcome to", + "annotation": ["has_partial"], + "metadata": { + "start_time": 1.28, + "end_time": 1.6 + } } ], - "metadata": { "start_time": 0.36, "end_time": 0.92 } + "metadata": { + "start_time": 1.28, + "end_time": 1.6, + "processing_time": 0.307 + } } ``` -### `ADD_SEGMENT` +**Fields:** -Emitted when a final segment has been detected. Contains the speaker ID, if diarization is enabled. If there are multiple speakers, this will be emitted each time a speaker stops speaking. +- `speaker_id` - Speaker label (e.g., `"S1"`, `"S2"`) +- `is_active` - `true` if speaker is in focus (based on `speaker_config`) +- `text` - Current partial transcription text +- `annotation` - Status flags (see annotation section below) +- `metadata.start_time` - Segment start time (seconds since session start) +- `metadata.end_time` - Segment end time (seconds since session start) -The `metadata` contains the start and end time for the segment. The `annotation` field contains additional information about the contents of the segment. +Top-level `metadata` contains the same timing plus `processing_time`. + +#### ADD_SEGMENT + +Emitted when a segment is finalized. Contains stable, final transcription text. + +```python +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + for segment in message["segments"]: + speaker = segment["speaker_id"] + text = segment["text"] + start = message["metadata"]["start_time"] + print(f"[{start:.2f}s] {speaker}: {text}") +``` + +**Payload:** ```json { @@ -313,7 +396,7 @@ The `metadata` contains the start and end time for the segment. The `annotation` { "speaker_id": "S1", "is_active": true, - "timestamp": "2025-09-15T19:47:29.096+00:00", + "timestamp": "2025-11-11T23:18:37.189+00:00", "language": "en", "text": "Welcome to Speechmatics.", "annotation": [ @@ -322,53 +405,316 @@ The `metadata` contains the start and end time for the segment. The `annotation` "ends_with_final", "ends_with_eos", "ends_with_punctuation" - ] + ], + "metadata": { + "start_time": 1.28, + "end_time": 8.04 + } } ], - "metadata": { "start_time": 0.36, "end_time": 1.32 } + "metadata": { + "start_time": 1.28, + "end_time": 8.04, + "processing_time": 0.187 + } } ``` -### `END_OF_TURN` +**Annotation Flags:** + +- `has_final` - Contains finalized words +- `has_partial` - Contains partial (interim) words +- `starts_with_final` - First word is finalized +- `ends_with_final` - Last word is finalized +- `ends_with_eos` - Ends with end-of-sentence +- `ends_with_punctuation` - Ends with punctuation +- `fast_speaker` - Speaker is speaking quickly (may appear in some segments) +- `has_disfluency` - Contains disfluencies like "um", "er" (may appear in some segments) + +#### END_OF_TURN + +Emitted when a speaker's turn is complete. Timing depends on `end_of_utterance_mode`. -Emitted when a turn has ended. This is a signal that the user has finished speaking and the system has finished processing the turn. +```python +@client.on(AgentServerMessageType.END_OF_TURN) +def on_turn_end(message): + duration = message["metadata"]["end_time"] - message["metadata"]["start_time"] + print(f"Turn ended (duration: {duration:.2f}s)") +``` -The message is emitted differently for the different `EndOfUtterance` modes: +**Payload:** -- `FIXED` -> emitted when the fixed delay has elapsed with the STT engine -- `ADAPTIVE` -> emitted when the adaptive delay has elapsed with the client -- `SMART_TURN` -> emitted when the acoustic model predicts the turn has completed (requires optional smart extras; falls back to `ADAPTIVE` if unavailable) -- `EXTERNAL` -> emitted when an external trigger forces the end of turn +```json +{ + "message": "EndOfTurn", + "turn_id": 0, + "metadata": { + "start_time": 1.28, + "end_time": 8.04 + } +} +``` -The `ADAPTIVE` mode takes into consideration a number of factors to determine whether the most recent speaker as completed their turn. These include: +### Speaker Events -- The speed of speech -- Whether they have been using disfluencies (e.g. "um", "er", "ah") -- If the words spoken are considered to be a complete sentence +#### SPEAKER_STARTED -Finally, `SMART_TURN` mode, in addition to the above, takes into consideration the output of the acoustic model to determine whether the most recent speaker as completed their turn. +Emitted when a speaker starts speaking (voice activity detected). -The `end_of_utterance_silence_trigger` is used to calculate the baseline `FIXED`, `ADAPTIVE` and `SMART_TURN` delays. As a fallback, the `end_of_utterance_max_delay` is used to trigger the end of turn after a fixed amount of time, regardless of the content of what the most recent speaker has said. +```python +@client.on(AgentServerMessageType.SPEAKER_STARTED) +def on_speaker_start(message): + speaker = message["speaker_id"] + time = message["time"] + print(f"{speaker} started speaking at {time}s") +``` -When using `EXTERNAL` mode, call `client.finalize()` to force the end of turn. +**Payload:** ```json { - "message": "EndOfTurn", - "metadata": { "end_time": 9.16, "start_time": 11.08 } + "message": "SpeakerStarted", + "is_active": true, + "speaker_id": "S1", + "time": 1.28 +} +``` + +#### SPEAKER_ENDED + +Emitted when a speaker stops speaking (silence detected). + +```python +@client.on(AgentServerMessageType.SPEAKER_ENDED) +def on_speaker_end(message): + speaker = message["speaker_id"] + time = message["time"] + print(f"{speaker} stopped speaking at {time}s") +``` + +**Payload:** + +```json +{ + "message": "SpeakerEnded", + "is_active": false, + "speaker_id": "S1", + "time": 2.64 } ``` +#### SPEAKERS_RESULT + +Emitted when speaker enrollment completes. + +```python +# Request speaker IDs at end of session +await client.send_message({"message": "GetSpeakers", "final": True}) + +@client.on(AgentServerMessageType.SPEAKERS_RESULT) +def on_speakers(message): + for speaker in message["speakers"]: + print(f"Speaker {speaker['label']}: {speaker['speaker_identifiers']}") +``` + +### Additional Events + +**`START_OF_TURN`** - Emitted at the beginning of a new turn. + +**`END_OF_TURN_PREDICTION`** - Emitted during `ADAPTIVE` or `SMART_TURN` mode to predict turn completion (fires before `END_OF_TURN`). + +**`END_OF_UTTERANCE`** - Low-level STT engine event (fires when silence threshold is reached). + +**`ADD_PARTIAL_TRANSCRIPT` / `ADD_TRANSCRIPT`** - Legacy word-level events from underlying Real-Time API (not typically needed with Voice SDK). + +## Common Usage Patterns + +### Simple Transcription + +```python +client = VoiceAgentClient(api_key=api_key, preset="scribe") + +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + for segment in message["segments"]: + print(f"{segment['speaker_id']}: {segment['text']}") +``` + +### Conversational AI with Turn Detection + +```python +config = VoiceAgentConfig( + language="en", + enable_diarization=True, + end_of_utterance_mode=EndOfUtteranceMode.ADAPTIVE, +) + +client = VoiceAgentClient(api_key=api_key, config=config) + +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + user_text = message["segments"][0]["text"] + # Process user input + +@client.on(AgentServerMessageType.END_OF_TURN) +def on_turn_end(message): + # User finished speaking - generate AI response + pass +``` + +### Live Captions with Timestamps + +```python +client = VoiceAgentClient(api_key=api_key, preset="captions") + +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + start_time = message["metadata"]["start_time"] + for segment in message["segments"]: + print(f"[{start_time:.1f}s] {segment['text']}") +``` + +### Speaker Identification + +```python +from speechmatics.voice import SpeakerIdentifier + +# Use known speakers from previous session +known_speakers = [ + SpeakerIdentifier(label="Alice", speaker_identifiers=["XX...XX"]), + SpeakerIdentifier(label="Bob", speaker_identifiers=["YY...YY"]) +] + +config = VoiceAgentConfig( + enable_diarization=True, + known_speakers=known_speakers +) + +client = VoiceAgentClient(api_key=api_key, config=config) + +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + for segment in message["segments"]: + # Will show "Alice" or "Bob" instead of "S1", "S2" + print(f"{segment['speaker_id']}: {segment['text']}") +``` + +### Manual Turn Control + +```python +config = VoiceAgentConfig( + end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL +) + +client = VoiceAgentClient(api_key=api_key, config=config) + +# Manually trigger turn end +await client.finalize(end_of_turn=True) +``` + +### Focus on Specific Speaker + +```python +from speechmatics.voice import SpeakerFocusConfig, SpeakerFocusMode + +config = VoiceAgentConfig( + enable_diarization=True, + speaker_config=SpeakerFocusConfig( + focus_speakers=["S1"], # Only emit S1's speech + focus_mode=SpeakerFocusMode.RETAIN + ) +) + +client = VoiceAgentClient(api_key=api_key, config=config) + +@client.on(AgentServerMessageType.ADD_SEGMENT) +def on_segment(message): + # Only S1's segments will appear here + for segment in message["segments"]: + if segment["is_active"]: + print(f"{segment['text']}") + +# Dynamically change focused speaker during session +await client.update_diarization_config( + SpeakerFocusConfig( + focus_speakers=["S2"], # Switch focus to S2 + focus_mode=SpeakerFocusMode.RETAIN + ) +) +``` + ## Environment Variables -- `SPEECHMATICS_API_KEY` - Your Speechmatics API key +- `SPEECHMATICS_API_KEY` - Your Speechmatics API key (required) - `SPEECHMATICS_RT_URL` - Custom WebSocket endpoint (optional) -- `SMART_TURN_MODEL_PATH` - Path for the SMART_TURN ONNX model (optional) -- `SMART_TURN_HF_URL` - Override download URL for the SMART_TURN ONNX model (optional) +- `SMART_TURN_MODEL_PATH` - Path for SMART_TURN ONNX model cache (optional) +- `SMART_TURN_HF_URL` - Override SMART_TURN model download URL (optional) + +## Examples + +See the `examples/voice/` directory for complete working examples: + +- **`simple/`** - Basic microphone transcription +- **`scribe/`** - Note-taking with custom vocabulary +- **`cli/`** - Full-featured CLI with all options + +## API Reference + +### VoiceAgentClient + +```python +class VoiceAgentClient: + def __init__( + self, + api_key: str, + url: Optional[str] = None, + config: Optional[VoiceAgentConfig] = None, + preset: Optional[str] = None + ): + """Create Voice Agent client. + + Args: + api_key: Speechmatics API key + url: Custom WebSocket URL (optional) + config: Voice Agent configuration (optional) + preset: Preset name ("scribe", "low_latency", etc.) (optional) + """ + + async def connect(self) -> None: + """Connect to Speechmatics service.""" + + async def disconnect(self) -> None: + """Disconnect from service.""" + + async def send_audio(self, audio_data: bytes) -> None: + """Send audio data for transcription.""" + + async def finalize(self) -> None: + """Manually trigger end of turn (EXTERNAL mode only).""" + + async def send_message(self, message: dict) -> None: + """Send control message to service.""" + + def on(self, event: AgentServerMessageType, callback: Callable) -> None: + """Register event handler.""" + + def once(self, event: AgentServerMessageType, callback: Callable) -> None: + """Register one-time event handler.""" + + def off(self, event: AgentServerMessageType, callback: Callable) -> None: + """Unregister event handler.""" +``` + +## Requirements + +- Python 3.8+ +- Speechmatics API key ([Get one here](https://portal.speechmatics.com/)) ## Documentation -- **Speechmatics API**: https://docs.speechmatics.com +- [Speechmatics Documentation](https://docs.speechmatics.com/) +- [Real-Time Quickstart](https://docs.speechmatics.com/speech-to-text/realtime/quickstart) +- [Authentication](https://docs.speechmatics.com/get-started/authentication) ## License From 654fbcfa204299d9d6fc3bd8bbf790b68b9afaf1 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:23:17 +0000 Subject: [PATCH 210/215] Support for the JWT auth, as with the RT AsyncClient. --- sdk/voice/speechmatics/voice/_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index 8802544c..e1575b30 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -19,6 +19,7 @@ from speechmatics.rt import AsyncClient from speechmatics.rt import AudioEncoding from speechmatics.rt import AudioFormat +from speechmatics.rt import AuthBase from speechmatics.rt import ConversationConfig from speechmatics.rt import ServerMessageType from speechmatics.rt import SpeakerDiarizationConfig @@ -78,6 +79,7 @@ class VoiceAgentClient(AsyncClient): def __init__( self, + auth: Optional[AuthBase] = None, api_key: Optional[str] = None, url: Optional[str] = None, app: Optional[str] = None, @@ -87,6 +89,8 @@ def __init__( """Initialize the Voice Agent client. Args: + auth: Authentication instance. If not provided, uses StaticKeyAuth + with api_key parameter or SPEECHMATICS_API_KEY environment variable. api_key: Speechmatics API key. If None, uses SPEECHMATICS_API_KEY env var. url: REST API endpoint URL. If None, uses SPEECHMATICS_RT_URL env var or defaults to production endpoint. @@ -144,7 +148,7 @@ def __init__( url = os.getenv("SPEECHMATICS_RT_URL") or "wss://eu2.rt.speechmatics.com/v2" # Initialize the client - super().__init__(api_key=api_key, url=self._get_endpoint_url(url, app)) + super().__init__(auth=auth, api_key=api_key, url=self._get_endpoint_url(url, app)) # Logger self._logger = get_logger(__name__) From 2379faa68519b4259af199b1d46664df64f69930 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:33:52 +0000 Subject: [PATCH 211/215] to_json for the config --- sdk/voice/speechmatics/voice/_models.py | 7 +++++++ tests/voice/test_04_models.py | 23 +++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index eff3fefb..2b00230f 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -638,9 +638,16 @@ class VoiceAgentConfig(BaseConfigModel): # Parse JSON @classmethod def from_json(cls, json_data: str) -> VoiceAgentConfig: + """Convert a JSON string to a VoiceAgentConfig object.""" cfg: VoiceAgentConfig = cls.model_validate_json(json_data) return cfg + # To JSON + def to_json(self) -> str: + """Convert the model to a JSON string.""" + config_str: str = self.model_dump_json(exclude_none=True, exclude_defaults=True, exclude_unset=True) + return config_str + # ============================================================================== # SESSION & INFO MODELS diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 32b0c1e0..6e3af3d0 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -28,18 +28,21 @@ async def test_voice_agent_config(): ) # Test JSON serialisation - json_data = config.model_dump() - assert json_data["language"] == "en" - assert json_data["max_delay"] == 1.5 - assert json_data["enable_diarization"] is True - assert json_data["speaker_sensitivity"] == 0.7 - assert len(json_data["additional_vocab"]) == 1 - assert json_data["additional_vocab"][0]["content"] == "Speechmatics" - assert len(json_data["known_speakers"]) == 1 - assert json_data["known_speakers"][0]["label"] == "John" + config_dict = config.model_dump() + assert config_dict["language"] == "en" + assert config_dict["max_delay"] == 1.5 + assert config_dict["enable_diarization"] is True + assert config_dict["speaker_sensitivity"] == 0.7 + assert len(config_dict["additional_vocab"]) == 1 + assert config_dict["additional_vocab"][0]["content"] == "Speechmatics" + assert len(config_dict["known_speakers"]) == 1 + assert config_dict["known_speakers"][0]["label"] == "John" + + # Get JSON from the model + config_json = config.to_json() # Test JSON deserialisation - config_from_json = VoiceAgentConfig.model_validate(json_data) + config_from_json = VoiceAgentConfig.from_json(config_json) assert config_from_json.language == config.language assert config_from_json.max_delay == config.max_delay assert config_from_json.enable_diarization == config.enable_diarization From 0c79d499999f92ad21c183c99d92c4448ff521d8 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:40:20 +0000 Subject: [PATCH 212/215] updated README --- sdk/voice/README.md | 140 +++++++++++++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 41 deletions(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index e6a27e15..3c94ad2c 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -1,6 +1,7 @@ # Speechmatics Voice SDK -[![License](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/speechmatics/speechmatics-python-voice/blob/master/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/speechmatics-tts)](https://pypi.org/project/speechmatics-tts/) +![PythonSupport](https://img.shields.io/badge/Python-3.9%2B-green) Python SDK for building voice-enabled applications with the Speechmatics Real-Time API. Optimized for conversational AI, voice agents, transcription services, and real-time captioning. @@ -110,14 +111,6 @@ client = VoiceAgentClient(api_key=api_key, preset="conversation_smart_turn") client = VoiceAgentClient(api_key=api_key, preset="captions") ``` -**Available Presets:** - -- **`low_latency`** - FIXED mode, 0.7s max delay. Fast responses with minimal delay. -- **`conversation_adaptive`** - ADAPTIVE mode, 0.7s max delay. Natural conversation with intelligent turn detection. -- **`conversation_smart_turn`** - SMART_TURN mode, 0.7s max delay. Complex conversation with ML-based turn detection. -- **`scribe`** - FIXED mode, 1.0s max delay. Optimized for note-taking and meeting transcription. -- **`captions`** - FIXED mode, 0.9s max delay. Live captioning with final segments only. - ### Custom Configuration ```python @@ -138,16 +131,19 @@ client = VoiceAgentClient(api_key=api_key, config=config) ### Basic Parameters **`language`** (str, default: `"en"`) -Language code for transcription (e.g., `"en"`, `"es"`, `"fr"`). +Language code for transcription (e.g., `"en"`, `"es"`, `"fr"`). See [supported languages](https://docs.speechmatics.com/speech-to-text/languages). -**`enable_diarization`** (bool, default: `False`) -Enable speaker diarization to identify and label different speakers. +**`operating_point`** (OperatingPoint, default: `ENHANCED`) +Balance accuracy vs latency. Options: `STANDARD` or `ENHANCED`. -**`sample_rate`** (int, default: `16000`) -Audio sample rate in Hz. +**`domain`** (str, default: `None`) +Domain-specific model (e.g., `"finance"`, `"medical"`). See [supported languages and domains](https://docs.speechmatics.com/speech-to-text/languages). -**`audio_encoding`** (AudioEncoding, default: `PCM_S16LE`) -Audio encoding format. +**`output_locale`** (str, default: `None`) +Output locale for formatting (e.g., `"en-GB"`, `"en-US"`). See [supported languages and locales](https://docs.speechmatics.com/speech-to-text/languages). + +**`enable_diarization`** (bool, default: `False`) +Enable speaker diarization to identify and label different speakers. ### Turn Detection Parameters @@ -221,9 +217,6 @@ config = VoiceAgentConfig( ### Language & Vocabulary -**`output_locale`** (str, default: `None`) -Output locale for formatting (e.g., `"en-GB"`, `"en-US"`). - **`additional_vocab`** (list[AdditionalVocabEntry], default: `[]`) Custom vocabulary for domain-specific terms. @@ -245,13 +238,15 @@ config = VoiceAgentConfig( **`punctuation_overrides`** (dict, default: `None`) Custom punctuation rules. -### Advanced Parameters +### Audio Parameters -**`operating_point`** (OperatingPoint, default: `ENHANCED`) -Balance accuracy vs latency. Options: `STANDARD` or `ENHANCED`. +**`sample_rate`** (int, default: `16000`) +Audio sample rate in Hz. -**`domain`** (str, default: `None`) -Domain-specific model (e.g., `"finance"`, `"medical"`). +**`audio_encoding`** (AudioEncoding, default: `PCM_S16LE`) +Audio encoding format. + +### Advanced Parameters **`transcription_update_preset`** (TranscriptionUpdatePreset, default: `COMPLETE`) Controls when to emit updates: `COMPLETE`, `COMPLETE_PLUS_TIMING`, `WORDS`, `WORDS_PLUS_TIMING`, or `TIMING`. @@ -268,9 +263,6 @@ Include word-level timing data in segments. **`include_partials`** (bool, default: `True`) Emit partial segments. Set to `False` for final-only output. -**`enable_preview_features`** (bool, default: `False`) -Enable experimental features. - ### Configuration with Overlays Use presets as a starting point and customize with overlays: @@ -285,6 +277,27 @@ config = VoiceAgentConfigPreset.SCRIBE( max_delay=0.8 ) ) + +# Available presets +presets = VoiceAgentConfigPreset.list_presets() +# ['low_latency', 'conversation_adaptive', 'conversation_smart_turn', 'scribe', 'captions'] +``` + +### Configuration Serialization + +Export and import configurations as JSON: + +```python +from speechmatics.voice import VoiceAgentConfigPreset, VoiceAgentConfig + +# Export preset to JSON +config_json = VoiceAgentConfigPreset.SCRIBE().to_json() + +# Load from JSON +config = VoiceAgentConfig.from_json(config_json) + +# Or create from JSON string +config = VoiceAgentConfig.from_json('{"language": "en", "enable_diarization": true}') ``` ## Event Messages @@ -666,48 +679,93 @@ See the `examples/voice/` directory for complete working examples: class VoiceAgentClient: def __init__( self, - api_key: str, + auth: Optional[AuthBase] = None, + api_key: Optional[str] = None, url: Optional[str] = None, + app: Optional[str] = None, config: Optional[VoiceAgentConfig] = None, preset: Optional[str] = None ): """Create Voice Agent client. Args: - api_key: Speechmatics API key - url: Custom WebSocket URL (optional) + auth: Authentication instance (optional) + api_key: Speechmatics API key (defaults to SPEECHMATICS_API_KEY env var) + url: Custom WebSocket URL (defaults to SPEECHMATICS_RT_URL env var) + app: Optional application name for endpoint URL config: Voice Agent configuration (optional) preset: Preset name ("scribe", "low_latency", etc.) (optional) """ async def connect(self) -> None: - """Connect to Speechmatics service.""" + """Connect to Speechmatics service. + + Establishes WebSocket connection and starts transcription session. + Must be called before sending audio. + """ async def disconnect(self) -> None: - """Disconnect from service.""" + """Disconnect from service. + + Closes WebSocket connection and cleans up resources. + """ + + async def send_audio(self, payload: bytes) -> None: + """Send audio data for transcription. + + Args: + payload: Audio data as bytes + """ + + def update_diarization_config(self, config: SpeakerFocusConfig) -> None: + """Update diarization configuration during session. + + Args: + config: New speaker focus configuration + """ - async def send_audio(self, audio_data: bytes) -> None: - """Send audio data for transcription.""" + def finalize(self, end_of_turn: bool = False) -> None: + """Finalize segments and optionally trigger end of turn. - async def finalize(self) -> None: - """Manually trigger end of turn (EXTERNAL mode only).""" + Args: + end_of_turn: Whether to emit end of turn message (default: False) + """ async def send_message(self, message: dict) -> None: - """Send control message to service.""" + """Send control message to service. + + Args: + message: Control message dictionary + """ def on(self, event: AgentServerMessageType, callback: Callable) -> None: - """Register event handler.""" + """Register event handler. + + Args: + event: Event type to listen for + callback: Function to call when event occurs + """ def once(self, event: AgentServerMessageType, callback: Callable) -> None: - """Register one-time event handler.""" + """Register one-time event handler. + + Args: + event: Event type to listen for + callback: Function to call once when event occurs + """ def off(self, event: AgentServerMessageType, callback: Callable) -> None: - """Unregister event handler.""" + """Unregister event handler. + + Args: + event: Event type + callback: Function to remove + """ ``` ## Requirements -- Python 3.8+ +- Python 3.9+ - Speechmatics API key ([Get one here](https://portal.speechmatics.com/)) ## Documentation From 1615f0d599f4231884c353509bddbdb9b01d1250 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:41:17 +0000 Subject: [PATCH 213/215] README update --- sdk/voice/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/voice/README.md b/sdk/voice/README.md index 3c94ad2c..4c8085ea 100644 --- a/sdk/voice/README.md +++ b/sdk/voice/README.md @@ -1,6 +1,6 @@ # Speechmatics Voice SDK -[![PyPI](https://img.shields.io/pypi/v/speechmatics-tts)](https://pypi.org/project/speechmatics-tts/) +[![PyPI](https://img.shields.io/pypi/v/speechmatics-voice)](https://pypi.org/project/speechmatics-voice/) ![PythonSupport](https://img.shields.io/badge/Python-3.9%2B-green) Python SDK for building voice-enabled applications with the Speechmatics Real-Time API. Optimized for conversational AI, voice agents, transcription services, and real-time captioning. From 4fd86ce2e67a51ad870c5e9378ffdead48fc6eb5 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:44:36 +0000 Subject: [PATCH 214/215] main README update --- README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bf660863..5b866f51 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,19 @@ pip install speechmatics-flow A Voice Agent Python client for Speechmatics Real-Time API. ```bash +# Standard installation pip install speechmatics-voice + +# With SMART_TURN (ML-based turn detection) +pip install speechmatics-voice[smart] +``` + +### (Beta) TTS Client (`speechmatics-tts`) + +An async Python client for Speechmatics TTS API. + +```bash +pip install speechmatics-tts ``` ## Development @@ -60,16 +72,21 @@ speechmatics-python-sdk/ │ ├── flow/ │ │ ├── pyproject.toml │ │ └── README.md -│ +│ │ │ ├── voice/ │ │ ├── pyproject.toml │ │ └── README.md +│ │ +│ ├── tts/ +│ │ ├── pyproject.toml +│ │ └── README.md │ ├── tests/ │ ├── batch/ -│ └── rt/ -│ └── flow/ -│ └── voice/ +│ ├── rt/ +│ ├── flow/ +│ ├── voice/ +│ └── tts/ │ ├── examples/ ├── Makefile @@ -110,7 +127,8 @@ Each package can be installed separately: pip install speechmatics-rt pip install speechmatics-batch pip install speechmatics-flow -pip install speechmatics-voice +pip install speechmatics-voice[smart] +pip install speechmatics-tts ``` ## Docs From 65198016957d184bab4d5a41d214f1c469e412da Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Tue, 11 Nov 2025 23:56:27 +0000 Subject: [PATCH 215/215] Update test.yaml --- .github/workflows/test.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 49d61fe7..7928371f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -89,3 +89,23 @@ jobs: run: make test-voice - name: Build Voice Agent SDK run: make build-voice + + test-tts: + name: Test TTS SDK + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: make install-dev + - name: Lint TTS SDK + run: make lint-tts + - name: Test TTS SDK + run: make test-tts + - name: Build TTS SDK + run: make build-tts