fix(realtime): post-tool-call function_response id omission (#30446)

This commit is contained in:
Sameer Kankute 2026-06-23 21:10:52 +05:30 committed by GitHub
parent 23808c2a09
commit e73cbfb026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 82 additions and 7 deletions

View file

@ -5537,9 +5537,7 @@ class BaseLLMHTTPHandler:
import websockets
from websockets.asyncio.client import ClientConnection
url = self._append_query_params(
provider_config.get_complete_url(api_base, model, api_key), query_params
)
url = provider_config.get_complete_url(api_base, model, api_key)
headers = provider_config.validate_environment(
headers=headers,
model=model,

View file

@ -103,6 +103,10 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
# bypassing spend and budget accounting.
self._pending_usage_metadata: Optional[dict] = None
def _include_function_response_id(self) -> bool:
"""Google AI Studio Gemini 3.5+ accepts ``id`` on functionResponses; Vertex AI rejects it."""
return True
@staticmethod
def _usage_detail_alias(details: Any, defaults: Dict[str, int]) -> Dict[str, Any]:
if not isinstance(details, dict):
@ -604,10 +608,9 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
)
# Build Gemini toolResponse format
function_response = {
"id": call_id,
"response": output_dict,
}
function_response: dict[str, Any] = {"response": output_dict}
if self._include_function_response_id() and call_id:
function_response["id"] = call_id
if function_name:
function_response["name"] = function_name

View file

@ -32,6 +32,9 @@ class VertexAIRealtimeConfig(GeminiRealtimeConfig):
self._project = project
self._location = location
def _include_function_response_id(self) -> bool:
return False
# ------------------------------------------------------------------
# URL
# ------------------------------------------------------------------

View file

@ -346,3 +346,74 @@ def test_vertex_does_not_warn_when_dropping_non_guardrail_session_update(caplog)
"Vertex AI Realtime" in record.message and "session.update" in record.message
for record in caplog.records
)
async def test_async_realtime_does_not_forward_client_query_params_to_vertex_backend(
monkeypatch,
):
"""Regression: forwarding client ?model=/?intent= to the Vertex Live WSS URL causes 1007 errors.
Exercises ``async_realtime`` end-to-end so that re-adding ``_append_query_params``
(the reverted bug) would push ``model=``/``intent=`` onto the backend URL and fail here.
"""
import websockets
from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler
cfg = VertexAIRealtimeConfig(
access_token="tok", project="my-proj", location="us-central1"
)
captured = {}
def fake_connect(url, *args, **kwargs):
captured["url"] = url
raise RuntimeError("stop before establishing the backend connection")
monkeypatch.setattr(websockets, "connect", fake_connect)
await BaseLLMHTTPHandler().async_realtime(
model="gemini-live-2.5-flash-native-audio",
websocket=AsyncMock(),
logging_obj=MagicMock(),
provider_config=cfg,
headers={},
query_params={
"model": "gemini-live-2.5-flash-native-audio",
"intent": "chat",
},
)
assert "?" not in captured["url"]
assert "model=" not in captured["url"]
assert "intent=" not in captured["url"]
def test_vertex_function_call_output_omits_id():
"""Regression: Vertex Live rejects ``id`` on toolResponse.functionResponses (1007)."""
cfg = VertexAIRealtimeConfig(
access_token="tok", project="my-proj", location="us-central1"
)
cfg._tool_call_id_to_name["call_abc123"] = "terminate_call"
messages = cfg.transform_realtime_request(
json.dumps(
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_abc123",
"output": '{"status": "ok"}',
},
}
),
"gemini-live-2.5-flash-native-audio",
session_configuration_request="existing",
)
assert len(messages) == 1
payload = json.loads(messages[0])
function_response = payload["toolResponse"]["functionResponses"][0]
assert "id" not in function_response
assert function_response["name"] == "terminate_call"
assert function_response["response"] == {"status": "ok"}