I fixed a flaky agent last week by removing tools.
Not by tuning the prompt.
Not by adding more examples.
Not by giving GPT-5.4 a longer lecture about how to behave.
I deleted half the tool list, tightened the schemas, and the agent immediately got less stupid.
That sounds obvious in hindsight, but I see teams make the same mistake over and over: when tool calling gets unreliable, they assume the model needs more instructions.
Usually it needs fewer choices.
The setup: too many tools that all kind of did the same thing
The agent had access to:
- Slack
- Notion
- HubSpot
- a couple of internal APIs
Pretty normal stack for an automation workflow.
The problem: GPT-5.4 kept calling the wrong function. Claude Opus did too, just with slightly better posture.
My first move was the classic bad move:
- add rules
- add exceptions
- add examples
- add anti-examples
- add more prompt text explaining when to use which tool
The prompt got bigger.
The behavior did not get better.
That was the real clue: the prompt was no longer the bottleneck. The tool surface was.
The real bug: overlapping tools create semantic collisions
If you give a model 15 functions that all smell like “retrieve customer info,” you have not created flexibility.
You have created ambiguity.
Example:
find_contact_hubspotget_account_notes_notionsearch_customer_records_internalsearch_slack_customer_thread
A human can see the differences.
A model sees four partially overlapping stories and has to rank them before it can even start solving the user request.
That’s where tool calling reliability starts to break.
Anthropic is pretty direct about this in their tool use docs: Claude chooses tools based on the user request and the tool description. They recommend good descriptions and input_examples.
That sounds like a prompt-writing problem.
It’s mostly a boundary-design problem.
If two tool descriptions overlap, the model has to guess which one you meant.
And OpenAI is pushing the same idea from another angle. Their function-calling guidance now recommends deferring rarely used tools with tool_search instead of stuffing everything into a single request.
That’s not subtle.
It’s basically: if your function list looks like a junk drawer, stop blaming the model.
Bigger toolboxes make agents worse faster than most people expect
Once you cross a certain number of tools, the model is doing semantic ranking work before it does task work.
That’s expensive in tokens, fragile in behavior, and hard to patch with prompt text.
OpenAI’s newer Agents building blocks made this more official, but most of us learned it the annoying way in production:
runtime tool selection beats front-loading everything.
The useful question is no longer:
How do I make the prompt smarter?
It’s:
How do I make the decision surface smaller?
Three approaches, and only two hold up in real systems
| Approach | What actually happens |
|---|---|
| OpenAI function tools | JSON-schema-defined functions, strict: true for schema adherence, tool_search to defer rarely used tools |
| Anthropic client tools |
name, description, input_schema, optional input_examples, optional defer_loading, and disable_parallel_tool_use
|
| Prompt-only tool disambiguation | No hard enforcement, relies on instructions alone, gets worse as tool count and overlap increase |
My opinion: prompt-only tool disambiguation is fine for demos and terrible for real automations.
If your n8n, Make, Zapier, or custom agent flow touches Slack, HubSpot, and Notion in production, you want contracts, not vibes.
Small schemas beat smart prompts
The boring weather-tool examples in the docs are actually great examples.
Why do they work?
Because they are narrow.
One required field. Maybe one enum. Clear intent. Very little room to improvise.
OpenAI-style tool
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
},
"strict": true
}
Anthropic-style tool
{
"name": "get_weather",
"description": "Get the current weather for a given location.",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature"
}
},
"required": ["location"]
}
}
That’s the whole trick in miniature.
Narrow tools are easier to choose and harder to misuse.
What I changed first
Here’s the order I’d use again.
1) Remove overlapping tools from the same turn
If multiple tools can answer basically the same request, don’t expose them all at once.
Bad:
find_customer_hubspotlookup_customer_notionsearch_customer_records
Better:
- route first
- then expose only the relevant subset
If you’re using OpenAI, use tool_search or equivalent runtime loading patterns.
If you’re using Anthropic, lean on defer_loading patterns and tighter tool registration.
2) Rename tools so they sound mutually exclusive
Tool names are not decoration.
They are part of the interface.
Bad:
customer_lookup_v2
record_search
account_tool
Better:
get_customer_billing_status
read_customer_support_notes
find_recent_slack_thread_for_customer
Those names create separation.
3) Shorten descriptions by increasing contrast
A long description is only useful if it defines boundaries.
Bad description:
Use this tool to retrieve customer information, account history, support context, messages, records, and related updates.
Better description:
Use this tool only to fetch billing status from HubSpot. Do not use it for support notes, Slack history, or internal CRM records.
That second one gives the model a much cleaner decision.
4) Constrain arguments hard
Use enums.
Use required fields sparingly.
Avoid giant free-form objects.
Bad schema:
{
"type": "object",
"properties": {
"customer": { "type": "object" },
"options": { "type": "object" },
"mode": { "type": "string" }
}
}
Better schema:
{
"type": "object",
"properties": {
"customer_id": { "type": "string" },
"source": {
"type": "string",
"enum": ["hubspot", "internal_crm"]
}
},
"required": ["customer_id", "source"]
}
5) Turn off parallel tool chaos when side effects matter
If a workflow can send Slack messages, update HubSpot, and write to Notion, parallel tool use is not always a feature.
Sometimes it’s just a faster way to create cleanup work.
For Anthropic-style control:
{
"tool_choice": {
"type": "auto",
"disable_parallel_tool_use": true
}
}
I think this setting is underrated.
One clean action beats three half-right actions every time.
A sneaky failure mode: better descriptions that make selection worse
This one catches teams a lot.
You improve every tool description.
You add more detail.
You add more context.
You feel good about it.
And then tool selection still sucks.
Why?
Because you made every description richer in the same vocabulary.
Now all five tools mention:
- customer
- account
- support
- history
- messages
- updates
So yes, the descriptions are better written.
They are also more semantically similar.
That can make selection harder, not easier.
The fix is contrast.
A good tool description should say:
- what this tool does
- when to use it
- when not to use it
- which nearby tool should be used instead
That last one matters more than most people realize.
Prompts still matter, just not for this job
This is not an argument against prompts.
It’s an argument against using prompts to compensate for sloppy tool design.
Here’s the split I use now.
Use prompts for policy
Examples:
- never issue a refund without manager approval
- never message a customer after 9 PM local time
- ask for confirmation before deleting a Notion page
Use schemas and tool boundaries for behavior
Examples:
- valid enum values
- required fields
- mutually exclusive tools
- one-tool-per-turn limits for risky actions
If you mix those up, you end up writing a 900-word prompt to solve a 9-line schema problem.
The safest pattern for side effects is slower on purpose
Anthropic’s client-tool flow gets this right.
The model emits a tool_use block.
Your app executes it.
You send back a tool_result.
Then the model continues.
That rhythm is slower than letting an agent freestyle through six actions in one shot.
It is also much safer for:
- customer data
- billing actions
- internal systems
- external APIs with side effects
I’d rather have GPT-5.4 or Claude Opus take one careful step than confidently do three wrong things in parallel.
A practical cleanup checklist
If your agent keeps choosing the wrong tool, this is the first pass I’d do.
# 1) List every tool exposed in a single turn
# 2) Group tools by overlapping intent
# 3) Remove or defer duplicates
# 4) Tighten names and descriptions
# 5) Add enums / required fields / strict schemas
# 6) Disable parallel tool use for side-effecting actions
Or more explicitly:
[ ] Are multiple tools able to satisfy the same request?
[ ] Do tool names clearly signal exclusive intent?
[ ] Do descriptions say when NOT to use the tool?
[ ] Are optional fields actually necessary?
[ ] Can free-form inputs be replaced with enums?
[ ] Should this workflow allow parallel actions at all?
Why this matters more in production automations
A toy demo with three tools can survive on vibes.
A real workflow can’t.
Especially if you’re running agents inside:
- n8n
- Make
- Zapier
- OpenClaw
- custom internal agent frameworks
Once a workflow touches Slack, Notion, HubSpot, Discord, internal APIs, and customer data, tool ambiguity becomes an operational problem.
And it gets expensive fast if you’re paying per token while the model burns cycles deciding between five overlapping functions.
That’s one reason I’m bullish on Standard Compute’s model for this kind of work: if you’re iterating on agent behavior, testing tool schemas, and running automations continuously, flat-rate API access is a lot easier to live with than token anxiety on every debugging pass.
If your stack already speaks the OpenAI API, the swap is pretty painless.
The main takeaway
When LLM tool use starts failing, most people add things:
- more prompt
- more examples
- more fallback logic
- more tools
The highest-leverage fix is often subtraction:
- fewer active tools
- fewer overlapping descriptions
- fewer optional fields
- fewer chances for parallel side effects
That was the fix for me.
Not a smarter speech.
Not a giant system prompt.
Not another layer of “be careful” instructions.
Just fewer tools competing for attention.
If your agent keeps making bad tool choices, try deleting half the toolbox before you write another 500 tokens of prompt.
You might be solving a UI problem for the model, not an intelligence problem.
Top comments (1)
One place where you can't delete the overlap: UI-control tools. Clicking an element by id and clicking at x,y coordinates are both "click", and you need both, because some apps expose nothing useful in the accessibility tree. What helped there was writing the coordinate tool's description as a precondition instead of a purpose: "use only when the element list has no match for the target". The model stopped reaching for coordinates first once the rule pointed at state it had already seen, not at intent it had to guess.
The other cheap lever: when the wrong tool gets called anyway, make its error name the right one ("no element 14 in the current list, call list_elements first"). A tool result steers the next turn more reliably than another prompt line.
Did tool_search / defer_loading cost you a noticeable extra round trip on the first call, or was that noise next to the misroutes it removed?