feat: implement OpenAI-compatible Realtime API with authentication and management features
- Added support for public Realtime API, including new routes for managing API keys and handling WebRTC connections. - Introduced RealtimeApiKey model and associated CRUD operations for admin management of API keys. - Implemented authentication mechanisms for API keys and client secrets. - Enhanced environment configuration with new secrets for Realtime API. - Created OpenAIRealtime session management and event processing for real-time interactions. - Updated schemas and settings to accommodate new features and ensure compatibility with existing systems.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""Small, explicit validation helpers for the supported Realtime event subset."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
SUPPORTED_CLIENT_EVENTS = {
|
||||
"session.update",
|
||||
"conversation.item.create",
|
||||
"conversation.item.truncate",
|
||||
"input_audio_buffer.append",
|
||||
"input_audio_buffer.commit",
|
||||
"input_audio_buffer.clear",
|
||||
"output_audio_buffer.clear",
|
||||
"response.create",
|
||||
"response.cancel",
|
||||
"x.interactive_media.capabilities.update",
|
||||
"x.interactive_media.session.variables.update",
|
||||
}
|
||||
|
||||
SUPPORTED_CAPABILITIES = {
|
||||
"dynamic_variables",
|
||||
"workflow_events",
|
||||
"handoff",
|
||||
"video_track",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RealtimeEventError(ValueError):
|
||||
message: str
|
||||
code: str = "invalid_request_error"
|
||||
param: str | None = None
|
||||
event_id: str | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.message
|
||||
|
||||
|
||||
def require_event(message: object) -> dict[str, Any]:
|
||||
if not isinstance(message, dict):
|
||||
raise RealtimeEventError("Event must be a JSON object")
|
||||
event_type = str(message.get("type") or "")
|
||||
if event_type not in SUPPORTED_CLIENT_EVENTS:
|
||||
raise RealtimeEventError(
|
||||
f"Unsupported client event: {event_type or '<missing>'}",
|
||||
code="invalid_event",
|
||||
param="type",
|
||||
event_id=str(message.get("event_id") or "") or None,
|
||||
)
|
||||
return message
|
||||
|
||||
|
||||
def assistant_id_from_model(model: object) -> str:
|
||||
value = str(model or "").strip()
|
||||
if not value.startswith("assistant:") or not value.removeprefix("assistant:"):
|
||||
raise RealtimeEventError(
|
||||
"model must use assistant:asst_xxx",
|
||||
code="invalid_model",
|
||||
param="session.model",
|
||||
)
|
||||
return value.removeprefix("assistant:")
|
||||
|
||||
|
||||
def normalize_turn_detection(value: object) -> dict[str, Any] | None:
|
||||
if value is None:
|
||||
return None
|
||||
if not isinstance(value, dict) or value.get("type") != "server_vad":
|
||||
raise RealtimeEventError(
|
||||
"turn_detection must be server_vad or null",
|
||||
param="session.audio.input.turn_detection",
|
||||
)
|
||||
try:
|
||||
threshold = float(value.get("threshold", 0.7))
|
||||
prefix_ms = int(value.get("prefix_padding_ms", 200))
|
||||
silence_ms = int(value.get("silence_duration_ms", 600))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise RealtimeEventError(
|
||||
"Invalid server_vad threshold, padding, or silence duration",
|
||||
param="session.audio.input.turn_detection",
|
||||
) from exc
|
||||
if (
|
||||
not 0 <= threshold <= 1
|
||||
or not 0 <= prefix_ms <= 5000
|
||||
or not 100 <= silence_ms <= 10000
|
||||
):
|
||||
raise RealtimeEventError(
|
||||
"Invalid server_vad threshold, padding, or silence duration",
|
||||
param="session.audio.input.turn_detection",
|
||||
)
|
||||
return {
|
||||
"type": "server_vad",
|
||||
"threshold": threshold,
|
||||
"prefix_padding_ms": prefix_ms,
|
||||
"silence_duration_ms": silence_ms,
|
||||
"create_response": bool(value.get("create_response", True)),
|
||||
"interrupt_response": bool(value.get("interrupt_response", True)),
|
||||
}
|
||||
|
||||
|
||||
def error_event(error: RealtimeEventError) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "error",
|
||||
"event_id": f"event_{uuid4().hex}",
|
||||
"error": {
|
||||
"type": "invalid_request_error",
|
||||
"code": error.code,
|
||||
"message": error.message,
|
||||
"param": error.param,
|
||||
"event_id": error.event_id,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user