MLflow Integration for LLM Observability
This document describes deploying MLflow alongside Phoenix for LLM trace
collection in Rossoctl E2E tests. Note that Phoenix is an optional component
(components.phoenix.enabled, default: false) and can be deployed independently of MLflow.
Overview
MLflow Tracing provides LLM observability similar to Phoenix. This integration allows comparing both tools and validating that weather agent traces are captured correctly.
Architecture
Weather Agent (with dual instrumentation)
│
├─ OpenInference spans (openinference-instrumentation-langchain)
├─ GenAI spans (opentelemetry-instrumentation-openai)
│
│ OTLP (port 8335)
▼
OTEL Collector
│
├──► [filter/phoenix] ──► Phoenix (OpenInference spans only)
│
└──► [transform/genai_to_openinference] ──► MLflow (transformed spans)
Current State
- Phoenix receives traces via OTEL Collector filter for
openinference.*scopes - Weather agent uses dual instrumentation:
- OpenInference (
openinference-instrumentation-langchain) for Phoenix - GenAI semantic conventions (
opentelemetry-instrumentation-openai) for MLflow
- OpenInference (
- OTEL Collector transforms GenAI spans to OpenInference format for MLflow
MLflow Integration Approach
Option 1: Dual Export (Recommended)
Add MLflow as a second exporter in OTEL Collector:
- Export the same OpenInference spans to both Phoenix and MLflow
- MLflow can ingest OTLP traces directly (since MLflow 2.14+)
Option 2: GenAI Auto-Instrumentation + Transform (Implemented)
- Add OpenTelemetry GenAI auto-instrumentation to weather agent
- Use OTEL Collector transform processor to convert GenAI spans to OpenInference
- Export to both Phoenix and MLflow
Status: Implemented in PR. Weather agent now has dual instrumentation.
Components Added
1. MLflow Helm Template (charts/rossoctl-deps/templates/mlflow.yaml)
Deploys MLflow tracking server with:
- PostgreSQL backend (shared with Phoenix)
- OTLP receiver endpoint
- UI access via HTTPRoute/Route
- OAuth2 authentication via mlflow-oidc-auth plugin (when enabled)
2. OTEL Collector Pipeline Update
Added MLflow exporter pipeline with GenAI to OpenInference transform:
processors:
transform/genai_to_openinference:
trace_statements:
- context: span
statements:
# Convert GenAI model name to OpenInference format
- set(attributes["llm.model_name"], attributes["gen_ai.request.model"])
# Convert GenAI token counts to OpenInference format
- set(attributes["llm.token_count.prompt"], attributes["gen_ai.usage.input_tokens"])
- set(attributes["llm.token_count.completion"], attributes["gen_ai.usage.output_tokens"])
exporters:
otlphttp/mlflow:
endpoint: http://mlflow:5000
tls:
insecure: true
pipelines:
traces/mlflow:
receivers: [otlp]
processors: [memory_limiter, transform/genai_to_openinference, batch]
exporters: [otlphttp/mlflow]
3. MLflow OAuth Secret Generator (rossoctl/auth/mlflow-oauth-secret/)
mlflow_oauth_secret.py- Keycloak client registrationrequirements.txt- python-keycloak, kubernetes dependenciesDockerfile- container image for job
4. E2E Test for MLflow (rossoctl/tests/e2e/common/test_mlflow_auth.py)
Verify weather agent traces appear in MLflow after E2E tests run.
Environment Variables
| Variable | Description | Default |
|---|---|---|
MLFLOW_URL | MLflow server URL | Auto-detect from cluster |
MLFLOW_TRACKING_URI | MLflow tracking server URL | Auto-detect from cluster |
Success Criteria
- MLflow pod running and accessible
- Weather agent traces visible in MLflow UI
- E2E test validates traces exist in MLflow
- OAuth authentication works when enabled
Implementation Status
Phase 1: Fix Basic Deployment ✅ COMPLETE
- Fix startup probe (use TCP socket for startup,
/versionfor readiness/liveness) - Configure proper
--allowed-hostsbased on domain:- OpenShift: Use route hostname from
{{ .Values.domain }} - Kind: Use
mlflow.localtest.me
- OpenShift: Use route hostname from
- Add mlflow database creation to postgres init script (
phoenix-postgres.yaml) - Fix pip install permissions with
HOME=/tmp - Use
psycopg[binary]for MLflow 3.x PostgreSQL driver - Increase memory limits (512Mi request, 2Gi limit) - MLflow 3.x needs more RAM
Phase 2: OAuth2 Integration ✅ COMPLETE
-
Create
rossoctl/auth/mlflow-oauth-secret/directory with:mlflow_oauth_secret.py- Keycloak client registrationrequirements.txt- python-keycloak, kubernetes dependenciesDockerfile- container image for job
-
Create
charts/rossoctl/templates/mlflow-oauth-secret-job.yaml:- Register Keycloak client for MLflow (confidential client)
- Create Kubernetes secret with OAuth credentials
- RBAC for cross-namespace secret access
-
Update
charts/rossoctl-deps/templates/mlflow.yaml:- Add init container to wait for OAuth secret
- Inject OAuth environment variables from secret
- Configure MLflow with mlflow-oidc-auth plugin
-
Add values configuration to:
charts/rossoctl/values.yaml- MLflow OAuth secret creator configcharts/rossoctl-deps/values.yaml- MLflow auth settings
Phase 3: E2E Tests ✅ COMPLETE
-
Create
rossoctl/tests/e2e/common/test_mlflow_auth.py:- Test MLflow accessible (200/401/302)
- Test OAuth secret exists with required keys
- Test authenticated access with Keycloak token
- Test MLflow traces exist after weather agent runs
-
Update test fixtures to support MLflow authentication:
- Reuse Keycloak token fixture from Phoenix tests
- Add MLflow-specific URL and endpoint helpers
Option 2: GenAI Auto-Instrumentation ✅ COMPLETE
-
Add OpenTelemetry GenAI auto-instrumentation to weather agent
- Added
opentelemetry-instrumentation-openaito pyproject.toml - Added
OpenAIInstrumentor().instrument()in agent.py - PR: https://github.com/ladas/agent-examples branch: genai-autoinstrumentation
- Added
-
Add OTEL Collector transform processor to convert GenAI spans to OpenInference
- Added
transform/genai_to_openinferenceprocessor - Converts
gen_ai.*attributes tollm.*OpenInference format
- Added
-
Update weather agent Shipwright builds to use fork until PR merged
weather_agent_shipwright_build.yamlweather_agent_shipwright_build_ocp.yaml
Pending: After Agent-Examples PR Merged
- Revert Shipwright builds back to
rossoctl/examples+main - Build and push
mlflow-oauth-secretcontainer image to GHCR
Authentication Options
| Option | Pros | Cons |
|---|---|---|
| mlflow-oidc-auth | Native MLflow integration, single container | Requires pip install at runtime |
| oauth2-proxy sidecar | Standard pattern, no MLflow changes | Additional container, more resources |
| Ingress-level auth | Centralized, works with any backend | Requires ingress controller support |
Chosen: Use mlflow-oidc-auth plugin for consistency with Phoenix's native
OAuth approach. Install via pip in container command along with psycopg[binary].
Security Considerations
- Never use
--allowed-hosts allin production - configure specific domains - Use confidential OAuth client (like Phoenix) - secrets stored server-side
- TLS termination at Route/Ingress level, not in MLflow container
- Minimal RBAC - MLflow only reads its own OAuth secret