Skip to main content
Looking to use this in code? See Write Custom Verifiers for practical examples and API details.
Requirements are Mellea’s mechanism for enforcing constraints on generative output. They serve two roles simultaneously: they appear in the prompt so the model knows what to aim for, and they are evaluated after generation so Mellea can detect and repair failures automatically. This page explains the requirements system in depth. For a quick introduction, see The Instruction Model.

What a requirement is

A Requirement is a Component that wraps a natural-language description and an optional validation function. During the instruct–validate–repair (IVR) loop:
  1. Mellea renders the requirement descriptions into the prompt alongside the instruction.
  2. After the model generates output, each requirement is validated against that output.
  3. If any requirement fails, Mellea sends the model a repair request, listing which requirements failed and why.
  4. The loop retries up to loop_budget times (default: 2).
Passing plain strings directly to instruct() is equivalent — they are converted to Requirement objects internally:

req() and check() shorthands

req() and check() are concise constructors from mellea.stdlib.requirements:
The difference matters: when check_only=True, the requirement description is evaluated after generation but not embedded in the prompt. This avoids the purple elephant effect — where mentioning something in a negative instruction (e.g., “do not mention purple elephants”) paradoxically increases the chance the model produces it. Use req() for positive constraints you want the model to aim for. Use check() for negative or hard-to-explain constraints that are better left out of the prompt.

Custom validation functions

For deterministic checks, attach a validation_fn. Mellea skips LLM-as-a-judge and runs your function directly:
simple_validate is a convenience wrapper. It accepts a function that receives the most recent model output as a string and returns either:
  • bool — pass or fail; no reason is captured
  • tuple[bool, str] — pass/fail plus a reason string that Mellea includes in the repair request

ValidationResult in depth

simple_validate produces ValidationResult objects automatically. When you write a full validation function directly, you construct ValidationResult yourself:
The validation_fn signature is Callable[[Context], ValidationResult]. The Context object gives you access to the full session state if needed — not just the last output. ValidationResult fields: The reason field is the most useful in practice — a clear reason string helps the model make a targeted repair rather than regenerating blindly.

Preconditions in generative functions

The @generative decorator supports precondition_requirements alongside the standard requirements. Preconditions are validated against the inputs to the function before generation starts. If they fail, Mellea raises PreconditionException immediately — no generation attempt is made and no IVR loop runs.
PreconditionException.validation is a list of ValidationResult objects for every requirement that failed, giving you a complete picture of what went wrong.
Note: precondition_requirements require a strategy to be specified (e.g., RejectionSamplingStrategy()). Without a strategy the precondition check is skipped with a warning.

Inspecting validation results

When you use return_sampling_results=True, instruct() returns a SamplingResult instead of a ModelOutputThunk. This exposes per-attempt validation results:
SamplingResult.sample_validations is a list of attempts, each containing a list of (Requirement, ValidationResult) tuples. SamplingResult.result_validations gives you the same for the final selected output only.

LLM-as-a-judge vs custom validators

LLM-as-a-judge requirements call the backend for each validation, which adds latency. For high-throughput workloads, prefer simple_validate for deterministic checks and reserve LLM-based requirements for subjective criteria that cannot be coded directly.
Advanced: ALoraRequirement (from mellea.stdlib.requirements) uses a fine-tuned LoRA adapter for validation instead of LLM-as-a-judge. It falls back to LLM-as-a-judge if the adapter is unavailable. See LoRA and aLoRA Adapters.
For a full walkthrough of using LLM-as-a-judge for output quality evaluation, see Evaluate with LLM-as-a-Judge.

Composing requirements

Requirements are composable: mix strings, req(), check(), and Requirement objects freely in the same list:
All requirements are validated after each generation attempt. The repair request lists every requirement that failed, not just the first one, so the model can address all issues in a single repair pass.

Streaming validation

stream_validate() is the streaming counterpart to validate(). It is called once per semantic chunk as tokens arrive from the model, before the full output is available. Requirements that need to detect problems early — too many sentences, a prohibited keyword in the first paragraph, unexpected JSON structure mid-output — override stream_validate() to express that logic. stream_validate() returns a PartialValidationResult with a tri-state success field:
  • "unknown" — no conclusion yet; the chunk is passed to the consumer and validate() will be called at stream end.
  • "pass" — the chunk looks valid so far; it is passed to the consumer and validate() is still called at stream end (a streaming pass is informational, not final).
  • "fail" — the stream is cancelled immediately; no further chunks reach the consumer; validate() is skipped for this requirement.
State isolation is per-clone: stream_with_chunking() copies each requirement with copy() before starting the orchestrator, so the original objects are never mutated. Requirements that accumulate state across chunks (e.g. a running word count) should reassign mutable containers rather than mutate in place, since clones share the original’s __dict__ values at copy time.
See also: Streaming with per-chunk validation