{"record":{"id":"14b178423f93f981","repo":"zylon-ai/private-gpt","slug":"tool-result-blocks-must-match-the-tool-use-ids-in","errorCode":null,"errorMessage":"Tool result blocks must match the tool use IDs in the same message. Found tool use IDs: {tool_uses_ids}, but tool result IDs: {tool_results_ids}","messagePattern":"Tool result blocks must match the tool use IDs in the same message\\. Found tool use IDs: (.+?), but tool result IDs: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"private_gpt/server/chat/chat_models.py","lineNumber":415,"sourceCode":"                        if block.id in tool_uses_ids:\n                            raise ValueError(f\"Duplicate tool use ID found: {block.id}\")\n                        tool_uses_ids.add(block.id)\n\n                    elif isinstance(block, ToolResultBlock):\n                        if block.tool_use_id not in tool_uses_ids:\n                            raise ValueError(\n                                f\"Tool result block references an unknown tool use ID: {block.tool_use_id}\"\n                            )\n                        tool_results_ids.add(block.tool_use_id)\n\n                    elif isinstance(block, TLDRBlock):\n                        if message.role != \"assistant\":\n                            raise ValueError(\n                                f\"TLDR blocks can only be used in assistant messages: {message}\"\n                            )\n\n        if tool_results_ids != tool_uses_ids:\n            raise ValueError(\n                \"Tool result blocks must match the tool use IDs in the same message.\"\n                f\" Found tool use IDs: {tool_uses_ids}, but tool result IDs: {tool_results_ids}\"\n            )\n\n        return self\n","sourceCodeStart":397,"sourceCodeEnd":421,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/server/chat/chat_models.py#L397-L421","documentation":"Final consistency check of the ChatMessages validator: after scanning all messages, the set of tool_use ids and the set of tool_result ids must be identical — every tool call must have exactly one result within the same request. The error lists both sets so you can see which id is missing a result (present in tool uses but not results) or which tool_use was never paired.","triggerScenarios":"An assistant ToolUseBlock exists but no ToolResultBlock with that id appears anywhere in the messages (result turn was truncated, still pending, or omitted); or the pairing check fires after passing the earlier per-block checks because ids match individually but one direction is incomplete.","commonSituations":"Truncating history from the tail and cutting the last tool_result; sending an in-flight conversation where the tool result has not been appended yet; hand-crafted histories that include the assistant tool call but forget the result turn.","solutions":["Append a ToolResultBlock for every ToolUseBlock id shown in 'Found tool use IDs' but missing from 'tool result IDs' (even a synthetic 'skipped' result)","Prune history on tool_use/tool_result pair boundaries so pairing always stays complete","Never submit the assistant tool-call turn before the tool result is available — send both in the same request"],"exampleFix":"# before (tool_use tu_1 has no result)\n[assistant(tu_1), user(\"thanks\")]\n\n# after\n[assistant(tu_1), user(tool_result tu_1 = \"skipped\"), user(\"thanks\")]","handlingStrategy":"validation","validationCode":"use_ids = {b.id for m in messages for b in (m.content or []) if isinstance(b, ToolUseBlock)}\nresult_ids = {b.tool_use_id for m in messages for b in (m.content or []) if isinstance(b, ToolResultBlock)}\nif use_ids != result_ids:\n    missing_results = use_ids - result_ids\n    for uid in missing_results:\n        append_synthetic_tool_result(uid, \"skipped\")","typeGuard":"def tool_pairs_are_complete(messages: list) -> bool:\n    use_ids, result_ids = set(), set()\n    for m in messages:\n        for b in (m.get(\"content\", []) if isinstance(m, dict) else (m.content or [])):\n            t = b.get(\"type\") if isinstance(b, dict) else getattr(b, \"type\", None)\n            if t == \"tool_use\":\n                use_ids.add(b.get(\"id\") if isinstance(b, dict) else b.id)\n            if t == \"tool_result\":\n                result_ids.add(b.get(\"tool_use_id\") if isinstance(b, dict) else b.tool_use_id)\n    return use_ids == result_ids","tryCatchPattern":"try:\n    ChatMessages(messages=msgs)\nexcept ValueError as e:\n    if \"must match the tool use IDs\" in str(e):\n        backfill_missing_tool_results(msgs)\n    else:\n        raise","preventionTips":["Prune history in tool_use+tool_result pairs, never singly","Always submit the result turn together with the pending tool call","Diff the two id sets client-side before each send"],"tags":["chat","tool-use","validation","messages"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}