instruct()with user variables and requirements- Rejection sampling and
SamplingResult - Composing generative functions into a pipeline
Prerequisites: Quick Start complete, Mellea installed (@generativein depth: This tutorial uses@generativein the final pipeline step. For a dedicated walkthrough of typed returns,Literal, and Pydantic models, see Tutorial 03: Using Generative Stubs.
uv add mellea), Ollama running locally with granite4.1:3b downloaded.
Step 1: One instruction
Start with the smallest possible program: a single call toinstruct().
Sample output
Note: LLM output is non-deterministic. Your result will vary in wording.
instruct() returns a ModelOutputThunk. Calling str() on it (or accessing
.value) gives you the string. This is already a generative program: it calls an
LLM and returns structured text.
The problem is reliability. The model might return two sentences, or three, or
include a preamble. Move to the next step to enforce the format.
Step 2: Adding user variables
Hardcoding the text in the instruction string makes the function impossible to reuse. Useuser_variables and {{double_braces}} template syntax:
Sample output
Note: LLM output is non-deterministic. Your result will vary in wording, but should be a single sentence.The description is now a Jinja2 template. Variables are rendered at generation time, not embedded in the source code.
Step 3: Enforcing constraints with requirements
Pass a list of plain-English requirements to constrain the output. Mellea checks each requirement after generation and retries if any fail:Sample output
Note: LLM output is non-deterministic. Your result will vary in wording, but should be a single sentence capturing both the negative and positive aspects.Requirements are validated by LLM-as-a-judge by default. If a requirement fails, Mellea sends the model the failure reason and asks it to repair the output.
Step 4: Deterministic validation
For facts you can check in code — word counts, format, length — usesimple_validate:
Sample output
Note: LLM output is non-deterministic. Your result will vary in wording, but should be a single sentence capturing fewer than 30 words.The word-count check is deterministic: it runs in microseconds. The “single sentence” check is left for LLM-as-a-judge since counting sentences is harder to code reliably.
Step 5: Rejection sampling and inspecting results
By default,instruct() retries up to twice if any requirement fails. Use
RejectionSamplingStrategy to control the budget and inspect results:
Sample output
Note: LLM output is non-deterministic. Your result will vary in wording, but should be a single sentence fewer than 30 words.With
return_sampling_results=True, instruct() returns a SamplingResult with
.success, .result, and .sample_generations. This gives you programmatic
control over what to do when the model can not satisfy your requirements.
Step 6: Composing the pipeline
Assemble all the pieces into a complete pipeline:Sample output
Note: LLM output is non-deterministic. Wording will vary, butEach step in the pipeline is an independent LLM call with a typed interface. The output ofSentimentwill be one ofpositive,negative, ormixed, andFeedbackIssuesfields will be populated strings.
summarize_feedback feeds classify_sentiment; the original feedback
feeds extract_issues. There is no global state, no prompt accumulation — each
call is self-contained.
Full example: docs/examples/instruct_validate_repair/101_email_with_requirements.py
What you have built
See also: Tutorial 02: Streaming and Async | Instruct, Validate, Repair | The Requirements System | Generative Functions | MObjects and mify | Use Images and Vision