Skip to content

Evaluation and modelsGuide

How to evaluate LLMs on real production workloads

Benchmarks narrow the field; your workload makes the decision. Build an evaluation set that measures success, cost, latency and operational failure together, and re-run it whenever something changes.

By INFRO Editorial

Published

Updated

9 min read

Multiple abstract AI models passing through a rigorous illuminated evaluation course

In short

Evaluate models on a sample of your own production tasks with a pass criterion per task, run each candidate several times on identical inputs, and record success rate, cost per successful task, latency and malformed-output rate together. Public leaderboards are useful for shortlisting; the decision comes from your traffic, and it has to be re-run when models, prompts or providers change.

Key takeaways

  • Sample real tasks, keep the difficult tail, and version the dataset together with the prompt.
  • Give every task a programmatic pass criterion where possible; audit any model-based judge, which has documented biases.
  • Run each candidate several times on identical inputs and record success, cost per successful task, latency and malformed output together.
  • Translate results into a routing matrix: default model, eligible substitutes, escalation, and workloads where fallback is forbidden.
  • Re-run on every prompt, model or provider change, and on a schedule, because hosted models change without notice.
  • After rollout, keep scoring a small sample of production answers in the background so a regression shows up between offline runs.

Model comparisons age faster than almost anything else written about AI. A table of prices and context windows is out of date within weeks, a leaderboard ranking describes someone else's tasks, and impressions of which model "feels better at tool calling" rarely survive contact with your own schemas. What lasts is the method: a way to find out, on your traffic, which model does the job at an acceptable cost — and to find out again when something changes.

What public benchmarks can and cannot tell you

Public benchmarks are useful for building a shortlist. They show whether a model is in the right class for a kind of task, and they catch obvious gaps. They cannot tell you how a model handles your prompts, your tools, your documents, your latency budget or your failure costs, because they were built from someone else's distribution of tasks. When several candidates score close together on a public suite, the differences between them on your workload are the only differences that matter.

Use benchmarks to decide what to test. Use your own evaluation to decide what to ship.

Build the dataset from real traffic

  1. Sample from production, following your privacy process for any customer data. A few hundred carefully chosen tasks per workload is usually more informative than thousands of synthetic prompts.
  2. Stratify. Include ordinary requests, difficult ones, malformed inputs, very long inputs and safety-sensitive cases in deliberate proportions. Models tend to separate in the tail, not at the median.
  3. Record the expected outcome for each task — a correct label, a valid structure, a test that must pass — at the time you sample it.
  4. Version the dataset together with the prompt. Otherwise a prompt improvement is indistinguishable from a model improvement.
  5. Grow it from incidents. Every production failure that could have been caught becomes a new case.

Define success per task

The grader should match the task. Where a program can decide, let it.

Task typeGraderWatch for
Extraction and classificationExact match or schema validation against the expected valueLabels that are ambiguous in the source data
Code generationUnit tests executed in isolationTests that pass trivially
Retrieval-grounded answersCitation present and supported by the retrieved passageAnswers correct by luck without grounding
Tool use and agentsThe tool sequence completes and the final state is correctSuccess on a mocked tool that behaves unlike the real one
Open-ended writingRubric scored by people, or by a model judge audited against peopleJudge bias
Match the grader to the task, and prefer a program to a judge when a program can decide.

Model-based judges are convenient and imperfect. Research on using models as judges documents position bias, verbosity bias and self-enhancement bias1. Mitigate them: randomize the order of candidates, score against a rubric rather than asking which answer is better, use a judge from a different model family than the candidates, and hand-check a sample of verdicts before trusting the rest.

A newer option for rubric grading is a decision model: instead of writing a verdict in prose, it answers a typed question — yes or no, one of several labels, or a level on a rubric — with a probability. That makes the threshold explicit and the grader's uncertainty visible, though the same audit against human labels still applies. The engines that exist and how they differ are in decision models compared.

Run it properly

  • Identical inputs. Every candidate receives the same prompts, tools and parameters. Tune prompts per model only after comparing defaults, and only for the top candidates.
  • Repeated runs. Run each task several times. A single run measures one sample of a random process.
  • Fixed parameters. Record temperature and any reasoning or output-length settings; changing them changes the result.
  • Record operational failures, not just scores. A model that produces the right answer in invalid JSON fails in production.

For each candidate and workload, keep five numbers:

MetricWhy it is on the list
Success rateThe share of tasks that met the pass criterion
Cost per successful taskTotal cost of all attempts divided by successful tasks
p95 total latency and time to first tokenWhether the model fits the experience's time budget
Malformed-output rateInvalid structure or tool arguments; predicts on-call work
Variance across runsHow much the success rate moves between identical runs
The minimum a model decision should be based on.

Two operational details decide whether the numbers mean anything. First, turn off every response cache between the harness and the model. An exact-match cache replays the first answer to an identical request, so three runs at temperature 0 become one run counted three times and the variance column reads zero. Provider prompt caching is different: it reuses the processing of a prompt prefix, not the answer, and is safe to leave on.

Second, scheduled evaluation runs have no user waiting, so they fit batch pricing where a provider offers it: OpenAI prices its Batch API at half the cost of its synchronous APIs, with a 24-hour completion window2, and Anthropic charges batch usage at 50% of standard prices3.

Cost per successful task

The comparison that decides most model choices combines the first two metrics.

How this figure fits into wider cost control, in order of risk, is in the cost optimization playbook.

A minimal harness

The harness below runs every candidate on every task several times through one OpenAI-compatible endpoint and records the numbers above. It assumes each task carries its own check function; replace the evaluation logic with your graders.

evaluate.py
import os
import statistics
import time
from openai import OpenAI

client = OpenAI(base_url="https://api.infro.io/v1", api_key=os.environ["INFRO_API_KEY"])

CANDIDATES = ["anthropic/claude-sonnet-5.5", "openai/gpt-6-sol", "openai/gpt-6-luna"]
RUNS = 3

def run(model, task):
    started = time.monotonic()
    response = client.chat.completions.create(
        model=model,
        messages=task["messages"],
        temperature=0,
        extra_body={
            "metadata": {"tags": {"eval_run": task["run_id"]}},
            "cache": {"mode": "bypass"},  # every run must reach the model
        },
    )
    elapsed = time.monotonic() - started
    text = response.choices[0].message.content or ""
    return {
        "passed": task["check"](text),
        "cost": response.usage.model_extra.get("cost"),  # estimated provider cost; None if unpriced
        "seconds": elapsed,
    }

def evaluate(tasks):
    for model in CANDIDATES:
        results = [run(model, task) for task in tasks for _ in range(RUNS)]
        passed = [r for r in results if r["passed"]]
        costs = [r["cost"] for r in results if r["cost"] is not None]
        print(model, {
            "success_rate": len(passed) / len(results),
            "cost_per_success": (sum(costs) / len(passed)) if passed and costs else None,
            "p95_seconds": statistics.quantiles([r["seconds"] for r in results], n=20)[18],
        })

Only the model string changes between candidates, so the harness stays the same when the list does. The cache field is INFRO's per-request control for its response cache; against another endpoint, use whatever switch its cache offers, or drop the line if there is no cache.

Turn results into a routing matrix

An evaluation is finished when it has changed a decision someone can read. Write the result down per workload:

WorkloadDefault modelEligible substitutesEscalate toFallback to a different model
Ticket classificationThe passing model with the lowest cost per successful taskAny other model that passedNoneAllowed among passing models
Reply draftingThe model with the best pass rate within budgetSame model on another accountA stronger model on validation failureOnly models that passed the drafting evaluation
Refund decisionsThe highest-accuracy modelSame model on another account onlyA personForbidden
The artifact an evaluation produces. Routing and failover configuration should be derived from it.

When the evaluation shows that one workload splits by difficulty — short lookups pass on a small model, multi-step requests only on a large one — the matrix needs a row per class and something that decides the class per request. That can be a rule on request properties, or a small classifier that labels the prompt before routing, with a timeout and a default class so routing never waits on it. The trade-offs of putting a classifier in the request path are in classifiers in the request path. Evaluate the classifier too: a misrouted hard prompt fails on the small model, and that failure belongs in the cost per successful task.

When to re-run

  • Every prompt, retrieval or tool change for the workload.
  • Every model version change, including a provider's silent update to a hosted model.
  • Every change of provider or account serving the model.
  • On a fixed schedule, because hosted behaviour drifts.

Roll a winning change out gradually: send a small share of production traffic to it and compare against the unchanged path before widening it4.

Between offline runs, two lighter checks keep watch. Score a sample in production: after an answer has been returned, grade a small share of real prompt-and-answer pairs with the same rubric, in the background, so the user never waits for the grader and a drop in the mean score per model shows up within days rather than at the next scheduled run. Watch for silent changes: record the model id the provider says answered each request, and alert when a new id appears or when output length, finish reasons or the cached share of input move sharply. Neither replaces the evaluation set; both tell you when to run it.

Evaluations in INFRO: tagged runs, routes and sampled scores

The harness above runs unchanged against every model your connected providers serve, because INFRO keeps one OpenAI-compatible request shape across them, and every call runs on your own provider account. Each response includes usage.cost, an estimate of what your provider will bill — null when the model is unpriced, never 0 — and each request is stored with its model, provider, latency and token counts. Tagging requests with an evaluation run id in metadata lets you pull exactly that run's records from GET /v1/requests with a tag.eval_run filter and compute per-model figures from them.

INFRO's response cache is off unless an owner or admin turns it on; where it is on, "cache": {"mode": "bypass"} sends the request to the provider without reading or writing an entry, which is what the harness sets.

Once the matrix is written, routes express it as configuration rather than code: targets in priority order or by weight, if-rules on request properties, and a prompt classifier whose class (class, class_confidence) a rule can read, with a fallback_class used on a timeout, an error or low confidence.

The two production checks have counterparts too. An eval monitor is a decision policy with a monitor block and a sample_rate from 0.001 to 1; after an answer has been returned, it scores a sampled share of chat completions in the background on a decision model you connect, records each score as its own row under the request it judged, and shows the mean score per served model on the policy's page (Decisions API). Traffic changes raises a finding when a model is answered by a new snapshot, or when output tokens, the cached share of prompt tokens, fallbacks or length-limited answers move past documented thresholds. It reads request metadata only, so it can say that something changed, not that answers got worse; that is what the evaluation set is for.

From the team that builds it

Where this shows up in INFRO

INFRO is a control plane in front of the provider accounts you already hold. The capabilities below are the parts of it this article touches, each with the documentation that defines its behaviour.

  • Text — OpenAI-compatible

    Available with a plan

    POST /v1/chat/completions with the request shape the OpenAI SDKs already send, including streaming and tool calling.

    Read the documentation for Text — OpenAI-compatible
  • Usage and cost per request

    Available with a plan

    Every request records its model, latency, token or unit counts, and estimated provider cost, queryable by project and key.

    Read the documentation for Usage and cost per request
  • Spend attribution

    Available with a plan

    Group cost by model, project, key, or an end-user id you attach to the request.

    Read the documentation for Spend attribution
  • Health-aware routing

    Available with a plan

    Named routes use priority or weighted targets over your own connections, with health checks, retries and failover.

    Read the documentation for Health-aware routing
  • Eval monitors

    Available with a plan

    Score a share of served chat answers against your rubric in the background with Jev or any other decision model you connect, recorded per request and summarized per served model.

    Read the documentation for Eval monitors
  • Prompt classifier on routes

    Available with a plan

    A route can have Jev, or any other decision model you connect, pick a class for each prompt; route rules send each class to its own targets, and an unsure or unreachable classifier falls back to a class you choose.

    Read the documentation for Prompt classifier on routes
  • Traffic changes

    Available with a plan

    Changes in your traffic that come with no error: a model answered by a new snapshot, longer answers, a lost prompt cache, more fallbacks, answers cut off by the length limit, slower responses or a new error mix. Each finding names the likely cause and the estimated weekly change in provider cost. INFRO sees request metadata, not answers, so it does not judge answer quality.

    Read the documentation for Traffic changes
  • Response cache

    Available with a plan

    Opt-in, exact-match cache for chat completions. An identical request inside the time-to-live you set is answered from the cache and never reaches your provider, so your provider does not bill it. Every plan; the longest time-to-live depends on the plan. No semantic matching.

    Read the documentation for Response cache

INFRO accounts are self-serve. Provider usage is billed to you by your providers; INFRO charges a software subscription and takes 0% of model spend. See plans.

Sources

Numbered in order of first citation. Each was opened and checked against the claim on the date shown.

  1. 1.Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (Zheng et al., 2023) (opens in a new tab) — arXiv. Accessed Oct 13, 2026.
  2. 2.Batch API (opens in a new tab) — OpenAI. Accessed Oct 13, 2026.
  3. 3.Batch processing (opens in a new tab) — Anthropic. Accessed Oct 13, 2026.
  4. 4.Canarying Releases — The Site Reliability Workbook (opens in a new tab) — Google. Accessed Oct 13, 2026.

About this article

Written by the team building the INFRO gateway from general evaluation practice and published research, with the harness shown written against INFRO's OpenAI-compatible API. Sources were re-opened on 13 October 2026; INFRO behaviour is described from its public documentation and gateway source. It contains no benchmark results; the cost example uses hypothetical rates.

Last checked against its sources on . The next review is due by . No separate named reviewer is recorded for this article.

How articles are written, sourced and corrected is set out in the editorial standards.

Revision history

  1. Reviewed against every source. The harness now bypasses response caching, which would otherwise replay one answer across repeated runs, and uses current model ids. Added model-based graders that return probabilities, classifier routing as a way to apply a routing matrix per request, and sampled background scoring and snapshot-change detection as the checks that run between offline evaluations. Reworded the batch-pricing note.
  2. Expanded into a full guide and merged the method from the retired comparison "GPT-5.1 vs Claude vs Gemini vs DeepSeek: price-performance", which now redirects here. Its model price table, context-window figures and first-person performance impressions were removed because they could not be sourced or kept current. Added grader guidance, a runnable harness, a routing matrix and cited sources.