Repository navigation
fix(anthropic): return max_tokens stop_reason instead of 400 for tiny max_tokens on GPT-5.x - #35063
devin-ai-integration[bot] wants to merge 3 commits into
Conversation
… max_tokens on reasoning models
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
Greptile SummaryThe PR narrowly translates provider output-limit errors in the Anthropic Messages-to-completions adapter into successful
Confidence Score: 5/5The PR appears safe to merge, with the targeted error translation preserving unrelated failures and using the established Anthropic response adapters. The matched provider error is converted into the normalized length finish reason for both synchronous and asynchronous paths, while the streaming iterator supports the required protocols and unrelated bad requests continue to propagate.
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/experimental_pass_through/adapters/handler.py | Adds narrowly matched output-limit error translation into a synthetic length-finished response for sync, async, and streaming paths; no actionable defect was established. |
| tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_max_tokens_truncation.py | Adds isolated mocked regression tests covering async and sync responses, streaming SSE behavior, and unrelated-error propagation. |
Reviews (1): Last reviewed commit: "refactor(anthropic): drop getattr and tr..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Opened #36859, which widens this same mapping down to the OpenAI and Azure chat request helpers, so direct /v1/chat/completions callers and streaming are covered too. |
|
Yours is the more general fix and no files overlap. Want me to keep this as a backstop for other providers, or close it? |
|
Closing as stale: no activity in the last 30 days. Feel free to reopen if this is still relevant. |
TLDR
Problem this solves:
/modelprobe (max_tokens=1) 400s on GPT-5.xHow it solves it:
stop_reason="max_tokens"User Flow
Before: a developer running Claude Code against LiteLLM as an Anthropic gateway cannot switch to a GPT-5.x model, because the client's availability probe comes back as an error
ANTHROPIC_BASE_URL=http://litellm-domain:4000and run/model gpt-5.2{"model":"gpt-5.2","max_tokens":1,"messages":[{"role":"user","content":"hello"}]}litellm.BadRequestError: OpenAIException - Could not finish the message because max_tokens or model output limit was reached. Please try again with higher max_tokens."max_tokens": 16returns 200 with real content and"stop_reason": "end_turn", so the model and the key are healthyAfter: the same probe comes back as an ordinary truncated turn, and the model switch goes through
/model gpt-5.2against the same gateway"max_tokens": 1"stop_reason": "max_tokens", an empty text block, and zerooutput_tokens"stream": truereturns a well-formed SSE lifecycle ending in"stop_reason": "max_tokens"instead of an error event"max_tokens": 16and above behave exactly as before, returning real content and"stop_reason": "end_turn"temperature, still comes back as a 400 with the provider's messageRelevant issues
Fixes #35061
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Live proxy hitting the real OpenAI API (GPT-5.x), no mocks. Config routes the Anthropic
/v1/messagespath through chat/completions (use_chat_completions_url_for_anthropic_messages: true), which is the path issue #35061 hits and the one used for Azure GPT-5.x and other OpenAI-compatible reasoning backendsProxy started with
Before the fix (base
c274cf321c), the Claude Code probe returns 400 verbatim from the issueAfter the fix (
fbbebf5044), the same probe succeeds withstop_reason="max_tokens"Streaming (
fbbebf5044) emits a well-formed Anthropic SSE lifecycle ending instop_reason: max_tokensControl (
fbbebf5044): a normal budget is untouched and still finishes with real content andstop_reason="end_turn"The merge commit
75ee8d45dcthat brought the branch up to current staging did not change any of this behavior; it only moved the guard into two helpers so the call sites keep staging's single-assignmentcompletion_response: Final = ...shape. The regression suite and the 596 tests undertests/test_litellm/llms/anthropic/experimental_pass_through/pass at that commitType
🐛 Bug Fix
Changes
Claude Code sends a
max_tokens=1probe when switching models with/model. On GPT-5.x that budget is too small for the model to finish even one token, so OpenAI rejects the request with a 400 ("Could not finish the message because max_tokens or model output limit was reached"). The Anthropic/v1/messageschat/completions translation path inLiteLLMMessagesToCompletionTransformationHandlersurfaced that verbatim as aBadRequestError, so Claude Code concluded the model was unavailable. Anthropic itself answers the same probe with HTTP 200 andstop_reason="max_tokens", so the gateway should tooBoth the async and sync handlers now go through a wrapper around the completion call that catches that specific provider 400 (identified by the output-limit marker in the message). When it matches, it synthesizes an empty
finish_reason="length"ModelResponseand feeds it through the existing translation path, which already mapslengthto Anthropicmax_tokens; for streaming it wraps that response inMockResponseIteratorso the same SSE adapter produces the normal message lifecycle. Any otherBadRequestErrorre-raises unchanged, so unrelated failures keep their current behaviorThe sync helper is the same shape over
litellm.completion, and each call site stays a singlecompletion_response: Final = ...assignmentRegression coverage lives in
tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_max_tokens_truncation.pyand asserts the async/sync non-streaming responses carrystop_reason="max_tokens", the streaming path emits themax_tokensSSE, and an unrelatedBadRequestErrorstill propagatesCaveats (if any)
choices=[...]needsmutable-ok:ModelResponse.__init__ignores non-list choicesFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/a1b43c1b83a14a26801c3dcb35b11959