Skip to content

anthropic_llm: part_to_message_block does not sanitize tool_use IDs, causing BadRequestError on session resume #5074

@BenPerel

Description

@BenPerel

Description

When using an Anthropic/Claude model via ADK deployed on Vertex AI Agent Engine, resuming a session causes anthropic.BadRequestError. There are multiple related bugs in how session history is reconstructed for non-Gemini models.

Note: These bugs manifest specifically when accessed via Gemini Enterprise / AgentSpace console. The Vertex AI Agent Engine playground console and Python SDK work correctly.

Bug 1: preserve_function_call_ids strips tool IDs on session replay

Location: contents.py:47-53, functions.py:192-214

preserve_function_call_ids is hardcoded False for all non-Gemini models:

preserve_function_call_ids = (
    isinstance(canonical_model, Gemini)
    and canonical_model.use_interactions_api
)

This causes remove_client_function_call_id to run on every replayed content for Claude, stripping adk-* prefixed IDs to None. These adk-* IDs exist because:

  1. Vertex AI Claude streaming may return ToolUseBlock with id = None
  2. populate_client_function_call_id (functions.py:188) generates adk-<uuid> fallback
  3. On replay, remove_client_function_call_id strips adk-*None
  4. part_to_message_block (anthropic_llm.py:117) converts None"" → Anthropic rejects

Error: messages.1.content.1.tool_use.id: String should match pattern '^[a-zA-Z0-9_-]+$'

Fix: Extend preserve_function_call_ids to include AnthropicLlm:

preserve_function_call_ids = (
    isinstance(canonical_model, AnthropicLlm)
    or (isinstance(canonical_model, Gemini) and canonical_model.use_interactions_api)
)

Bug 2: Broken history reconstruction via Gemini Enterprise

Observed behavior: When Gemini Enterprise replays session history for a follow-up turn, the reconstructed llm_request.contents contains only assistant messages with tool_use blocks — the corresponding user messages with tool_result blocks are completely missing.

Correct structure (what Anthropic expects):

msg[0] user: "question"
msg[1] assistant: [text, tool_use, tool_use, tool_use]
msg[2] user: [tool_result, tool_result, tool_result]
msg[3] assistant: [text, tool_use, tool_use]
msg[4] user: [tool_result, tool_result]
msg[5] assistant: [text]  ← final response
msg[6] user: "follow-up"  ← new turn

Actual structure from Gemini Enterprise replay:

msg[0] user: "follow-up"
msg[1] assistant: [tool_use, tool_use, tool_use]  ← IDs are None
msg[2] assistant: [tool_use, tool_use]             ← IDs are None, no user msg between

The tool_result events and the original user/assistant text messages appear to be dropped or not included in the session replay via Gemini Enterprise. This may be a Gemini Enterprise-specific issue rather than an ADK issue.

Error: messages.1: tool_use ids were found without tool_result blocks immediately after

This bug is NOT fixable with a workaround — the tool_result data is simply not present in the replayed history.

Bug 3 (minor): part_to_message_block doesn't sanitize IDs

anthropic_llm.py:117 passes IDs raw without validating against Anthropic's ^[a-zA-Z0-9_-]+$ pattern. Should sanitize regardless of source.

Workaround

Using a before_model_callback that sanitizes invalid characters from tool_use IDs in llm_request.contents before the model call. This mitigates Bug 3 for the SDK and Agent Engine playground paths. Bugs 1 and 2 require framework-level fixes.

Current recommendation: Use the Vertex AI Agent Engine playground console (which works correctly) instead of Gemini Enterprise for session resumption with Claude models.

Environment

  • google-adk==1.27.2
  • Model: claude-opus-4-6 via Vertex AI
  • Deployment: Vertex AI Agent Engine with VertexAiSessionService
  • Gemini Enterprise: broken on session resume
  • Agent Engine playground: works correctly
  • Python SDK: works correctly

Metadata

Metadata

Assignees

Labels

models[Component] Issues related to model support

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions