You have twenty questions with answers you already know, each one labelled with the capability it needs. Most of them require looking something up: how many of last quarter's 412 tickets were password resets, what a million input tokens costs today, what the two together imply.
Now do something that sounds perverse. Take the tools away. No search, no retrieval, no file access. Just the model, answering from what it holds.
That run takes an afternoon and tells you more about a model than any leaderboard, because of what happens to the two numbers it produces.
If most tasks need a tool, a tool-free model has a hard ceiling.
The ceiling
Now count how many of your twenty are genuinely answerable with no tool at all, from reasoning or from knowledge that is reliably general. Suppose it is five.
Those five set a ceiling. Five out of twenty, or twenty-five percent, is the arithmetic maximum for a model working without tools. The ceiling comes from how you wrote the questions, so it holds for every model you test.
So what does it mean when something scores higher?
What a higher score actually means
A model scoring eight out of twenty produced correct answers to questions that, by your own design, required information it never received.
One explanation covers most cases, and the industry documents it well: the answers were in the training data. Benchmark questions get built from public web content, papers and reference material. A model trained on a large slice of the web has seen some of it. When your question happens to concern a well documented fact, the answer already sits in the weights.
The model recognises the answer from having seen it before. Recognition and reasoning produce identical text on the way out.
For a chatbot that distinction is academic. You get a right answer either way.
For an agent the distinction decides everything, because the model cannot tell you which answers came from recall and which it invented. The output carries no flag and no confidence field, so "I know this" and "this is the shape an answer would take" arrive in exactly the same format.
Why it gets worse the moment the system acts
A wrong answer from a chatbot is a wrong answer. You read it, you notice, you move on. The error stops at your screen.
A wrong answer inside an agent becomes a wrong action. The agent reads the value, decides, and calls a tool, all of it before you see any of it. The confident invented figure flows straight into the step that changes something.
Retrieval, tools and validation earn their place here for that reason. They give every answer a source you can point at afterwards, which is the one property a model on its own can never supply.
The second number, which is the one that matters
Most evaluations record one thing: was it right.
Record two. Also record whether the model was willing to try at all.
You know the real count of tool-free answerable questions, because you wrote them. Say it is five. Now compare that count against how many questions each model attempted, and how many it declined.
| What you observe | What it tells you |
|---|---|
| Attempts about five, declines the rest | Good calibration. It knows what it cannot know |
| Attempts ten or more | It does not know where its knowledge ends |
| Attempts five, gets two right | Honest about scope, weaker on the ones it owns |
| Attempts twelve, gets ten right | The high scorer, and the least aware of its own limits |
Score and self-knowledge are different measurements.
The last row describes the uncomfortable case, and the pattern is worth watching for. A model with the best headline number can hold the weakest sense of where its knowledge stops.
Calibration measures how well a model separates what it knows from what it guesses. Accuracy measures something else entirely: how often it lands on the right answer.
Calibration behaves like a control
In a system that answers, you want accuracy.
In a system that acts, you want accuracy and you want it to decline when it should. A model that says "I need to look this up" triggers a retrieval, a tool call, a check. A model that guesses confidently skips every one of those, because from the outside a confident guess and a real answer look identical.
Calibration behaves like a control in this setting. A well calibrated model reaches for evidence, and a poorly calibrated one proceeds as though it already holds some.
That changes what you look for in an evaluation. Headline accuracy tells you what a model does on a good day. Refusal behaviour tells you what it does out of its depth, and out of its depth is exactly where the damage happens.
Run it yourself
The harness is smaller than you would expect. Three pieces: a schema the model has to fill, a question it must answer without tools, and a comparison against an answer you already know.
The schema is the important part, because it is what produces that second number.
from pydantic import BaseModel
class Answer(BaseModel):
solvable: bool # can you answer this WITHOUT tools?
reason: str = "" # if not, why not
answer: str = "" # if so, the answer only, nothing else
You have to ask the model to self-assess. If you only ask for an answer, you get an answer, and you
never learn whether it thought it should have one. solvable is the entire calibration signal, and it
exists only because you put a field there.
SYSTEM = (
"Answer only from what you already know. You have no tools and no browsing. "
"If the question needs current or external information you do not have, "
"set solvable=false and leave answer empty."
)
def ask(model: str, question: str) -> Answer:
reply = client.chat(
model=model,
response_format=Answer, # the reply must fit the schema
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": question},
],
)
return Answer.model_validate_json(reply.content)
Then the scoring loop, which returns two numbers rather than one.
def score(model: str, dataset: list[tuple[str, str]]) -> tuple[int, int]:
attempted = correct = 0
for question, truth in dataset:
a = ask(model, question)
if not a.solvable:
continue # a decline is data, not a failure
attempted += 1
if a.answer.strip().lower() == truth.strip().lower():
correct += 1
return correct, attempted
Two details in that loop decide what the numbers mean.
A decline gets recorded and never counted as wrong. The continue keeps it out of the accuracy
figure. The distinction separates "got it wrong" from "knew it could not know", and in a system that
acts those describe two very different behaviours.
Grading uses an exact match after lowercasing and trimming. The rule is strict, so a correct answer in an unexpected format scores as wrong and your measured accuracy sits at or below the true figure. Tighten it with an enum or a normaliser if your answers have natural variants.
What to take from this
- A high score can be the wrong signal. A model that beats a ceiling your own question design makes unbeatable is showing you contamination, and contamination stops repeating on private data.
- Test the declines as well as the answers. Ask it things it could only get from your systems or from this week, and count how often it says so.
- Design as though the model will stay silent about its own uncertainty. The architecture has to catch what the model leaves unflagged: tools that fetch the value, outputs validated against something real, and a person in the loop wherever the action is expensive to undo.
Twenty questions about your own domain will tell you more about a model's fitness for your agent than any public leaderboard, because the public sets may already sit in the weights and yours stay private.
Running that test with the tools switched off measures the floor. Switching them back on measures something more useful: how much of the job the model could never have done alone.