Step 5 - MLflow GenAI Evaluation¶
Goal¶
In this step you will move from "capturing traces" (Step 2) to "measuring output quality" with MLflow 3.x GenAI Evaluation.
By the end you will be able to:
- Define an evaluation dataset with inputs and expected responses.
- Score outputs with heuristic scorers – no Azure / LLM required.
- Score outputs with LLM-judge scorers (Correctness, RelevanceToQuery).
- Implement your own custom
@scorerfor domain-specific metrics. - Compare evaluation runs side-by-side in the CLI and in the MLflow UI.
flowchart LR
Dataset["Evaluation dataset\n(inputs + expected_response)"]
App["Target app\nscripts/mlflow/vanilla.py"]
subgraph Scorers
H["heuristic\n(exact_match / contains)"]
J["LLM judge\n(Correctness / Relevance)"]
C["custom @scorer\n(token_overlap)"]
end
Eval["mlflow.genai.evaluate()"]
UI[("MLflow UI :5000\nTraces / Evaluations")]
Dataset --> Eval
App --> Eval
Scorers --> Eval
Eval --> UI
Steps 1-3 are available without Azure
The trace, dataset, evaluate, and custom-scorer subcommands run
entirely on your laptop – no Azure credentials required. Only judge
needs a deployed chat model.
Why this step exists¶
Step 2 captures what happened. Step 5 answers was it good?
Evaluation lets you:
- Catch regressions when you change a prompt or swap a model.
- Compare configurations (different prompts, temperatures, models) on a fixed dataset.
- Quantify quality with numbers instead of manual eyeballing.
MLflow GenAI Evaluation keeps everything local: the dataset, the scoring logic, and the result store all live on your laptop. You can graduate to remote experiments later without changing the scoring code.
Prerequisites¶
make mlflowis running (MLflow server onhttp://127.0.0.1:5000).- The project dependencies are installed (
uv sync). - For the
judgesubcommand only: Azure credentials (az login) and a deployed chat model (e.g.azure_ai:gpt-5).
Step 5a – Record a trace¶
Run the built-in QA function once and record an MLflow trace:
Expected output:
Open http://127.0.0.1:5000 to see the trace under the default experiment.
Step 5b – Inspect the evaluation dataset¶
Print the five built-in QA pairs:
Each row contains:
| Field | Description |
|---|---|
inputs.question |
The question sent to the model |
expected_response |
The gold-standard answer used by scorers |
Save it to a file for inspection or offline editing:
Step 5c – Heuristic evaluation (no Azure required)¶
Run the QA function against all five dataset rows and score each output:
Three pure-Python scorers are applied:
| Scorer | What it measures |
|---|---|
exact_match |
1.0 when output == expected_response (case-insensitive) |
contains |
1.0 when output contains expected_response |
non_empty |
1.0 when output is non-empty |
The command prints an aggregate metrics summary followed by a per-row table. The run is also stored in MLflow so you can view it in the UI.
from mlflow.genai.scorers import scorer
@scorer
def exact_match(outputs: str, expected_response: str) -> float:
return 1.0 if outputs.strip().lower() == expected_response.strip().lower() else 0.0
Step 5d – LLM-judge evaluation (requires Azure)¶
Run the QA function with MLflow's built-in LLM judges:
Judges applied:
| Judge | What it measures |
|---|---|
Correctness |
Is the answer factually correct? |
RelevanceToQuery |
Is the answer relevant to the question? |
Azure credentials required
The judge subcommand calls the chat model to evaluate each output.
If Azure credentials are missing or the model is unreachable the command
prints a clear skip message and exits cleanly with code 0.
Step 5e – Custom scorer¶
Implement and apply your own token_overlap scorer:
The scorer computes the Jaccard similarity
between the token sets of outputs and expected_response:
from mlflow.genai.scorers import scorer
@scorer
def token_overlap(outputs: str, expected_response: str) -> float:
"""Jaccard similarity between output and expected_response token sets."""
a = set(outputs.lower().split())
b = set(expected_response.lower().split())
if not a and not b:
return 0.0
return len(a & b) / len(a | b)
Use this pattern to add any domain-specific metric (e.g. BLEU, ROUGE, safety classifier score) as a first-class MLflow scorer.
Step 5f – Compare evaluation runs¶
After running two or more evaluation subcommands, compare them:
The command fetches recent runs from the configured MLflow experiment, selects the metric columns, and prints a side-by-side table sorted by most recent first.
For an interactive comparison with charts and filtering, open the MLflow UI:
Navigate to Experiments → [experiment name] → Evaluation to see a colour-coded comparison across all runs.
Verify¶
Run all three local subcommands in sequence and confirm they complete without errors:
uv run python scripts/mlflow/vanilla.py trace
uv run python scripts/mlflow/vanilla.py dataset
uv run python scripts/mlflow/vanilla.py evaluate
uv run python scripts/mlflow/vanilla.py custom-scorer
uv run python scripts/mlflow/vanilla.py compare
All five commands should print results to stdout and exit with code 0.
The evaluate, custom-scorer and judge runs also appear in the MLflow
UI under the default experiment.
Troubleshooting¶
MLflow server is not running
Start the server before running any subcommand:
The CLI reads MLFLOW_TRACKING_URI from .env (default http://127.0.0.1:5000).
ModuleNotFoundError: mlflow.genai
The mlflow.genai namespace requires mlflow>=3.12.0. Check the installed
version:
Re-sync dependencies if needed:
judge exits with a skip message
The judge subcommand gracefully skips LLM evaluation when credentials are
missing or the model is unreachable. To enable it:
- Sign in with
az login. - Set
AZURE_AI_PROJECT_ENDPOINT(and optionallyAZURE_AI_MODEL) in your.env. - Confirm the model is deployed in your Foundry project.
- Re-run:
uv run python scripts/mlflow/vanilla.py judge --model azure_ai:gpt-5
Evaluation result table is empty
mlflow.genai.evaluate() requires the data argument to be a list of
dicts with an inputs key. The built-in dataset already follows this
shape. If you supply a custom dataset, verify its schema matches.
What's next¶
You now have a repeatable local evaluation loop. Potential next steps:
- Regression gating in CI: add an
evaluatestep to a GitHub Actions workflow and fail the build when a key metric drops below a threshold. - Prompt optimisation: use
mlflow.genai.optimize_prompt()to automatically search for a better prompt given your dataset and scorers. - Remote experiment store: point
MLFLOW_TRACKING_URIat a shared MLflow server so the team can compare runs from different branches.
Return to the Tutorial Overview or jump to Appendix - References for links to all referenced documents.