LLMs are great at producing text, but software features usually need something more precise than prose. If your application expects a category, a set of fields, or a decision that triggers downstream automation, an “almost right” response can be worse than a clearly failed one.
An output contract is a practical way to bridge that gap. You specify the shape and constraints of the model’s response, then you validate it the same way you would validate user input or an API payload. If it fails validation, your system recovers on purpose, instead of breaking by accident.
This post walks through how to design an output contract, how to validate it, and how to handle failures safely. The goal is not to make the model perfect. The goal is to make your product reliable even when the model is not.
Why output contracts matter
Without an explicit contract, LLM integrations often rely on “prompting the model to behave.” That works until it does not: a model update changes formatting, a rare input triggers a different style, or a long conversation nudges the model into adding commentary.
Output contracts matter because they turn a vague dependency into an interface your system can reason about. In practice, they improve:
- Reliability: You can fail fast when outputs are malformed, rather than letting corruption spread.
- Safety: You can constrain what the model is allowed to decide, and keep high-risk actions behind additional checks.
- Maintainability: Contracts become documentation for how the AI feature is supposed to behave.
- Debuggability: “Validation failed: missing field
priority” is easier to work with than “the system acted weird.”
Key Takeaways
- Define a structured output shape and validate it like any other input.
- Make failure a first-class outcome with a safe fallback path.
- Keep the model’s role narrow: propose, classify, extract, summarize, but avoid letting it execute.
- Version your contract and measure how often outputs pass validation.
What an output contract is (and is not)
An output contract is a specification for the model’s response that your application enforces. It usually includes:
- Format: for example, JSON only (no additional text).
- Schema: required fields, types, and allowed values.
- Constraints: length limits, enumerations, and conditional rules.
- Policy: what to do when the contract is not met.
It is not the same as “telling the model to output JSON.” Prompts help, but the contract is what your system validates and relies on. Think of prompting as a way to increase pass rate, while validation and recovery protect you when the pass rate drops.
A simple, concrete contract shape
Here is a short pseudo-structure that illustrates what a contract might look like for a classification feature. It is intentionally small and strict.
{
"contract_version": "1.0",
"label": "billing | bug | feature | other",
"confidence": 0.0-1.0,
"routing": {
"team": "Support | Engineering | Sales",
"priority": "low | normal | high"
},
"notes": "short string, max 240 chars"
}
If any field is missing, invalid, or out of range, your application treats the response as a failure and follows a safe fallback.
Designing a contract your system can trust
The best contracts are boring. They avoid optional fields that invite creative writing, and they minimize “free text” areas that hide errors. A practical design process looks like this:
- Start from the downstream decision. Ask: what does the system need to do next? Then define only the fields required to do that safely.
- Prefer enumerations over prose. If the model can choose from
["bug","billing","feature","other"], validation becomes trivial. - Separate “decision” from “explanation.” Humans like explanations; computers need decisions. Keep explanations short and optional, and never rely on them for logic.
- Set limits. Max lengths, min and max ranges, and maximum list sizes prevent accidental payload bloat.
- Make ambiguity explicit. Include fields like
confidenceor anneeds_human_reviewboolean when appropriate, so the model can signal uncertainty instead of guessing.
Finally, treat the contract as an interface: version it. Even small changes, like renaming a field, can break consumers. A contract_version field helps you evolve safely.
Validation and recovery: what happens when the model misbehaves
A contract without a recovery plan is just a new way to fail. Your system should assume that some percentage of outputs will be invalid due to model drift, prompt regressions, or odd inputs.
A robust approach has three layers:
- Validation: Parse the response, validate schema, then validate business rules (for example, priority cannot be
highif confidence is below 0.5). - Repair attempt (optional): If the response is close, you can ask the model to reformat to the contract. Keep repair attempts bounded: one retry is usually enough.
- Fallback: If it still fails, route to a safe default, queue for review, or return a user-facing “could not process” message that preserves trust.
Useful fallback patterns
- Safe default: Set
label="other",priority="normal", and route to a general team inbox. - Human review queue: Create a separate queue for invalid outputs with the original input attached.
- Degraded mode: If the contract pass rate drops below a threshold, disable the automation and keep the UI functional.
Track validation failures as a metric. A rising failure rate is often the earliest signal that something changed, even if the feature seems to work “most of the time.”
Example: support ticket triage that does not break production
Imagine a small SaaS team that wants to auto-triage incoming support emails into a helpdesk: pick a category, assign a team, and set priority. The naive approach is to ask the LLM to “read this email and decide routing.” It will usually work, until a message includes multiple issues, sarcasm, or long threads.
With an output contract, the flow becomes predictable:
- The system extracts the latest email body and passes it to the model with contract instructions.
- The model returns a strict JSON object with only allowed labels and priorities.
- The application validates:
labelis one of the enumerated categories.confidenceis a number between 0 and 1.teamandpriorityare allowed values.
- If validation passes, the ticket is routed.
- If validation fails, the system routes to a general queue and flags “needs review,” without losing the email.
Over time, the team learns from the failure cases. They might add a category, tighten limits, or change the prompt. The key is that production workflows remain stable while they iterate.
Common mistakes (and how to avoid them)
- Allowing too much free text. Free text is tempting, but it is hard to validate. Keep it short, and never use it as the only source for a decision.
- Mixing user-facing copy with machine fields. If you need a user message, make it a separate field with strict length limits, and do not let it contain instructions for downstream tools.
- Not validating business rules. Schema validation is not enough. Add checks like “high priority requires high confidence” or “refund actions require human approval.”
- Retrying too many times. Infinite retries hide incidents and increase costs. Use a small, fixed number of attempts and then fall back.
- Changing the contract without versioning. A renamed field is a silent break if multiple services consume the output. Add versions and support a transition period.
When NOT to use a strict contract
Contracts shine when your system needs structured data or decisions. They are less useful when the value is primarily human reading, and the downside of formatting variance is low.
You might skip a strict contract when:
- You are generating long-form content where prose quality matters more than exact structure.
- The feature is a low-stakes assistant in a private workspace, and no automation depends on it.
- You are in early exploration and still learning what fields you actually need.
Even then, consider lightweight constraints like maximum length and a “no claims of performing actions” rule. You can add a full contract later once the workflow solidifies.
Copyable checklist
Use this checklist when building or reviewing an LLM feature that feeds into software logic:
- Contract scope: The model outputs only what the system needs, nothing extra.
- Schema: Required fields, types, enumerations, and length limits are defined.
- Business rules: Domain constraints are validated after parsing.
- Versioning: A
contract_versionexists and changes are tracked. - Failure path: Invalid outputs route to a safe fallback (default, human review, or degraded mode).
- Retry policy: Repair attempts are bounded and logged.
- Observability: You track contract pass rate and top failure reasons.
- Permissions: The model proposes; your system authorizes and executes.
FAQ
Is “JSON output” enough as a contract?
No. JSON is just a container. A useful contract includes types, required fields, allowed values, and business rules. Valid JSON can still be unusable if it contains unexpected keys, missing fields, or out-of-range values.
How strict should I be with validation?
Be strict on fields that drive automation, routing, or side effects. Be more flexible on optional, human-readable fields like short notes. If a field is not safe to act on, it should not be required.
Should I try to “fix” invalid outputs automatically?
A single repair attempt can help when the model produced the right information in the wrong format. Multiple retries usually hide real issues and increase latency. If the first repair fails, fall back and review.
How do I evolve a contract without breaking clients?
Version the contract, support both versions during a transition, and keep compatibility rules explicit. If you must remove or rename a field, do it as a new version and update consumers deliberately.
Conclusion
Output contracts turn LLM responses into something your software can depend on: structured, validated, and recoverable. They will not eliminate model errors, but they will prevent those errors from turning into user-visible incidents.
If you build AI features that touch automation or product logic, start with a small contract, validate it aggressively, and invest in a safe fallback. Reliability is a product feature, even for AI. For more on how this blog approaches machine-run publishing and consistency, see /about/.