Skip to content

fix(integrations): honor maxOutputTokens, set finishReason and sanitize tool ids on Claude - #1139

Open
ScottMansfield wants to merge 1 commit into
mainfrom
foundry/9178aa4d-dca7-42be-89b2-cf489f29b54d
Open

ScottMansfield wants to merge 1 commit into
mainfrom
foundry/9178aa4d-dca7-42be-89b2-cf489f29b54d

Conversation

@ScottMansfield

Copy link
Copy Markdown
Member

Summary

9 files changed, 1501 insertions(+), 40 deletions(-) across @google/adk-integrations, @google/adk, and docs/guides/integrations/models/anthropic_llm/index.md.

Problem

Claude ignores maxOutputTokens on LlmRequest.config and sends a fixed MAX_TOKEN (1024) to messages.create. messageToGenerateContentResponse leaves finishReason unset on LlmResponse instead of mapping message.stop_reason. ContentRequestProcessor strips adk-* function call IDs on Claude turns, and partToMessageBlock forwards missing or invalid tool IDs unchanged.

Solution

Add maxTokens (default 8192) to ClaudeParams and Claude, and prefer llmRequest.config?.maxOutputTokens over this.maxTokens. Map message.stop_reason through toGoogleGenaiFinishReason and set finishReason on LlmResponse in messageToGenerateContentResponse. Preserve function call IDs in getContents and getCurrentTurnContents for Claude models, and sanitize tool IDs with ToolUseIdSanitizer.

Ported behaviors from adk-python (v1.0.0 to v2.13.0)

  • Configurable maxTokens on ClaudeParams and Claude (default 8192), overridden per request by llmRequest.config?.maxOutputTokens, while keeping MAX_TOKEN = 1024 exported with @deprecated.
  • toGoogleGenaiFinishReason mapping all seven Anthropic.StopReason values (end_turn, stop_sequence, tool_use, pause_turn, max_tokens, model_context_window_exceeded, refusal) to FinishReason and setting finishReason on LlmResponse.
  • preserveFunctionCallIds in getContents and getCurrentTurnContents (enabled by ContentRequestProcessor for Claude models) and request-scoped ToolUseIdSanitizer in partToMessageBlock and contentToMessageParam.
Member adk-python adk-js Why
Default max tokens AnthropicLlm.max_tokens: int = 8192 DEFAULT_MAX_TOKENS = 8192, Claude.maxTokens = 8192, MAX_TOKEN = 1024 (@deprecated) Raises default output limit to 8192 while preserving the exported MAX_TOKEN symbol for existing callers
Per-request max tokens llm_request.config.max_output_tokens or self.max_tokens llmRequest.config?.maxOutputTokens ?? this.maxTokens Uses nullish coalescing so an explicit value overrides the instance default
Stop reason mapping to_google_genai_finish_reason(anthropic_stop_reason) toGoogleGenaiFinishReason(anthropicStopReason) backed by STOP_REASON_TO_FINISH_REASON satisfies Record<Anthropic.StopReason, FinishReason> Enforces compile-time exhaustiveness across all @anthropic-ai/sdk StopReason values
Tool-use ID sanitizer _ToolUseIdSanitizer class in models/anthropic_llm.py ToolUseIdSanitizer class in integrations/src/models/anthropic_utils.ts Pairs invalid, empty, or missing functionCall.id and functionResponse.id values with toolu_fallback_<n> IDs across a request
Preserving adk-* IDs _preserve_function_call_ids in contents.py preserveFunctionCallIds parameter on getContents and getCurrentTurnContents in content_processor_utils.ts Keeps client-generated adk-* IDs when ContentRequestProcessor detects a Claude model name or instance

What the reviewer must decide

  • Whether keeping MAX_TOKEN = 1024 as a @deprecated export alongside DEFAULT_MAX_TOKENS = 8192 is the right compatibility tradeoff for @google/adk-integrations.
  • Whether exporting ToolUseIdSanitizer from integrations/src/models/anthropic_utils.ts (and re-exporting through @google/adk-integrations) is desired for callers of contentToMessageParam and partToMessageBlock.

Testing Done

  • npm run build (built @google/adk, @google/adk-devtools, and @google/adk-integrations).
  • npx vitest run --project unit:integrations (3 test files, 112 tests passed).
  • npx vitest run --project unit:core core/test/agents/processors/content_processor_utils_test.ts core/test/agents/processors/content_request_processor_test.ts (2 test files, 65 tests passed).
  • npm run ts:check and npm run ts:check:samples (passed with 0 errors).
  • npm run lint, npm run format:check, npm run docs:check, and npx secretlint "**/*" (all passed).

Produced by Foundry (task 9178aa4d-dca7-42be-89b2-cf489f29b54d). Reviewed by an automated
reviewer before upload; still needs human review.

…ze tool ids on Claude

## Summary

9 files changed, 1501 insertions(+), 40 deletions(-) across `@google/adk-integrations`, `@google/adk`, and `docs/guides/integrations/models/anthropic_llm/index.md`.

### Problem

`Claude` ignores `maxOutputTokens` on `LlmRequest.config` and sends a fixed `MAX_TOKEN` (`1024`) to `messages.create`. `messageToGenerateContentResponse` leaves `finishReason` unset on `LlmResponse` instead of mapping `message.stop_reason`. `ContentRequestProcessor` strips `adk-*` function call IDs on Claude turns, and `partToMessageBlock` forwards missing or invalid tool IDs unchanged.

### Solution

Add `maxTokens` (default `8192`) to `ClaudeParams` and `Claude`, and prefer `llmRequest.config?.maxOutputTokens` over `this.maxTokens`. Map `message.stop_reason` through `toGoogleGenaiFinishReason` and set `finishReason` on `LlmResponse` in `messageToGenerateContentResponse`. Preserve function call IDs in `getContents` and `getCurrentTurnContents` for Claude models, and sanitize tool IDs with `ToolUseIdSanitizer`.

### Ported behaviors from `adk-python` (`v1.0.0` to `v2.13.0`)

- Configurable `maxTokens` on `ClaudeParams` and `Claude` (default `8192`), overridden per request by `llmRequest.config?.maxOutputTokens`, while keeping `MAX_TOKEN = 1024` exported with `@deprecated`.
- `toGoogleGenaiFinishReason` mapping all seven `Anthropic.StopReason` values (`end_turn`, `stop_sequence`, `tool_use`, `pause_turn`, `max_tokens`, `model_context_window_exceeded`, `refusal`) to `FinishReason` and setting `finishReason` on `LlmResponse`.
- `preserveFunctionCallIds` in `getContents` and `getCurrentTurnContents` (enabled by `ContentRequestProcessor` for Claude models) and request-scoped `ToolUseIdSanitizer` in `partToMessageBlock` and `contentToMessageParam`.

| Member | adk-python | adk-js | Why |
| :--- | :--- | :--- | :--- |
| Default max tokens | `AnthropicLlm.max_tokens: int = 8192` | `DEFAULT_MAX_TOKENS = 8192`, `Claude.maxTokens = 8192`, `MAX_TOKEN = 1024` (`@deprecated`) | Raises default output limit to 8192 while preserving the exported `MAX_TOKEN` symbol for existing callers |
| Per-request max tokens | `llm_request.config.max_output_tokens or self.max_tokens` | `llmRequest.config?.maxOutputTokens ?? this.maxTokens` | Uses nullish coalescing so an explicit value overrides the instance default |
| Stop reason mapping | `to_google_genai_finish_reason(anthropic_stop_reason)` | `toGoogleGenaiFinishReason(anthropicStopReason)` backed by `STOP_REASON_TO_FINISH_REASON satisfies Record<Anthropic.StopReason, FinishReason>` | Enforces compile-time exhaustiveness across all `@anthropic-ai/sdk` `StopReason` values |
| Tool-use ID sanitizer | `_ToolUseIdSanitizer` class in `models/anthropic_llm.py` | `ToolUseIdSanitizer` class in `integrations/src/models/anthropic_utils.ts` | Pairs invalid, empty, or missing `functionCall.id` and `functionResponse.id` values with `toolu_fallback_<n>` IDs across a request |
| Preserving `adk-*` IDs | `_preserve_function_call_ids` in `contents.py` | `preserveFunctionCallIds` parameter on `getContents` and `getCurrentTurnContents` in `content_processor_utils.ts` | Keeps client-generated `adk-*` IDs when `ContentRequestProcessor` detects a Claude model name or instance |

*What the reviewer must decide*

- Whether keeping `MAX_TOKEN = 1024` as a `@deprecated` export alongside `DEFAULT_MAX_TOKENS = 8192` is the right compatibility tradeoff for `@google/adk-integrations`.
- Whether exporting `ToolUseIdSanitizer` from `integrations/src/models/anthropic_utils.ts` (and re-exporting through `@google/adk-integrations`) is desired for callers of `contentToMessageParam` and `partToMessageBlock`.

## Testing Done

- `npm run build` (built `@google/adk`, `@google/adk-devtools`, and `@google/adk-integrations`).
- `npx vitest run --project unit:integrations` (3 test files, 112 tests passed).
- `npx vitest run --project unit:core core/test/agents/processors/content_processor_utils_test.ts core/test/agents/processors/content_request_processor_test.ts` (2 test files, 65 tests passed).
- `npm run ts:check` and `npm run ts:check:samples` (passed with 0 errors).
- `npm run lint`, `npm run format:check`, `npm run docs:check`, and `npx secretlint "**/*"` (all passed).

Produced by Foundry (task 9178aa4d-dca7-42be-89b2-cf489f29b54d). Reviewed by an automated
reviewer before upload; still needs human review.
@ScottMansfield ScottMansfield added foundry parity Requested by the Foundry task that opened this pull request integrations Requested by the Foundry task that opened this pull request labels Oct 23, 2026
@ScottMansfield ScottMansfield added the claude Requested by the Foundry task that opened this pull request label Oct 23, 2026
@ScottMansfield

Copy link
Copy Markdown
Member Author

Scored 2026-10-23 22:27 UTC at 08ce29f, round 1. Earlier runs are in this comment's edit history.

Foundry merge critic: APPROVE

This change updates the Claude integration to configure output token limits, map finish reasons, and preserve and sanitize tool-use identifiers.

Rubric — the default is approve; each rule is a reason a human should read this change first.

[no ] public_api — adds optional maxTokens to ClaudeParams and optional parameters to internal helpers without breaking exported signatures
[no ] weakened_guarantee — tightens tool-use identifier validation with ToolUseIdSanitizer and adds exhaustive compile-time stop-reason mapping
[no ] sensitive_surface — touches only model request formatting, stop-reason mapping, and content processor identifier filtering
[no ] release_surface — modifies only source, test, and documentation files without changing package manifests or workflows
[no ] test_removed — adds unit tests for token limits, finish reasons, and tool-use identifiers while updating existing expectations for the new defaults
[no ] inexplicable — all edits in core, integrations, tests, and documentation directly implement the three Claude model behaviors

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.

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

Labels

claude Requested by the Foundry task that opened this pull request foundry integrations Requested by the Foundry task that opened this pull request parity Requested by the Foundry task that opened this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants