Agent Testing
A way to test AI agents so that a new model, a re-indexed knowledge base or a different vector database is validated by re-running tests that already exist — not by writing new ones every time.
Built and demonstrated on a working reference agent. The background and results are in the blog post; this page is the method itself. It's part of how we deliver AI implementation work.
Every change becomes a testing project
Agents sit on top of components that keep moving — model versions, embedding models, vector databases, chunking, documents, prompts, tools, frameworks. Each change can quietly break behaviour that worked yesterday, and when tests are coupled to exact outputs or implementation details, each change also means rewriting tests. Teams end up either freezing the agent or under-testing it. The fix is structural: decouple what an agent must do from how it's built.
Seven principles
Every check reads a neutral record of one run — input, output, tool calls, retrieved context, usage, latency, errors — aligned with the OpenTelemetry GenAI conventions.
- One small adapter per agent turns a run into a trace
- Moving to another agent framework changes the adapter, not the tests
A test case is a few lines of YAML: input, checks, trials. The only code ever written is a new kind of check — which every case can then use.
- Output, trajectory, retrieval, grounding, operational and judge-based checks
- Existing evaluation metrics (Ragas, DeepEval) wrap in as one more check
Assert what a good result must have, not what yesterday’s result happened to say — that’s what lets a case survive a model swap or a re-index.
- "Retrieved context contains EUR 1,800" — not "chunk 2 ranked first"
- "Under the channel limit, links to the site" — not an output snapshot
One reusable test class per interface — embedder, vector store, LLM, agent. A replacement is accepted when it honours the same contract as the original.
- A new vector database is one fixture, not a new test suite
- A model that can’t make valid tool calls fails in seconds, before a full run
Every run produces a report; the production report is the baseline. A change ships when it’s not worse than what runs today, beyond an agreed tolerance.
- Blocking: a passing case now fails, or a layer metric drops beyond tolerance
- Each report records the exact model, embedder, store, prompt and corpus
Each case states how many trials to run and how many must pass — so model variance is a parameter, not a flaky build.
- Safety cases hold on every trial
- Behaviour cases may accept 2 of 3 while a model is new, then tighten
A rejected output or a bad production trace becomes a case. Swapping a component never does.
- The suite grows from evidence
- Cases are never edited to make a change pass
Five layers
Each test case belongs to exactly one layer, so a change only re-runs the layers it can affect.
| Layer | Question | What's checked |
|---|---|---|
| Component | Does each part honour its interface? | Contract tests |
| Retrieval | Does the knowledge base return the facts a question needs? | Facts and sources retrieved |
| Behaviour | Does the agent do the job the right way? | Tool use, output properties, grounding, judge |
| Safety | Does it refuse what the business must never say or do? | Forbidden claims, grounding, every trial |
| Operational | Is it bounded in cost, latency and loop length? | Steps, tokens, latency, errors |
What each change re-runs
Combined changes take the union. When in doubt, run everything — the matrix keeps routine changes cheap, it doesn't excuse skipping tests.
| Change | Contracts | Layers |
|---|---|---|
| Model | LLM | Behaviour · Safety · Operational |
| Prompt | — | Behaviour · Safety |
| Embedding model | Embedder · Vector store | Retrieval · Behaviour |
| Vector database | Vector store | Retrieval · Operational |
| Chunking | — | Retrieval · Behaviour |
| Knowledge base | — | Retrieval · Behaviour · Safety |
| Tools | Agent | Behaviour · Safety · Operational |
| AI gateway routing | LLM · Gateway | Behaviour · Safety · Operational |
| Framework | All | All |
Every change, the same seven steps
- 01Classify the change, and let the impact matrix select contracts and layers
- 02Make the change as a diff — config, prompt, corpus or adapter wherever possible
- 03Run the component contracts; a failure stops here
- 04Run the selected layers against the production baseline, with the required trials
- 05Review regressions, and spot-read outputs even when everything passes
- 06Ship, fix and re-run, or waive with a written reason — never edit a case to pass
- 07Promote the new baseline, then sample production traces for new cases
Fast on every commit, real before every release
Offline lane. A deterministic stand-in model runs the full pipeline and the regression gate in under a second, with no GPU or API key. It proves the plumbing, not model quality.
Live lane. The same cases against the real models — local open-weight or hosted — with a separate judge model, compared to that configuration's own baseline.