refactor(interactions): remove expired use_legacy_interactions_schema shim

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-09-17 20:14:07 +00:00
parent decbb96382
commit 07d593d831
6 changed files with 87 additions and 268 deletions

View file

@ -267,10 +267,6 @@ route_all_chat_openai_to_responses: bool = (
# When True, Gemini/Vertex Live setup is deferred until client `session.update`.
# Default False preserves historical behavior (auto-send setup on connect).
gemini_live_defer_setup: bool = os.getenv("LITELLM_GEMINI_LIVE_DEFER_SETUP", "false").lower() == "true"
use_legacy_interactions_schema: bool = (
os.getenv("LITELLM_USE_LEGACY_INTERACTIONS_SCHEMA", "false").lower() == "true"
) # When True, sends Api-Revision: 2026-05-07 to Google so responses use the legacy `outputs`
# schema instead of the new `steps` schema. Remove this flag after June 8, 2026.
retry = True
### AUTH ###
api_key: Optional[str] = None

View file

@ -33,11 +33,8 @@ class LiteLLMResponsesInteractionsStreamingIterator:
streaming events (output.text.delta, response.completed, etc.) to Interactions
API streaming events.
Schema selection:
- New schema (default, use_legacy_interactions_schema=False):
interaction.created -> step.start -> step.delta ... -> step.stop -> interaction.completed
- Legacy schema (use_legacy_interactions_schema=True, remove after June 8 2026):
interaction.start -> content.start -> content.delta ... -> content.stop -> interaction.complete
Emits the event sequence
``interaction.created -> step.start -> step.delta ... -> step.stop -> interaction.completed``.
"""
def __init__(
@ -49,8 +46,6 @@ class LiteLLMResponsesInteractionsStreamingIterator:
custom_llm_provider: str | None = None,
litellm_metadata: dict[str, Any] | None = None,
):
import litellm
self.model = model
self.responses_stream_iterator = litellm_custom_stream_wrapper
self.request_input = request_input
@ -61,10 +56,6 @@ class LiteLLMResponsesInteractionsStreamingIterator:
self.collected_text = ""
self.sent_interaction_start = False
self.sent_content_start = False
# Capture the schema flag once at construction time so all events
# emitted by this stream use a consistent schema, even if the global
# flag is mutated mid-stream (e.g. by a config reload).
self._use_legacy: bool = litellm.use_legacy_interactions_schema
# Buffer of events that have been derived from upstream chunks but not
# yet returned to the caller. A single Responses API chunk may expand
# into multiple Interactions API events (e.g. the first text delta
@ -85,9 +76,8 @@ class LiteLLMResponsesInteractionsStreamingIterator:
# ------------------------------------------------------------------
def _build_interaction_start_event(self, interaction_id: str) -> InteractionsAPIStreamingResponse:
event_type: Final = "interaction.start" if self._use_legacy else "interaction.created"
return InteractionsAPIStreamingResponse(
event_type=event_type,
event_type="interaction.created",
id=interaction_id,
object="interaction",
status="in_progress",
@ -95,13 +85,6 @@ class LiteLLMResponsesInteractionsStreamingIterator:
)
def _build_content_start_event(self, interaction_id: str) -> InteractionsAPIStreamingResponse:
if self._use_legacy:
return InteractionsAPIStreamingResponse(
event_type="content.start",
id=interaction_id,
object="content",
delta={"type": "text", "text": ""},
)
return InteractionsAPIStreamingResponse(
event_type="step.start",
index=0,
@ -109,13 +92,6 @@ class LiteLLMResponsesInteractionsStreamingIterator:
)
def _build_text_delta_event(self, interaction_id: str, delta_text: str) -> InteractionsAPIStreamingResponse:
if self._use_legacy:
return InteractionsAPIStreamingResponse(
event_type="content.delta",
id=interaction_id,
object="content",
delta={"type": "text", "text": delta_text},
)
return InteractionsAPIStreamingResponse(
event_type="step.delta",
index=0,
@ -123,28 +99,12 @@ class LiteLLMResponsesInteractionsStreamingIterator:
)
def _build_content_stop_event(self, interaction_id: str | None) -> InteractionsAPIStreamingResponse:
if self._use_legacy:
return InteractionsAPIStreamingResponse(
event_type="content.stop",
id=interaction_id,
object="content",
delta={"type": "text", "text": self.collected_text},
)
return InteractionsAPIStreamingResponse(
event_type="step.stop",
index=0,
)
def _build_completion_event(self, response_id: str) -> InteractionsAPIStreamingResponse:
if self._use_legacy:
return InteractionsAPIStreamingResponse(
event_type="interaction.complete",
id=response_id,
object="interaction",
status="completed",
model=self.model,
outputs=[{"type": "text", "text": self.collected_text}],
)
return InteractionsAPIStreamingResponse(
event_type="interaction.completed",
id=response_id,
@ -234,7 +194,7 @@ class LiteLLMResponsesInteractionsStreamingIterator:
"""
Build the events to flush when the upstream stream ends without a
ResponseCompletedEvent. Ensures consumers always observe a terminal
interaction.completed/interaction.complete carrying the full text.
interaction.completed carrying the full text.
"""
if self._sent_completion_event:
return []

View file

@ -3899,9 +3899,8 @@ class Logging(LiteLLMLoggingBaseClass):
) -> InteractionsAPIResponse | None:
"""
The Interactions API streaming iterator hands the terminal event to the
success handlers: the new schema (Api-Revision: 2026-05-20) emits
``interaction.completed`` carrying the full interaction object, the
legacy schema (2026-05-07) emits a chunk with ``status="completed"``
success handlers: ``interaction.completed`` may carry the full
interaction object, or the final chunk may carry ``status="completed"``
and usage on the chunk itself. Build the equivalent non-streaming
response so cost calculation and spend tracking see one shape.
"""

View file

@ -6,10 +6,7 @@ Per OpenAPI spec (https://ai.google.dev/static/api/interactions.openapi.json):
- Get: GET https://generativelanguage.googleapis.com/{api_version}/interactions/{interaction_id}
- Delete: DELETE https://generativelanguage.googleapis.com/{api_version}/interactions/{interaction_id}
Schema versioning:
- Default (Api-Revision: 2026-05-20): new `steps` schema.
- Legacy (Api-Revision: 2026-05-07): old `outputs` schema, controlled via
litellm.use_legacy_interactions_schema = True. Remove flag after June 8, 2026.
Requests use Api-Revision 2026-05-20 (`steps` schema).
"""
from typing import TYPE_CHECKING, Any, Final, Protocol, TypeAlias
@ -17,7 +14,6 @@ from typing import TYPE_CHECKING, Any, Final, Protocol, TypeAlias
import httpx
from typing_extensions import ReadOnly, TypedDict
import litellm
from litellm._logging import verbose_logger
from litellm.litellm_core_utils.core_helpers import process_response_headers
from litellm.litellm_core_utils.url_utils import encode_url_path_segment
@ -137,13 +133,7 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
if api_key:
headers["x-goog-api-key"] = api_key
# Inject the Api-Revision header to select the response schema.
# Default to the new `steps` schema unless the operator has opted out.
# Remove this conditional after June 8, 2026 and always use 2026-05-20.
if litellm.use_legacy_interactions_schema:
headers["Api-Revision"] = "2026-05-07"
else:
headers["Api-Revision"] = "2026-05-20"
headers["Api-Revision"] = "2026-05-20"
return headers
@ -180,17 +170,11 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
"""
Build request body per OpenAPI spec.
When on the new schema (use_legacy_interactions_schema=False, the default):
- ``response_mime_type`` is folded into ``response_format`` and stripped from
the body (the field was removed in Api-Revision 2026-05-20).
- ``generation_config.image_config`` is moved to a ``response_format`` entry
with ``"type": "image"`` (also removed from generation_config in 2026-05-20).
When on the legacy schema (use_legacy_interactions_schema=True):
- All fields are forwarded as-is.
"""
use_legacy: Final[bool] = litellm.use_legacy_interactions_schema
request_body: Final[dict[str, object]] = {}
# Model or Agent (one required)
@ -205,7 +189,6 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
if input is not None:
request_body["input"] = input
# Pass through optional params — legacy schema keeps all fields as-is.
optional_keys: Final = [
"tools",
"system_instruction",
@ -220,58 +203,51 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
if optional_params.get(key) is not None:
request_body[key] = optional_params[key]
if use_legacy:
# Legacy schema: forward response_mime_type and response_format as-is.
for key in ("response_format", "response_mime_type", "generation_config"):
if optional_params.get(key) is not None:
request_body[key] = optional_params[key]
else:
# New schema (Api-Revision: 2026-05-20):
# response_mime_type is removed — fold it into response_format.
response_format = optional_params.get("response_format")
response_mime_type: Final = optional_params.get("response_mime_type")
if (
response_mime_type
and not isinstance(response_format, list)
and (not isinstance(response_format, dict) or "mime_type" not in response_format)
):
# Wrap the legacy schema into the new polymorphic format.
new_rf: Final[dict[str, object]] = {
"type": "text",
"mime_type": response_mime_type,
}
if response_format is not None:
new_rf["schema"] = response_format
response_format = new_rf
# response_mime_type is removed — fold it into response_format.
response_format = optional_params.get("response_format")
response_mime_type: Final = optional_params.get("response_mime_type")
if (
response_mime_type
and not isinstance(response_format, list)
and (not isinstance(response_format, dict) or "mime_type" not in response_format)
):
# Wrap the legacy schema into the new polymorphic format.
new_rf: Final[dict[str, object]] = {
"type": "text",
"mime_type": response_mime_type,
}
if response_format is not None:
request_body["response_format"] = response_format
new_rf["schema"] = response_format
response_format = new_rf
if response_format is not None:
request_body["response_format"] = response_format
# image_config moves out of generation_config into response_format.
generation_config: dict[str, Any] | None = optional_params.get("generation_config")
if generation_config is not None:
image_config = None
if isinstance(generation_config, dict):
generation_config = dict(generation_config) # avoid mutating the caller's dict
image_config = generation_config.pop("image_config", None)
if not generation_config:
generation_config = None
# image_config moves out of generation_config into response_format.
generation_config: dict[str, Any] | None = optional_params.get("generation_config")
if generation_config is not None:
image_config = None
if isinstance(generation_config, dict):
generation_config = dict(generation_config) # avoid mutating the caller's dict
image_config = generation_config.pop("image_config", None)
if not generation_config:
generation_config = None
request_body["generation_config"] = generation_config
if generation_config is not None:
request_body["generation_config"] = generation_config
if image_config is not None:
# Move image_config to response_format with type=image.
image_rf: Final[_JsonObject] = {"type": "image", **image_config}
existing_rf: Final = request_body.get("response_format")
if existing_rf is None:
request_body["response_format"] = image_rf
elif isinstance(existing_rf, list):
request_body["response_format"] = [*existing_rf, image_rf]
else:
# Convert single entry to array for multimodal output.
request_body["response_format"] = [existing_rf, image_rf]
if image_config is not None:
# Move image_config to response_format with type=image.
image_rf: Final[_JsonObject] = {"type": "image", **image_config}
existing_rf: Final = request_body.get("response_format")
if existing_rf is None:
request_body["response_format"] = image_rf
elif isinstance(existing_rf, list):
request_body["response_format"] = [*existing_rf, image_rf]
else:
# Convert single entry to array for multimodal output.
request_body["response_format"] = [existing_rf, image_rf]
return request_body

View file

@ -6037,13 +6037,6 @@ class ProxyConfig:
health_check_interval = general_settings.get("health_check_interval", DEFAULT_HEALTH_CHECK_INTERVAL)
health_check_concurrency = general_settings.get("health_check_concurrency", None)
health_check_details = general_settings.get("health_check_details", True)
### INTERACTIONS API SCHEMA ###
_use_legacy_interactions_schema: Final = general_settings.get("use_legacy_interactions_schema")
if _use_legacy_interactions_schema is not None:
if isinstance(_use_legacy_interactions_schema, str):
litellm.use_legacy_interactions_schema = _use_legacy_interactions_schema.lower() == "true"
else:
litellm.use_legacy_interactions_schema = bool(_use_legacy_interactions_schema)
# Health-check-driven routing (opt-in, passes through to Router later)
_enable_hc_routing = general_settings.get("enable_health_check_routing", False)
_hc_staleness = general_settings.get("health_check_staleness_threshold", None)

View file

@ -2,7 +2,7 @@
Tests for Gemini Interactions API transformation.
Covers:
- validate_environment: x-goog-api-key header, Api-Revision schema selection
- validate_environment: x-goog-api-key header, Api-Revision header
- get_complete_url: API key excluded from URL
- get/delete/cancel interaction request URLs
- transform_request: response_mime_type coalescing, image_config migration
@ -13,7 +13,6 @@ from unittest.mock import MagicMock, patch
import pytest
import litellm
from litellm.interactions.litellm_responses_transformation.streaming_iterator import (
LiteLLMResponsesInteractionsStreamingIterator,
)
@ -83,22 +82,10 @@ class TestValidateEnvironment:
assert headers["X-Custom"] == "value"
assert headers["x-goog-api-key"] == "test-key"
def test_api_revision_new_schema_by_default(self, config, monkeypatch: pytest.MonkeyPatch):
# Default: use_legacy_interactions_schema=False → new steps schema
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", False)
headers = config.validate_environment(
headers={}, model="gemini-2.5-flash", litellm_params=None
)
def test_sets_api_revision_header(self, config):
headers = config.validate_environment(headers={}, model="gemini-2.5-flash", litellm_params=None)
assert headers["Api-Revision"] == "2026-05-20"
def test_api_revision_legacy_schema_when_flag_set(self, config, monkeypatch: pytest.MonkeyPatch):
# Flag on → legacy outputs schema until June 8, 2026
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", True)
headers = config.validate_environment(
headers={}, model="gemini-2.5-flash", litellm_params=None
)
assert headers["Api-Revision"] == "2026-05-07"
class TestGetCompleteUrl:
def test_url_excludes_api_key(self, config):
@ -158,9 +145,7 @@ class TestTransformRequest:
assert request_body["agent"] == "my-custom-slides-agent"
assert request_body["environment"] == "remote"
assert request_body["stream"] is False
assert request_body["input"] == [
{"type": "text", "text": "Create a 5-slide presentation about AI trends."}
]
assert request_body["input"] == [{"type": "text", "text": "Create a 5-slide presentation about AI trends."}]
def test_passes_environment_object_to_request_body(self, config):
environment_config = {
@ -221,24 +206,15 @@ class TestTransformRequest:
class TestStreamingIterator:
def _make_iterator(
self, use_legacy: bool = False
) -> LiteLLMResponsesInteractionsStreamingIterator:
original = litellm.use_legacy_interactions_schema
litellm.use_legacy_interactions_schema = use_legacy
try:
return LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=MagicMock(),
request_input="hi",
optional_params={},
)
finally:
litellm.use_legacy_interactions_schema = original
def _make_iterator(self) -> LiteLLMResponsesInteractionsStreamingIterator:
return LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=MagicMock(),
request_input="hi",
optional_params={},
)
def _make_text_delta(
self, text: str, item_id: str = "item_1"
) -> OutputTextDeltaEvent:
def _make_text_delta(self, text: str, item_id: str = "item_1") -> OutputTextDeltaEvent:
event = MagicMock(spec=OutputTextDeltaEvent)
event.delta = text
event.item_id = item_id
@ -251,58 +227,29 @@ class TestStreamingIterator:
def test_step_delta_includes_type_field(self):
"""step.delta events must carry delta.type='text' so the UI can display them."""
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
it.sent_interaction_start = True
it.sent_content_start = True
chunk = it._transform_responses_chunk_to_interactions_chunk(
self._make_text_delta("Hello")
)
chunk = it._transform_responses_chunk_to_interactions_chunk(self._make_text_delta("Hello"))
assert chunk is not None
assert chunk.event_type == "step.delta"
assert chunk.delta == {"type": "text", "text": "Hello"}
def test_content_delta_legacy_schema(self):
"""Legacy schema emits content.delta with type and text fields."""
it = self._make_iterator(use_legacy=True)
it.sent_interaction_start = True
it.sent_content_start = True
chunk = it._transform_responses_chunk_to_interactions_chunk(
self._make_text_delta("Hello")
)
assert chunk is not None
assert chunk.event_type == "content.delta"
assert chunk.delta == {"type": "text", "text": "Hello"}
def test_response_created_emits_interaction_created(self):
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
chunk = it._transform_responses_chunk_to_interactions_chunk(
self._make_response_created()
)
chunk = it._transform_responses_chunk_to_interactions_chunk(self._make_response_created())
assert chunk is not None
assert chunk.event_type == "interaction.created"
assert chunk.id == "resp_123"
assert it.sent_interaction_start is True
def test_response_created_emits_interaction_start_legacy(self):
it = self._make_iterator(use_legacy=True)
chunk = it._transform_responses_chunk_to_interactions_chunk(
self._make_response_created()
)
assert chunk is not None
assert chunk.event_type == "interaction.start"
assert chunk.id == "resp_123"
def test_text_delta_sequence_new_schema(self):
def test_text_delta_sequence(self):
"""First chunk yields created + step.start + step.delta; later chunks yield step.delta."""
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
first_events = it._events_for_chunk(self._make_text_delta("Hello"))
assert [e.event_type for e in first_events] == [
@ -322,24 +269,8 @@ class TestStreamingIterator:
assert [e.event_type for e in third_events] == ["step.delta"]
assert third_events[0].delta == {"type": "text", "text": "!"}
def test_text_delta_sequence_legacy_schema(self):
"""Legacy: first chunk yields interaction.start + content.start + content.delta."""
it = self._make_iterator(use_legacy=True)
first_events = it._events_for_chunk(self._make_text_delta("Hello"))
assert [e.event_type for e in first_events] == [
"interaction.start",
"content.start",
"content.delta",
]
assert first_events[-1].delta == {"type": "text", "text": "Hello"}
second_events = it._events_for_chunk(self._make_text_delta(" World"))
assert [e.event_type for e in second_events] == ["content.delta"]
assert second_events[0].delta == {"type": "text", "text": " World"}
def test_first_text_delta_without_item_id_uses_fallback_id(self):
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
event = self._make_text_delta("Hi")
event.item_id = None
@ -350,11 +281,9 @@ class TestStreamingIterator:
def test_first_text_delta_emits_text_via_compat_shim(self):
"""The legacy single-chunk shim must surface the synthetic events AND the delta."""
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
first = it._transform_responses_chunk_to_interactions_chunk(
self._make_text_delta("Hello")
)
first = it._transform_responses_chunk_to_interactions_chunk(self._make_text_delta("Hello"))
assert first is not None
assert first.event_type == "interaction.created"
@ -369,7 +298,7 @@ class TestStreamingIterator:
def test_response_created_then_text_delta_emits_step_start_and_delta(self):
"""Realistic flow: response.created arrives first, then text delta."""
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
first = it._events_for_chunk(self._make_response_created())
assert [e.event_type for e in first] == ["interaction.created"]
@ -380,7 +309,7 @@ class TestStreamingIterator:
def test_no_text_token_is_dropped_during_streaming(self):
"""Concatenated step.delta payloads must equal the upstream text."""
it = self._make_iterator(use_legacy=False)
it = self._make_iterator()
chunks = ["Hello", " ", "world", "!"]
emitted_text = ""
@ -401,17 +330,12 @@ class TestStreamingIterator:
sync_iter.__iter__ = lambda self: self
sync_iter.__next__ = MagicMock(side_effect=[text_event, StopIteration])
original = litellm.use_legacy_interactions_schema
litellm.use_legacy_interactions_schema = False
try:
it = LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=sync_iter,
request_input="hi",
optional_params={},
)
finally:
litellm.use_legacy_interactions_schema = original
it = LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=sync_iter,
request_input="hi",
optional_params={},
)
emitted: list = []
try:
@ -450,17 +374,12 @@ class TestStreamingIterator:
sync_iter.__iter__ = lambda self: self
sync_iter.__next__ = MagicMock(side_effect=[text_event, completed])
original = litellm.use_legacy_interactions_schema
litellm.use_legacy_interactions_schema = False
try:
it = LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=sync_iter,
request_input="hi",
optional_params={},
)
finally:
litellm.use_legacy_interactions_schema = original
it = LiteLLMResponsesInteractionsStreamingIterator(
model="gpt-5.4",
litellm_custom_stream_wrapper=sync_iter,
request_input="hi",
optional_params={},
)
emitted: list = []
try:
@ -506,9 +425,7 @@ class TestInteractionOperationUrls:
),
],
)
def test_url_excludes_key(
self, config, method_name, interaction_id, expected_suffix
):
def test_url_excludes_key(self, config, method_name, interaction_id, expected_suffix):
with patch(_PATCH_GET_API_KEY, return_value="secret-key"):
url, params = getattr(config, method_name)(
interaction_id=interaction_id,
@ -550,8 +467,7 @@ class TestInteractionOperationUrls:
class TestTransformRequestSchemaCoalescing:
"""Test new-schema request coalescing (Api-Revision: 2026-05-20)."""
def test_response_mime_type_folded_into_response_format(self, config, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", False)
def test_response_mime_type_folded_into_response_format(self, config):
body = config.transform_request(
model="gemini/gemini-2.5-flash",
agent=None,
@ -571,8 +487,7 @@ class TestTransformRequestSchemaCoalescing:
assert rf["mime_type"] == "application/json"
assert "schema" in rf
def test_image_config_moved_to_response_format(self, config, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", False)
def test_image_config_moved_to_response_format(self, config):
body = config.transform_request(
model="gemini/gemini-2.5-flash",
agent=None,
@ -594,9 +509,8 @@ class TestTransformRequestSchemaCoalescing:
assert rf["type"] == "image"
assert rf["aspect_ratio"] == "1:1"
def test_response_mime_type_skipped_when_response_format_is_list(self, config, monkeypatch: pytest.MonkeyPatch):
def test_response_mime_type_skipped_when_response_format_is_list(self, config):
"""Lists are already polymorphic; do not wrap them into schema."""
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", False)
rf_list = [
{"type": "text", "mime_type": "application/json"},
{"type": "image", "aspect_ratio": "1:1"},
@ -619,10 +533,8 @@ class TestTransformRequestSchemaCoalescing:
def test_image_config_appended_to_response_format_list_without_mutating_input(
self,
config,
monkeypatch: pytest.MonkeyPatch,
):
"""When response_format is already a list, image_config must not mutate optional_params."""
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", False)
text_rf = {"type": "text", "mime_type": "application/json"}
optional_params = {
"response_format": [text_rf],
@ -659,20 +571,3 @@ class TestTransformRequestSchemaCoalescing:
)
assert len(optional_params["response_format"]) == 1
assert body_retry["response_format"] == body["response_format"]
def test_legacy_schema_passes_fields_unchanged(self, config, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(litellm, "use_legacy_interactions_schema", True)
body = config.transform_request(
model="gemini/gemini-2.5-flash",
agent=None,
input="hello",
optional_params={
"response_mime_type": "application/json",
"generation_config": {"image_config": {"aspect_ratio": "16:9"}},
},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert body["response_mime_type"] == "application/json"
assert body["generation_config"]["image_config"]["aspect_ratio"] == "16:9"