Repository navigation
Conversation
Claude.anthropicContentBlockToPart converted only text and tool_use
blocks and threw UnsupportedOperationException("Not supported yet.")
for any other block. The Claude 5 models use adaptive thinking when the
request has no thinking field, which ADK Java never sets, so their
responses can contain thinking blocks without being asked, and each
such response ended the agent run.
Convert the blocks the same way as ADK Python:
- thinking -> Part(text, thought=true, thoughtSignature=signature)
- redacted_thinking -> Part(thought=true, thoughtSignature=data)
partToAnthropicMessageBlock sends them back unchanged, which Anthropic
requires within a tool-use turn: a thought with a signature becomes a
thinking block when it has text and a redacted_thinking block when it
has none. Only signatures stored this way, as UTF-8 text, are sent
back. A binary signature that another model such as Gemini left on a
thought is not Claude's, and Anthropic rejects signatures it cannot
decode.
Two deliberate differences from ADK Python:
- A thought with empty text (display "omitted", the Claude 5 default)
goes back as the thinking block it came from. ADK Python treats the
empty text as absent and sends a redacted_thinking block whose data
is the signature.
- Any other thought is never sent as a thinking block. It is still
sent as text, as before, or dropped if its text is empty, as in ADK
Python, because Anthropic rejects empty text blocks. ADK Python
sends an unsigned thought with text as a thinking block with an
empty signature.
hemasekhar-p
force-pushed
the
fix/claude-thinking-blocks
branch
from
October 8, 2026 10:54
105f79e to
e47cf7c
Compare
Contributor
|
Hi @innoprej, We appreciate your contribution and the effort you put into this pull request. It is currently under review by our team. We will update you if any additional details are needed. |
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.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
Claude.anthropicContentBlockToPartconverts onlytextandtool_useblocks and throwsUnsupportedOperationException("Not supported yet.")for any other block. The Claude 5 models use adaptive thinking when the request has nothinkingfield, which ADK Java never sets, so a response can contain athinkingblock without being asked, and the agent run ends with that exception. The request side has the matching gap:partToAnthropicMessageBlocksends a thoughtPartwith text as a plain text block, dropping its signature, and throws for one that has only a signature.Solution:
Use the same mapping as ADK Python (google/adk-python@16952bd39,
src/google/adk/models/anthropic_llm.py):thinking→Part(text, thought=true, thoughtSignature=signature).redacted_thinking→Part(thought=true, thoughtSignature=data).thinkingblock when it has text and aredacted_thinkingblock when it has none. A tool-use turn therefore sends the blocks back unchanged and in order, which the Anthropic thinking docs ("Preserving thinking blocks") require.Two deliberate differences from ADK Python, each marked with a one-line comment:
display: "omitted", the Claude 5 default, returns) goes back as thethinkingblock it came from. ADK Python'sif part.thought and part.text:treats the empty text as absent and sends aredacted_thinkingblock whosedatais the signature. In Java,Part.text()is anOptional, so empty and absent text can be told apart.thinkingblock. It is still sent as text, as before, or dropped if its text is empty, as ADK Python's_is_empty_text_partdoes (google/adk-python@6e8546c88), because Anthropic rejects empty text blocks. ADK Python sends an unsigned thought with text as athinkingblock withsignature="".Testing Plan
Unit Tests:
./mvnw -pl core test -Dtest=ClaudeTest: 12 tests, 0 failures (Microsoft Build of OpenJDK 17.0.19, Windows 11). The four new tests go throughgenerateContentwith a mockedAnthropicClient:maingenerateContent_thinkingBlocks_becomeThoughtPartsUnsupportedOperationExceptiongenerateContent_toolTurn_sendsThinkingBlocksBackUnchanged:[thinking, redacted_thinking, text, thinking, tool_use], then the next requestUnsupportedOperationExceptiongenerateContent_emptyThoughtWithoutSignature_isNotSentgenerateContent_thoughtWithBinarySignature_isSentAsTextmainsends every thought with text as text)Partial implementations fail them too: ignoring
redacted_thinkingfails the first two; sending a zero-length signature fails the third; ADK Python's "text is non-empty" rule, or sending the thinking text back empty, fails the round-trip test; and dropping the UTF-8 check fails the fourth.Full
./mvnw -pl core test: 2124 run, 0 failures (24 skipped).Manual End-to-End (E2E) Tests:
Not run against the live API. With a mocked
AnthropicClient, anLlmAgentwith aFunctionToolrun throughInMemoryRunnerreceives[thinking, tool_use]and then text: the second request sends[thinking (same signature), tool_use], and the run ends with the text.Checklist
Additional context
Parts produced by another model in the same agent's history (the question in Claude model throws UnsupportedOperationException("Not supported yet.") on thinking blocks from Claude 5 models #1630): with this change they go out as text, as before, or are dropped if empty, which matches the preserved thinking FAQ ("Send the other model's output astextandtool_usecontent"). If you would rather drop them, it is a small change.system,toolsand the earlier messages are unchanged, and for accounts created on or after 2026-08-31 the API returns a 400 when they change. ADK rebuilds the system instruction on every request (for example{state}placeholders), andContentskeeps every earlier turn's signed thoughts, so once the instruction changes, later requests in that session fail. Sending back only the current turn's thinking blocks would make this much rarer, andthinking.block_binding.prefix_mismatch_behavior: "drop_block"(beta headerthinking-binding-controls-2026-08-01) makes the API drop such blocks instead. Both change behavior beyond this fix, so I left them for a follow-up.thinking,effortanddisplayrequest settings, themaxTokensdefault, and streaming (feat: implement streaming support for Claude model #1221, which also needs thinking deltas and signatures). A non-thoughtPartthat carries only a signature, which Gemini leaves in the history, still throws inpartToAnthropicMessageBlock; ADK Python drops it since google/adk-python@a5d6238cb, and I can send that as a separate change.