zylon-ai/private-gpt · error · ValueError
Tool result block references an unknown tool use ID: {block.
Error message
Tool result block references an unknown tool use ID: {block.tool_use_id} What it means
Raised while validating ChatMessages: a ToolResultBlock references a tool_use_id that was never declared by any preceding ToolUseBlock in the message list. The validator first collects all ToolUseBlock ids (assistant messages only) and then checks each ToolResultBlock against that set; an unmatched reference means the conversation history is inconsistent — a tool result without its originating tool call.
Source
Thrown at private_gpt/server/chat/chat_models.py:403
tool_uses_ids: set[str] = set()
tool_results_ids: set[str] = set()
for message in self.messages:
if isinstance(message.content, list):
for block in message.content:
if block is None:
raise ValueError("Block cannot be None")
elif isinstance(block, ToolUseBlock):
if message.role != "assistant":
raise ValueError(
f"Tool use blocks can only be used in assistant messages: {message}"
)
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
- Ensure every ToolResultBlock.tool_use_id exactly matches the id of a ToolUseBlock present earlier in the same messages array
- When trimming history, remove each tool_use/tool_result pair together, never the tool_use alone
- If you inject synthetic tool results, first inject (or keep) the corresponding assistant tool_use block with the same id
Example fix
# before
messages = [
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_999", "content": "ok"}]},
]
# after
messages = [
{"role": "assistant", "content": [{"type": "tool_use", "id": "tu_999", "name": "search", "input": {"q": "x"}}]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_999", "content": "ok"}]},
] Defensive patterns
Strategy: validation
Validate before calling
use_ids = {b.id for m in messages if m.role == "assistant" 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)]
missing = [r for r in result_ids if r not in use_ids]
assert not missing, f"tool results without matching tool_use: {missing}" Type guard
def tool_results_are_grounded(messages: list) -> bool:
use_ids = set()
for m in messages:
blocks = m.get("content", []) if isinstance(m, dict) else (m.content or [])
for b in blocks:
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":
rid = b.get("tool_use_id") if isinstance(b, dict) else b.tool_use_id
if rid not in use_ids:
return False
return True Try / catch
try:
ChatMessages(messages=msgs)
except ValueError as e:
if "unknown tool use ID" in str(e):
drop_orphan_tool_results(msgs)
else:
raise Prevention
- Treat tool_use/tool_result as an atomic pair when editing history
- Never synthesize tool results with invented ids
- Validate pair grounding before sending the request
When it happens
Trigger: Sending a user/tool message containing a ToolResultBlock whose tool_use_id is misspelled, was generated client-side without a matching ToolUseBlock, or whose assistant tool_use message was dropped (e.g. history truncation removed the assistant turn but kept the result).
Common situations: Sliding-window history pruning that cuts assistant tool_use turns but keeps tool result turns; clients that synthesize tool results manually (e.g. 'error' placeholder results) with arbitrary ids; migrating histories where ids were regenerated.
Related errors
- Duplicate tool use ID found: {block.id}
- Tool result blocks must match the tool use IDs in the same m
- 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/3643f44469ef5d6b.
Report an issue: GitHub.