Skip to content

fix(anthropic): remap disabled thinking to between_tools on Sonnet 5.5 - #44299

Open
pcbeingused333 wants to merge 7 commits into
BerriAI:mainfrom
pcbeingused333:sonnet-5-5-between-tools-thinking-fallback
Open

pcbeingused333 wants to merge 7 commits into
BerriAI:mainfrom
pcbeingused333:sonnet-5-5-between-tools-thinking-fallback

Conversation

@pcbeingused333

@pcbeingused333 pcbeingused333 commented Oct 3, 2026 •

Copy link
Copy Markdown

TLDR

Problem this solves:

  • Sonnet 5.5 keeps full adaptive thinking on when a caller asks to disable it
  • thinking.type=disabled gets dropped entirely instead of mapped to the model's real off switch

How it solves it:

  • Remap disabled to between_tools, Sonnet 5.5's documented off-equivalent, at low/medium/high effort
  • Keep the existing drop-and-warn fallback at xhigh/max effort (where between_tools itself 400s) and for every other always-on-thinking family, whose own off-equivalent is not confirmed yet
  • With litellm.modify_params=True, stop the missing-thinking_blocks guard in the chat and Converse transformations from dropping disabled on tool-result turns. That guard runs before the remap, so it used to remove disabled first and the provider resumed adaptive thinking

Intentional product change: a caller asking for disabled thinking on Sonnet 5.5 at low/medium/high effort now gets between_tools instead of silent full adaptive thinking. This changes model behavior and cost for those requests, matching what disabled meant on every Claude model before Sonnet 5.5 removed it.

User Flow

Before: a developer running a latency-sensitive agent on Claude Sonnet 5.5 asks for thinking off and silently gets full adaptive thinking instead

  1. They call litellm.completion(model="anthropic/claude-sonnet-5-5", messages=[...], thinking={"type": "disabled"})
  2. LiteLLM drops the thinking field and forwards the request with none, logging a warning that thinking cannot be disabled for this model
  3. Anthropic runs full adaptive thinking at the configured effort, so the response is slower and costs more than planned, with nothing in the response itself showing why

After: the same call runs the model's real thinking-off mode

  1. They send the same litellm.completion(...) call with thinking={"type": "disabled"}
  2. LiteLLM forwards thinking={"type": "between_tools"} instead, logging that it made the substitution
  3. Anthropic responds without thinking before its answer, the same outcome disabled gives on every earlier Claude model

Relevant issues

Fixes #44298

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

I do not have Anthropic API credentials in this environment, so I could not run this against a live proxy hitting the real API as the template asks. The commands below call the actual AnthropicModelInfo.maybe_drop_disabled_thinking function directly, the same function the request path calls, against the two commits below, and show the real log line and the real optional_params["thinking"] value each one produces. No mocks: this is the production code path, just invoked without a network call after it since the model-info branch and the mutation it makes do not depend on one. A maintainer with API access can confirm the end-to-end behavior with:

curl -X POST http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-1234" \
  -d '{"model": "claude-sonnet-5-5", "messages": [{"role": "user", "content": "hi"}], "thinking": {"type": "disabled"}}'

and checking that the request LiteLLM forwards carries thinking: {"type": "between_tools"} and the response contains no up-front thinking block.

Before (22bd49e)

  1. python3 -c "from litellm.llms.anthropic.common_utils import AnthropicModelInfo; params = {'thinking': {'type': 'disabled'}, 'output_config': {'effort': 'medium'}}; AnthropicModelInfo.maybe_drop_disabled_thinking(model='claude-sonnet-5-5', optional_params=params, custom_llm_provider='anthropic'); print(params.get('thinking', '<absent>'))"
  2. Output: LiteLLM:WARNING: Dropping thinking={'type': 'disabled'} for model=claude-sonnet-5-5: thinking is always on for this model and cannot be disabled (the alternative is a provider 400). The model will still think adaptively, its response can contain thinking blocks, and those thinking tokens are billed as output tokens. then <absent>

After (2927cbd)

medium effort

  1. Same command as above, run on this branch
  2. Output: LiteLLM:WARNING: Replacing thinking={'type': 'disabled'} with thinking={'type': 'between_tools'} for model=claude-sonnet-5-5: this model 400s on disabled, and between_tools is its lowest thinking setting. The model will not think before responding; progress notes it writes between tool calls still come back as thinking blocks. then {'type': 'between_tools'}

xhigh effort

  1. Same command with "effort": "xhigh"
  2. Output: the same drop warning and <absent> as Before, since between_tools itself 400s at xhigh/max and adaptive thinking is the only off-equivalent at that effort

Type

🐛 Bug Fix
✅ Test

Caveats (if any)

Medium

  • No live-API proof: no Anthropic credentials in this environment, see Screenshots section
  • supports_between_tools_thinking is set only on Sonnet 5.5 cost-map entries. Opus 5.5, Fable 5(.1), and Mythos 5(.1) keep the drop-and-warn path because I have not found documentation confirming between_tools (or an equivalent) works on them

Low

  • With modify_params=True, a tool-result turn on a model that accepts disabled now forwards thinking={"type": "disabled"} instead of dropping it. Both mean thinking off, and disabled needs no thinking_blocks in history
  • between_tools also rejects display, budget_tokens, and block_binding on the thinking object per the migration guide, but the only shape this function ever matches is the bare {"type": "disabled"} the caller sent, so there is nothing to strip

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

maybe_drop_disabled_thinking dropped thinking={type: disabled} for every
always-on-thinking model, including Sonnet 5.5, which silently left thinking
on at whatever effort the caller set. Sonnet 5.5 has a real off-equivalent,
between_tools, valid at low/medium/high effort. Remap to that instead of
dropping, and keep the existing drop-and-warn path for xhigh/max effort
(where between_tools 400s) and for every other always-on-thinking family,
whose own off-equivalent is not confirmed yet.

Fixes BerriAI#44298
@CLAassistant

CLAassistant commented Oct 3, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Adds thinking mode remapping for one Anthropic model variant.

Fix the tool-continuation path before merging so requests to disable thinking remain effective after tool calls

Findings

  1. P1 Thinking turns back on ▶

Summary

This PR maps Sonnet 5.5 requests with thinking.type=disabled to between_tools, using a new model capability flag

  • Both model maps, Python types, and the JSON schema include the new capability
  • Tests cover supported effort levels and the existing fallback
  • The fix still misses tool-result turns when litellm.modify_params=True
  • pcbeingused333 explicitly identifies the changed Sonnet 5.5 behavior as intentional
  • pcbeingused333 explicitly retains adaptive thinking at xhigh/max and for other always-on families because their off setting is unsupported or unconfirmed
  • pcbeingused333 acknowledges missing live API proof because Anthropic credentials were unavailable

Reviews (1) · Last reviewed commit: "fix(anthropic): remap disabled thinking ..."

Comment thread litellm/llms/anthropic/common_utils.py Outdated
Comment on lines +771 to +775
if AnthropicModelInfo._supports_between_tools_thinking(
model, custom_llm_provider
) and not AnthropicModelInfo._effort_rejects_between_tools(optional_params):
litellm.verbose_logger.warning(REMAP_DISABLED_TO_BETWEEN_TOOLS_WARNING, model)
optional_params["thinking"] = {"type": "between_tools"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Thinking turns back on

With litellm.modify_params=True, a Sonnet 5.5 tool-result turn whose assistant history has no thinking_blocks loses thinking={"type":"disabled"} before this remap runs. Both chat and Converse remove the setting in their earlier history guard. The helper then returns without supplying between_tools, so the provider resumes adaptive thinking and the turn can take longer and cost more

Exempt disabled requests from those guards and add a regression test that sends a tool-result turn through the request transformation

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai Fixed in 601a409: both guards now skip disabled thinking, with regression tests through the chat and Converse request transformations

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed fixed in 601a4092d09a50202178786e806816ee7cbdeb55. Both request transformations now preserve the bare {"type": "disabled"} value through the missing-thinking-blocks guard, allowing the existing Sonnet 5.5 remap to {"type": "between_tools"}. The new chat and Converse regression tests exercise the tool-result turn with litellm.modify_params=True and assert the forwarded thinking mode. No further issue here.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 31 untouched benchmarks


Comparing pcbeingused333:sonnet-5-5-between-tools-thinking-fallback (c572b99) with main (ac98cab)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (102619d) during the generation of this report, so ac98cab was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

….py cost-map schema

test_aaamodel_prices_and_context_window_json_is_valid validates the cost map
against its own copy of the schema, separate from
model_prices_and_context_window.schema.json. Missed updating it in the
previous commit.
@krrish-berri-2

Copy link
Copy Markdown
Contributor

can you address the greptile feedback? @pcbeingused333

…locks guard

With modify_params=True, a tool-result turn whose assistant history has no
thinking_blocks dropped thinking={'type': 'disabled'} before
maybe_drop_disabled_thinking could remap it to between_tools on Sonnet 5.5,
so the provider fell back to adaptive thinking. Disabled thinking needs no
thinking_blocks, so both the chat and Converse guards now skip it
…tools-thinking-fallback

# Conflicts:
#	model_prices_and_context_window.schema.json
#	tests/unit/llms/anthropic/test_anthropic_common_utils.py
@pcbeingused333

Copy link
Copy Markdown
Author

@krrish-berri-2 addressed in 601a409: both the chat and Converse request transformations now keep the bare {"type": "disabled"} through the missing-thinking-blocks guard when litellm.modify_params=True, so the Sonnet 5.5 remap to between_tools still applies on tool-result turns. Added regression tests for both paths; Greptile confirmed the fix.

The two red Postgres jobs (proxy-behavior, roi-database) are only the Codecov upload step failing — the runner hit an SSL handshake error downloading the Codecov CLI. The tests themselves passed. Could you re-run those jobs when you get a chance?

…tools-thinking-fallback

# Conflicts:
#	litellm/model_prices_and_context_window_backup.json
#	model_prices_and_context_window.json
#	tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py
pcbeingused333 and others added 2 commits October 12, 2026 01:49
…e write

maybe_drop_disabled_thinking mutates optional_params by design (its
signature already carries mutable-ok); annotate the new store so the
type-discipline gate's LIT011 ceiling doesn't grow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bare dict narrowed from optional_params has unknown key/value types,
so .get() added a reportUnknownMemberType error past the basedpyright
ceiling. A membership check plus subscript reads the same value without
the member access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Sonnet 5.5 thinking.disabled silently drops to full adaptive thinking instead of between_tools

3 participants