Projects/Weather Tool Agent/Part 4
Evals and Tests
Test tools, the parser, goal predicates, and golden transcripts with a tiny stdlib runner — no pytest required in the browser.
If you cannot test the agent, you do not have an agent. You have a vibe. This part writes checks that fail in CI (or in the Try it box) when the loop regresses. Joeven's browser runtime has no pytest; a 30-line runner is enough. On your machine, paste the same assertions into pytest.
What to test (the pyramid)
| Layer | Example | Speed | Catches |
|---|---|---|---|
| Tools | geocode("Paris") has lat | Instant | Catalog and error codes |
| Parser | extra keys rejected | Instant | Protocol drift |
| Policy-in-the-loop | golden goals → answers | Fast | Sequencing bugs |
| Evals | a table of goals and expected substrings | Fast | Product regressions |
You do not need a live LLM to test the loop if the policy is injected. That is dependency injection: run_agent(goal, policy=fake_model). When you add a real model, keep the fake tests and add a smaller live eval you run nightly, not on every save.
Goal predicates
Write goal_satisfied before you love the sentence the model wrote. For weather:
- Status is
ok. - Answer contains a number (temperature) or starts with
cannot:. - If the city is in the catalog, the answer must not start with
cannot:. - If the city is unknown, the answer must start with
cannot:. - Transcript must include a
geocodetool event.
A predicate that only checks status == "ok" will bless empty poetry.
Golden transcripts
A golden test freezes the sequence of tool names for a known goal. Paris should be geocode → weather → finish. Atlantis should be geocode → finish. If a "smart" refactor calls weather for Atlantis, the golden fails. That is the point.
Do not freeze temperatures in goldens unless you want catalog edits to break tests. Freeze shape: tool order, error codes, presence of °C or C.
Run to execute this in your browser. Nothing is sent to a server.
Step-by-step: a test you can extend
- Isolate tools. If
geocode("Paris")breaks, you do not debug the loop. - Isolate parse_action. Feed dicts, strings, extra keys, missing args.
- Run a table. Each row is a goal, expected tool sequence, and a boolean for
goal_satisfied. - Count failures, print the table, then assert zero. Printing first makes the Try it box educational when it fails.
- Keep tests deterministic. No
randomin default tools.
Eval vs unit test
A unit test pins a function. An eval pins a behavior the product cares about: "unknown places refuse." You will later score a real LLM the same way: same table, flaky rows marked quarantine, pass rate vs a threshold. Start with 100% on the fake model. That is your oracle. When the LLM scores 80%, you know the drop is the model, not the loop.
What not to assert
Do not assert exact English except for the cannot: prefix you control. Models paraphrase. Even fake models get refactored. Assert structure, codes, and a temperature digit.
Tip:Golden tool-name sequences are the highest value per line of test code in beginner agents.
Exercise
Add a case Weather in paris (lowercase). It should still pass. Add a case with no city (How is the weather?). Decide the product: refuse with cannot: no city, or default to a configured hometown. Write the predicate before you change the policy. Either choice is valid; untested defaults are not.
Check your understanding