Skip to content

fix(anthropic): self-heal on missing thinking-signature errors from Bedrock/Vertex - #33719

Merged
krrish-berri-2 merged 4 commits into
litellm_internal_stagingfrom
litellm_anthropic_thinking_signature_detector
Jul 17, 2026
Merged

fix(anthropic): self-heal on missing thinking-signature errors from Bedrock/Vertex#33719
krrish-berri-2 merged 4 commits into
litellm_internal_stagingfrom
litellm_anthropic_thinking_signature_detector

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Resolves LIT-4514
Fixes #26005

Supersedes #26152, which only dropped the "block" keyword but still required "invalid", so it does not cover the Bedrock error body (see below)

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • 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

Screenshots / Proof of Fix

Root cause: is_anthropic_invalid_thinking_signature_error gates a safe self-heal on the /v1/messages path (strip thinking blocks, one bounded retry; the retry re-signs the request so Bedrock works too). It only returned True when the error contained all of "invalid", "signature", "thinking" and "block". When history from another provider (e.g. Fireworks) carries a thinking block with a missing/empty signature into Anthropic/Bedrock, the provider raises a Pydantic-style validation error that has neither "invalid" nor "block", so the self-heal never fired and the request 400'd

Bedrock raw body the user hit: {"message":"messages.2.content.0.thinking.signature.str: Input should be a valid string"}

Detector behavior on that exact Bedrock body, before vs after, run against the real function at each commit:

=== BEFORE (561b6796bc) ===
$ python -c "from litellm.llms.anthropic.common_utils import is_anthropic_invalid_thinking_signature_error as f; print(f('{\"message\":\"messages.2.content.0.thinking.signature.str: Input should be a valid string\"}'))"
False

=== AFTER (d2fbb966cc) ===
$ python -c "from litellm.llms.anthropic.common_utils import is_anthropic_invalid_thinking_signature_error as f; print(f('{\"message\":\"messages.2.content.0.thinking.signature.str: Input should be a valid string\"}'))"
True

End-to-end self-heal against the real Anthropic API (claude-opus-4-8, live call costing real $, captured at d2fbb96), sending an assistant turn whose thinking block has an empty signature followed by a new user question:

INITIAL_STATUS: 400
INITIAL_TEXT: {"type":"error","error":{"type":"invalid_request_error","message":"messages.1.content.0: Invalid `signature` in `thinking` block"},"request_id":"req_011Cd7ycHUthTYYsFRxgm24r"}

Anthropic /v1/messages: invalid thinking signature; stripping thinking blocks and retrying (attempt 2/2).

SUCCESS: {'model': 'claude-opus-4-8', 'id': 'msg_011Cd7ycLG5ZoBtQ65oLZNSm', 'type': 'message', 'role': 'assistant', 'content': [{'type': 'text', 'text': '6'}], 'stop_reason': 'end_turn', ...}

Note on scope of the live run: the exact Bedrock body could not be reproduced against a live endpoint because the available Bedrock test credentials lack bedrock:InvokeModel (403 before request validation). The Anthropic direct run above exercises the full strip-and-retry recovery pipeline that this detector gates; the before/after snippet proves the detector now matches the Bedrock/Vertex format that previously slipped through

Type

🐛 Bug Fix

Changes

is_anthropic_invalid_thinking_signature_error now matches when the lowercased error contains both "thinking" and "signature", dropping the "invalid" and "block" requirements. Both known non-Anthropic formats include those two tokens, and the gated recovery (strip thinking blocks, retry once) is safe and bounded, so a looser match is preferable to silently 400'ing valid conversations

-    return "invalid" in lower and "signature" in lower and "thinking" in lower and "block" in lower
+    return "thinking" in lower and "signature" in lower

Known formats now covered: the Bedrock {"message": "...thinking.signature.str: Input should be a valid string"} body, the Vertex AI messages.N.content.M.thinking.signature.str: Input should be a valid string form, and the classic Anthropic messages.N.content.M: Invalid \signature` in `thinking` block`

Regression tests were added in TestAnthropicThinkingSignatureSelfHeal covering the Bedrock and Vertex positives plus negatives that fail if either the "invalid" or "block" requirement is reintroduced or the return is flipped

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

Link to Devin session: https://app.devin.ai/sessions/4d342e8d4f9f4502a7cb9533b8a018a7
Requested by: @krrish-berri-2

…edrock/Vertex

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@krrish-berri-2 krrish-berri-2 self-assigned this Jul 17, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR widens the is_anthropic_invalid_thinking_signature_error detector from a four-keyword AND ("invalid" + "signature" + "thinking" + "block") to a two-keyword AND ("thinking" + "signature"), so Bedrock and Vertex validation errors that lack "invalid" and "block" now trigger the existing strip-and-retry self-heal instead of bubbling up as hard 400s.

  • The core one-line change in common_utils.py is correct and well-motivated; the recovery path (strip thinking blocks, retry once) is already bounded, so false-positive matches cause at most one extra HTTP round-trip.
  • Two new positive unit tests cover the Bedrock JSON body and the Vertex plain-text form; three new negative tests verify partial matches still return False.
  • No negative test exercises a message containing both words in an unrelated context, which would make the intended boundary explicit for future maintainers.

Confidence Score: 4/5

Safe to merge; the change is a one-line detector relaxation gated behind an existing HTTP 400 check, with a bounded single-retry recovery that is a no-op when thinking blocks are absent.

The logic change is correct and the trade-off is intentional. The only open question is whether a 400 error body that coincidentally contains both "thinking" and "signature" for an unrelated reason could trigger a spurious strip-and-retry — a real but low-probability scenario whose worst outcome is one extra HTTP call before the original error surfaces.

The single-line change in litellm/llms/anthropic/common_utils.py at line 919 warrants a second look — specifically the widened match condition and whether a boundary negative test should be added.

Important Files Changed

Filename Overview
litellm/llms/anthropic/common_utils.py Loosened is_anthropic_invalid_thinking_signature_error to require only "thinking" + "signature" instead of all four keywords; correctly fixes Bedrock/Vertex validation-error formats, with a deliberate trade-off of a slightly wider match.
tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py Added two positive unit tests for Bedrock and Vertex error formats, and three additional negative tests; all are pure mock/unit tests with no real network calls.

Reviews (1): Last reviewed commit: "fix(anthropic): self-heal on missing thi..." | Re-trigger Greptile

Comment thread litellm/llms/anthropic/common_utils.py Outdated
return False
lower = error_text.lower()
return "invalid" in lower and "signature" in lower and "thinking" in lower and "block" in lower
return "thinking" in lower and "signature" in lower

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.

P2 Wider match could trigger spurious strip-and-retry on unrelated 400s

Any 400 response body that contains both "thinking" and "signature" will now trigger the self-heal path — stripping thinking blocks from the request and issuing one extra HTTP call — even if the root cause is unrelated (e.g. a hypothetical error like "signature validation for thinking_budget parameter invalid"). The recovery is bounded to one retry and is otherwise safe, but it can mask the real error for a full round-trip. Adding a negative test for a message that contains both words in an unrelated context would make the intended boundary explicit and guard against accidental scope creep in future edits.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

krrish-berri and others added 2 commits July 17, 2026 17:15
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codspeed-hq

codspeed-hq Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_anthropic_thinking_signature_detector (b20023e) with litellm_internal_staging (a7d01cb)

Open in CodSpeed

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@krrish-berri-2
krrish-berri-2 enabled auto-merge (squash) July 17, 2026 18:15
@krrish-berri-2
krrish-berri-2 merged commit e59add1 into litellm_internal_staging Jul 17, 2026
72 of 74 checks passed
@krrish-berri-2
krrish-berri-2 deleted the litellm_anthropic_thinking_signature_detector branch July 17, 2026 18:18
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]: complexity router not working for claude code after auto model switching from GLM-5 to Anthropic models

3 participants