Skip to main content
In this tutorial you build a document analysis pipeline that extracts a summary, classifies sentiment, and surfaces key issues from customer feedback. You start with the simplest possible Mellea program and add reliability and structure at each step. By the end you will have covered:
  • instruct() with user variables and requirements
  • Rejection sampling and SamplingResult
  • Composing generative functions into a pipeline
@generative in depth: This tutorial uses @generative in the final pipeline step. For a dedicated walkthrough of typed returns, Literal, and Pydantic models, see Tutorial 03: Using Generative Stubs.
Prerequisites: Quick Start complete, Mellea installed (uv add mellea), Ollama running locally with granite4.1:3b downloaded.

Step 1: One instruction

Start with the smallest possible program: a single call to instruct().
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. Use user_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 — use simple_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, but Sentiment will be one of positive, negative, or mixed, and FeedbackIssues fields will be populated strings.
Each step in the pipeline is an independent LLM call with a typed interface. The output of 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