
Why Your AI Agent Calls the Wrong Tool (and How to Test Tool Use)
A customer wrote in about a broken pair of headphones. The agent read the message, understood it perfectly, and issued a refund — for the wrong order. Not because it misunderstood the customer, but because it filled the order ID field with a number it had seen four turns earlier. Every sentence it wrote about the refund was fluent, polite, and wrong. Nothing in the transcript looked like a failure. The only evidence was in the accounting.
Tool use is where the words start costing money
Up to the moment an agent calls a tool, a mistake is just text — embarrassing, recoverable, invisible to your ledger. After it calls a tool, a mistake is an action: a refund issued, an email sent to the wrong supplier, a row written, a ticket closed. That is why tool use deserves its own grade rather than being folded into "is the agent smart". Smart is not the question. The question is whether it reaches for the right instrument, holds it correctly, and then actually looks at what came back.
Three failures, and they are not the same failure
In the Clinic's battery, tool use breaks into three tests, because an agent can be excellent at one and dangerous at the next:
- —Tool selection — does it reach for the right tool? The classic miss is answering an order-status question out of the help-center search because the wording sounded like a policy question.
- —Argument validity — are the parameters real? Watch for hallucinated fields the schema never declared, IDs carried over from earlier turns, and integers arriving as strings.
- —Result integration — having called the tool, does it use what came back? This is the one teams skip, and it is the one that produces confident, fluent, fabricated answers.
An agent that calls no tool gives you a bad answer. An agent that calls the tool and ignores the result gives you a bad answer with a receipt attached.
The failure mode nobody tests: the agent grading its own homework
Here is a trap worth naming, because we walked into it ourselves. If you evaluate an agent from a transcript alone, the tool results in that transcript were written by the agent. It supplies the evidence and is then graded against it — it writes both the exam and the answer key. We ran exactly this check on a specimen agent: it emitted a tool result for an order that did not exist, invented a $150 refund inside it, and repeated the number in its closing answer. Our own grader scored that transcript full marks for truthfulness, on the grounds that every claim was "directly based on tool call results". The claims were. The tool results were fiction.
The lesson generalises past our harness: any tool-use test that trusts agent-authored output is measuring fluency, not tool use. Either hold a fixture of expected results and diff the agent's calls against it — which is what the probe below does — or mark the tool-grounded tests unverifiable and exclude them from the score. Quietly trusting the agent's own account is the one thing an evaluation must never do.
A probe you can run this afternoon
This audit declares three tools with real schemas, sends six scenarios where the correct first call is known in advance, validates the arguments against the declared schema, and then feeds back a fixed, harness-owned result to check whether the final answer actually reflects it. Because the results come from the harness rather than the agent, the third number means something.
"""Tool-use audit: does the agent pick the right tool, fill it correctly, and use what came back?"""
import json
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-opus-4-8"
TOOLS = [
{
"name": "get_order",
"description": "Look up one order by its ID. Use for order status, contents, shipping.",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
{
"name": "issue_refund",
"description": "Refund an order. Only call after the order has been looked up.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"amount_cents": {"type": "integer"},
},
"required": ["order_id", "amount_cents"],
},
},
{
"name": "search_help_center",
"description": "Search policy and how-to articles. Use for general questions, not order data.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
]
SCHEMAS = {t["name"]: t["input_schema"] for t in TOOLS}
# Each case: the user turn, the tool a correct agent must reach for first, and a
# distinctive token the final answer has to carry if the result was really used.
CASES = [
("Where is order A-1029?", "get_order", "DHL"),
("What is your return window?", "search_help_center", "30 days"),
("Order B-7781 arrived broken, I want my money back.", "get_order", "B-7781"),
("Has A-1029 shipped yet, and what was in it?", "get_order", "Kestrel"),
("How do I change the card on file?", "search_help_center", "Billing"),
("Refund A-1029, it never came.", "get_order", "DHL"),
]
# Ground truth the harness returns, so "did it use the result" is checkable.
RESULTS = {
"get_order": {
"carrier": "DHL",
"eta": "2026-08-04",
"items": ["1x Kestrel headphones"],
"order_id": "A-1029",
},
"search_help_center": {
"article": "Returns & Billing",
"body": "Returns accepted within 30 days. Billing details change under Account > Billing.",
},
"issue_refund": {"refund_state": "processed"},
}
def check_args(name, args):
"""Validate arguments against the declared schema. Returns a list of defects."""
schema = SCHEMAS[name]
defects = []
for key in schema["required"]:
if key not in args:
defects.append(f"missing required arg '{key}'")
for key, val in args.items():
if key not in schema["properties"]:
defects.append(f"hallucinated arg '{key}'")
continue
want = schema["properties"][key]["type"]
ok = isinstance(val, str) if want == "string" else isinstance(val, int)
if not ok:
defects.append(f"arg '{key}' should be {want}, got {type(val).__name__}")
return defects
selection_hits = argument_clean = integration_hits = 0
integration_attempts = 0
for prompt, expected_tool, marker in CASES:
msgs = [{"role": "user", "content": prompt}]
first = client.messages.create(
model=MODEL, max_tokens=1024, tools=TOOLS, messages=msgs
)
calls = [b for b in first.content if b.type == "tool_use"]
if not calls:
print(f"NO TOOL CALL | {prompt[:44]:44} | expected {expected_tool}")
continue
call = calls[0]
picked_right = call.name == expected_tool
selection_hits += picked_right
defects = check_args(call.name, call.input)
argument_clean += not defects
# Feed the real result back and see whether the answer reflects it.
msgs += [
{"role": "assistant", "content": first.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": call.id,
"content": json.dumps(RESULTS[call.name]),
}
],
},
]
final = client.messages.create(
model=MODEL, max_tokens=1024, tools=TOOLS, messages=msgs
)
answer = " ".join(b.text for b in final.content if b.type == "text")
if picked_right:
integration_attempts += 1
used = marker.lower() in answer.lower()
integration_hits += used
else:
used = None
flag = "ok " if picked_right and not defects else "FAIL"
print(
f"{flag} | {prompt[:44]:44} | picked {call.name:19} "
f"| args {'clean' if not defects else '; '.join(defects)} "
f"| used result: {used}"
)
n = len(CASES)
print(f"\ntool-selection {selection_hits}/{n}")
print(f"argument-validity {argument_clean}/{n}")
print(
"result-integration "
f"{integration_hits}/{integration_attempts}"
if integration_attempts
else "result-integration n/a — no correct tool call to integrate"
)Reading the three numbers
Selection is usually the healthiest of the three on a modern model, which is why teams stop there and declare tool use solved. Argument validity is where schema drift shows up: if you added a field last sprint and the score dropped, the agent is still filling the old shape. Result integration is the number to actually fear — a low score there means the agent is calling your tools as decoration and answering from prior, and every one of those answers will read as confident. Note also the case the printout marks NO TOOL CALL: an agent that answers an order question with no lookup at all has failed before selection was even in play.
One honest caveat, the same one we apply to our own reports: if a scenario produced no correct tool call, there is nothing to integrate, and the probe prints the integration score over the number of attempts rather than over six. A ratio with a shrinking denominator is not a better score — it is less evidence. Report coverage alongside the number, or you will congratulate yourself for a test that never ran.
How to raise the score
Tool use responds to constraint far better than to prompting. In rough order of impact:
- —Tool allowlists per task type — the cheapest fix for selection errors is making the wrong tool unreachable in that context.
- —Schema-locked arguments with pre-call validation — validate against the declared schema before the call executes, and reject hallucinated fields rather than passing them through.
- —Mandatory result citation — require the answer to quote a field from the tool result. An agent that must cite cannot silently answer from memory.
- —Never let an ID cross turns implicitly — most wrong-order refunds are a stale identifier surviving a topic change.
Re-run the probe after each change. The pattern to expect is selection near-perfect early, arguments cleaning up quickly once validation lands, and result integration moving last and slowest — because that one is a habit, not a capability.
Run it yourself, or send it to the Clinic
The probe above audits one dimension with fixtures you maintain by hand, and it is worth wiring into CI before every deploy. What it will not do is weigh tool use against the other five dimensions, run the adversarial passes, or hand you a grade you can show a customer. The Clinic does: an 18-test, six-dimension diagnostic with tool-use quality as one axis, an honest A–F, and any dimension the evidence could not support marked NOT TESTED and excluded rather than guessed. There is a dedicated breakdown of this dimension at the tool-use page under /clinic, and if you want the wider methodology first, our guide on how to test an AI agent covers the framework. Watch the free demo scan grade a specimen agent — ten seconds, six probes, one letter — and you will see what a wrong tool call costs on the radar. Not sure tool use is your problem? Start from the six modes and work down.
Run a free demo scan on a specimen agent — 10 seconds, 6 probes, one honest letter grade. The real scan is $0.99.


