{"record":{"id":"80c9df3a9db0f1fc","repo":"langchain-ai/langchain","slug":"unrecognized-content-block-at-messages-i-conten-80c9df","errorCode":null,"errorMessage":"Unrecognized content block at messages[{i}].content[{j}] has 'type': 'tool_use', but is missing expected key(s) {missing}. Full content block:\n\n{block}","messagePattern":"Unrecognized content block at messages\\[(.+?)\\]\\.content\\[(.+?)\\] has 'type': 'tool_use', but is missing expected key\\(s\\) (.+?)\\. Full content block:\n\n(.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/messages/utils.py","lineNumber":1814,"sourceCode":"                # OpenAI audio format\n                elif (\n                    block.get(\"type\") == \"input_audio\"\n                    and isinstance(block.get(\"input_audio\"), dict)\n                    and isinstance(block.get(\"input_audio\", {}).get(\"data\"), str)\n                    and isinstance(block.get(\"input_audio\", {}).get(\"format\"), str)\n                ):\n                    content.append(block)\n                elif block.get(\"type\") == \"tool_use\":\n                    if missing := [\n                        k for k in (\"id\", \"name\", \"input\") if k not in block\n                    ]:\n                        err = (\n                            f\"Unrecognized content block at \"\n                            f\"messages[{i}].content[{j}] has 'type': \"\n                            f\"'tool_use', but is missing expected key(s) \"\n                            f\"{missing}. Full content block:\\n\\n{block}\"\n                        )\n                        raise ValueError(err)\n                    if not any(\n                        tool_call[\"id\"] == block[\"id\"]\n                        for tool_call in cast(\"AIMessage\", message).tool_calls\n                    ):\n                        oai_msg[\"tool_calls\"] = oai_msg.get(\"tool_calls\", [])\n                        oai_msg[\"tool_calls\"].append(\n                            {\n                                \"type\": \"function\",\n                                \"id\": block[\"id\"],\n                                \"function\": {\n                                    \"name\": block[\"name\"],\n                                    \"arguments\": json.dumps(\n                                        block[\"input\"], ensure_ascii=False\n                                    ),\n                                },\n                            }\n                        )\n                elif block.get(\"type\") == \"function_call\":  # OpenAI Responses","sourceCodeStart":1796,"sourceCodeEnd":1832,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/messages/utils.py#L1796-L1832","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["Add an `id` (any unique string, Anthropic uses `toolu_...`) to every `tool_use` block.","Prefer building AIMessage with `tool_calls=[...]` and let LangChain render content blocks, instead of hand-writing `tool_use` dicts.","If the block is informational only, move the data into a `text` block instead of `tool_use`.","Validate blocks with a small pre-check (see defense) before conversion and drop/repair incomplete ones."],"exampleFix":"// before\n{\"type\": \"tool_use\", \"name\": \"search\", \"input\": {\"q\": \"cats\"}}\n\n// after\n{\"type\": \"tool_use\", \"id\": \"toolu_01XFDUDYJgAACzvnptvVoYEL\", \"name\": \"search\", \"input\": {\"q\": \"cats\"}}","handlingStrategy":"validation","validationCode":"def valid_tool_use(b: dict) -> bool:\n    return b.get(\"type\") == \"tool_use\" and all(k in b for k in (\"id\", \"name\", \"input\"))\n\nfor i, m in enumerate(messages):\n    for j, b in enumerate(m.content if isinstance(m.content, list) else []):\n        if b.get(\"type\") == \"tool_use\" and not valid_tool_use(b):\n            raise ValueError(f\"incomplete tool_use at messages[{i}].content[{j}]\")","typeGuard":"def is_complete_tool_use(b: dict) -> bool:\n    return (\n        b.get(\"type\") == \"tool_use\"\n        and isinstance(b.get(\"id\"), str)\n        and isinstance(b.get(\"name\"), str)\n        and isinstance(b.get(\"input\"), dict)\n    )","tryCatchPattern":"try:\n    oai = convert_to_openai_messages(messages)\nexcept ValueError as e:\n    if \"tool_use\" in str(e):\n        # repair: synthesize an id, or drop the block if it carries no real call\n        ...","preventionTips":["Build assistant tool messages via AIMessage(tool_calls=[...]) instead of raw blocks","Pair every tool_use with its tool_result by id in the same history","Validate streamed tool calls only after the final delta, not mid-stream"],"tags":["messages","tool-calls","anthropic","openai-conversion","validation"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}