# When the model picks the wrong tool

Web page: https://academy.memberpress.ai/courses/tool-use/l3/

Lesson 3 of 9 in [Tool Use — From Calculator to Agent](https://academy.memberpress.ai/courses/tool-use/), 18 min.

Video: https://www.youtube.com/watch?v=r3T12z6wrJc

You have three tools defined. The user asks a question that should trigger one of them. The model answers from memory instead, or calls the wrong one, or calls none. The error is not in your handler. The error is in the signals you gave the model to make the decision — the tool name, the description, and the schema constraints. These are the only inputs the model has. Fix them and the behavior changes.

**Tool selection is a model decision, not a random one**

Claude chooses which tool to call — and whether to call any at all — based on three signals in order of importance: the tool's name, its description, and the constraints in its input schema. These signals are read every time. Change any of them and the selection behavior changes. This lesson is about reading the signals the model actually uses, not guessing at them.

**Version 1 — the bad definition**

```python
# scripts/selection_demo.py
import anthropic

client = anthropic.Anthropic()

tools_v1 = [{
    "name": "db_op",
    "description": "Performs a database operation.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string"}
        },
        "required": ["query"]
    }
}]

r = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    tools=tools_v1,
    messages=[{"role": "user", "content": "What orders does customer 42 have?"}]
)
print(r.stop_reason)    # often "end_turn" — model answers from memory instead of calling
print(r.content)
```

The description "performs a database operation" gives the model nothing. It cannot tell whether this tool reads, writes, which table it touches, or what inputs make sense. The model defaults to answering from its weights — which for a question about a specific customer's orders will produce a fabricated or generic answer.

**Version 2 — improved description**

A good description names what the tool does, names what it explicitly does not do, and provides at least one example of when to use it. These three elements are the minimum for reliable selection.

```python
tools_v2 = [{
    "name": "query_orders",
    "description": (
        "Query the orders table. Returns the ten most recent orders for a given "
        "customer ID. Use this when the user asks about order history, recent "
        "purchases, or what a specific customer has bought. Do NOT use for "
        "inventory lookups or product searches."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "customer_id": {"type": "integer", "description": "Numeric customer ID"}
        },
        "required": ["customer_id"]
    }
}]

r = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    tools=tools_v2,
    messages=[{"role": "user", "content": "What orders does customer 42 have?"}]
)
print(r.stop_reason)                           # → tool_use
print(r.content[0].name)                       # → query_orders
print(r.content[0].input)                      # → {"customer_id": 42}
```

**Version 3 — schema constraints**

Schema constraints are the second dial. Add them after you have a good description. They do not replace the description — they complement it by eliminating impossible inputs before the call reaches your handler.

```python
tools_v3 = [{
    "name": "query_orders",
    "description": (
        "Query the orders table. Returns up to 10 recent orders for a given "
        "customer ID and optional status filter. Use for order history queries."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "customer_id": {
                "type": "integer",
                "minimum": 1,
                "description": "Numeric customer ID, must be positive"
            },
            "status": {
                "type": "string",
                "enum": ["pending", "shipped", "delivered", "cancelled"],
                "description": "Filter by order status. Omit to return all statuses."
            },
            "limit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 10,
                "default": 10
            }
        },
        "required": ["customer_id"]
    }
}]
```

The `enum` on `status` means the model will only pass one of those four values. It will not invent `"processing"` or `"in_transit"`. The `minimum`/`maximum` on `limit` means it will not request 1000 rows. Schema constraints shrink the surface of malformed calls without requiring you to validate inside the handler.

**`tool_choice` — use sparingly**

```python
# Force the model to call a specific tool, ignoring its selection logic
r = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    tools=tools_v3,
    tool_choice={"type": "tool", "name": "query_orders"},
    messages=[{"role": "user", "content": "Tell me about customer 42."}]
)
```

`tool_choice: {"type": "tool", "name": "..."}` bypasses selection entirely. Use it for structured-extraction steps where you are certain the tool should fire — not as a workaround for a bad description. If you need `tool_choice` to get reliable behavior, fix the description first. `tool_choice` is a shortcut; a bad description is still a broken contract.

`tool_choice: {"type": "any"}` forces the model to call some tool without specifying which one. Rarely useful outside evaluation harnesses.

**When to merge tools instead of fixing descriptions**

If two tools overlap enough that the model consistently picks the wrong one despite good descriptions, the tools may be conceptually too similar. Merge them into one tool with a `type` or `action` parameter:

```python
# Instead of query_orders and query_returns as separate tools:
{
    "name": "query_customer_records",
    "description": "Query order or return records for a customer.",
    "input_schema": {
        "type": "object",
        "properties": {
            "record_type": {"type": "string", "enum": ["orders", "returns"]},
            "customer_id": {"type": "integer"}
        },
        "required": ["record_type", "customer_id"]
    }
}
```

Merging removes the ambiguity at the selection step by making it impossible to call the wrong tool — there is only one tool.

**If you get stuck**

*Model consistently returns `end_turn` instead of using the tool:* The description does not match the user's phrasing. The model cannot tell the tool applies. Add concrete use-case language to the description — the exact phrases the model is likely to see from users.

*Model calls the tool but passes bad inputs:* Add schema constraints. Check that `required` lists all fields the handler actually needs. If the model passes `null` for a required field, it read the schema and could not determine the value — your description is not specific enough about what to pass.

*Model calls the wrong tool when you have many:* Two descriptions overlap in vocabulary. Either split them more precisely (distinct verbs, distinct nouns, explicit "do NOT use for X") or merge the overlapping tools. Running both prompts side by side against the same question surfaces the overlap quickly.

**Cross-reference**

The `tool_choice` parameter interacts with the multi-turn loop in l7. When you force a tool call in an agent, the model cannot choose to stop — you must ensure the iteration cap is in place or the loop will never terminate on a forced-tool turn.

**Deliverable**

`scripts/selection_demo.py` — run the same prompt against all three tool versions, log `stop_reason` and `content[0]` for each. Add a comment above each version explaining what changed and why the behavior shifted. The v1 run should show `end_turn`; v2 and v3 should show `tool_use` with a valid input dict.

**Further reading**

- ["Switch Channels, Don't Dig"](https://sethshoultes.com/blog/switch-channels-dont-dig.html) — when the model keeps picking the wrong tool, the operator's instinct is to add constraints. The essay names why that fails and what to do instead.
- ["Skipping Is a Feature"](https://sethshoultes.com/blog/skipping-is-a-feature.html) — sometimes the right behavior is no tool call at all, and the description has to license that explicitly.

## Try these prompts

- Rewrite this tool description to be a reliable decision rule: "Performs a database operation." The tool queries the orders table for a given customer ID.
- Write a tool description that includes a positive use-case trigger and a negative exclusion, for a tool that queries order history but should not be used for inventory lookups.
- Explain when to use tool_choice: {"type": "tool", "name": "..."} and when to fix the description instead. Give a concrete example of each.

Next: [Database read tools](https://academy.memberpress.ai/courses/tool-use/l4/)
