mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
Merge 67bcf90068 into 82ca10cc2d
This commit is contained in:
commit
2d3cd7fce6
4 changed files with 101 additions and 16 deletions
|
|
@ -391,7 +391,7 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
return None
|
||||
|
||||
@staticmethod
|
||||
def _normalize_session_payload_for_mapping(session: dict) -> dict:
|
||||
def _normalize_session_payload_for_mapping(session: dict) -> dict[str, object]:
|
||||
"""Normalize GA-remapped session fields back to their beta keys.
|
||||
|
||||
``map_openai_params`` only recognises the flat OpenAI-beta key names
|
||||
|
|
@ -472,6 +472,39 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
)
|
||||
return setup
|
||||
|
||||
def _warn_session_update_dropped(self, session_payload: Mapping[str, object], *, provider: str) -> None:
|
||||
"""Report, once per session, that a later ``session.update`` reached nothing.
|
||||
|
||||
Live accepts ``setup`` as the first-and-only client message, so a second one
|
||||
closes the socket with 1007 and the update has to be dropped. That is
|
||||
unavoidable; being quiet about it is not. This used to log at debug, which on
|
||||
a proxy means the fields never arrive and nothing says so -- the caller sees
|
||||
the model running on provider defaults and no instructions, voice or persona
|
||||
of theirs (#44825).
|
||||
|
||||
Warned once because a GA client sends several ``session.update`` messages
|
||||
while configuring; later drops stay at debug.
|
||||
"""
|
||||
dropped: Final = sorted(key for key in session_payload if key != "type")
|
||||
if getattr(self, "_session_update_drop_warned", False):
|
||||
verbose_logger.debug(
|
||||
"%s: dropping a further session.update (setup already sent): %s",
|
||||
provider,
|
||||
dropped,
|
||||
)
|
||||
return
|
||||
self._session_update_drop_warned = True
|
||||
verbose_logger.warning(
|
||||
"%s: dropping session.update because setup was already sent, so these "
|
||||
"fields do not reach the model: %s. Live accepts setup as the "
|
||||
"first-and-only client message. To have the client's first "
|
||||
"session.update carry them instead, set litellm.gemini_live_defer_setup "
|
||||
"= True (env LITELLM_GEMINI_LIVE_DEFER_SETUP=true), which defers setup "
|
||||
"until that update arrives. Further drops in this session log at debug.",
|
||||
provider,
|
||||
dropped,
|
||||
)
|
||||
|
||||
def _handle_session_update(
|
||||
self,
|
||||
json_message: _OpenAIRealtimeClientEvent,
|
||||
|
|
@ -527,7 +560,7 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
"suppress the model's auto-response mid-session."
|
||||
)
|
||||
else:
|
||||
verbose_logger.debug("Gemini Realtime: Ignoring session.update (setup already sent)")
|
||||
self._warn_session_update_dropped(session_payload, provider="Gemini Realtime")
|
||||
return []
|
||||
|
||||
def _handle_conversation_item(self, json_message: _OpenAIRealtimeClientEvent) -> list[str]:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Auth: OAuth2 Bearer token (not an API key).
|
|||
"""
|
||||
|
||||
import json
|
||||
from collections.abc import Awaitable, Callable
|
||||
from collections.abc import Awaitable, Callable, Mapping
|
||||
from typing import Final
|
||||
|
||||
from litellm import verbose_logger
|
||||
|
|
@ -238,7 +238,10 @@ class VertexAIRealtimeConfig(GeminiRealtimeConfig):
|
|||
"Vertex AI in non-deferred mode."
|
||||
)
|
||||
else:
|
||||
verbose_logger.debug("Vertex AI Realtime: Ignoring session.update (setup already sent)")
|
||||
# Annotated rather than passed straight from json.loads, whose Any
|
||||
# would reach the helper's Mapping parameter as an unknown type.
|
||||
dropped_session: Final[Mapping[str, object]] = json_message.get("session") or {}
|
||||
self._warn_session_update_dropped(dropped_session, provider="Vertex AI Realtime")
|
||||
return []
|
||||
|
||||
return super().transform_realtime_request(message, model, session_configuration_request)
|
||||
|
|
|
|||
|
|
@ -2235,3 +2235,34 @@ def test_gemini_realtime_response_done_reports_no_grounding_when_none_ran():
|
|||
|
||||
assert input_details.get("web_search_requests") is None
|
||||
assert input_details.get("google_maps_grounding_requests") is None
|
||||
|
||||
|
||||
def test_gemini_warns_once_when_dropping_a_session_update(caplog):
|
||||
"""The Gemini path reports a dropped session.update the same way Vertex does.
|
||||
|
||||
Both drop it for the same reason -- Live accepts setup as the first-and-only
|
||||
client message -- so both should say so rather than log at debug and let the
|
||||
caller discover their instructions never arrived (#44825).
|
||||
"""
|
||||
import logging
|
||||
|
||||
cfg = GeminiRealtimeConfig()
|
||||
session_update = json.dumps({"type": "session.update", "session": {"instructions": "Be concise."}})
|
||||
already_set_up = json.dumps({"setup": {"model": "x"}})
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
|
||||
for _ in range(2):
|
||||
cfg.transform_realtime_request(
|
||||
session_update,
|
||||
"gemini-live-2.5-flash-preview-native-audio-09-2025",
|
||||
session_configuration_request=already_set_up,
|
||||
)
|
||||
|
||||
warnings = [
|
||||
record
|
||||
for record in caplog.records
|
||||
if "Gemini Realtime" in record.message and "session.update" in record.message
|
||||
]
|
||||
assert len(warnings) == 1
|
||||
assert "instructions" in warnings[0].message
|
||||
assert "gemini_live_defer_setup" in warnings[0].message
|
||||
|
|
|
|||
|
|
@ -418,9 +418,19 @@ def test_vertex_warns_when_dropping_guardrail_turn_detection_update(caplog):
|
|||
)
|
||||
|
||||
|
||||
def test_vertex_does_not_warn_when_dropping_non_guardrail_session_update(caplog):
|
||||
"""A subsequent session.update without ``create_response: False`` is a
|
||||
routine drop and should stay at debug level (no warning)."""
|
||||
def test_vertex_warns_once_when_dropping_a_session_update(caplog):
|
||||
"""A dropped session.update is reported, and reported once.
|
||||
|
||||
This inverts what the test here previously asserted. It held that dropping a
|
||||
subsequent session.update was "a routine drop" that should stay at debug --
|
||||
with ``{"instructions": "Be concise."}`` as its own fixture, which is exactly
|
||||
the case reported in #44825: the caller's instructions never reach the model
|
||||
and nothing on a default proxy log says so.
|
||||
|
||||
The drop itself is unavoidable (Live rejects a second setup with 1007). Only
|
||||
the silence changes. It warns once because a GA client sends several
|
||||
session.updates while configuring; the rest stay at debug.
|
||||
"""
|
||||
import logging
|
||||
|
||||
cfg = VertexAIRealtimeConfig(
|
||||
|
|
@ -429,20 +439,28 @@ def test_vertex_does_not_warn_when_dropping_non_guardrail_session_update(caplog)
|
|||
|
||||
session_update = {
|
||||
"type": "session.update",
|
||||
"session": {"instructions": "Be concise."},
|
||||
"session": {"instructions": "Be concise.", "voice": "Puck"},
|
||||
}
|
||||
already_set_up = json.dumps({"setup": {"model": "x"}})
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
|
||||
cfg.transform_realtime_request(
|
||||
json.dumps(session_update),
|
||||
"gemini-live-2.5-flash-preview-native-audio-09-2025",
|
||||
session_configuration_request=json.dumps({"setup": {"model": "x"}}),
|
||||
)
|
||||
for _ in range(3):
|
||||
cfg.transform_realtime_request(
|
||||
json.dumps(session_update),
|
||||
"gemini-live-2.5-flash-preview-native-audio-09-2025",
|
||||
session_configuration_request=already_set_up,
|
||||
)
|
||||
|
||||
assert not any(
|
||||
"Vertex AI Realtime" in record.message and "session.update" in record.message
|
||||
warnings = [
|
||||
record
|
||||
for record in caplog.records
|
||||
)
|
||||
if "Vertex AI Realtime" in record.message and "session.update" in record.message
|
||||
]
|
||||
assert len(warnings) == 1, "three drops, one warning"
|
||||
# the operator needs to know what was lost and what to do about it
|
||||
assert "instructions" in warnings[0].message
|
||||
assert "voice" in warnings[0].message
|
||||
assert "gemini_live_defer_setup" in warnings[0].message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue