InterviewPrepKit

Home / Coding / Agent Coding / Evaluation / A Rule-Based Eval Harness

A Rule-Based Eval Harness

medium 00:00
Solving tips
  • An eval harness is just a loop: run the agent on each input, apply a check, and tally the results. Keep the runner dumb and put all the judgment in the per-case check.
  • A case that crashes is a failing case, not a crashed harness. Wrap both the agent call and the check so one bad case never takes down the whole run.
  • Report per-case results AND an aggregate rate. A single pass_rate hides which cases regressed; a list of booleans without a rate hides the trend.

Before you trust an agent, you have to measure it, and the smallest useful measurement is a rule-based eval: a fixed set of inputs, each paired with a check that decides pass or fail. This exercise is the harness that runs those checks and reports a score. It is the scaffolding every larger eval suite is built on, so the control logic has to be boringly reliable.

How a case works

A case is a dict describing one test. It always carries an input to feed the agent, and exactly one kind of check:

  • A check functioncase["check"] is a callable that takes the agent’s output string and returns True for a pass. Use this when correctness is a predicate (length, format, a parsed value, an exact match).
  • An expected substringcase["expected"] is a string that must appear somewhere in the output. This is the common “did it mention the answer” check.

You run agent_fn(case["input"]) to get the output, then apply whichever check the case carries. The agent is passed in, so the same cases can grade any implementation, and swapping in a stub agent makes the harness deterministic and testable on its own.

Task

Complete run_eval(agent_fn, cases):

  1. Iterate the cases in order. For each, resolve its name (default "case_<i>" using the zero-based index).
  2. Call agent_fn(input). If it raises, the case fails with output=None and the exception text in error.
  3. Apply the check. A "check" case passes when bool(check(output)) is true; an "expected" case passes when the substring is in the output. If the check itself raises, the case fails.
  4. Collect one result dict per case: {"name", "passed", "output", "error"} (error is None on success).
  5. Return the summary dict with results, passed, total, and pass_rate (passed / total, or 0.0 when there are no cases).

Example

def agent(x):
    # toy agent: echoes a canned answer for known inputs
    return {"2+2": "the answer is 4", "cap": "Paris is the capital"}.get(x, "i don't know")

cases = [
    {"name": "math",     "input": "2+2", "expected": "4"},
    {"name": "geo",      "input": "cap", "check": lambda o: "Paris" in o},
    {"name": "unknown",  "input": "???", "expected": "42"},
]

summary = run_eval(agent, cases)

summary["passed"]                       # -> 2
summary["total"]                        # -> 3
round(summary["pass_rate"], 3)          # -> 0.667
[r["passed"] for r in summary["results"]]  # -> [True, True, False]
summary["results"][2]["output"]         # -> "i don't know"

Constraints

  • One failing or crashing case must not stop the run; every case gets a result.
  • Assume each case has exactly one of check or expected; if both are somehow present, prefer check.
  • Preserve case order in results.
  • Do not call any real LLM, network, or clock — agent_fn and the checks are all provided.

Write your solution, then hit Run tests to check it — or get a mock grade from the AI coach.

The coach remembers this session — revise your code and ask again, and it grades your progress. It gives hints, not the answer.
Report a bug