JJoeven

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)

LayerExampleSpeedCatches
Toolsgeocode("Paris") has latInstantCatalog and error codes
Parserextra keys rejectedInstantProtocol drift
Policy-in-the-loopgolden goals → answersFastSequencing bugs
Evalsa table of goals and expected substringsFastProduct 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 geocode tool 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.

Live Pythonpython
Output
Run to execute this in your browser. Nothing is sent to a server.

Step-by-step: a test you can extend

  1. Isolate tools. If geocode("Paris") breaks, you do not debug the loop.
  2. Isolate parse_action. Feed dicts, strings, extra keys, missing args.
  3. Run a table. Each row is a goal, expected tool sequence, and a boolean for goal_satisfied.
  4. Count failures, print the table, then assert zero. Printing first makes the Try it box educational when it fails.
  5. Keep tests deterministic. No random in 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

Why inject fake_model instead of calling a paid API in unit tests?