Skip to content

feat(anthropic): add AnthropicToolIdSanitizationMiddleware#37096

Open
Mason Daugherty (mdrxy) wants to merge 1 commit into
masterfrom
mdrxy/thropic-tool-sanitization
Open

feat(anthropic): add AnthropicToolIdSanitizationMiddleware#37096
Mason Daugherty (mdrxy) wants to merge 1 commit into
masterfrom
mdrxy/thropic-tool-sanitization

Conversation

@mdrxy
Copy link
Copy Markdown
Member

Add AnthropicToolIdSanitizationMiddleware so cross-provider conversation histories replay cleanly against Anthropic. Anthropic enforces tool_use.id matching ^[a-zA-Z0-9_-]+$, but providers like Kimi-K2 emit IDs of the form functions.<name>:<idx> — replaying such a thread raises a 400. The middleware rewrites only the outgoing ModelRequest, leaving graph state and persisted checkpoints untouched, so it composes cleanly with HITL resume.

Changes

  • Build a deterministic bad_id -> safe_id map per request: substitute illegal characters with _, then fall back to a truncated sha256 suffix (with a counter for further collisions) when the base would shadow an already-valid id or another rewritten id (_make_safe_id).
  • Rewrite IDs across every Anthropic-supported shape: AIMessage.tool_calls[*].id, AIMessage.content blocks (tool_use, server_tool_use, mcp_tool_use), ToolMessage.tool_call_id, and *_tool_result content blocks (tool_result, web_search_tool_result, code_execution_tool_result, bash_tool_result, mcp_tool_result). Suffix matching on *_tool_use / *_tool_result keeps future block kinds covered.
  • Drift-correct position-paired tool_calls[i] and the i-th client tool_use block onto one canonical id, then propagate that as an alias to the immediately-following ToolMessage results so cross-message linkage survives even when the input list disagreed.
  • Provider-gate via _should_run: non-ChatAnthropic models follow the unsupported_model_behavior knob — 'ignore' (default; logs a debug breadcrumb so the bypass is observable), 'warn', or 'raise'. The constructor runtime-validates the literal so typos raise ValueError instead of silently degrading to ignore semantics. The langchain import is guarded with a narrowed ModuleNotFoundError so genuine import-time failures inside langchain.agents.middleware aren't masked by a misleading "install langchain" message.
  • Sync wrap_model_call and async awrap_model_call share the same sanitization path.

@github-actions github-actions Bot added anthropic `langchain-anthropic` package issues & PRs feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration internal size: XL 1000+ LOC labels Apr 30, 2026
return candidate


class AnthropicToolIdSanitizationMiddleware(AgentMiddleware):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance we want a more general version of this middleware in LangChain?

Something like StandardizationMiddleware and it'll handle some logic for standardizing things across providers?

OK if we think we want to keep it specific to Anthropic

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

Labels

anthropic `langchain-anthropic` package issues & PRs feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration internal size: XL 1000+ LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants