Files
2026-07-13 13:22:34 +08:00

129 lines
4.1 KiB
Python

"""
CLI commands for evaluating traces with scorers.
"""
import json
from typing import Literal
import click
import pandas as pd
import mlflow
from mlflow.cli.genai_eval_utils import (
extract_assessments_from_results,
format_table_output,
resolve_scorers,
)
from mlflow.entities import Trace
from mlflow.genai.evaluation import evaluate
from mlflow.tracking import MlflowClient
from mlflow.utils.string_utils import _create_table
def _gather_traces(trace_ids: str, experiment_id: str) -> list[Trace]:
"""
Gather and validate traces from the tracking store.
Args:
trace_ids: Comma-separated list of trace IDs to gather
experiment_id: Expected experiment ID for all traces
Returns:
List of Trace objects
Raises:
click.UsageError: If any trace is not found or belongs to wrong experiment
"""
trace_id_list = [tid.strip() for tid in trace_ids.split(",")]
client = MlflowClient()
traces = []
for trace_id in trace_id_list:
try:
trace = client.get_trace(trace_id, display=False)
except Exception as e:
raise click.UsageError(f"Failed to get trace '{trace_id}': {e}")
if trace is None:
raise click.UsageError(f"Trace with ID '{trace_id}' not found")
if trace.info.experiment_id != experiment_id:
raise click.UsageError(
f"Trace '{trace_id}' belongs to experiment '{trace.info.experiment_id}', "
f"not the specified experiment '{experiment_id}'"
)
traces.append(trace)
return traces
def evaluate_traces(
experiment_id: str,
trace_ids: str,
scorers: str,
output_format: Literal["table", "json"] = "table",
) -> None:
"""
Evaluate traces with specified scorers and output results.
Args:
experiment_id: The experiment ID to use for evaluation
trace_ids: Comma-separated list of trace IDs to evaluate
scorers: Comma-separated list of scorer names
output_format: Output format ('table' or 'json')
"""
mlflow.set_experiment(experiment_id=experiment_id)
traces = _gather_traces(trace_ids, experiment_id)
traces_df = pd.DataFrame([{"trace_id": t.info.trace_id, "trace": t} for t in traces])
scorer_names = [name.strip() for name in scorers.split(",")]
resolved_scorers = resolve_scorers(scorer_names, experiment_id)
trace_count = len(traces)
scorers_list = ", ".join(scorer_names)
if trace_count == 1:
trace_id = traces[0].info.trace_id
click.echo(f"Evaluating trace {trace_id} with scorers: {scorers_list}...")
else:
click.echo(f"Evaluating {trace_count} traces with scorers: {scorers_list}...")
try:
results = evaluate(data=traces_df, scorers=resolved_scorers)
evaluation_run_id = results.run_id
except Exception as e:
raise click.UsageError(f"Evaluation failed: {e}")
results_df = results.result_df
output_data = extract_assessments_from_results(results_df, evaluation_run_id)
if output_format == "json":
# Convert EvalResult objects to dicts for JSON serialization
json_data = [
{
"trace_id": result.trace_id,
"assessments": [
{
"name": assessment.name,
"result": assessment.result,
"rationale": assessment.rationale,
"error": assessment.error,
}
for assessment in result.assessments
],
}
for result in output_data
]
if len(json_data) == 1:
click.echo(json.dumps(json_data[0], indent=2))
else:
click.echo(json.dumps(json_data, indent=2))
else:
table_output = format_table_output(output_data)
# Extract string values from Cell objects for table display
table_data = [[cell.value for cell in row] for row in table_output.rows]
# Add new line in the output before the final result.
click.echo("")
click.echo(_create_table(table_data, headers=table_output.headers))