DeepEvaldeepeval 4.2 ยท Python 3.9+
0%
1
Curious builder0 XP earned ยท 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
23 small wins to finish your pathNext lesson โ†’

Tool correctness: did it call the right tool

An agent that answers from memory instead of looking something up will often sound right. The cheapest way to catch it is to check which tools it called.

Example
from deepeval.metrics import ToolCorrectnessMetric
from pretend_judge import PretendJudge
from deepeval.test_case import LLMTestCase, ToolCall

used_the_search = LLMTestCase(
    input="Where is order A17?",
    actual_output="Order A17 shipped on 3 March by courier.",
    tools_called=[ToolCall(name="retrieve")],
    expected_tools=[ToolCall(name="retrieve")],
)
tools = ToolCorrectnessMetric(model=PretendJudge())
tools.measure(used_the_search)
print(tools.score, tools.is_successful())
print(tools.reason)

tools_called is what the run actually used, expected_tools is what it should have used, and both are lists of ToolCall. The bot searched, which is what the question needed, so the score is 1.

This metric is not judged by a model. It compares the two lists and counts, which is why the reason reads like a tally rather than an opinion. It still wants a model when you create it, and passing the stand-in judge is the simplest way to satisfy that: nothing ever asks it anything.

An agent that did not bother

Example
guessed = LLMTestCase(
    input="Where is order A17?",
    actual_output="I think it shipped last week.",
    tools_called=[],
    expected_tools=[ToolCall(name="retrieve")],
)
tools.measure(guessed)
print(tools.score, tools.is_successful())
print(tools.reason)

No tools called at all, and a confident answer. Every metric so far might have liked that sentence. This one fails it, because the run never looked anything up.

An agent that did too much

Example
too_many = LLMTestCase(
    input="Where is order A17?",
    actual_output="Order A17 shipped on 3 March by courier.",
    tools_called=[ToolCall(name="retrieve"), ToolCall(name="refund")],
    expected_tools=[ToolCall(name="retrieve")],
)
tools.measure(too_many)
print(tools.score, tools.is_successful())

The search was called, and so was a refund tool nobody asked for, and the score is still 1. By default the metric asks one question: was every expected tool called? An extra call is not a failure, which matters, because an agent that also refunded something would pass this check.

Example
exact = ToolCorrectnessMetric(model=PretendJudge(), should_exact_match=True)
exact.measure(too_many)
print(exact.score, exact.is_successful())

should_exact_match requires the two lists to be the same, so now the extra call fails. should_consider_ordering is the middle setting: the same tools, in the order you expected.

Where the tool list comes from
In a traced run, DeepEval collects the tool spans for you. Writing the lists by hand, as here, is the clearest way to see what the metric does with them.
Try it yourself
  • Turn on should_exact_match and measure the first test case again.
  • Expect two tools and call them in the other order, with and without should_consider_ordering.

This is what real progress feels like.