{"record":{"id":"10882ddab5d8841e","repo":"langchain-ai/langchain","slug":"unrecognized-content-block-at-messages-i-conten-10882d","errorCode":null,"errorMessage":"Unrecognized content block at messages[{i}].content[{j}] has 'type': 'tool_result', but is missing expected key(s) {missing}. Full content block:\n\n{block}","messagePattern":"Unrecognized content block at messages\\[(.+?)\\]\\.content\\[(.+?)\\] has 'type': 'tool_result', 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":1872,"sourceCode":"                                \"function\": {\n                                    \"name\": block.get(\"name\"),\n                                    \"arguments\": block.get(\"arguments\"),\n                                },\n                            }\n                        )\n                    if pass_through_unknown_blocks:\n                        content.append(block)\n                elif block.get(\"type\") == \"tool_result\":\n                    if missing := [\n                        k for k in (\"content\", \"tool_use_id\") if k not in block\n                    ]:\n                        msg = (\n                            f\"Unrecognized content block at \"\n                            f\"messages[{i}].content[{j}] has 'type': \"\n                            f\"'tool_result', but is missing expected key(s) \"\n                            f\"{missing}. Full content block:\\n\\n{block}\"\n                        )\n                        raise ValueError(msg)\n                    tool_message = ToolMessage(\n                        block[\"content\"],\n                        tool_call_id=block[\"tool_use_id\"],\n                        status=\"error\" if block.get(\"is_error\") else \"success\",\n                    )\n                    # Recurse to make sure tool message contents are OpenAI format.\n                    tool_messages.extend(\n                        convert_to_openai_messages(\n                            [tool_message], text_format=text_format\n                        )\n                    )\n                elif (block.get(\"type\") == \"json\") or \"json\" in block:\n                    if \"json\" not in block:\n                        msg = (\n                            f\"Unrecognized content block at \"\n                            f\"messages[{i}].content[{j}] has 'type': 'json' \"\n                            f\"but does not have a 'json' key. Full \"\n                            f\"content block:\\n\\n{block}\"","sourceCodeStart":1854,"sourceCodeEnd":1890,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/messages/utils.py#L1854-L1890","documentation":"While converting an Anthropic-style tool result, `convert_to_openai_messages` found a block with `\"type\": \"tool_result\"` lacking `content` or `tool_use_id`. Both are mandatory: `tool_use_id` becomes the ToolMessage's `tool_call_id` (OpenAI requires it to match the originating call) and `content` becomes the tool output. Missing either makes the resulting OpenAI `tool` message invalid, so conversion fails.","triggerScenarios":"`{\"type\": \"tool_result\", \"content\": \"42\"}` with no `tool_use_id`, or `{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_1\"}` with no `content` key; also `is_error: true` blocks truncated during capture.","commonSituations":"Manually appending tool outputs to conversation history for Anthropic-style agents; replaying logged Anthropic conversations where `content` was serialized as `None` and dropped; mismatched sanitizers that delete empty strings.","solutions":["Always include both keys: `{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_1\", \"content\": \"result text\"}`.","If content is empty, use an empty string/list rather than omitting the key.","Prefer LangChain's ToolMessage (`ToolMessage(content, tool_call_id=...)`) which the converter handles natively.","Cross-check that every `tool_result.tool_use_id` matches an earlier `tool_use.id` in the same history."],"exampleFix":"// before\n{\"type\": \"tool_result\", \"content\": \"Paris: 22C\"}\n\n// after\n{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_01ABC\", \"content\": \"Paris: 22C\"}","handlingStrategy":"validation","validationCode":"def valid_tool_result(b: dict) -> bool:\n    return b.get(\"type\") == \"tool_result\" and \"content\" in b and \"tool_use_id\" in b\n\nfor b in blocks:\n    if b.get(\"type\") == \"tool_result\" and not valid_tool_result(b):\n        b.setdefault(\"content\", \"\")\n        # tool_use_id cannot be invented; raise with a clear message instead\n        if \"tool_use_id\" not in b:\n            raise ValueError(f\"tool_result missing tool_use_id: {b}\")","typeGuard":"def is_complete_tool_result(b: dict) -> bool:\n    return (\n        b.get(\"type\") == \"tool_result\"\n        and isinstance(b.get(\"tool_use_id\"), str)\n        and (isinstance(b.get(\"content\"), (str, list)) or \"content\" in b)\n    )","tryCatchPattern":"try:\n    oai = convert_to_openai_messages(history)\nexcept ValueError as e:\n    if \"tool_result\" in str(e):\n        # locate the message pair by id in the error text and re-emit a complete ToolMessage\n        ...","preventionTips":["Use ToolMessage(content=..., tool_call_id=...) as the source of truth for results","Keep empty string content rather than deleting the key","Round-trip test full tool-call/tool-result cycles through your persistence layer"],"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"}