zylon-ai/private-gpt · error · ValueError
Tool result blocks must match the tool use IDs in the same m
Error message
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} What it means
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.
Source
Thrown at private_gpt/server/chat/chat_models.py:415
if block.id in tool_uses_ids:
raise ValueError(f"Duplicate tool use ID found: {block.id}")
tool_uses_ids.add(block.id)
elif isinstance(block, ToolResultBlock):
if block.tool_use_id not in tool_uses_ids:
raise ValueError(
f"Tool result block references an unknown tool use ID: {block.tool_use_id}"
)
tool_results_ids.add(block.tool_use_id)
elif isinstance(block, TLDRBlock):
if message.role != "assistant":
raise ValueError(
f"TLDR blocks can only be used in assistant messages: {message}"
)
if tool_results_ids != tool_uses_ids:
raise ValueError(
"Tool result blocks must match the tool use IDs in the same message."
f" Found tool use IDs: {tool_uses_ids}, but tool result IDs: {tool_results_ids}"
)
return self
View on GitHub (pinned to 4a030776a3)
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
Example fix
# before (tool_use tu_1 has no result)
[assistant(tu_1), user("thanks")]
# after
[assistant(tu_1), user(tool_result tu_1 = "skipped"), user("thanks")] Defensive patterns
Strategy: validation
Validate before calling
use_ids = {b.id for m in messages for b in (m.content or []) if isinstance(b, ToolUseBlock)}
result_ids = {b.tool_use_id for m in messages for b in (m.content or []) if isinstance(b, ToolResultBlock)}
if use_ids != result_ids:
missing_results = use_ids - result_ids
for uid in missing_results:
append_synthetic_tool_result(uid, "skipped") Type guard
def tool_pairs_are_complete(messages: list) -> bool:
use_ids, result_ids = set(), set()
for m in messages:
for b in (m.get("content", []) if isinstance(m, dict) else (m.content or [])):
t = b.get("type") if isinstance(b, dict) else getattr(b, "type", None)
if t == "tool_use":
use_ids.add(b.get("id") if isinstance(b, dict) else b.id)
if t == "tool_result":
result_ids.add(b.get("tool_use_id") if isinstance(b, dict) else b.tool_use_id)
return use_ids == result_ids Try / catch
try:
ChatMessages(messages=msgs)
except ValueError as e:
if "must match the tool use IDs" in str(e):
backfill_missing_tool_results(msgs)
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Duplicate tool use ID found: {block.id}
- Tool result block references an unknown tool use ID: {block.
- No user message found in request.
- TLDR blocks can only be used in assistant messages: {message
- INVALID_REQUEST_AUDIO_MAX_NUM_ERROR
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/14b178423f93f981.
Report an issue: GitHub.