fix(interactions): raise on a non-2xx Gemini interaction fetch

AsyncHTTPHandler.get never raises for status and the Gemini GET transform
only raised when the body was not JSON, so a 500 or 404 carrying Gemini's
JSON error body parsed as an interaction with no status. A delete on a
replica other than the creator then claimed the settlement as released and
forwarded the delete instead of failing closed, and the bill was lost. The
transform now raises GeminiError with the vendor's status, as the delete
transform already does; the in-process poll already retries a fetch that
raises
This commit is contained in:
mateo-berri 2026-10-02 18:03:37 -07:00
parent 6ce60b9492
commit 44c3026aeb
2 changed files with 34 additions and 1 deletions

View file

@ -313,6 +313,12 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> InteractionsAPIResponse:
if not 200 <= raw_response.status_code < 300:
raise GeminiError(
message=raw_response.text,
status_code=raw_response.status_code,
headers=dict(raw_response.headers),
)
try:
raw_json: Final = _interaction_body(raw_response)
except Exception:

View file

@ -10,12 +10,13 @@ Covers:
from unittest.mock import MagicMock, patch
import httpx
import pytest
from litellm.interactions.litellm_responses_transformation.streaming_iterator import (
LiteLLMResponsesInteractionsStreamingIterator,
)
from litellm.llms.gemini.common_utils import GeminiError
from litellm.llms.gemini.interactions.transformation import (
GoogleAIStudioInteractionsConfig,
)
@ -464,6 +465,32 @@ class TestInteractionOperationUrls:
)
class TestGetInteractionResponse:
@pytest.mark.parametrize("status_code", [404, 500])
def test_non_2xx_raises_even_when_the_error_body_is_json(
self, config: GoogleAIStudioInteractionsConfig, status_code: int
) -> None:
raw_response = httpx.Response(
status_code,
json={"error": {"code": status_code, "message": "boom", "status": "INTERNAL"}},
request=httpx.Request("GET", "https://generativelanguage.googleapis.com/v1beta/interactions/x"),
)
with pytest.raises(GeminiError) as raised:
config.transform_get_interaction_response(raw_response=raw_response, logging_obj=MagicMock())
assert raised.value.status_code == status_code
assert "boom" in str(raised.value)
def test_2xx_parses_the_interaction(self, config: GoogleAIStudioInteractionsConfig) -> None:
raw_response = httpx.Response(
200,
json={"id": "interaction-1", "object": "interaction", "status": "completed", "steps": []},
request=httpx.Request("GET", "https://generativelanguage.googleapis.com/v1beta/interactions/x"),
)
response = config.transform_get_interaction_response(raw_response=raw_response, logging_obj=MagicMock())
assert response.id == "interaction-1"
assert response.status == "completed"
class TestTransformRequestSchemaCoalescing:
"""Test new-schema request coalescing (Api-Revision: 2026-05-20)."""