MObjects and mify
Looking to use this in code? See Tutorial 05: MIFYing Legacy Code for a practical walkthrough.
Object-oriented programming organizes related data and the methods that operate on it into classes. Mellea applies the same principle to LLM interactions: an MObject is a Python class whose fields and methods can be exposed to a model in a controlled, structured way.
The @mify decorator turns any class into an MObject. You specify exactly which fields and
methods are visible to the LLM — nothing else is exposed.
The @mify decorator
import mellea
from mellea.stdlib.components import mify
from mellea.stdlib.components.mify import MifiedProtocol
@mify(fields_include={"table"}, template="{{ table }}")
class SalesDatabase:
table: str = """| Store | Sales |
| ---------- | ------ |
| Northeast | $250 |
| Southeast | $80 |
| Midwest | $420 |"""
def internal_method(self):
# not exposed to the LLM
...
m = mellea.start_session()
db = SalesDatabase()
assert isinstance(db, MifiedProtocol)
answer = m.query(db, "What were sales for the Northeast branch this month?")
print(str(answer))
# Output will vary — LLM responses depend on model and temperature.
fields_include controls which fields appear in the prompt. template is a Jinja2 template
that controls how those fields are rendered. The m.query() call sends the rendered object
plus the question to the model.
@mify is useful whenever you need to expose structured data to a model without leaking
internal state.
Methods as tools
When you mify a class, every method that has a docstring is automatically registered as a
tool the LLM can call. Use funcs_include or funcs_exclude to control which methods
are exposed:
from mellea.stdlib.components import mify
@mify(funcs_include={"from_markdown"})
class DocumentLoader:
def __init__(self) -> None:
self.content = ""
@classmethod
def from_markdown(cls, text: str) -> "DocumentLoader":
"""Load a document from a Markdown string."""
doc = DocumentLoader()
doc.content = text
return doc
def internal_helper(self) -> str:
# no docstring, and not in funcs_include — never exposed
return "..."
Only from_markdown is registered as a tool. The model can call it during a m.transform()
or m.query() operation; internal_helper is invisible.
When a class method and an LLM operation would produce the same result, Mellea will note that the direct method call is available:
# Both of these transform the table in the same way.
# Mellea will suggest using the direct method call instead.
table_transposed = m.transform(table, "Transpose the table.")
table_transposed_direct = table.transpose()
Working with documents
Mellea provides mified wrappers around Docling
documents for working with PDFs and other rich documents.
from mellea.stdlib.components.docs.richdocument import RichDocument
rd = RichDocument.from_document_file("https://arxiv.org/pdf/1906.04043")
This loads the PDF and parses it into Mellea's intermediate representation. From there you can extract structured elements:
from mellea.stdlib.components.docs.richdocument import Table
table: Table = rd.get_tables()[0]
print(table.to_markdown())
Table is already an MObject, so you can pass it directly to m.transform() or m.query():
from mellea.backends import ModelOption
from mellea import start_session
m = start_session()
# Try a few seeds to find a run that returns a parsable table
for seed in [x * 12 for x in range(5)]:
result = m.transform(
table,
"Add a column 'Model' that extracts which model was used, or 'None' if none.",
model_options={ModelOption.SEED: seed},
)
if isinstance(result, Table):
print(result.to_markdown())
break
The seed loop is a simple retry strategy: LLM output is non-deterministic, so iterating over seeds gives multiple independent samples until one produces a valid table structure.
Note: LLM output is non-deterministic. Your exact results will vary.
When to use MObjects
MObjects are well-suited for:
- Document querying — wrap a document, expose only the relevant sections, query or transform them with the model
- Tool registration — expose a controlled set of methods as tools the LLM can invoke during generation
- Evolving existing codebases — add
@mifyto an existing class to make it LLM-accessible without rewriting it
For simple one-off generation, m.instruct() is usually sufficient. MObjects add value when
you have structured data or methods that the model needs to reason about or call.
See also: Context and Sessions | Generative Functions