How to Write Acceptance Criteria for LLM Output (Not Just 'Be Accurate')
▶ Watch on YouTube & subscribe to The Stack Underflow
“Extract the invoice accurately” sounds like a complete instruction. It is not. Run that prompt three times against the same document and you may get a total returned as a string in one response, a hallucinated PO number in a second, and a silently dropped currency field in a third. The model did not break — your prompt had gaps, and the model filled them its own way, differently every time.
This is not a model reliability problem. It is a specification problem. Every unanswered question in a prompt becomes a decision you have silently delegated to the model, and the model will answer that decision — consistently, plausibly, and often incorrectly for your use case. The fix is not a better adjective. It is a checklist.
The one-sentence version: Vague quality words in prompts are wishes — real acceptance criteria specify output format, edge-case handling, missing-field behavior, and ambiguity tie-breakers so precisely that two engineers reading the prompt would write identical test graders from it.
The Core Problem: Silent Delegation
Every prompt has two layers: what you wrote and what you left out. The model answers both. Consider the phrase “extract the invoice accurately.” You communicated a task. You left open:
- Output format — should
totalbe a number (1200) or a formatted string ("$1,200.00")? - Missing fields — what happens when the PO number is absent from the document?
- Date ambiguity — is
04/05/2024April 5th or May 4th? - Currency — is the number
1200in USD, EUR, or whatever the model infers from surrounding text?
The model resolves every one of these silently. In any individual run the choice may be reasonable. Across runs, across documents, or after a model update (from Claude Sonnet 4.6 to Claude Opus 4.8, for instance), those choices drift. Drift makes your pipeline untestable.
Prompt gap What the model decides
─────────────────────────────────────────────────
format unspecified → string on run 1, number on run 2
PO missing, no rule → null on run 1, invented value on run 2
date ambiguous → MM/DD on run 1, DD/MM on run 2
currency unspecified → USD inferred from $ symbol, or not
Each gap is a free variable. Free variables compound. A prompt with four unresolved gaps produces not one output shape but potentially sixteen — and your tests cannot cover a space that large.
The Four Rules for Turning Wishes into Criteria
Rule 1 — Format: Shape and Types Are a Contract
Specify the exact output schema including field names, value types, and — for string fields — the enumerated or constrained format. “Return the invoice data as JSON” still leaves dozens of decisions open. The schema is the contract.
// BAD — shape is unspecified; the model invents it
"Return the invoice data as JSON."
// GOOD — shape and types are the contract
{
"invoice_no": "string",
"total": "number (not a formatted string — no currency symbol)",
"currency": "ISO 4217 three-letter code, e.g. USD",
"line_items": "array of objects: { description: string, amount: number }",
"issued_date": "ISO 8601 date string, e.g. 2024-04-05"
}
If total must be a number, say so. If currency must be ISO 4217, say so. The model can infer a great deal from context — your schema should remove the inferences you cannot afford to be wrong about. As of 2025, the Anthropic Messages API also supports a native structured outputs feature (output_config.format with a JSON schema, or strict: true on tool definitions) that enforces schema compliance at the generation layer (docs.anthropic.com, 2025). Schema-enforcement at the API layer and an explicit schema in your prompt are complementary: the API constraint prevents malformed JSON; the prose constraint tells the model which fields to populate and how.
Rule 2 — Edge Cases: Name the Weird Stuff Upfront
Your business rules are not obvious to a language model. If a negative total means a credit note, state that. If a multi-page invoice requires summing line items rather than trusting a printed total, state that. The model is not guessing randomly — it is making plausible assumptions. Your job is to replace its assumptions with your rules.
If total < 0, set is_credit_note: true.
If the invoice spans multiple pages, sum all line_items[*].amount
and use that as total; ignore any printed "Total" figure.
These are testable. “Handle edge cases appropriately” is not. The difference is that a tester reading the first version knows exactly what assertion to write; a tester reading the second version does not.
Rule 3 — Missing Data: Give Every Absent Field an Explicit Fate
The hallucinated PO number in the opening example happened because the prompt was silent on what to do when a field was not present in the document. The model’s default behavior — absent explicit instruction — is often to produce something plausible. One line eliminates this entire class of error:
Any field not present in the source document MUST be set to null.
Do not infer, guess, or generate values for absent fields.
This is not a workaround for a model weakness. It is a specification. Every system that processes untrusted documents needs an explicit missing-data rule. The null convention is also what makes your output schema machine-checkable: a downstream validator can assert that every field is either a value of the correct type or null, and nothing else.
Rule 4 — Ambiguity: Every Tie Gets an Explicit Breaker or an Honest Escape Hatch
Some ambiguities cannot be resolved from the document alone. A date written as 04/05 is genuinely ambiguous without knowing the source locale. The right response is not to pick silently — it is to commit to a deterministic rule or surface the uncertainty for a downstream resolver.
Date format: interpret as DD/MM/YYYY — source documents are European.
If a date cannot be parsed unambiguously after applying this rule,
set the field to null and set needs_review: true.
The needs_review flag is the escape hatch. It is an honest acknowledgment that a human (or a second model pass, or a routing step) needs to resolve this case. Routing ambiguous cases to review is a feature, not a failure mode — and it is testable: a grader can assert that whenever a date field is null, needs_review is also true.
Before and After: The Same Prompt, Two Outcomes
| Dimension | Before (wish) | After (criteria) |
|---|---|---|
| Format | ”return JSON” | Exact schema: field names, types, units, codes |
| Edge cases | ”handle correctly” | Explicit rule per known business edge case |
| Missing data | (silent) | null + “NEVER invent a value” |
| Ambiguity | (silent) | Explicit tie-breaker or needs_review: true |
| Testability | Two engineers write different graders | Two engineers write the same grader |
| Variance across runs | High — every gap is a free variable | Low — every gap is closed |
The video demonstrates this directly: the same model, the same source documents, the same task — with the prompt rewritten to include these four rules — produces three identical, stable outputs where before it produced three different ones. Nothing about the model changed. Only the specification did.
The “Two Engineers” Test
Here is a practical heuristic for evaluating your own prompts before shipping them:
If two engineers reading your prompt could build different graders — different test assertions, different pass/fail logic — it is still a wish.
Acceptance criteria are statements a tester could check. They are not adjectives. Accurate, good, appropriate, reasonable — none of these are checkable. They describe a desired feeling, not an observable output property. Run the two-engineers test on every sentence in your prompt. Any sentence that could be interpreted two ways needs to be rewritten.
The corollary is useful for knowing when you are done: stop writing criteria when you can hand the prompt to two engineers who have never discussed the task and they independently write identical test suites.
How to Apply This in Practice
A practical workflow for any new extraction or classification task:
Step 1 — Draft the schema
List every field the downstream system will read.
Assign a type and a format constraint to each.
Mark which fields are optional vs. required.
Step 2 — Run the document set mentally
Walk through 5-10 representative inputs.
Write down every question the model would have to answer silently.
Each question becomes either a rule or a needs_review trigger.
Step 3 — Add the missing-data rule
"Any field absent in the source → null. No invention."
This is always needed. Add it always.
Step 4 — Add the ambiguity section
List every field that can be written multiple ways.
Give a resolution rule for each. Where no rule applies, → null + needs_review.
Step 5 — Apply the two-engineers test
Hand the prompt to a colleague. Ask them to write three test assertions.
If their assertions differ from yours, the gap is still open.
When using the Anthropic API in 2025-2026, pair this with native structured outputs (output_config.format on the Messages API, or strict: true on tool definitions) to enforce schema compliance at the generation layer. The schema in your prompt teaches the model what to populate; the API constraint ensures the structure is always valid JSON. They are not redundant — they catch different failure modes.
Common Misconceptions
-
“Specifying the schema will constrain the model too much.” A schema constrains the shape of the output, which is exactly what you need to constrain. The model still applies full reasoning to fill the fields — you are removing ambiguity about what the fields should look like, not how to extract them. Schema constraints reduce variance without reducing intelligence.
-
“I can just post-process the output to fix format issues.” Post-processing that silently discards or coerces fields is a test you are not writing and a failure mode you are not surfacing. Every silent coercion hides a gap in your specification. Specify upfront; validate on output; treat any post-processing need as feedback to tighten the criteria.
-
“The model is too unpredictable — criteria won’t help.” The variability you observe in LLM output is almost always a function of underspecification, not fundamental model randomness. Temperature and sampling noise are small effects compared to the variance introduced by prompt gaps. Closing the gaps dramatically reduces variance even at non-zero temperature.
-
“‘Do not hallucinate’ is a valid instruction.” It is not. The model does not experience its own confabulations as such at generation time. Telling it not to hallucinate does not give it a mechanism to detect when it lacks a value. The
null+ no-invention rule for missing fields, combined withneeds_reviewflags for genuine ambiguity, are the actual mechanisms that eliminate the behaviors the phrase “do not hallucinate” is trying to prevent.
Frequently Asked Questions
How detailed does the schema specification need to be? Detailed enough that two engineers would write identical test assertions from it. In practice that means: field names, value types, allowed formats for string fields (especially dates, codes, and enumerations), and explicit behavior for null/missing cases. If you are returning nested structures, specify one level of nesting at a time. The level of detail that feels excessive when writing it is usually the level that actually closes all the gaps.
What if the source document format varies a lot between inputs? Your criteria still need to specify the output shape — that does not change based on input variance. What changes is the edge-case section: enumerate the known input variations and state how each should be handled. If you discover a new variation at runtime (an invoice format you did not anticipate), treat that as feedback to add a new edge-case rule. Input variance is not a reason to make criteria vaguer; it is a reason to make the edge-case section longer.
Is the needs_review flag reliable? What if the model sets it incorrectly?
No automated flag is perfectly reliable. The value of the escape hatch is that it surfaces the cases the model itself identifies as ambiguous — which are typically the ones that would have produced the most variance in a less-specified prompt. False negatives (missed ambiguities that should have been flagged) are reduced by explicit criteria; false positives (over-flagging) are usually harmless, routing a case to human review that could have been handled automatically. The flag is most valuable as a routing signal, not as a binary guarantee.
Can these criteria apply to non-extraction tasks — summarization, classification, generation? Yes. The four rules generalize: specify the output format (length constraints, section structure, tone limits), name the edge cases (what to do with empty inputs, conflicting signals, extremely short source texts), define missing-data behavior (what to output when there is not enough information to produce a meaningful result), and provide tie-breakers for classification boundaries. The content of each rule changes per task; the structure does not. A classification prompt benefits from explicit label definitions and tie-breaking rules just as an extraction prompt benefits from a schema.
Does this interact with the Anthropic structured outputs feature?
Yes, and the combination is stronger than either alone. The API-level structured output feature (output_config.format with a JSON schema, available in the Messages API as of 2025 — docs.anthropic.com) guarantees that the response parses as valid JSON matching your schema shape. The prose criteria in your prompt govern the semantics: which fields to populate, what to do with missing data, how to resolve ambiguity. Both layers are needed: the API constraint stops malformed JSON from reaching your validator; the prose criteria stop semantically wrong JSON from passing your tests.
How do I know when I have enough criteria? Apply the two-engineers test after every draft. When you can no longer find an instruction that two people would interpret differently, you are done. A practical shortcut: ask your largest language model (Claude Opus 4.8, for complex documents) to read your draft criteria and list every decision it would have to make that the criteria do not cover. Its questions are your gaps.
Where This Fits in the Series
This tutorial is part of How Claude Actually Works — a course that builds a mechanistic understanding of how Claude reasons, encodes context, and operates in production. The preceding episode covered temperature, top-p, and top-k (Temperature, Top-P, Top-K Explained), establishing that sampling parameters alone cannot fix a vague specification. This episode addresses the specification itself. The next episode in the series picks up directly from the needs_review escape hatch introduced here: making the model surface its own uncertainty through confidence fields so your pipeline can route ambiguous cases rather than silently committing to a wrong answer (Confidence Fields and Human-in-the-Loop Routing). For broader context on how structured output fits into the full Claude stack, see The Claude Stack Mental Model. Browse all tutorials to follow the full series.
Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.
Subscribe on YouTube →