Code-Based Evals (Assertions)

Deterministic checks for objective, rule-based failures. Use them whenever possible. They’re fast, cheap, deterministic, interpretable, easy to debug, and need no human labels to trust (only Pass and Fail test cases for the check itself).

Examples

Failure modePossible check
Invalid output structureParse (JSON/schema) and check required fields
Missing or forbidden textString / regex match (e.g., leaked UUIDs, competitor names)
Citation not in retrieved docsCompare cited IDs with retrieved IDs
Bad tool callValidate args against the tool schema, or run in a safe sandbox
Wrong valueCompare with a reference value
Contact creation worksCreate a contact, then query the DB: exactly one matching record with the requested fields
Listing searchScenario “one match” → len(listings)==1; “multiple” → >1; “none” → ==0
Code generationExecute; run unit tests; static analysis (ruff, mypy, bandit)
Agent outcomeState check on the environment (reservation row exists, order status changed)
Transcript constraintsn_turns <= 10, token or latency budgets

Rechat’s “no exposed UUID” test

const noExposedUUID = message => {
  // Remove all text within double curly braces
  const sanitizedComment = message.comment.replace(/\{\{.*?\}\}/g, '')
  // Search for exposed UUIDs
  const regexp = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig
  const matches = Array.from(sanitizedComment.matchAll(regexp))
  expect(matches.length, 'Exposed UUIDs').to.equal(0, 'Exposed UUIDs found')
}

CRM results fed to the LLM contain UUIDs that users should never see. The prompt says not to include them, and the regex checks that it doesn’t.

How to write one (write-code-eval skill)

  1. Start from a failure mode found in Error Analysis. Write one check per failure mode, like a unit test that asserts what should hold for each trace.
  2. State the rule and identify the trace fields or reference data it needs. If the rule needs interpretation, use an LLM-as-Judge.
  3. Implement it in your project’s language and framework. Return a result and a reason.
  4. Test known passes and failures, including borderline cases, and inspect mistakes on real traces. If the rule is a proxy for human judgment, compare it with human labels.
  5. For a failure with objective and interpretive parts: check the objective part with code and use a judge for the rest.

Organizing assertions (Rechat lessons)

  • Break the LLM’s scope into features × scenarios and write scoped tests per scenario. Add generic tests that apply everywhere (no UUIDs, valid JSON).
  • Reuse assertions beyond tests: data cleaning, and automatic retries at inference time, where the assertion error helps course-correct.
  • Make them fast enough to run on every code change.
  • Update them constantly from failures observed in data. Rechat has hundreds.
  • You don’t need 100%. The pass rate is a product decision about which failures you’ll tolerate. “One signal you are writing good tests is when the model struggles to pass them.”
  • Brainstorm assertions with an LLM, but ground them in real traces.

Caveat from Anthropic: don’t over-specify paths

Checking that an agent followed an exact sequence of tool calls is often too rigid, because agents find valid paths you didn’t anticipate. Grade what the agent produced (the outcome), not the path, unless the path itself is the requirement (e.g., authorization before an action; see Agentic Workflow Evaluation).

When to Build an Automated Evaluator · LLM-as-Judge · Three Levels of Evaluation · CI Evals vs Production Monitoring