[LLM Translation] fix query params for realtime api intent (#12838)

* fix query params for realtime api intent

* fix my py

* Add typed dict

* remove typed dict

* fix comments

* add test

* add test

* added proxt log revert

* add real time q params
This commit is contained in:
Jugal D. Bhatt
2025-07-25 10:09:53 -07:00
committed by GitHub
parent c2833e693e
commit c66b288bcf
5 changed files with 99 additions and 9 deletions
+16 -6
View File
@@ -9,20 +9,26 @@ from typing import Any, Optional, cast
from ....litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from ....litellm_core_utils.realtime_streaming import RealTimeStreaming
from ..openai import OpenAIChatCompletion
from litellm.types.realtime import RealtimeQueryParams
class OpenAIRealtime(OpenAIChatCompletion):
def _construct_url(self, api_base: str, model: str) -> str:
def _construct_url(self, api_base: str, query_params: RealtimeQueryParams) -> str:
"""
Example output:
"BACKEND_WS_URL = "wss://localhost:8080/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01"";
Construct the backend websocket URL with all query parameters (excluding 'model' if present).
"""
from httpx import URL
api_base = api_base.replace("https://", "wss://")
api_base = api_base.replace("http://", "ws://")
url = URL(api_base).join("/v1/realtime")
return str(url.copy_add_param("model", model))
url = URL(api_base)
# Set the correct path
url = url.copy_with(path="/v1/realtime")
# Build query dict excluding 'model'
query_dict = {k: v for k, v in query_params.items() if k != "model"}
if query_dict:
url = url.copy_with(params=query_dict)
return str(url)
async def async_realtime(
self,
@@ -33,6 +39,7 @@ class OpenAIRealtime(OpenAIChatCompletion):
api_key: Optional[str] = None,
client: Optional[Any] = None,
timeout: Optional[float] = None,
query_params: Optional[RealtimeQueryParams] = None,
):
import websockets
from websockets.asyncio.client import ClientConnection
@@ -42,7 +49,10 @@ class OpenAIRealtime(OpenAIChatCompletion):
if api_key is None:
raise ValueError("api_key is required for Azure OpenAI calls")
url = self._construct_url(api_base, model)
# Use all query params if provided, else fallback to just model
if query_params is None:
query_params = {"model": model}
url = self._construct_url(api_base, query_params)
try:
async with websockets.connect( # type: ignore
+6
View File
@@ -147,6 +147,7 @@ from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.litellm_core_utils.sensitive_data_masker import SensitiveDataMasker
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
from litellm.types.realtime import RealtimeQueryParams
from litellm.proxy._experimental.mcp_server.rest_endpoints import (
router as mcp_rest_endpoints_router,
)
@@ -4680,15 +4681,20 @@ from litellm import _arealtime
async def websocket_endpoint(
websocket: WebSocket,
model: str,
intent: str = fastapi.Query(None, description="The intent of the websocket connection."),
user_api_key_dict=Depends(user_api_key_auth_websocket),
):
import websockets
await websocket.accept()
# Only use explicit parameters, not all query params
query_params: RealtimeQueryParams = {"model": model, "intent": intent}
data = {
"model": model,
"websocket": websocket,
"query_params": query_params, # Only explicit params
}
headers = dict(websocket.headers.items()) # Convert headers to dict first
+4 -1
View File
@@ -15,6 +15,7 @@ from ..litellm_core_utils.get_litellm_params import get_litellm_params
from ..litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from ..llms.azure.realtime.handler import AzureOpenAIRealtime
from ..llms.openai.realtime.handler import OpenAIRealtime
from litellm.types.realtime import RealtimeQueryParams
from ..utils import client as wrapper_client
azure_realtime = AzureOpenAIRealtime()
@@ -32,6 +33,7 @@ async def _arealtime(
azure_ad_token: Optional[str] = None,
client: Optional[Any] = None,
timeout: Optional[float] = None,
query_params: Optional[RealtimeQueryParams] = None,
**kwargs,
):
"""
@@ -132,6 +134,7 @@ async def _arealtime(
api_key=api_key,
client=None,
timeout=timeout,
query_params=query_params,
)
else:
raise ValueError(f"Unsupported model: {model}")
@@ -170,7 +173,7 @@ async def _realtime_health_check(
)
elif custom_llm_provider == "openai":
url = openai_realtime._construct_url(
api_base=api_base or "https://api.openai.com/", model=model
api_base=api_base or "https://api.openai.com/", query_params=RealtimeQueryParams(model=model)
)
else:
raise ValueError(f"Unsupported model: {model}")
+6
View File
@@ -41,3 +41,9 @@ class RealtimeModalityResponseTransformOutput(TypedDict):
current_conversation_id: Optional[str]
current_delta_chunks: Optional[List[OpenAIRealtimeResponseDelta]]
current_delta_type: Optional[ALL_DELTA_TYPES]
class RealtimeQueryParams(TypedDict, total=False):
model: str
intent: Optional[str]
# Add more fields as needed
@@ -19,9 +19,74 @@ def test_openai_realtime_handler_url_construction(api_base):
handler = OpenAIRealtime()
url = handler._construct_url(
api_base=api_base, model="gpt-4o-realtime-preview-2024-10-01"
api_base=api_base, query_params = {
"model": "gpt-4o-realtime-preview-2024-10-01",
}
)
assert (
url
== f"wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01"
== f"wss://api.openai.com/v1/realtime"
)
def test_openai_realtime_handler_url_with_extra_params():
from litellm.llms.openai.realtime.handler import OpenAIRealtime
from litellm.types.realtime import RealtimeQueryParams
handler = OpenAIRealtime()
api_base = "https://api.openai.com/v1"
query_params: RealtimeQueryParams = {
"model": "gpt-4o-realtime-preview-2024-10-01",
"intent": "chat"
}
url = handler._construct_url(api_base=api_base, query_params=query_params)
# 'model' should be excluded from the query string
assert url.startswith("wss://api.openai.com/v1/realtime?")
assert "intent=chat" in url
import asyncio
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
@pytest.mark.asyncio
async def test_async_realtime_success():
from litellm.llms.openai.realtime.handler import OpenAIRealtime
from litellm.types.realtime import RealtimeQueryParams
handler = OpenAIRealtime()
api_base = "https://api.openai.com/v1"
api_key = "test-key"
model = "gpt-4o-realtime-preview-2024-10-01"
query_params: RealtimeQueryParams = {"model": model, "intent": "chat"}
dummy_websocket = AsyncMock()
dummy_logging_obj = MagicMock()
mock_backend_ws = AsyncMock()
class DummyAsyncContextManager:
def __init__(self, value):
self.value = value
async def __aenter__(self):
return self.value
async def __aexit__(self, exc_type, exc, tb):
return None
with patch("websockets.connect", return_value=DummyAsyncContextManager(mock_backend_ws)) as mock_ws_connect, \
patch("litellm.llms.openai.realtime.handler.RealTimeStreaming") as mock_realtime_streaming:
mock_streaming_instance = MagicMock()
mock_realtime_streaming.return_value = mock_streaming_instance
mock_streaming_instance.bidirectional_forward = AsyncMock()
await handler.async_realtime(
model=model,
websocket=dummy_websocket,
logging_obj=dummy_logging_obj,
api_base=api_base,
api_key=api_key,
query_params=query_params,
)
mock_realtime_streaming.assert_called_once()
mock_streaming_instance.bidirectional_forward.assert_awaited_once()