Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Patterns for Claude tool use including tool definition schemas, multi-tool orchestration, parallel tool calls, error handling, and result formatting. Use when the user is defining tools for Claude, building agentic workflows with tool calling, handling tool errors, or implementing multi-step tool pipelines.
.claude/skills/majiayu000-tool-use-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 65% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 67% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 105% | 0% |
Patterns for defining, orchestrating, and handling Claude tool use. Covers schemas, multi-tool flows, parallel execution, error handling, and result formatting.
Tools are defined with a name, description, and JSON Schema for input_schema. The description is critical -- Claude uses it to decide when to call the tool.
pythontools = [ { "name": "get_weather", "description": "Get current weather for a city. Use this when the user asks about weather, temperature, or forecast for a specific location.", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "City name, e.g. 'San Francisco, CA'" }, "units": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature units. Default: fahrenheit." } }, "required": ["city"] } } ] message = client.messages.create( model="claude-sonnet-4-6-20250514", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "What's the weather in Tokyo?"}] )
The core pattern: send a message, check if Claude wants to use tools, execute them, return results, and repeat until Claude produces a final text response.
pythondef run_agent(user_message: str, tools: list, system: str = "") -> str: messages = [{"role": "user", "content": user_message}] while True: response = client.messages.create( model="claude-sonnet-4-6-20250514", max_tokens=4096, system=system, tools=tools, messages=messages, ) # Collect tool use blocks and text tool_results = [] final_text = "" for block in response.content: if block.type == "tool_use": # Execute the tool result = execute_tool(block.name, block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result) }) elif block.type == "text": final_text = block.text # If no tool calls, return the final text if response.stop_reason == "end_turn": return final_text # Append assistant response and tool results messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})
Claude can call multiple tools simultaneously. Handle all tool results in a single user message to maintain correct conversation structure.
python# Claude may return multiple tool_use blocks in one response # Example: user asks "Compare weather in NYC and London" for block in response.content: if block.type == "tool_use": # Execute each tool call result = execute_tool(block.name, block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, # Must match the specific tool_use block "content": json.dumps(result) }) # Return ALL results in a single user message messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})
Return errors as tool results so Claude can reason about them and recover.
pythondef execute_tool(name: str, inputs: dict) -> dict: try: if name == "get_weather": return get_weather(**inputs) elif name == "search_database": return search_database(**inputs) else: return {"error": f"Unknown tool: {name}"} except ValueError as e: return {"error": f"Invalid input: {str(e)}"} except TimeoutError: return {"error": "Request timed out. Try again or use a different query."} except Exception as e: return {"error": f"Tool execution failed: {str(e)}"} # Alternatively, use the is_error flag for explicit error signaling tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": "City not found. Please check the spelling.", "is_error": True # Tells Claude this is an error, not a valid result })
Control whether Claude must use a tool, can choose, or must not use tools.
python# Force Claude to use a specific tool message = client.messages.create( model="claude-sonnet-4-6-20250514", max_tokens=1024, tools=tools, tool_choice={"type": "tool", "name": "get_weather"}, # Must use this tool messages=[{"role": "user", "content": "Tokyo forecast"}] ) # Force Claude to use any tool (but must pick one) tool_choice = {"type": "any"} # Let Claude decide (default behavior) tool_choice = {"type": "auto"}
Structure tool results so Claude can reason about them effectively.
python# Good: structured, clear result result = { "status": "success", "data": { "city": "Tokyo", "temperature": 22, "units": "celsius", "conditions": "partly cloudy", "humidity": 65 } } # Good: include metadata for pagination or follow-up result = { "status": "success", "results": items[:10], "total_count": 247, "has_more": True, "next_cursor": "abc123" }
required fields in the input schematool_use_id (causes API error)tool_choice: {"type": "tool"} in a loop (causes infinite tool calling)| Field | Purpose | |-------|---------| | name | Tool identifier, snake_case, max 64 chars | | description | When and why to use -- be specific and directive | | input_schema | JSON Schema object with properties and required | | tool_use_id | Links tool result back to the specific tool call | | is_error | Boolean flag signaling error in tool result | | tool_choice | auto (default), any (must call one), {"type":"tool","name":"X"} |
Conversation structure for tool use:
user message -> Claude responds with tool_use blocksassistant message (Claude's response with tool_use) -> user message with tool_result blocksstop_reason is end_turn| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 17,402 | 18,984 | +9% | 1 | 1 | 0% | 3,525 | 4,858 | +38% | 0 | 0 | — |
case-02 | pass→pass | 14,455 | 16,538 | +14% | 1 | 1 | 0% | 2,196 | 3,625 | +65% | 0 | 0 | — |
case-03 | pass→pass | 20,288 | 15,293 | -25% | 1 | 1 | 0% | 2,895 | 4,849 | +67% | 0 | 0 | — |
case-04 | pass→pass | 13,302 | 16,594 | +25% | 1 | 1 | 0% | 1,988 | 4,076 | +105% | 0 | 0 | — |
case-05 | pass→pass | 16,437 | 12,864 | -22% | 1 | 1 | 0% | 2,069 | 3,280 | +59% | 0 | 0 | — |
case-06 | pass→pass | 5,477 | 10,353 | +89% | 1 | 1 | 0% | 1,052 | 2,862 | +172% | 0 | 0 | — |
case-07 | pass→pass | 18,654 | 9,027 | -52% | 1 | 1 | 0% | 2,436 | 3,648 | +50% | 0 | 0 | — |
case-08 | pass→pass | 4,052 | 3,808 | -6% | 1 | 1 | 0% | 733 | 2,505 | +242% | 0 | 0 | — |
case-09 | pass→pass | 14,047 | 22,356 | +59% | 1 | 1 | 0% | 2,423 | 4,270 | +76% | 0 | 0 | — |
case-10 | pass→pass | 18,542 | 14,456 | -22% | 1 | 1 | 0% | 2,225 | 3,474 | +56% | 0 | 0 | — |
case-11 | pass→pass | 13,398 | 11,003 | -18% | 1 | 1 | 0% | 1,482 | 3,137 | +112% | 0 | 0 | — |
case-12 | pass→pass | 6,749 | 5,040 | -25% | 1 | 1 | 0% | 1,211 | 2,739 | +126% | 0 | 0 | — |
case-13 | pass→pass | 16,363 | 16,147 | -1% | 1 | 1 | 0% | 2,056 | 3,879 | +89% | 0 | 0 | — |
case-14 | fail→pass | 22,428 | 19,189 | -14% | 1 | 1 | 0% | 2,922 | 4,268 | +46% | 0 | 0 | — |
case-15 | fail→fail | 11,876 | 10,113 | -15% | 1 | 1 | 0% | 2,191 | 3,803 | +74% | 0 | 0 | — |
case-16 | pass→pass | 14,238 | 13,227 | -7% | 1 | 1 | 0% | 1,917 | 3,393 | +77% | 0 | 0 | — |
case-17 | pass→pass | 12,782 | 7,117 | -44% | 1 | 1 | 0% | 1,914 | 3,120 | +63% | 0 | 0 | — |
case-18 | fail→pass | 19,470 | 14,427 | -26% | 1 | 1 | 0% | 2,325 | 3,419 | +47% | 0 | 0 | — |
case-19 | pass→pass | 15,693 | 21,390 | +36% | 1 | 1 | 0% | 2,872 | 4,742 | +65% | 0 | 0 | — |
case-20 | pass→pass | 7,321 | 9,957 | +36% | 1 | 1 | 0% | 1,284 | 2,775 | +116% | 0 | 0 | — |
case-21 | pass→pass | 18,253 | 14,990 | -18% | 1 | 1 | 0% | 2,118 | 3,588 | +69% | 0 | 0 | — |
case-22 | pass→pass | 3,100 | 3,002 | -3% | 1 | 1 | 0% | 524 | 2,305 | +340% | 0 | 0 | — |
case-23 | pass→pass | 15,701 | 12,145 | -23% | 1 | 1 | 0% | 2,007 | 3,159 | +57% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.