TL;DR
An agent doesn’t fail by saying the wrong words, it fails by taking the wrong action. It calls refund_order when it should have called check_status, or it calls the right tool with a malformed argument, and now there’s a real-world side effect. This is the eval everyone skips because it’s invisible in the chat transcript. The benchmarks show how hard it is: on the Berkeley Function Calling Leaderboard V4, even strong models top out around 0.73, and realistic multi-turn agent benchmarks are harder still. Tool-selection and argument correctness is the difference between a helpful agent and a harmful one.
The failure that doesn’t show up in the transcript
When you evaluate a chatbot, you read what it said. When you evaluate an agent, reading what it said is almost beside the point, what matters is what it did. An agent can produce a perfectly polite, fluent message (“I’ve processed your refund!”) while having called the wrong API, called the right API with the wrong amount, or called a destructive tool it should never have touched. The text is fine. The action is a disaster. And because most eval pipelines grade the final text, the action goes ungraded.
This is why tool-use correctness is the eval everyone skips: it requires inspecting the agent’s trajectory, the sequence of tool calls and arguments, not just its output. It’s more work to set up, so teams default to grading the words and quietly ship an agent whose actions nobody verified. In a system that can move money, send emails, or modify records, that’s the most dangerous corner to cut.
delete_account() call cannot. The eval has to grade the call, the tool chosen, and every argument, before the side effect happens, ideally in a sandbox.
The dimensions of tool-use correctness
“Did it use the tool right” decomposes into several independent questions, and the benchmark designers have done this decomposition for us. BFCL V4 evaluates across six dimensions: simple calls, parallel invocations, multiple-function selection, relevance detection (knowing when not to call a function), multi-turn interactions, and multi-step reasoning, using over 2,000 question-function-answer pairs (Gorilla / BFCL V4). For your own agent, the practical breakdown is:
- Tool selection, given the request, did it choose the correct tool from the available set?
- Abstention, when no tool applies, did it correctly decline to call one instead of forcing a bad fit? (The relevance-detection dimension, and a common failure.)
- Argument extraction, are the parameters correct in name, type, and value, especially for nested objects and enums?
- Sequencing, in multi-step tasks, did it call tools in a valid order, passing outputs forward correctly?
- Parallelism, when independent calls could run together, did it batch them rather than serializing or duplicating?
The benchmarks say this is genuinely hard
It’s tempting to assume modern models “just do” function calling well. The leaderboards say otherwise. On the harder agentic version of BFCL V4, leading models score around 0.73, meaning more than a quarter of evaluated tool-use scenarios are handled incorrectly (BFCL-V4 Leaderboard). And BFCL is relatively structured. The more realistic τ-bench (tau2-bench) from Sierra Research, which simulates customer-service scenarios where an agent must use API tools to resolve requests while following company policy across multiple turns, is harder still, because it tests tool use under the constraints real products impose. The gap between “demo works” and “reliable in production” is exactly this quarter-of-cases failure rate.
Evaluate the trajectory, not the chatter
The core technique: for each eval case, define the expected tool call(s) and assert the agent’s actual trajectory matches on selection and arguments, in a sandbox where the tools are mocked so destructive calls have no real effect.
# Grade the trajectory: right tool, right args, right (non-)call
def eval_tool_use(agent, case):
trace = agent.run(case.request, tools=SANDBOXED_TOOLS) # no real side effects
calls = trace.tool_calls
# 1. Abstention: should it have called anything at all?
if case.expected_calls == []:
return {'correct': calls == [], 'reason': 'should_not_have_called'}
# 2. Tool selection: did it pick the right tool(s)?
selected = [c.name for c in calls]
expected = [c.name for c in case.expected_calls]
if selected != expected:
return {'correct': False, 'reason': f'wrong_tool: {selected} != {expected}'}
# 3. Argument correctness: name, type, and value (incl. nested/enums)
for actual, exp in zip(calls, case.expected_calls):
diff = arg_diff(actual.args, exp.args)
if diff:
return {'correct': False, 'reason': f'bad_args: {diff}'}
return {'correct': True}
# Aggregate into per-dimension scores and gate the build
def tool_use_gate(agent, eval_set):
by_dim = defaultdict(list)
for case in eval_set:
r = eval_tool_use(agent, case)
by_dim[case.dimension].append(r['correct'])
scores = {d: sum(v)/len(v) for d, v in by_dim.items()}
# Destructive-tool selection must be near-perfect; informational can be lower
assert scores.get('destructive_selection', 1.0) >= 0.99, \
f"unsafe tool selection: {scores['destructive_selection']:.1%}"
assert scores.get('abstention', 1.0) >= 0.95, \
f"agent calls tools when it shouldn't: {scores['abstention']:.1%}"
return scores
get_weather call is a nuisance; a wrong issue_refund or delete_record call is an incident. Set per-tool thresholds, destructive and irreversible tools demand near-perfect selection and argument accuracy, while read-only tools can tolerate more error.
The argument-correctness trap
Tool selection gets the attention, but argument errors are the quieter, more frequent failure. The model picks the right tool and then fills it with a hallucinated order ID, a date in the wrong format, an amount off by a decimal place, or an enum value that doesn’t exist in your schema. These pass any check that only verifies which tool was called. Your eval must diff arguments structurally, comparing types, required fields, and value correctness against ground truth, and it should be strictest on the arguments that drive irreversible effects, like amounts and target identifiers.
Abstention is the dimension teams forget
Of all the tool-use dimensions, the one teams most reliably skip is abstention, knowing when not to call a tool at all. It’s why BFCL V4 carves out relevance detection as its own axis. The failure looks like this: a user asks something the agent can’t actually help with given its tools, and instead of saying so, the model forces a call to the nearest-looking tool with made-up arguments, because it has been trained to be helpful and a tool is right there. Now you have an action taken on a request that warranted none, a status lookup on an order that doesn’t exist, a refund initiated for a transaction that was never made.
Abstention failures are insidious because they pass any eval that only checks “was the right tool called when a tool was needed.” You have to include negative cases, requests where the correct behavior is to call nothing and explain, and assert the agent declines. An agent that always finds a tool to call is not a capable agent; it’s an over-eager one, and over-eagerness with real-world tools is exactly how side effects happen on inputs that never asked for them.
The bottom line
Agents fail in their actions, not their words, and the action is exactly what most eval pipelines never look at. The benchmarks make the stakes concrete: even strong models handle a quarter of tool-use scenarios incorrectly on BFCL V4, and realistic multi-turn benchmarks like τ-bench are harder still. Evaluate the trajectory, tool selection, abstention, argument correctness, sequencing, parallelism, in a sandbox before real side effects can occur, decompose it into per-dimension scores, and gate by blast radius so destructive tools demand near-perfect accuracy. The agent that says the right thing while doing the wrong thing is the one that ends up in your incident review. Grade the call, not the chatter.
Ship AI on Evidence, Not Vibes
alt.qa Eval turns "seems fine" into measurable pass/fail, continuous evaluation, regression gates, and groundedness scoring for your AI outputs.
Try alt.qa Free →