The Instruction Model
Prerequisites: Quick Start complete,
pip install mellea, Ollama running locally.
instruct() is the primary API in Mellea. It builds a structured Instruction
component — not a raw chat message — with a description, requirements, user variables,
grounding context, few-shot examples, and images. The instruction is rendered through
Jinja2 templates and run through an instruct–validate–repair (IVR) loop by default.
Basic instruct()
# Requires: mellea
# Returns: str
import mellea
m = mellea.start_session()
email = m.instruct("Write an email inviting interns to an office party at 3:30pm.")
print(str(email))
# Output will vary — LLM responses depend on model and temperature.
instruct() returns a ModelOutputThunk. Access the result as a string with
str(email) or via email.value.
User variables
Embed dynamic values in your description using {{double_braces}}. The description
is a Jinja2 template; values are injected at generation time via user_variables:
# Requires: mellea
# Returns: str
import mellea
def write_email(m: mellea.MelleaSession, name: str, notes: str) -> str:
email = m.instruct(
"Write an email to {{name}} using the notes following: {{notes}}.",
user_variables={"name": name, "notes": notes},
)
return str(email)
m = mellea.start_session()
print(write_email(m, name="Olivia", notes="Organized intern events."))
# Output will vary — LLM responses depend on model and temperature.
Variables work in requirements too — you can use the same {{var}} syntax anywhere
in the instruction description or requirement strings.
Requirements
Requirements are declarative constraints. They serve two purposes:
- They are embedded in the prompt so the model knows what to aim for.
- They are checked after generation; if any fail, the IVR loop asks the model to repair its output.
Pass plain strings for LLM-checked requirements:
# Requires: mellea
# Returns: str
import mellea
m = mellea.start_session()
email = m.instruct(
"Write an email inviting the team to a meeting.",
requirements=[
"The email should have a salutation.",
"Use only lower-case letters.",
],
)
print(str(email))
# Output will vary — LLM responses depend on model and temperature.
Custom validation functions
For deterministic checks, attach a validation_fn to a Requirement:
# Requires: mellea
# Returns: str
from mellea import start_session
from mellea.core import Requirement
from mellea.stdlib.requirements import simple_validate
word_limit_req = Requirement(
"Use fewer than 100 words.",
validation_fn=simple_validate(lambda output: len(output.split()) < 100),
)
m = start_session()
email = m.instruct(
"Write an email inviting the team to a meeting.",
requirements=["Be formal.", word_limit_req],
)
print(str(email))
simple_validate wraps a callable that returns a bool (or a (bool, str) tuple
with a failure reason) into a validation function.
Shorthand helpers
req() and check() are concise constructors for Requirement:
# Requires: mellea
# Returns: str
from mellea import start_session
from mellea.stdlib.requirements import check, req, simple_validate
m = start_session()
email = m.instruct(
"Write an email to {{name}}.",
requirements=[
req("The email should have a salutation."),
req(
"Use only lower-case letters.",
validation_fn=simple_validate(lambda x: x.lower() == x),
),
check("Do not mention purple elephants."),
],
user_variables={"name": "Olivia"},
)
print(str(email))
# Output will vary — LLM responses depend on model and temperature.
req(description)— creates aRequirementwith an optionalvalidation_fncheck(description)— alias forreq(), reads naturally for boolean constraints
Sampling strategies and the IVR loop
By default, instruct() uses RejectionSamplingStrategy(loop_budget=2): it
generates once, validates all requirements, and retries up to two times if any fail.
Configure the loop explicitly with strategy:
# Requires: mellea
# Returns: SamplingResult
from mellea import start_session
from mellea.stdlib.requirements import req, simple_validate
from mellea.stdlib.sampling import RejectionSamplingStrategy
m = start_session()
result = m.instruct(
"Write an email to {{name}}.",
requirements=[
req(
"Use only lower-case letters.",
validation_fn=simple_validate(lambda x: x.lower() == x),
),
],
strategy=RejectionSamplingStrategy(loop_budget=5),
user_variables={"name": "Olivia"},
return_sampling_results=True,
)
if result.success:
print(str(result.result))
else:
# All attempts failed — fall back to the first generation
print(str(result.sample_generations[0].value))
With return_sampling_results=True, instruct() returns a SamplingResult instead
of a ModelOutputThunk. This lets you inspect whether validation passed and access
all intermediate generations.
Advanced: SOFAI (
SOFAISamplingStrategy) is a dual-model strategy that routes between a fast and a slow model based on confidence. See Inference-Time Scaling.
Grounding context
Attach reference documents to an instruction for retrieval-augmented generation:
from mellea import start_session
m = start_session()
answer = m.instruct(
"Given the documents in the context, answer: {{query}}",
user_variables={"query": "What is the capital of France?"},
grounding_context={"doc0": "France is a country in Western Europe. Its capital is Paris."},
)
print(str(answer))
# Output will vary — LLM responses depend on model and temperature.
grounding_context maps string keys to document text. The keys are arbitrary
labels — they appear in the prompt as [key] = value so the model can reference
them by name, but there is no required naming convention (e.g. "doc0", "annual_report",
"spec" all work). See Working with Data for richer
document handling using MObjects and RichDocument.