Repository navigation
fix(query): keep the truncation retry within the model's output and context limits - #978
Merged
Merged
Conversation
…ontext limits When a reply hit the output limit, the query loop re-sent the same request once with max_tokens=64000. Models and gateways that cap output below that rejected the retry, and the turn ended with an error (#958). The retry still starts at 64,000, then is lowered to the model's known output ceiling (a catalog row's documented maximum, or settings.modelLimits.maxOutputTokens for an uncatalogued model), to CLAUDE_CODE_MAX_OUTPUT_TOKENS, and to the context window left after the truncated request's input. When that allows no more output than the truncated attempt had, the retry is skipped and the loop goes straight to the recovery lane, which keeps the partial reply. A model with no known limit still retries at 64,000. - models/configs: ModelConfig.max_output_tokens_ceiling, set on the legacy Claude 3 / 3.5 and GPT-4-era rows whose documented maximum is below 64K. Current rows leave it unset: their max_output_tokens is a first-attempt or advisory value, not a ceiling. - models/context: get_max_output_tokens_ceiling and get_env_max_output_tokens. The modelLimits lookup reads a non-mapping value as unset instead of raising (a JSON list made every Anthropic-wire request for an uncatalogued model fail), and matches exact keys that contain a colon, such as llama3.1:8b. - compact: summary requests ask for min(8192, the model's normal max_tokens), so a 4K model's summaries are no longer rejected. The catalog stays authoritative: modelLimits never changes the limits of a catalogued model. Supersedes #977. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #958. Supersedes #977.
Problem
When a reply is cut off at the output limit, the query loop retries the same request once with
max_tokens=64000. Models and gateways that cap output below that reject the retry, and the turn ends with an error instead of continuing.Fix
The retry still starts at 64,000, then is lowered to whichever of these is known:
ModelConfig.max_output_tokens_ceiling), orsettings.modelLimits.maxOutputTokensfor a model the catalog doesn't know;CLAUDE_CODE_MAX_OUTPUT_TOKENS;If the result allows no more output than the truncated attempt had (the
max_tokensit sent on the Anthropic wire, or the output it actually produced), the retry is skipped. The loop goes straight to the existing recovery lane, which keeps the partial reply and asks the model to resume. A model with no known limit still retries at 64,000.Also fixed:
modelLimits(a JSON list rather than an object) raised out of the limit lookup. On main that made every Anthropic-wire request for an uncatalogued model fail. It now reads as "nothing configured".modelLimitskeys containing a colon (llama3.1:8b) were skipped. They now match like any other key; host-qualified keys behave as before.The catalog stays authoritative, as the README says:
modelLimitsnever changes the limits of a catalogued model.Catalog ceilings
max_output_tokensis not a ceiling on most rows. On current Anthropic rows it is the first-attempt value (32K, below the real maximum, so the retry has room). On OpenAI-compatible rows it is advisory and only feeds the auto-compact reservation. Somax_output_tokens_ceilingis set only on the 11 legacy rows whose documented maximum is below 64K: Claude 3 and 3.5,gpt-4o,gpt-4o-mini,gpt-4-turbo,gpt-4andgpt-3.5-turbo.claude-opus-4-20250514is left unset because itsclaude-opus-4prefix also resolves Opus 4.5 and 4.6, which allow far more.Why not #977
max_tokens, so for an uncatalogued model capped at 4,096 the bounded retry was identical to the request that had just been cut off.claude-opus-4-8withmaxOutputTokens: 8192went 32,000 → 8,192 → 32,000), and broad prefix keys such as"claude"would have capped mainstream models.modelLimitsraised out of the query loop.Testing
tests/test_query_escalation_limits.py(22 tests) drives the realquery()loop through a realOpenAIProviderandAnthropicProvider, with only the network call mocked, and checks themax_tokenseach request sends. On main the tests for each bug above fail. The rest guard behaviour that must not change, such asclaude-opus-4-8still retrying 32,000 → 64,000.origin/maincheckout (test_ch04_api_round4transport lanes,TestStopHooks,test_advisor_effort_wiring,test_agent_loop_compat_model_error).origin/main.The code comments citing TS behaviour (
query.tsskipping the escalation whileCLAUDE_CODE_MAX_OUTPUT_TOKENSis set,compact.tstaking themin, the 1,000-token buffer inwithRetry.ts) weren't checked against a local copy of the TS source.Not covered
modelLimitsentry still retries at 64,000. If its endpoint rejects that with an error that doesn't mentionmax_tokens, the turn still ends. Falling back to the partial reply when the retry is rejected would close that.claude-opus-4-1still retries at 64,000 because it shares the Opus 4 catalog row.🤖 Generated with Claude Code