LangChain Tool Calling Error with Claude API: How to Fix the Schema Mismatch Before It Cascades

LangChain tool calling breaks when switching to Claude API. Fix schema validation errors, bind_tools() mismatches, and response parsing failures fast.

The agent ran. The tool call fired. Then the response came back as an empty tool_result block with no output and no useful error message — just a validation exception that pointed at the schema, not the logic.

Switching a working LangChain agent from OpenAI to Claude is one of those migrations that looks like a find-and-replace job. Swap the model class, update the API key, keep the tools. The first run usually throws an error that takes longer to diagnose than the original build did, because the failure happens three layers down from where you changed anything.

Where the Chain Actually Breaks

The assumption that causes this: LangChain abstracts provider differences, so tools defined for OpenAI should work with Claude. That assumption holds for basic completions. It breaks the moment tools enter the picture.

OpenAI’s function calling format and Claude’s tool-use format are structurally different at the schema level. LangChain’s internal tool conversion layer handles some of that translation automatically, but it does not handle all of it — and when it fails, the error surfaces as a schema validation exception inside the Anthropic SDK rather than a clear “wrong format” message from LangChain itself.

The cascade looks like this: you define a tool using LangChain’s standard @tool decorator or a BaseTool subclass, bind it to a ChatOpenAI instance using .bind_tools(), confirm it works, then swap in ChatAnthropic. The tool binding call succeeds. The model receives the schema. The first tool call fires. Then Claude returns either a malformed response or the SDK throws a ValidationError because the schema passed to the API doesn’t match what the Anthropic tool-use spec requires.

By the time you see the error, the problem already happened upstream — at the moment the tool schema was serialized and sent. Nothing in the stack trace points there directly.

What Claude’s Tool Schema Actually Requires

Claude’s tool-use API expects a strict JSON Schema object under the input_schema key, with explicit type declarations on every parameter. OpenAI’s format uses a parameters key with similar content, but the outer wrapper and required field declarations differ enough that a schema valid for one provider will silently fail or throw on the other.

The Anthropic tool-use specification (documented at docs.anthropic.com/en/docs/tool-use) requires this structure for each tool:

{
  "name": "get_weather",
  "description": "Returns current weather data for a given city.",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "The city name to look up."
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit."
      }
    },
    "required": ["city"]
  }
}

The critical field is input_schema, not parameters. If your tool schema lands in the request body under parameters, Claude’s API will reject it. LangChain’s ChatAnthropic adapter handles this conversion when you use bind_tools() correctly — but if you’re passing raw schema dicts, constructing tools manually, or using a version of langchain-anthropic that predates the fix for this specific conversion, the field name mismatch is the first thing to check.

OpenAI vs Claude Tool-Calling Syntax in LangChain

The differences between provider implementations aren’t cosmetic. The table below would normally show this as rows, but the meaningful version is this: with OpenAI, ChatOpenAI.bind_tools(tools) serializes your tool list into the functions or tools array using the parameters key internally. With ChatAnthropic.bind_tools(tools), the same call is supposed to produce an input_schema key instead — and the langchain-anthropic package handles that translation, provided you’re on a version that includes the correct schema transformer.

Where this breaks in practice: if you’ve defined tools using raw StructuredTool with a manually constructed args_schema dict rather than a Pydantic model, the automatic conversion can miss required field declarations or produce a schema with null types where Claude expects explicit string or integer declarations. Claude does not infer missing types. It returns a validation error. The error message usually references the schema structure but not the specific field that caused the rejection.

One other difference worth flagging: Claude’s tool-use response structure includes a tool_use content block with an id field that must be referenced in the subsequent tool_result message. OpenAI uses a different ID-passing convention. If you’re handling tool responses manually rather than letting LangChain’s agent loop manage the message chain, this ID mismatch will produce a silent failure on the second turn — the model receives the tool result but can’t associate it with the original call.

The Correct Bind Pattern for ChatAnthropic

The working implementation uses ChatAnthropic from langchain-anthropic with Pydantic-defined tool schemas, not raw dicts. Here’s the pattern that actually runs:

from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    city: str = Field(description="The city name to look up.")
    units: str = Field(default="celsius", description="Temperature unit: celsius or fahrenheit.")

@tool(args_schema=WeatherInput)
def get_weather(city: str, units: str = "celsius") -> str:
    """Returns current weather data for a given city."""
    # tool logic here
    return f"Weather in {city}: 22 degrees {units}"

llm = ChatAnthropic(
    model="claude-3-5-sonnet-20241022",
    temperature=0,
    max_tokens=1024
)

llm_with_tools = llm.bind_tools([get_weather])

response = llm_with_tools.invoke("What's the weather in Berlin?")
print(response)

The key decisions here: Pydantic BaseModel as the args_schema so LangChain can generate a valid JSON Schema with proper type annotations, and bind_tools() called on the ChatAnthropic instance rather than passing tools through a generic agent constructor that might not apply the Anthropic-specific schema transform. The langchain-anthropic package documentation at python.langchain.com/docs/integrations/chat/anthropic/ confirms this as the recommended pattern.

If you need to handle the tool response in a multi-turn loop, the agent needs to pass the tool_use_id back correctly. LangChain’s AgentExecutor handles this when you use the standard tool-calling agent pattern. If you’re building a custom loop, the second message in the conversation must include a tool_result content block with the matching tool_use_id from Claude’s response — missing this causes the model to either repeat the tool call or stall entirely.

Before and After: What the Failure Looked Like

Before the fix: Tool defined with a raw dict schema, bound to ChatAnthropic via bind_tools(). The model received the tool list, generated a tool_use block, but the SDK threw anthropic.BadRequestError: 400 tools.0.input_schema: Extra inputs are not permitted — pointing at the schema structure, not the logic. Every run failed at the same point. Debugging time: significant, because the error message referenced the Anthropic SDK layer, not the LangChain tool definition.
After the fix: Tool redefined with a Pydantic BaseModel as args_schema. Same bind_tools() call. The schema serialized correctly into input_schema format, the tool call completed, and the response parsed without errors. The real ROI here is not faster debugging on this one agent — it’s that every future tool definition now has a consistent schema contract that works across provider switches without manual inspection.

Debugging Checklist: Five Points Where This Chain Breaks

Run through these in order before changing anything else in your stack:

  1. Confirm you’re importing ChatAnthropic from langchain-anthropic, not from langchain_community. The community wrapper does not always include the latest Anthropic-specific schema transforms, and the import will succeed without warning you.
  2. Check your langchain-anthropic version with pip show langchain-anthropic. Versions below 0.1.15 had known issues with tool schema conversion. Run pip install --upgrade langchain-anthropic before debugging schema structure.
  3. Inspect the actual request body being sent to the API by enabling ANTHROPIC_LOG=debug in your environment. If the serialized tool shows parameters instead of input_schema, the schema transform is not firing — which points to an outdated package or a manually constructed schema bypassing the Pydantic path.
  4. Verify every tool parameter has an explicit type declaration in the Pydantic model. Optional fields without a default value and without an explicit type annotation will generate a schema with a missing or null type, which Claude’s API rejects with a 400 error referencing the specific field name.
  5. If you’re running a multi-turn agent loop manually, confirm the tool_result message includes the exact tool_use_id string from Claude’s prior response. A mismatch here does not throw an error on the API call — the model simply ignores the result and may re-invoke the tool or produce an incomplete answer.

Where This Does Not Solve the Problem

This fix handles schema validation failures and bind pattern mismatches. It does not cover every failure mode in the Claude tool-use integration.

If you’re running Claude through AWS Bedrock using ChatBedrockConverse rather than the direct Anthropic API, the tool schema handling has historically differed from ChatAnthropic — the LangChain AWS package has its own schema transform path, and alignment between the two was still inconsistent as of mid-2025. Check langchain-aws release notes separately before assuming the same fix applies.

Extended thinking mode in Claude 3.5+ has a known constraint: it cannot be combined with forced tool use (tool_choice={"type": "any"}). If your agent is configured to force tool calls, enabling thinking mode will produce an error that looks like a schema problem but is actually a conflicting configuration. The error message from newer versions of the SDK is more explicit about this, but older integrations surface it as a generic validation failure.

Finally, if your tool returns a complex nested object rather than a string or flat dict, Claude’s response parsing may succeed but the downstream LangChain output parser may mishandle the tool_result content. This is a parsing layer issue, not a schema issue — the fix there involves explicitly defining the tool’s return type annotation and confirming the output parser in your agent chain matches.

If you want the full working agent template — including the multi-turn loop with correct tool_use_id handling and a Pydantic schema pattern you can drop into any Claude integration — get the setup notes from the list. New breakdowns go out when a pattern shows up repeatedly enough to document.

Get the Claude Tool-Calling Template

Working bind_tools() pattern, multi-turn loop with correct ID handling, and schema checklist — sent directly. No generic “AI tips” content.

Check the ANTHROPIC_LOG=debug output before touching the schema. The serialized request body shows you exactly which field name landed in the payload — that single check eliminates roughly half the possible causes before you change a line of code.

Leave a Reply

Your email address will not be published. Required fields are marked *