Skip to main content
Most backends operate on text. Mellea converts Python objects to text using the TemplateFormatter — a Jinja2-based system that lets you control exactly how each component type is rendered for the model. This page is for advanced users and library authors who need to customize how objects are represented in prompts.

Templates

The TemplateFormatter uses Jinja2 templates stored in a directory tree under mellea/templates/prompts/. Each component type has a corresponding .jinja2 file that controls its textual representation. The default templates are in mellea/templates/prompts/default/. Templates can also be stored directly on the class by returning a TemplateRepresentation from format_for_llm(), rather than relying on a directory lookup.

Template lookup order

When rendering a component, the TemplateFormatter searches for a matching template in this order:
  1. The formatter’s in-memory cache (if the template has been looked up recently)
  2. The formatter’s configured template path
  3. The package that owns the object being formatted (mellea or a third-party package)
When searching a directory, the formatter traverses subdirectories that match the current model ID — for example, ibm-granite/granite-3.2-8b-instruct matches:
templates/prompts/granite/granite-3-2/instruct/
or falls back to:
templates/prompts/default/
The deepest matching directory wins. A given templates/ directory should not contain multiple matches for the same model ID (e.g. both granite/ and ibm/ paths for the same model string).

Template representations

A component’s format_for_llm() method controls how it is rendered. It returns either a plain string or a TemplateRepresentation object. Plain string — skip the template engine entirely:
def format_for_llm(self) -> str:
    return f"Table with {len(self.rows)} rows:\n{self.to_markdown()}"
TemplateRepresentation — use the template engine:
from mellea.stdlib.components import TemplateRepresentation

def format_for_llm(self) -> TemplateRepresentation:
    return TemplateRepresentation(
        component=self,
        args={"table": self.to_markdown(), "title": self.title},
        tools=[],
        template_order=["my_component", "*"],  # * = class name
    )
TemplateRepresentation fields:
FieldDescription
componentThe object being rendered (usually self)
argsDict of variables passed to the Jinja2 template
toolsList of tool/function descriptors exposed to the model
templateInline Jinja2 template string (alternative to template_order)
template_orderList of template filenames to search for, in priority order

Customizing templates for a component

To customize how an existing component is formatted for a specific model, subclass it and override format_for_llm(), then create a new .jinja2 template file.
class MyCustomTable(Table):
    def format_for_llm(self) -> TemplateRepresentation:
        return TemplateRepresentation(
            component=self,
            args={"table": self.to_markdown()},
            tools=list(self._get_tools()),
            template_order=["my_custom_table", "table", "*"],
        )
Place the template file at:
your_package/templates/prompts/default/my_custom_table.jinja2
or at a model-specific path:
your_package/templates/prompts/granite/granite-3-2/instruct/my_custom_table.jinja2
The model-specific template will be used for that model; all others fall back to default/.
Advanced: For a worked example of advanced template customization, see docs/examples/mify/rich_document_advanced.py in the source repository.
See also: MObjects and mify | Mellea core internals