BXUP Track progress & test yourself — free
QA & Testing · Playbook · Core · ~4 min read
QA & TESTING · PLAYBOOK · CORE

Build the Layered Assertion Hierarchy — Deterministic, Semantic, Judge

The most expensive mistake in LLM testing is reaching for an LLM judge to check things a regex can check. Assertions form a hierarchy — deterministic, then semantic, then judge — and the discipline is always the same: use the cheapest layer that can express the requirement, and let each layer filter for the next.

Tier: Core · Workflow area: Testing AI systems — foundations

This playbook decomposes a real LLM feature's requirements into the three layers, implements each with concrete assertions, and wires them so a two-cent judge call never runs on an output a free schema check already condemned.

When to use

USE THIS WHEN

Requirements are mixed

  • Your LLM feature has requirements ranging from "valid JSON" to "empathetic tone" and everything currently funnels through one judge prompt.
  • The eval bill is growing faster than the eval suite.
  • Judge verdicts feel arbitrary and nobody trusts a failed run enough to block on it.
SKIP IT WHEN

One layer suffices

  • The output is fully structured and post-validated — deterministic checks alone cover it (see the structured-output playbook).
  • You are exploring a brand-new feature with no stable requirements yet — grade by hand first; hierarchies come after you know what "good" means.

Prerequisites

  • A testable LLM feature with 10+ representative inputs and written requirements.
  • Promptfoo or DeepEval installed (examples use Promptfoo; the hierarchy is tool-agnostic).
  • An embeddings-capable API key for the semantic layer; a strong model for the judge layer.

The recipe

  1. Decompose requirements into the three layers. Take each requirement and ask: could a program check this? Then: could similarity to a reference check it? Only what survives both questions earns a judge. Worked example for a support-reply generator:

    Layer 1 — deterministic (free, instant, binary):
    - Valid JSON matching ReplySchema; reply_text ≤ 800 chars
    - Contains no email/phone/card patterns (PII regex)
    - Never contains "guarantee", "refund approved" (banned-phrase list)
    - Mentions the ticket ID it was given
    Layer 2 — semantic (cheap, graded):
    - reply_text is similar to the topic of the customer's issue
      (embedding similarity vs the ticket text ≥ 0.78)
    Layer 3 — judge (expensive, judgment):
    - Tone is professional and empathetic; does not blame the customer
    - Proposes a concrete next step consistent with policy context
    

    Most teams discover 60–70% of their "AI quality" requirements land in layer 1. That discovery alone pays for this exercise.

  2. Implement layer 1 as hard gates.

    # promptfooconfig.yaml (excerpt)
    defaultTest:
      assert:
        - type: is-json
          value: { $ref: "./schemas/reply.json" }
        - type: not-contains-any
          value: ["guarantee", "refund approved"]
        - type: javascript
          value: "!/[\\w.+-]+@[\\w-]+\\.[a-z]{2,}/i.test(output) || 'PII: email leaked'"
    

    These are binary and non-negotiable: a failure here fails the case with zero further spend. There is nothing to debate about invalid JSON.

  3. Implement layer 2 where meaning matters but judgment does not.

    assert:
      - type: similar
        value: "{{ticket_text}}"
        threshold: 0.78
    

    Calibrate the threshold empirically: run 20 known-good and 20 known-bad (off-topic) outputs, plot the two similarity distributions, and set the threshold in the gap. A threshold you did not calibrate is a random number with confidence.

  4. Reserve the judge for genuine judgment — and constrain it.

    assert:
      - type: llm-rubric
        provider: openai:gpt-5.2   # or your strongest available grader
        value: |
          Grade ONLY these two criteria, one verdict each:
          1. TONE: professional and empathetic; never blames the customer.
          2. ACTION: proposes a concrete next step a support agent could
             actually take given: {{policy_context}}
          Output JSON: {"tone": pass|fail, "action": pass|fail,
          "evidence": "<quote from the reply for each verdict>"}
    

    Constrained rubric, forced evidence quotes, structured verdict. A judge asked "is this a good reply?" is an opinion generator; a judge asked two specific questions with quote-backed answers is a measurement instrument (calibrating that instrument is its own playbook).

  5. Order execution cheap-to-expensive and short-circuit. Promptfoo evaluates asserts per test; keep judge asserts in a separate, later-stage config (or use assert weights with a CI script that skips stage 2 on stage-1 failure) so malformed outputs never reach paid grading. In DeepEval/pytest, this is just test ordering with an early assert:

    def test_reply(case):
        out = generate_reply(case.ticket)
        reply = ReplySchema.model_validate_json(out)   # layer 1: raises = fail, stop
        assert no_pii(reply.reply_text)                 # layer 1
        assert topic_similarity(reply, case) >= 0.78    # layer 2
        assert judge_tone_and_action(reply, case)       # layer 3: only now
    
  6. Review the hierarchy quarterly — demote aggressively. Every judge criterion is a candidate for demotion: once you notice the judge's "concrete next step" verdict correlates 0.95 with "reply contains one of 12 known action phrases," demote it to a deterministic list check and keep the judge for what still needs it. The hierarchy's health metric is judge-call share trending down while coverage grows.

Key Takeaway:

Cheapest-layer-first is not (only) about the bill. Deterministic checks are trustworthy and debuggable; semantic checks are calibratable; judges are neither until carefully engineered. Pushing every requirement down the hierarchy maximizes the fraction of your suite that is beyond argument.

Variations

  • DeepEval-native: layer 2 via AnswerRelevancyMetric, layer 3 via GEval with the same constrained-criteria discipline; pytest ordering gives you short-circuiting for free.
  • No embeddings available: approximate layer 2 with keyword/entity overlap checks (assistant-generated per case) — cruder, still cheaper and stabler than jumping to a judge.
  • RAG features: faithfulness ("claims are supported by the retrieved context") sits in layer 3, but citation-format and source-presence checks belong in layer 1 — split them.

Quality check

  • Every requirement is written down with its assigned layer and a one-line reason.
  • Layer-1 failures cost zero judge calls (verify by breaking the schema deliberately and watching spend).
  • Semantic thresholds have a recorded calibration (the two distributions, the gap, the date).
  • Judge rubrics grade named criteria with evidence quotes — no "overall quality" scores anywhere.
  • Judge-call share of total assertions is tracked and has a downward trend or a written justification.

Pitfalls

  • Judge-for-everything. The default failure mode. Symptoms: eval bills scaling with suite size, verdicts nobody can explain, thresholds tuned to make CI green.
  • Deterministic checks that overreach. Exact-string assertions on free text are layer-1 tools applied to a layer-2 problem — they create the flakiness that discredits the whole suite. Match the layer to the requirement, in both directions.
  • Uncalibrated similarity thresholds. 0.78 copied from a blog post means nothing for your embedding model and domain. Calibrate on your own good/bad outputs.
  • Silent layer skipping. A suite that runs the judge even when the schema check failed burns money grading garbage — and worse, a passing judge verdict on invalid output can mask the failure in aggregated dashboards.
Pro Tip

When a stakeholder asks "can the AI check X?", answer with the hierarchy question first: "Can a program check X?" You will be surprised how often the honest answer is yes — and every yes is a check that never flakes, never bills, and never argues.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in