first
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from openai.types.realtime import RealtimeSessionCreateRequest
|
||||
from openai.types.realtime.realtime_audio_config import RealtimeAudioConfig
|
||||
from openai.types.realtime.realtime_audio_config_input import RealtimeAudioConfigInput
|
||||
from openai.types.realtime.realtime_audio_config_output import RealtimeAudioConfigOutput
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from speech_to_speech.LLM.chat import Chat
|
||||
|
||||
|
||||
def _apply_update(current: BaseModel, update: BaseModel) -> None:
|
||||
"""Apply explicitly-set fields from *update* onto *current* in-place,
|
||||
recursing into nested BaseModel children so partial nested updates
|
||||
don't overwrite unset fields.
|
||||
|
||||
Only fields present in update.model_fields_set (i.e. actually
|
||||
sent by the client) are considered.
|
||||
"""
|
||||
for field_name in update.model_fields_set:
|
||||
new_val = getattr(update, field_name)
|
||||
old_val = getattr(current, field_name, None)
|
||||
if isinstance(new_val, BaseModel) and isinstance(old_val, BaseModel):
|
||||
_apply_update(old_val, new_val)
|
||||
else:
|
||||
setattr(current, field_name, new_val)
|
||||
|
||||
|
||||
class RuntimeConfig(BaseModel):
|
||||
"""
|
||||
Shared mutable configuration written by the RealtimeService on
|
||||
session.update and read by pipeline handlers (VAD, LLM, TTS) during
|
||||
processing. Python's GIL makes simple attribute reads/writes atomic,
|
||||
so no explicit locking is needed for primitive values.
|
||||
|
||||
The canonical state lives in 'session' (a full
|
||||
'RealtimeSessionCreateRequest').
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)
|
||||
|
||||
chat: Chat = Field(default_factory=lambda: Chat(10))
|
||||
session: RealtimeSessionCreateRequest = Field(
|
||||
default_factory=lambda: RealtimeSessionCreateRequest(type="realtime"),
|
||||
validate_default=True,
|
||||
)
|
||||
|
||||
@field_validator("session", mode="after")
|
||||
@classmethod
|
||||
def _ensure_audio_structure(cls, v: RealtimeSessionCreateRequest) -> RealtimeSessionCreateRequest:
|
||||
"""Guarantee 'audio.input' and 'audio.output' are never None."""
|
||||
if v.audio is None:
|
||||
v.audio = RealtimeAudioConfig()
|
||||
if v.audio.input is None:
|
||||
v.audio.input = RealtimeAudioConfigInput()
|
||||
if v.audio.output is None:
|
||||
v.audio.output = RealtimeAudioConfigOutput()
|
||||
return v
|
||||
|
||||
@property
|
||||
def interrupt_response_enabled(self) -> bool:
|
||||
"""Whether barge-in should cancel an active response.
|
||||
|
||||
Reads 'turn_detection.interrupt_response' from the session config,
|
||||
handling both Pydantic models ('ServerVad') and plain dicts.
|
||||
Defaults to 'True' (OpenAI API default).
|
||||
"""
|
||||
assert self.session.audio is not None and self.session.audio.input is not None
|
||||
td = self.session.audio.input.turn_detection
|
||||
if td is None:
|
||||
return True
|
||||
if hasattr(td, "interrupt_response"):
|
||||
val = td.interrupt_response
|
||||
elif isinstance(td, dict):
|
||||
val = td.get("interrupt_response", True)
|
||||
else:
|
||||
return True
|
||||
return val if val is not None else True
|
||||
|
||||
def apply_session_update(self, update: RealtimeSessionCreateRequest) -> None:
|
||||
"""Merge non-None, explicitly-set fields from 'update' into the
|
||||
current 'session', preserving any fields not present in the update."""
|
||||
_apply_update(self.session, update)
|
||||
Reference in New Issue
Block a user