Skip to main content
Concept overview: MObjects and mify explains the design and trade-offs.
This tutorial shows how to make existing Python objects queryable and transformable by the LLM using @mify — without changing their Python interface or behaviour. By the end you will have covered:
  • Applying @mify to an existing class
  • m.query() — ask questions about an object
  • m.transform() — produce a transformed version of an object
  • Controlling which fields and methods the LLM sees
  • Using stringify_func for custom text representations
Prerequisites: Tutorial 01 complete, pip install mellea, Ollama running locally with granite4.1:3b downloaded.

The scenario

You have a CustomerRecord class — existing code that you cannot rewrite. You want to start asking the LLM questions about individual records and generating personalised summaries.

Step 1: Apply @mify

Decorate the class with @mify. This adds the LLM-queryable protocol to every instance, without touching the class’s Python interface:
Sample output
Note: LLM output is non-deterministic, output may vary.
@mify adds the MObject protocol to every instance. The stringify_func controls the text the LLM receives. It is required here because without it the model sees Python’s default str() repr — which for a custom class contains no field values — and cannot answer questions about the object’s data.
Full example: docs/examples/mify/mify.py

Step 2: Control the text representation

Refine the stringify_func to produce well-labelled, human-readable text that gives the model the clearest possible view of the object:
Sample output
Note: LLM output is non-deterministic, output may vary.

Step 3: Limit which fields are visible

To hide internal state from the LLM, use fields_include with a Jinja2 template:
Sample output
Note: LLM output is non-deterministic, output may vary. The model only sees name and spend_ytdlast_purchase is excluded by fields_include and will never appear in the response.
The last_purchase field is not in fields_include so it is never sent to the model.

Step 4: Use m.transform()

m.transform() asks the LLM to produce a modified version of the object by calling one of its methods. Expose the target method with funcs_include:
Sample output
Note: m.transform() returns whatever to_summary() returns — here a new CustomerRecord constructed with the generated text as name. Access .name to retrieve it. Your wording will vary.
The LLM calls to_summary(summary=...) with the generated text, and the return value of that method is the result.

Step 5: Mify an object ad hoc

You can also mify an existing object instance without decorating its class — useful when you don’t own the class definition:
Sample output
Note: LLM output is non-deterministic, output may vary. When mify is called on an instance, stringify_func must be a zero-argument callable that closes over the instance (as above). This differs from the class-level @mify(stringify_func=lambda r: ...) form, where Python’s method binding passes the instance as the first argument. Using lambda r: on an instance will raise a TypeError at query time.

What you built

A set of patterns for making legacy Python objects LLM-queryable without modifying their class definitions: See also: MObjects and mify | Working with Data | Tutorial 03: Using Generative Stubs