Skip to main content
In classical programming, a pure function takes inputs and produces outputs deterministically. In a generative program, a function can have the same interface but delegate its implementation to an LLM. Mellea calls these generative functions and provides the @generative decorator to define them.
Looking to use this in code? See Generative Functions for practical examples and API details.

The @generative decorator

Decorate a function with @generative and give it a return type annotation. The function body is replaced by the LLM at call time — the signature and docstring guide the model in producing the output.
The session m is always the first argument when calling a generative function. Mellea constructs the prompt automatically from the function name, parameters, docstring, and return type. The Literal annotation constrains the output to exactly two values — the model cannot return anything else. Generative functions can also return Pydantic models for structured multi-field output:

Compositionality

One of the key benefits of generative functions is that they compose the same way ordinary functions do. Independent libraries can each expose generative functions, and those functions can be combined without either library knowing about the other. Consider two independent libraries: one that summarizes documents, and one that proposes decisions or risks from summaries.
These two libraries do not always compose meaningfully — a meeting transcript may or may not contain actionable risks. Calling generate_risk_mitigation on a summary that contains no risks produces noise.

Guarded nondeterminism

To compose libraries safely without coupling them, use generative functions as contracts — small classifiers that gate whether a composition makes sense:
These contracts let you write dynamic composition logic in ordinary Python:
This pattern — using generative functions as boolean guards on composition — is sometimes called guarded nondeterminism. It keeps the two libraries fully decoupled while still making nonsensical compositions impossible at runtime. Without these guards, your only options are to tightly couple the libraries (rewrite one to satisfy the other’s interface) or add requirements to the decision function that silently fail if unmet. Neither approach scales. With contracts, the coupling logic lives in the guard functions, which can be maintained and tested independently.

Generative functions vs instruct()

@generative and m.instruct() serve different purposes: Use @generative when you want a named, typed, reusable LLM-backed operation. Use m.instruct() for one-off generation where a function abstraction would be overhead. See also: Instruct, Validate, Repair | The Requirements System | Tools and Agents