Files
vocero-client/speech_to_speech/pipeline/events.py
T
2026-08-26 17:42:00 -04:00

100 lines
3.3 KiB
Python

"""Typed events flowing on ``text_output_queue``.
These are internal pipeline events produced by VAD, TranscriptionNotifier, and
LMOutputProcessor, consumed by the realtime ``WebSocketRouter`` send-loop and
``RealtimeService.dispatch_pipeline_event``. They replace the raw ``dict``
literals that were previously put on the queue.
"""
from __future__ import annotations
from typing import Literal, Optional
from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
from pydantic import BaseModel, Field
class PipelineEvent(BaseModel):
"""Base for all text_output_queue events.
The ``type`` field mirrors the former dict ``"type"`` key and acts as a
Pydantic discriminator.
"""
type: str
# ── VAD events ────────────────────────────────────────────────────────
class SpeechStartedEvent(PipelineEvent):
type: Literal["speech_started"] = "speech_started"
audio_start_ms: int = 0
turn_id: str | None = None
turn_revision: int | None = None
reopened: bool = False
interrupt_response: bool = Field(default=True, exclude=True)
class SpeechStoppedEvent(PipelineEvent):
type: Literal["speech_stopped"] = "speech_stopped"
duration_s: float = 0.0
audio_end_ms: int = 0
turn_id: str | None = None
turn_revision: int | None = None
# ── Transcription events (TranscriptionNotifier) ─────────────────────
class PartialTranscriptionEvent(PipelineEvent):
type: Literal["partial_transcription"] = "partial_transcription"
delta: str
turn_id: str | None = None
turn_revision: int | None = None
class TranscriptionCompletedEvent(PipelineEvent):
type: Literal["transcription_completed"] = "transcription_completed"
transcript: str
language_code: Optional[str] = None
turn_id: str | None = None
turn_revision: int | None = None
speech_stopped_at_s: float | None = Field(default=None, exclude=True)
# ── LLM output events (LMOutputProcessor) ────────────────────────────
class AssistantTextEvent(PipelineEvent):
type: Literal["assistant_text"] = "assistant_text"
text: str
tools: list[ResponseFunctionToolCall] = Field(default_factory=list)
turn_id: str | None = None
turn_revision: int | None = None
# Response generation that produced this text, mirroring AudioOutput. Lets the
# send loop discard stale assistant text by the same generation-aware rule as
# audio, instead of blanket-dropping while cancel_scope.discarding is set.
cancel_generation: int | None = None
class TokenUsageEvent(PipelineEvent):
type: Literal["token_usage"] = "token_usage"
input_tokens: int = 0
output_tokens: int = 0
turn_id: str | None = None
turn_revision: int | None = None
class ResponseFailedEvent(PipelineEvent):
"""Signals that a response could not be generated (e.g. invalid out-of-band input).
Dispatched to the service so it can close the in-progress response with
``status="failed"`` instead of the usual ``completed``.
"""
type: Literal["response_failed"] = "response_failed"
message: str = ""
turn_id: str | None = None
turn_revision: int | None = None