Documentation Index
Fetch the complete documentation index at: https://docs.mellea.ai/llms.txt
Use this file to discover all available pages before exploring further.
This example runs a session that exercises four different Mellea operations —
m.instruct(), a @generative classifier, a @generative entity extractor,
and a multi-turn m.chat() — while OpenTelemetry instrumentation records each
step. Two independent trace scopes control what gets recorded: the application
trace covers Mellea-level operations, and the backend trace covers raw LLM
calls.
Source file: docs/examples/telemetry/telemetry_example.py
Concepts covered
- The two independent trace scopes:
mellea.application and mellea.backend
- Controlling tracing with
MELLEA_TRACE_APPLICATION and
MELLEA_TRACE_BACKEND environment variables
- Using
start_session() as a context manager so session lifecycle is spanned
- Exporting spans to an OTLP endpoint (Jaeger)
- Using
mellea.stdlib.requirements.req to attach constraints to m.instruct()
Prerequisites
- Quick Start complete
- Ollama running locally with
granite4:micro pulled
- (Optional) Jaeger running locally for span
visualisation — see the Jaeger section below
Install with all extras to get the OpenTelemetry dependencies:
Trace scopes
Mellea defines two independent OpenTelemetry trace scopes.
| Scope | Env var | What it records |
|---|
| Application | MELLEA_TRACE_APPLICATION | Session lifecycle, @generative calls, aact, sampling, requirement validation |
| Backend | MELLEA_TRACE_BACKEND | Raw model generation calls, context-based generation, backend-specific operations |
Both default to false. Enable either or both independently depending on what
you need to observe.
| Configuration | Overhead |
|---|
| Both disabled (default) | Near-zero |
| Application only | ~1–2 % |
| Backend only | ~1–2 % |
| Both enabled | ~2–5 % |
Running the example
No tracing (baseline)
python docs/examples/telemetry/telemetry_example.py
Application tracing only
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=false
python docs/examples/telemetry/telemetry_example.py
Backend tracing only
export MELLEA_TRACE_APPLICATION=false
export MELLEA_TRACE_BACKEND=true
python docs/examples/telemetry/telemetry_example.py
Both scopes with console output for debugging
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=true
export MELLEA_TRACE_CONSOLE=true
python docs/examples/telemetry/telemetry_example.py
Export to an OTLP endpoint (Jaeger)
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=true
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
python docs/examples/telemetry/telemetry_example.py
Starting Jaeger
Run Jaeger in Docker to receive and visualise spans:
docker run -d --name jaeger \
-p 4317:4317 \
-p 16686:16686 \
jaegertracing/all-in-one:latest
After running the example, open http://localhost:16686, select the
mellea-example service, and browse the trace timeline.
The full example
Generative function declarations
from mellea import generative, start_session
from mellea.stdlib.requirements import req
@generative
def classify_sentiment(text: str) -> str:
"""Classify the sentiment of the given text as positive, negative, or neutral."""
@generative
def extract_entities(text: str) -> list[str]:
"""Extract named entities from the text."""
These two functions are declared at module level. @generative wires them up
to the runtime; no implementation is needed. Each call site below passes a
session m as the first argument, which binds the call to the current trace
context.
Session as a context manager and introspection
def main():
"""Run example with telemetry instrumentation."""
print("=" * 60)
print("Mellea OpenTelemetry Example")
print("=" * 60)
# Check which traces are enabled
from mellea.telemetry import (
is_application_tracing_enabled,
is_backend_tracing_enabled,
)
print(f"Application tracing: {is_application_tracing_enabled()}")
print(f"Backend tracing: {is_backend_tracing_enabled()}")
print("=" * 60)
is_application_tracing_enabled() and is_backend_tracing_enabled() reflect
the current environment variable state at runtime. Use these guards in your own
code when you want to conditionally add tracing context (for example, adding
custom span attributes only when tracing is on).
Operation 1: instruct with requirements
# Start a session - this will be traced if application tracing is enabled
with start_session() as m:
# Example 1: Simple instruction with requirements
print("\n1. Simple instruction with requirements...")
email = m.instruct(
"Write a professional email to {{name}} about {{topic}}",
requirements=[req("Must be formal"), req("Must be under 100 words")],
user_variables={"name": "Alice", "topic": "project update"},
)
print(f"Generated email: {str(email)[:100]}...")
Using start_session() as a context manager (with start_session() as m:)
means the session open and close events are recorded as the root span when
application tracing is enabled. All child operations appear nested under this
root.
req("Must be formal") attaches a soft requirement to the generation.
Requirements appear as span attributes in the trace so you can see which
constraints were applied and whether they triggered a retry.
Operation 2: @generative sentiment classifier
# Example 2: Using @generative function
print("\n2. Using @generative function...")
sentiment = classify_sentiment(
m, text="I absolutely love this product! It's amazing!"
)
print(f"Sentiment: {sentiment}")
Each @generative call produces its own child span in the application trace.
The span includes the function name, parameter names, and the inferred return
type.
# Example 3: Multiple operations
print("\n3. Multiple operations...")
text = "Apple Inc. announced new products in Cupertino, California."
entities = extract_entities(m, text=text)
print(f"Entities: {entities}")
Running multiple @generative calls inside the same with block keeps them
all under the same root span. In Jaeger you can see the sequence and duration of
each call on a single timeline.
Operation 4: multi-turn chat
# Example 4: Chat interaction
print("\n4. Chat interaction...")
response1 = m.chat("What is 2+2?")
print(f"Response 1: {response1!s}")
response2 = m.chat("Multiply that by 3")
print(f"Response 2: {response2!s}")
m.chat() is a stateful multi-turn method. The session accumulates turn
history, so response2 can refer back to the result of response1 without
repeating the context. Both turns appear as sibling spans under the root session
span.
Full file
# pytest: ollama, llm
"""Example demonstrating OpenTelemetry tracing in Mellea.
This example shows how to use the two independent trace scopes:
1. Application trace - tracks user-facing operations
2. Backend trace - tracks LLM backend interactions
Run with different configurations:
# Enable only application tracing
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=false
python telemetry_example.py
# Enable only backend tracing
export MELLEA_TRACE_APPLICATION=false
export MELLEA_TRACE_BACKEND=true
python telemetry_example.py
# Enable both traces
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=true
python telemetry_example.py
# Export to OTLP endpoint (e.g., Jaeger)
export MELLEA_TRACE_APPLICATION=true
export MELLEA_TRACE_BACKEND=true
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
python telemetry_example.py
# Enable console output for debugging
export MELLEA_TRACE_CONSOLE=true
python telemetry_example.py
"""
from mellea import generative, start_session
from mellea.stdlib.requirements import req
@generative
def classify_sentiment(text: str) -> str:
"""Classify the sentiment of the given text as positive, negative, or neutral."""
@generative
def extract_entities(text: str) -> list[str]:
"""Extract named entities from the text."""
def main():
"""Run example with telemetry instrumentation."""
print("=" * 60)
print("Mellea OpenTelemetry Example")
print("=" * 60)
# Check which traces are enabled
from mellea.telemetry import (
is_application_tracing_enabled,
is_backend_tracing_enabled,
)
print(f"Application tracing: {is_application_tracing_enabled()}")
print(f"Backend tracing: {is_backend_tracing_enabled()}")
print("=" * 60)
# Start a session - this will be traced if application tracing is enabled
with start_session() as m:
# Example 1: Simple instruction with requirements
print("\n1. Simple instruction with requirements...")
email = m.instruct(
"Write a professional email to {{name}} about {{topic}}",
requirements=[req("Must be formal"), req("Must be under 100 words")],
user_variables={"name": "Alice", "topic": "project update"},
)
print(f"Generated email: {str(email)[:100]}...")
# Example 2: Using @generative function
print("\n2. Using @generative function...")
sentiment = classify_sentiment(
m, text="I absolutely love this product! It's amazing!"
)
print(f"Sentiment: {sentiment}")
# Example 3: Multiple operations
print("\n3. Multiple operations...")
text = "Apple Inc. announced new products in Cupertino, California."
entities = extract_entities(m, text=text)
print(f"Entities: {entities}")
# Example 4: Chat interaction
print("\n4. Chat interaction...")
response1 = m.chat("What is 2+2?")
print(f"Response 1: {response1!s}")
response2 = m.chat("Multiply that by 3")
print(f"Response 2: {response2!s}")
print("\n" + "=" * 60)
print("Example complete!")
print("=" * 60)
print("\nTrace data has been exported based on your configuration.")
print("If OTEL_EXPORTER_OTLP_ENDPOINT is set, check your trace backend.")
print("If MELLEA_TRACE_CONSOLE=true, traces are printed above.")
if __name__ == "__main__":
main()
Span attributes
Each span in the application trace includes the following attributes where
applicable:
| Attribute | Description |
|---|
model_id | Model identifier used for the call |
backend | Backend class name (e.g. OllamaBackend) |
action_type | Component type (e.g. generative, instruct) |
context_size | Number of context items passed |
has_requirements | Whether requirements were specified |
strategy_type | Sampling strategy used |
tool_calls | Whether tool calling was enabled |
format_type | Response format class |
What to try next
- Set
OTEL_SERVICE_NAME=my-app to customise the service name in your trace
backend.
- See Tracing
for attribute schemas and advanced configuration.
- Add
MELLEA_TRACE_CONSOLE=true alongside an OTLP endpoint to confirm spans
are generated even when the remote collector is unavailable.