langchain-ai/langchain · error · ValueError

Unrecognized content block at messages[{i}].content[{j}] has

Error message

Unrecognized content block at messages[{i}].content[{j}] has 'type': 'tool_use', but is missing expected key(s) {missing}. Full content block:

{block}

What it means

Raised while converting an Anthropic-format AIMessage: a content block with `"type": "tool_use"` must carry `id`, `name`, and `input`, because the converter synthesizes an OpenAI `tool_calls` entry from them (`id` -> tool call id, `name`/`input` -> function name and JSON arguments). If any of the three keys is absent the mapping is impossible, so the conversion aborts with the missing key names listed in the message.

Source

Thrown at libs/core/langchain_core/messages/utils.py:1814

                # OpenAI audio format
                elif (
                    block.get("type") == "input_audio"
                    and isinstance(block.get("input_audio"), dict)
                    and isinstance(block.get("input_audio", {}).get("data"), str)
                    and isinstance(block.get("input_audio", {}).get("format"), str)
                ):
                    content.append(block)
                elif block.get("type") == "tool_use":
                    if missing := [
                        k for k in ("id", "name", "input") if k not in block
                    ]:
                        err = (
                            f"Unrecognized content block at "
                            f"messages[{i}].content[{j}] has 'type': "
                            f"'tool_use', but is missing expected key(s) "
                            f"{missing}. Full content block:\n\n{block}"
                        )
                        raise ValueError(err)
                    if not any(
                        tool_call["id"] == block["id"]
                        for tool_call in cast("AIMessage", message).tool_calls
                    ):
                        oai_msg["tool_calls"] = oai_msg.get("tool_calls", [])
                        oai_msg["tool_calls"].append(
                            {
                                "type": "function",
                                "id": block["id"],
                                "function": {
                                    "name": block["name"],
                                    "arguments": json.dumps(
                                        block["input"], ensure_ascii=False
                                    ),
                                },
                            }
                        )
                elif block.get("type") == "function_call":  # OpenAI Responses

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Add an `id` (any unique string, Anthropic uses `toolu_...`) to every `tool_use` block.
  2. Prefer building AIMessage with `tool_calls=[...]` and let LangChain render content blocks, instead of hand-writing `tool_use` dicts.
  3. If the block is informational only, move the data into a `text` block instead of `tool_use`.
  4. Validate blocks with a small pre-check (see defense) before conversion and drop/repair incomplete ones.

Example fix

// before
{"type": "tool_use", "name": "search", "input": {"q": "cats"}}

// after
{"type": "tool_use", "id": "toolu_01XFDUDYJgAACzvnptvVoYEL", "name": "search", "input": {"q": "cats"}}
Defensive patterns

Strategy: validation

Validate before calling

def valid_tool_use(b: dict) -> bool:
    return b.get("type") == "tool_use" and all(k in b for k in ("id", "name", "input"))

for i, m in enumerate(messages):
    for j, b in enumerate(m.content if isinstance(m.content, list) else []):
        if b.get("type") == "tool_use" and not valid_tool_use(b):
            raise ValueError(f"incomplete tool_use at messages[{i}].content[{j}]")

Type guard

def is_complete_tool_use(b: dict) -> bool:
    return (
        b.get("type") == "tool_use"
        and isinstance(b.get("id"), str)
        and isinstance(b.get("name"), str)
        and isinstance(b.get("input"), dict)
    )

Try / catch

try:
    oai = convert_to_openai_messages(messages)
except ValueError as e:
    if "tool_use" in str(e):
        # repair: synthesize an id, or drop the block if it carries no real call
        ...

Prevention

When it happens

Trigger: Hand-authored assistant messages containing `{"type": "tool_use", "name": "search", "input": {...}}` without `id`; replaying captured Anthropic streams where the final `tool_use` delta was dropped; stripping `id` during message sanitization before `convert_to_openai_messages`.

Common situations: Building few-shot conversational histories for agents that use Anthropic content blocks; deserializing stored messages with lossy schemas; partial tool-call payloads from interrupted streaming.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/80c9df3a9db0f1fc. Report an issue: GitHub.