rohitg00/ai-engineering-from-scratch · error · ProtocolError
tool_use requires name and object input
Error message
tool_use requires name and object input
What it means
Raised by _execute_tool when a model-emitted tool_use content block has a name that is not a string or an input that is not a dict. The Messages API contract requires every tool_use block to carry a string tool name and a JSON object as input; the offline runtime enforces this before dispatching to a handler.
Source
Thrown at certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py:120
return RunResult(_text_from_blocks(blocks), messages, turn)
if stop_reason != "tool_use":
raise ProtocolError(f"unsupported stop_reason: {stop_reason!r}")
tool_results = [self._execute_tool(block) for block in blocks if block["type"] == "tool_use"]
if not tool_results:
raise ProtocolError("stop_reason tool_use had no tool_use block")
messages.append({"role": "user", "content": tool_results})
raise ProtocolError(f"maximum turn count {self.max_turns} exceeded")
def _execute_tool(self, block: dict[str, Any]) -> dict[str, Any]:
tool_id = block.get("id")
name = block.get("name")
arguments = block.get("input")
if not isinstance(tool_id, str) or not tool_id:
raise ProtocolError("tool_use requires a non-empty id")
if not isinstance(name, str) or not isinstance(arguments, dict):
raise ProtocolError("tool_use requires name and object input")
handler = self.tools.get(name)
if handler is None:
return {
"type": "tool_result",
"tool_use_id": tool_id,
"content": f"Unknown tool: {name}",
"is_error": True,
}
try:
value = handler(arguments)
return {
"type": "tool_result",
"tool_use_id": tool_id,
"content": json.dumps(value, sort_keys=True),
}
except Exception as exc: # Tool failures become model-visible results.
return {View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Ensure the tool_use block includes a string 'name' matching a registered tool
- Pass 'input' as a dict, e.g. {"query":"..."} not a JSON string
- If mocking responses, build blocks via a helper that always sets both fields
Example fix
// before
{"type":"tool_use","id":"t1","name":"search","input":"{}"}
// after
{"type":"tool_use","id":"t1","name":"search","input":{"q":"x"}} Defensive patterns
Strategy: validation
Validate before calling
def is_valid_tool_use(block):
return (
isinstance(block, dict)
and isinstance(block.get("id"), str) and block["id"]
and isinstance(block.get("name"), str)
and isinstance(block.get("input"), dict)
) Type guard
def is_tool_use(block: object) -> bool:
b = block if isinstance(block, dict) else {}
return isinstance(b.get("name"), str) and isinstance(b.get("input"), dict) Try / catch
try:
result = agent.run(...)
except ProtocolError as exc:
if "tool_use requires" in str(exc):
log_and_repair_model_response(exc) Prevention
- Build tool_use blocks through one helper that sets id/name/input with correct types
- Validate scripted model responses with the same guard before feeding them to run()
When it happens
Trigger: Calling run() with a scripted model response whose tool_use block omits 'name', sets it to null/number, or supplies 'input' as a string/list instead of an object.
Common situations: Hand-rolled mock model responses in tests, replaying captured transcripts where input was serialized to a string, or porting from an API version that allowed absent input.
Related errors
- every content block needs a type
- response content must be a non-empty block list
- event arrived after message_stop
- stream ended without message_stop
- max_attempts must be positive
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/a0454e9f05222aa8.
Report an issue: GitHub.