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

  1. Append a ToolResultBlock for every ToolUseBlock id shown in 'Found tool use IDs' but missing from 'tool result IDs' (even a synthetic 'skipped' result)
  2. Prune history on tool_use/tool_result pair boundaries so pairing always stays complete
  3. 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

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


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/14b178423f93f981. Report an issue: GitHub.