mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(realtime): post-tool-call function_response id omission (#30446)
This commit is contained in:
parent
23808c2a09
commit
e73cbfb026
4 changed files with 82 additions and 7 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ class VertexAIRealtimeConfig(GeminiRealtimeConfig):
|
|||
self._project = project
|
||||
self._location = location
|
||||
|
||||
def _include_function_response_id(self) -> bool:
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# URL
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue