test(e2e): stop the fallback tests flaking on gpt-5.5's reasoning budget

max_tokens=64 caps reasoning plus visible output on gpt-5.5, so the fallback
target can legitimately return finish_reason="length" with empty content.
litellm-e2e build 90 hit exactly that: the response cost of $0.002005 backs
out to 64 completion tokens at gpt-5.5's $3e-05/token, i.e. the whole budget
spent reasoning about "say hi" with none left to answer. The fallback itself
worked -- 200, served by gpt-5.5-2026-04-23, x-litellm-attempted-fallbacks
present -- so the only thing that failed was an assertion about OpenAI's token
budgeting rather than about routing.

Raise the reliability helper's budget to 512 and accept empty content only
when finish_reason is "length". Empty content under any other finish_reason
still fails, so the tests keep catching a fallback that returns nothing for a
reason we do control.

The relaxed assertion lives in the helper both reliability fallback tests
share, so test_timeout_routes_to_fallback is covered too; it has the same
shape and had not tripped yet.
This commit is contained in:
Yuneng Jiang 2026-08-29 16:08:29 -07:00
parent 3e2999f29f
commit 6e341b79d8
No known key found for this signature in database
3 changed files with 26 additions and 6 deletions

View file

@ -333,6 +333,7 @@ class OutMessage(BaseModel):
class ChatChoice(BaseModel):
message: OutMessage | None = None
finish_reason: str | None = None
class PromptTokensDetails(BaseModel):

View file

@ -57,7 +57,7 @@ def chat_override(
json=ReliabilityChatBody(
model=model,
messages=[ChatMessage(role="user", content=content)],
max_tokens=64,
max_tokens=512,
stream=stream,
router_settings_override=override,
cache=cache,
@ -77,3 +77,13 @@ def content_of(resp: StreamingResponse) -> str | None:
return None
message = parsed.choices[0].message
return message.content if message is not None else None
def finish_reason_of(resp: StreamingResponse) -> str | None:
try:
parsed = ChatResponse.model_validate_json(resp.body)
except ValidationError:
return None
if not parsed.choices:
return None
return parsed.choices[0].finish_reason

View file

@ -3,9 +3,11 @@ healthy one.
Each test registers a primary deployment that fails (an unreachable base URL, or
a 1ms deadline) and calls it with a `router_settings_override` mapping it to the
real `gpt-5.5`. The proof the fallback fired is twofold: the response is a real
completion from `gpt-5.5` (a non-empty content string), and the proxy reports at
least one attempted fallback in the x-litellm-attempted-fallbacks header.
real `gpt-5.5`. The proof the fallback fired is twofold: the response is a
completion from `gpt-5.5`, and the proxy reports at least one attempted fallback
in the x-litellm-attempted-fallbacks header. Empty content is accepted only for
`finish_reason == "length"`, since gpt-5.5 counts reasoning against max_tokens
and can consume the whole budget before emitting any text.
"""
from __future__ import annotations
@ -22,6 +24,7 @@ from reliability_support import (
content_of,
create_bad_base_deployment,
create_timeout_deployment,
finish_reason_of,
)
pytestmark = pytest.mark.e2e
@ -30,10 +33,16 @@ pytestmark = pytest.mark.e2e
def _assert_served_by_fallback(resp: StreamingResponse) -> None:
assert resp.status_code == 200, f"expected 200 after fallback, got {resp.status_code}: {resp.body[:300]}"
content = content_of(resp)
assert isinstance(content, str) and content, (
f"the gpt-5.5 fallback should have returned a real completion, got content {content!r} "
finish_reason = finish_reason_of(resp)
assert isinstance(content, str), (
f"the gpt-5.5 fallback should have returned a completion body, got content {content!r} "
f"(body={resp.body[:300]})"
)
assert content or finish_reason == "length", (
f"the gpt-5.5 fallback returned empty content with finish_reason={finish_reason!r}; only "
f"finish_reason='length' may be empty, since gpt-5.5 can spend the whole max_tokens "
f"budget on reasoning (body={resp.body[:300]})"
)
attempted = resp.headers.get("x-litellm-attempted-fallbacks")
assert attempted is not None, "response is missing the x-litellm-attempted-fallbacks header"
assert int(attempted) >= 1, f"x-litellm-attempted-fallbacks should be >= 1, got {attempted!r}"