Skip to main content
MLflow ships a GenAI evaluation API whose third-party scorers include the Phoenix evaluators (Hallucination, QA, Relevance, Toxicity, Summarization) from mlflow.genai.scorers.phoenix. Each scorer is a plain callable that returns an MLflow Feedback, so you can run it on Arize AX data without an MLflow tracking server. This guide shows both ways to wire MLflow scorers into Arize AX: Flow 1 grades existing Arize AX traces with the Hallucination scorer and writes the results back via client.spans.update_evaluations(...); Flow 2 uploads a small dataset, runs an Arize AX experiment with the same scorer as its evaluator, and surfaces the scores in Datasets+Experiments. Both flows share the same setup. Run the code blocks below in order inside a single Python session — each block builds on imports and variables from earlier ones.

Prerequisites

  • Python 3.11+
  • An ARIZE_SPACE_ID and ARIZE_API_KEY from your Arize AX space settings
  • An OPENAI_API_KEY from OpenAI Platform (used as both the model under trace and the MLflow scorer’s judge LLM)

Launch Arize AX

If you don’t already have an Arize AX account, sign up at arize.com and grab your ARIZE_SPACE_ID and ARIZE_API_KEY from Settings → Space Settings.

Install

Configure credentials

Define evaluators

The shared setup: an MLflow Hallucination scorer backed by GPT-4.1, the canonical 2-row hallucination dataset that both flows score, and an Arize SDK client. The scorer’s model argument is an MLflow model URI (<provider>:/<model>), so openai:/gpt-4.1 calls OpenAI with your OPENAI_API_KEY. The scorer runs entirely in-process — no MLflow tracking server or mlflow.start_run() is needed.

Flow 1 — Evaluate existing traces

Source the spans

Instrument OpenAI with OpenInference, make two calls (each forced to echo a known answer so the trace contains predictable text), then pull the resulting spans back from Arize AX.

Run the evaluators

Call the scorer once per span. It takes keyword arguments only: inputs for the question, outputs for the response, and expectations={"context": ...} for the reference text it grades against. The Feedback it returns carries the label in value and the judge’s reasoning in rationale.

Log evaluations to Arize AX

update_evaluations(...) requires a context.span_id column (which export_to_df already provides) plus the reserved eval.<name>.{score,label,explanation} columns. Each MLflow Feedback becomes one row in this DataFrame.

Expected output

Verify in Arize AX

Open the project named mlflow-tracing-example-<timestamp> (the value printed above) in your Arize AX space. Each ChatCompletion span now carries a hallucination annotation column showing the score, label, and explanation written by update_evaluations(...).

Flow 2 — Run an experiment

Create a dataset

The dataset is the same two rows. The space= / examples= kwarg names match the v8 SDK exactly (note: not space_id= and not dataframe=).

Define the task

The task function receives the dataset row and returns whatever the experiment should grade. The parameter name must be one of input, output, metadata, or dataset_row — a single-arg task with an unrecognized name is bound to dataset_row by default. A real workflow would call an LLM here; this passthrough keeps the example deterministic.

Wrap the evaluators

The MLflow scorer is synchronous, so the experiment evaluator is a plain def. Return an EvaluationResult with score, label, and explanation populated — leaving any of those as None triggers unsupported cast from null to <type>: reserved column cannot be coerced to canonical type at upload time.

Run the experiment

Expected output

Verify in Arize AX

Open the Datasets + Experiments tab in Arize AX. The dataset mlflow-experiment-example-ds-<timestamp> and the experiment mlflow-experiment-example-<timestamp> (names printed above) appear with one run per dataset row, each carrying the hallucination score and label columns.

Troubleshooting

  • Unknown Phoenix metric: 'Hallucination'. Could not find 'HallucinationEvaluator' in 'phoenix.evals'. You have arize-phoenix-evals 3.x installed. MLflow’s Phoenix scorers wrap the legacy evaluator classes that 3.0 removed. Reinstall with pip install 'arize-phoenix-evals<3'.
  • MLflow scorer failed: .... The scorer doesn’t raise on judge errors — it returns a Feedback whose error field is set, which score_hallucination turns into an exception. The message underneath is the judge call’s error; the most common cause is a missing or invalid OPENAI_API_KEY.
  • Scores look inverted. The underlying Phoenix evaluator scores hallucinated as 1 (it measures hallucination, not correctness) and puts that number in feedback.metadata["score"]. The page maps the label instead so that 1.0 means factual, matching the other evaluation integrations. Use the QA scorer if you’d rather grade correctness directly — it returns correct / incorrect.
  • column "eval.<name>.label": unsupported cast from null to string: reserved column cannot be coerced to canonical type. Your evaluator returned a bare number or string instead of a fully-populated EvaluationResult(score=..., label=..., explanation=...). Arize AX rejects null values in reserved eval columns — populate all three fields.
  • Spans never appear after 60s. Span flush + ingest typically takes 5–15s. If the loop times out, check that ARIZE_SPACE_ID + ARIZE_API_KEY are right and that you’re connecting to the correct region’s OTLP endpoint (otlp.arize.com for US, otlp.eu.arize.com for EU).
  • task failed for example id .... Your task function’s parameter name isn’t one of the recognized names (input, output, metadata, dataset_row). Rename it to dataset_row if you want the whole row, or pick the field you actually need.
  • Experiment runs duplicate or the dataset already exists. Both names embed TIMESTAMP = int(time.time()) so a single re-run produces unique names. If you re-execute the same combined.py quickly, regenerate TIMESTAMP first or call arize.experiments.delete(...) / arize.datasets.delete(...) on the prior run’s names.
  • Using a different judge model. Pass another MLflow model URI as model=, for example anthropic:/claude-sonnet-4-6 with ANTHROPIC_API_KEY set. MLflow calls providers it supports natively and falls back to LiteLLM for the rest, so install litellm if MLflow logs that it is falling back.

Resources

MLflow Phoenix scorers

MLflow on GitHub

Logging evaluations to Arize AX

Phoenix Evals with Arize AX