Skip to main content
Advanced: This page is for developers who need to go beyond the standard @generative, instruct(), and m.chat() API. If you are getting started with Mellea, see the Quick Start first.
The Component Protocol is the fundamental unit of composition in Mellea. Every high-level API call — m.instruct(), @generative, m.chat() — is backed by a Component that formats its input for the LLM and parses the output into a typed result. This page shows you how to implement the protocol yourself.

When to build a custom component

Use the standard API in most cases. Build a custom Component when:
  • You need a domain-specific prompt structure that cannot be expressed as a @generative docstring or an instruct() template.
  • You need deterministic, reusable parsing logic across many call sites — not ad-hoc post-processing.
  • You want to unit-test prompt formatting and output parsing in isolation, without a real backend.
  • You are building a reusable library component that other developers will import.
  • You need to feed a ModelOutputThunk from one LLM call directly into the formatted input of another (lazy composition).
If none of these apply, @generative or instruct() covers your use case with less boilerplate.

The Component Protocol

Component is a Protocol generic over S, the return type produced when the component parses LLM output:
The protocol has three required methods and one public method that wraps _parse: You implement parts(), format_for_llm(), and _parse(). You do not override parse() — the base implementation calls _parse() and wraps any exception in a ComponentParseError so callers always get a consistent error type.

Type parameter

Component[S] is parameterised by S: the Python type your _parse method returns. For example, Component[str] returns a plain string, while Component[list[str]] returns a list. The type parameter is enforced at static analysis time by mypy.

Minimal example: FeedbackForm

The following component formats a structured feedback request and parses the model’s response into a Python dictionary.
Pass the component to m.act() to get a result:
You can also use MelleaSession.act() — the session method is a thin wrapper around the same functional API:

Using TemplateRepresentation for Jinja2-based rendering

For components that need model-specific prompt formatting, return a TemplateRepresentation from format_for_llm() instead of a plain string. TemplateRepresentation is a dataclass with these fields: The formatter resolves template files from a templates/prompts/ directory, traversing subdirectories that match the model ID before falling back to default/. See Mellea Core Internals for the full lookup order.
Place the template file at mellea/templates/prompts/default/FeedbackFormTemplate.jinja2:
Use inline template= for one-off components where a separate file is unnecessary:

Registering with act()

You do not need to register or annotate a custom component. Pass it directly to m.act() or mfuncs.act():
For async workflows, use mfuncs.aact():

Testing custom components

Because Component is a Protocol, you can test formatting and parsing without a real backend. Create a ModelOutputThunk with a known value to exercise _parse directly.
Note: ModelOutputThunk accepts a value keyword argument in tests. Check the current constructor signature in mellea/core/base.py if the import path changes in a future release. Tip: Keep _parse pure — no I/O, no side effects. This makes it trivial to unit test and means failures are always the model’s fault, not your parsing code.

Next steps

  • Mellea Core Internals — understand CBlock, ModelOutputThunk, and the full abstraction stack that custom components plug into.
  • Write Custom Verifiers — combine custom components with requirement validation to build structured output pipelines with automatic retry.