Repository navigation
feat(integrations): support extended thinking on Claude and skip thought and empty parts - #1160
Conversation
0988b16 to
7031104
Compare
7031104 to
4e64228
Compare
|
Scored 2026-10-28 16:56 UTC at 8d65696, round 1. Earlier runs are in this comment's edit history. Foundry merge critic: APPROVE Add extended thinking support to AnthropicLlm and Claude and skip empty text parts and foreign thought signatures that the Anthropic API rejects. Rubric — the default is approve; each rule is a reason a human should read this change first. [no ] public_api — leaves package exports in integrations/src/index.ts and AnthropicLlm and Claude signatures unchanged; new helpers and union members in anthropic_utils.ts are additive Posted by Foundry's merge critic as evidence rather than a request, so nothing here is waiting on you. Advisory only: Foundry has given no approval. |
…ght and empty parts
Adds extended thinking support to `AnthropicLlm` and `Claude` in `@google/adk-integrations`. It also skips request parts that the Anthropic Messages API rejects. Before this change, a session that ran a Gemini turn and then switched to Claude failed on every later turn, because `partToMessageBlock` threw on thought parts.
### Files changed
5 files changed, 838 insertions(+), 21 deletions(-):
- `integrations/src/models/anthropic_utils.ts` (+158 / -9)
- `integrations/src/models/anthropic_llm.ts` (+6 / -3)
- `integrations/test/models/anthropic_utils_test.ts` (+433 / -7)
- `integrations/test/models/anthropic_llm_test.ts` (+185 / -0)
- `docs/guides/integrations/models/anthropic_llm/index.md` (+56 / -2)
The deletions are the replaced throw on thought parts, the rewritten test that expected that throw, the unsupported-block test fixture (it used `thinking`, which is now supported), the request assembly lines in `AnthropicLlm`, and the outdated Limitations bullets in the guide.
### Ported adk-python behavior
- `thinkingConfig.thinkingBudget` maps to the Anthropic `thinking` parameter: `0` gives `disabled`, a negative value gives `{type: 'adaptive', display: 'summarized'}`, a positive value gives `{type: 'enabled', budget_tokens}`, and an unset budget throws.
- `thinking` and `redacted_thinking` response blocks become `thought: true` parts, and these parts convert back to the same blocks on later turns.
- A part that carries only a `thoughtSignature` from another model is dropped with a warning. A turn left with no blocks is not sent.
- Empty text parts are skipped. A user turn left empty only by skipped empty text sends one `'.'` text block.
- Empty and whitespace-only response text blocks are skipped.
- `usage.output_tokens_details.thinking_tokens` is reported as `thoughtsTokenCount` and subtracted from `candidatesTokenCount`. `totalTokenCount` stays input plus output.
### Interface differences
| Member | adk-python | adk-js | Why |
| :--- | :--- | :--- | :--- |
| `thought_signature` / `thoughtSignature` | `bytes` (UTF-8 encoded) | `string` | `@google/genai` types `Part.thoughtSignature` as `string`, so the code stores Anthropic `signature` and `data` as-is. |
| `_is_empty_text_part` / `isEmptyTextPart` | Checks `model_fields_set` | Checks `text === ''`, no signature, and no content payload field | `Part` is a plain interface with no record of which fields are set. |
| `_build_anthropic_thinking_param` / `buildAnthropicThinkingParam` | Returns a dict or `None` | Returns an `@anthropic-ai/sdk` `ThinkingConfig*` union or `undefined` | Native SDK types. |
### Verification
- `npx vitest run --project unit:integrations`: 3 files and 172 tests passed (104 in `anthropic_utils_test.ts`, 66 in `anthropic_llm_test.ts`, 2 in `version_test.ts`).
- `npm run build`, `ts:check`, `ts:check:samples`, `lint`, `format:check`, `docs:check`, and `npx secretlint "**/*"`: all exited 0.
- `npm run test:coverage` on Linux: 6558 tests passed. Three files failed only because of the local machine: the Playwright browser is not installed, and the local npm wrapper writes notices to stderr.
- Mutation check: changing `display: 'summarized'` to `'omitted'` made the 2 adaptive thinking tests fail.
### What the reviewer must decide
- Is a single `'.'` text block the right way to keep a user turn that held only empty text parts? Without it, the conversation ends on an assistant turn, which Anthropic treats as prefill.
- Is it right to store Anthropic `thinking.signature` and `redacted_thinking.data` verbatim in `Part.thoughtSignature`?
- A `thinking` block with empty text but a signature comes back as `redacted_thinking` on the next turn. adk-python has the same gap. Should a follow-up change fix it?
Produced by Foundry (task 4d0ea73c-114a-4942-8df3-1f045a4f6a06). Reviewed by an automated
reviewer before upload; still needs human review.
4e64228 to
8d65696
Compare
Adds extended thinking support to
AnthropicLlmandClaudein@google/adk-integrations. It also skips request parts that the Anthropic Messages API rejects. Before this change, a session that ran a Gemini turn and then switched to Claude failed on every later turn, becausepartToMessageBlockthrew on thought parts.Files changed
5 files changed, 838 insertions(+), 21 deletions(-):
integrations/src/models/anthropic_utils.ts(+158 / -9)integrations/src/models/anthropic_llm.ts(+6 / -3)integrations/test/models/anthropic_utils_test.ts(+433 / -7)integrations/test/models/anthropic_llm_test.ts(+185 / -0)docs/guides/integrations/models/anthropic_llm/index.md(+56 / -2)The deletions are the replaced throw on thought parts, the rewritten test that expected that throw, the unsupported-block test fixture (it used
thinking, which is now supported), the request assembly lines inAnthropicLlm, and the outdated Limitations bullets in the guide.Ported adk-python behavior
thinkingConfig.thinkingBudgetmaps to the Anthropicthinkingparameter:0givesdisabled, a negative value gives{type: 'adaptive', display: 'summarized'}, a positive value gives{type: 'enabled', budget_tokens}, and an unset budget throws.thinkingandredacted_thinkingresponse blocks becomethought: trueparts, and these parts convert back to the same blocks on later turns.thoughtSignaturefrom another model is dropped with a warning. A turn left with no blocks is not sent.'.'text block.usage.output_tokens_details.thinking_tokensis reported asthoughtsTokenCountand subtracted fromcandidatesTokenCount.totalTokenCountstays input plus output.Interface differences
thought_signature/thoughtSignaturebytes(UTF-8 encoded)string@google/genaitypesPart.thoughtSignatureasstring, so the code stores Anthropicsignatureanddataas-is._is_empty_text_part/isEmptyTextPartmodel_fields_settext === '', no signature, and no content payload fieldPartis a plain interface with no record of which fields are set._build_anthropic_thinking_param/buildAnthropicThinkingParamNone@anthropic-ai/sdkThinkingConfig*union orundefinedVerification
npx vitest run --project unit:integrations: 3 files and 172 tests passed (104 inanthropic_utils_test.ts, 66 inanthropic_llm_test.ts, 2 inversion_test.ts).npm run build,ts:check,ts:check:samples,lint,format:check,docs:check, andnpx secretlint "**/*": all exited 0.npm run test:coverageon Linux: 6558 tests passed. Three files failed only because of the local machine: the Playwright browser is not installed, and the local npm wrapper writes notices to stderr.display: 'summarized'to'omitted'made the 2 adaptive thinking tests fail.What the reviewer must decide
'.'text block the right way to keep a user turn that held only empty text parts? Without it, the conversation ends on an assistant turn, which Anthropic treats as prefill.thinking.signatureandredacted_thinking.dataverbatim inPart.thoughtSignature?thinkingblock with empty text but a signature comes back asredacted_thinkingon the next turn. adk-python has the same gap. Should a follow-up change fix it?Produced by Foundry (task 4d0ea73c-114a-4942-8df3-1f045a4f6a06). Reviewed by an automated
reviewer before upload; still needs human review.