Export MLflow traces to Future AGI over OTLP
Export new MLflow traces to Future AGI over OTLP, with environment-based routing and Python span processors for content and exception filtering.
Send new MLflow 3 traces to Observe using MLflow’s OpenTelemetry (OTLP) export. Environment variables route spans to Future AGI; the Python span processors under Privacy filter recorded content and exception events. This route does not use a traceAI MLflow instrumentor.
This is live export only. Traces already stored in an MLflow tracking server, Databricks or Unity Catalog are not imported. Only spans that end while the variables below are set are sent.
MLflow installs opentelemetry-sdk but not an OTLP exporter. Install opentelemetry-exporter-otlp-proto-http yourself, otherwise MLflow exports nothing and logs nothing about it.
Install
This recipe uses MLflow 3.16.1 on Python 3.12.
pip install mlflow==3.16.1 opentelemetry-exporter-otlp-proto-http==1.45.0 openai==2.54.0
Configure
Set these before the first span starts. MLflow builds its exporter once, on first use.
export FI_API_KEY="your-futureagi-api-key"
export FI_SECRET_KEY="your-futureagi-secret-key"
# The full URL. MLflow does not append /v1/traces to this variable.
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://YOUR_FI_COLLECTOR_ORIGIN/tracer/v1/traces"
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="http/protobuf" # MLflow defaults to gRPC
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="$(python -c 'import os; from urllib.parse import quote; print("x-api-key={0},x-secret-key={1}".format(quote(os.environ["FI_API_KEY"], safe=""), quote(os.environ["FI_SECRET_KEY"], safe="")))')"
export OTEL_RESOURCE_ATTRIBUTES="project_name=my-mlflow-app,project_type=observe"
export OTEL_SERVICE_NAME="my-mlflow-app"
export MLFLOW_ENABLE_OTEL_GENAI_SEMCONV=true # typed spans, plain model name, tokens
export MLFLOW_DISABLE_TELEMETRY=true
export MLFLOW_MODEL_CATALOG_URI="" # no model-catalog fetch from github.com
# These make MLflow send nothing, or send it elsewhere. Leave them unset.
unset OTEL_EXPORTER_OTLP_ENDPOINT OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
unset OTEL_EXPORTER_OTLP_HEADERS MLFLOW_TRACING_DESTINATION
The header command percent-encodes the keys so reserved characters do not change the header list.
Before running the example, review the exception-event and content handling in Privacy. Register the processors before the first span starts.
Then trace as usual:
import mlflow
mlflow.openai.autolog() # OpenAI Chat Completions; openai is installed above
@mlflow.trace(span_type="TOOL")
def get_refund_window(product: str) -> dict:
return {"product": product, "days": 30}
print(get_refund_window("headphones"))
For a self-hosted Future AGI use your own collector origin in place of YOUR_FI_COLLECTOR_ORIGIN (for example http://localhost:14338). Both /tracer/v1/traces and /v1/traces are accepted by the collector; leave compression off (the exporter default) for compatibility with older self-hosted versions. Future AGI v1.56.0 and later also accept gzip.
What is captured
With MLFLOW_ENABLE_OTEL_GENAI_SEMCONV=true (recommended):
| MLflow span | Future AGI |
|---|---|
model call (CHAT_MODEL, LLM) | span kind llm, named chat <model>, model and provider without quotes, prompt and completion tokens, cost when the model is in the price table |
TOOL function | span kind tool, named execute_tool |
CHAIN, RETRIEVER, EMBEDDING, RERANKER, GUARDRAIL, EVALUATOR | the matching span kind |
AGENT function | span invoke_agent, kind unknown |
| exception in a span | status ERROR on that span and its parents (the error text and stack trace too, unless you register the processors under Privacy) |
| trace hierarchy | preserved (parent/child) |
In Future AGI 1.56.1, the collector promotes gen_ai.input.messages and gen_ai.output.messages into the input and output columns when those attributes are present, while retaining the raw attributes. The experiment id is not exported unless you add mlflow.experiment_id=<id> to OTEL_RESOURCE_ATTRIBUTES; it is a resource attribute, not a Future AGI session. There is no session or user id.
With the switch off, MLflow exports its own mlflow.* keys, which Future AGI does not map: every span is kind unknown, and autolog model calls carry no model or tokens. Leave the switch on.
Privacy
MLflow has no setting that keeps tracing on and content off. By default MLflow exports the prompts, responses and tool inputs and outputs it records, as span attributes (gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, tool call arguments and results). This is on by default, like the traceAI instrumentors; you turn it off with a span processor.
Two processors, both registered before the first span:
import mlflow
from mlflow.entities import LiveSpan, SpanStatusCode
def _filter_events(span: LiveSpan, *, content: bool = False) -> None:
# Read through the public OTel events API. MLflow 3.16.1 / OTel 1.45
# have no public event-removal API, so replacing the backing collection
# is the only compatibility fallback. Never silently skip filtering.
def is_sensitive(event) -> bool:
if event.name == "exception" or any(
key.startswith("exception.") for key in event.attributes or {}
):
return True
if not content:
return False
return event.name in {
"gen_ai.system.message", "gen_ai.user.message", "gen_ai.assistant.message",
"gen_ai.tool.message", "gen_ai.choice", "gen_ai.input.messages",
"gen_ai.output.messages", "tool.input", "tool.output",
} or event.name.startswith("gen_ai.content.") or any(
key in {"body", "content", "input", "output", "prompt", "completion", "messages"}
or key.startswith(("gen_ai.input.", "gen_ai.output.", "gen_ai.system_instructions",
"gen_ai.tool.call.arguments", "gen_ai.tool.call.result"))
for key in event.attributes or {}
)
try:
otel_span = span._span
events = tuple(otel_span.events)
kept = tuple(event for event in events if not is_sensitive(event))
if len(kept) != len(events):
if not hasattr(otel_span, "_events"):
raise RuntimeError("Event removal is unavailable")
otel_span._events = list(kept)
if tuple(otel_span.events) != kept:
raise RuntimeError("Event removal did not take effect")
except Exception:
# MLflow catches Exception from processors and exports the span anyway.
# SystemExit deliberately stops finalization before the unsafe span is
# queued for export; neither the event nor its text is logged here.
raise SystemExit("Cannot safely filter MLflow events; refusing to export this span.") from None
def drop_exception_events(span: LiveSpan) -> None:
_filter_events(span)
def drop_content(span: LiveSpan) -> None:
_filter_events(span, content=True)
if span.inputs is not None:
span.set_inputs("[REDACTED]")
if span.outputs is not None:
span.set_outputs("[REDACTED]")
if span.status.status_code == SpanStatusCode.ERROR:
span.set_status("ERROR")
mlflow.tracing.configure(span_processors=[drop_exception_events, drop_content])
- Use
drop_exception_eventsalways to remove exception events while preserving unrelated events and their timestamps. Without it MLflow exports stack traces and local paths. The pinned versions have no public event-removal API, so the processor uses a compatibility fallback after reading events through the public API. If it cannot inspect or filter events, it stops the application before exporting the affected span rather than leaving sensitive events untouched. - Add
drop_contentto keep the trace tree, timings, model, tokens andERRORstatus but remove prompts, responses, tool arguments and results and the error text (a provider often puts request details in it). It also removes GenAI message events and events with content-bearing attributes; unrelated operational events stay. Custom attributes with other names are not automatically redacted. - Autolog also exports the tool schemas you pass to the model (
tools,gen_ai.tool.definitions). They are your code, not user content, and are not removed.
MLflow itself writes a telemetry file under ~/.config/mlflow; MLFLOW_DISABLE_TELEMETRY=true stops that.
Verify
Run the traced function and open Observe in Future AGI, then select the project named by project_name. You should see a trace for the function. Instrumented model calls and tool spans appear nested when they run inside a traced root function.
Troubleshooting
- Nothing arrives. MLflow gives no error. Check, in order: the traces endpoint is the full URL ending
/v1/traceswith no trailing slash (a bare origin, a trailing slash or/tracerreturn 404 and the spans are lost); the protocol ishttp/protobuf;opentelemetry-exporter-otlp-proto-httpis installed;OTEL_RESOURCE_ATTRIBUTEShasproject_name; the variables were set before the first span. OTEL_EXPORTER_OTLP_ENDPOINTis set. It also turns on gRPC metrics export, and without a gRPC exporter every span fails. Use only theTRACESvariables.mlflow.tracing.set_destination(...)orMLFLOW_TRACING_DESTINATION. The destination wins and OTLP can be silent. Remove it.- A Databricks tracking URI. MLflow then calls Databricks and a Unity Catalog linked experiment replaces the OTLP export. Not supported by this recipe.
- A
./mlflow.dbfile appears. Dual export (MLFLOW_TRACE_ENABLE_OTLP_DUAL_EXPORT=true) with no tracking URI also writes the trace to a local SQLite store. - Model shown with quotes and cost 0. The semconv switch is off; MLflow JSON-encodes every attribute value.
- Old traces are missing. Expected. This is live export, not a backfill.
Limitations
- Live export only; no historical import, no tracking-server client.
AGENTspans are typedunknown; function names of renamed spans are not kept when the semconv switch is on.- Content columns reflect the message attributes MLflow exports. Future AGI 1.56.1 promotes those attributes; the privacy processors above replace their content with
[REDACTED]. - Databricks, dual export and code-level
set_destination()are outside this recipe’s supported scope. - No session id or user id.
How this differs from related integrations
- traceAI instrumentors (for example OpenAI or LangChain) wrap the library and send spans from your process through
fi_instrumentation. Here MLflow itself is the exporter and there is no traceAI package. - Langfuse import copies historical data from another tool. This page does not import anything.
Questions & Discussion