DEV Community

marcelotaparelli
marcelotaparelli

Posted on Originally published at marcelotaparelli.com.br

Evals: I Stopped Asking Whether the LLM “Looks Good” and Started Measuring

When I started working with LLMs, one of the hardest questions looked deceptively simple:

how do I know the model is actually getting better?

Running a few examples by hand and thinking "that answer looks good" works at first.

But it does not scale.

And, more importantly, it produces no evidence.

That is when I started to understand the role of evals.

What is an eval?

An eval is a structured way of testing the behavior of an AI system.

The idea is fairly simple:

input
+
produced answer
+
expected answer
+
metric
=
evaluation
Enter fullscreen mode Exit fullscreen mode

Instead of subjectively asking whether an answer turned out well, you define up front what you expect and measure the distance between actual behavior and expected behavior.

An article by Martin Fowler on GenAI patterns describes this kind of mechanism as scoring and judging: the model's output goes through a scorer that produces metrics or feedback about the result.

That is exactly the principle I applied in my operational triage project.

Before the LLM, I wrote code

In ops-triage-ai, the system receives tickets and has to determine things like:

  • category;
  • priority;
  • risk;
  • suggested team.

Before putting an LLM on the problem, I implemented a deterministic classifier with hand-written rules in TypeScript.

Simplified:

ticket
   ↓
deterministic rules
   ↓
classification
Enter fullscreen mode Exit fullscreen mode

That created something extremely valuable:

a baseline.

I now had a concrete implementation to compare any AI-based solution against.

Then I wrote down expected answers

I set aside a collection of tickets and defined in advance what the correct classification for each one should be.

Conceptually something like:

{
  "ticket": {
    "title": "Production API unavailable",
    "description": "Users cannot access the service"
  },
  "expected": {
    "category": "INCIDENT",
    "priority": "CRITICAL",
    "risk": "HIGH"
  }
}
Enter fullscreen mode Exit fullscreen mode

The classifier receives the ticket.

Its output is compared against expected.

And then we compute metrics.

               ┌──────────────────┐
ticket ───────►│    classifier    │
               └────────┬─────────┘
                        │
                        ▼
                 predicted output
                        │
expected output ────────┤
                        ▼
                    scorer
                        │
                        ▼
                    metrics
Enter fullscreen mode Exit fullscreen mode

That scorer can be plain code.

It does not need to be another LLM.

My code became part of the experiment

That is where I found the most interesting idea.

The deterministic code I would normally write to solve the problem also became an experimental reference.

I could run:

dataset
   ├── deterministic classifier
   └── LLM classifier
Enter fullscreen mode Exit fullscreen mode

and compare both on exactly the same examples.

On the project's final held-out set of 70 synthetic tickets, for instance:

Category accuracy

Deterministic: 82.9%
LLM:           95.7%
Enter fullscreen mode Exit fullscreen mode

For HIGH/CRITICAL priority recall:

Deterministic: 78.6%
LLM:           100%
Enter fullscreen mode Exit fullscreen mode

But something even more important happened.

The model did not win everywhere.

On overall risk classification:

Deterministic: 95.7%
LLM:            91.4%
Enter fullscreen mode Exit fullscreen mode

Without an eval, it would have been easy to look at a few good LLM answers and conclude the model was simply better.

The metrics told a more interesting story.

And then the eval started shaping the architecture

At that point the question stopped being:

How do I make the LLM replace my rules?

and became:

Where does each approach work best?

That pushed the project toward a hybrid architecture.

                 ticket
                   │
          ┌────────┴────────┐
          ▼                 ▼
 deterministic           LLM
 classifier           classifier
          │                 │
          └────────┬────────┘
                   ▼
              hybrid policy
                   │
             ┌─────┴─────┐
             ▼           ▼
         decision    human review
Enter fullscreen mode Exit fullscreen mode

The baseline stopped being just an old version of the system.

It started serving as:

  • a reference;
  • a divergence signal;
  • a fallback;
  • a component of the human-review policy.

The eval did not just measure the architecture.

It helped determine the architecture.

It also changes how you develop with LLMs

Without structured evaluation, the loop tends to look like this:

change the prompt
↓
run a few examples
↓
looks better
↓
deploy
Enter fullscreen mode Exit fullscreen mode

With evals:

change prompt/model/policy
↓
run the dataset
↓
measure results
↓
compare against baseline
↓
analyze regressions
↓
decide
Enter fullscreen mode Exit fullscreen mode

That difference looks small.

But it is the difference between experimenting and simply trusting an impression.

Not every metric needs to come from an LLM

There is a lot of discussion about LLM-as-a-judge, where another model grades the produced answer.

That is useful when the criteria are subjective, such as:

  • clarity;
  • relevance;
  • coherence;
  • quality of an open-ended answer.

But when there is a verifiable answer, traditional code is usually simpler.

In my case:

predicted.category === expected.category;
Enter fullscreen mode Exit fullscreen mode

already answers an important question.

The evaluation tool should be proportional to the problem.

The main takeaway

I used to think of evals as something that happens after building an AI system.

I see it differently now.

The eval is part of development itself.

It helps answer:

did the model improve?
where did it get worse?
by how much?
in which cases?
compared to what?
Enter fullscreen mode Exit fullscreen mode

And perhaps the most important question:

does this improvement actually justify putting the LLM in this part of the system?

Without that, it is easy to build a convincing demo.

With it, engineering starts to show up.

Keep exploring

The full details — official metrics, trade-offs, and limitations — are in the project case.

Project: ops-triage-ai on GitHub

Top comments (0)