Compare Models in Production
Attach different models to the same agent, score every model with the same metrics, and compare them on a dashboard.
Overview
You can compare one deployed agent across multiple models by logging the model choice as trace metadata. Confident AI can then run the same online metrics for every trace and build dashboards that filter, split, and trend results by that metadata.
This guide shows the pattern across OpenAI Agents, LangGraph, Vercel AI SDK, and Strands Agents. The core idea is always the same: every request emits a trace, every trace includes a stable model_variant, and every model variant is scored by the same metric collection.
graph LR
User["User request"] --> Agent["Same agent"]
Agent -->|model_variant| M1["Model A"]
Agent -->|model_variant| M2["Model B"]
Agent -->|model_variant| M3["Model C"]
M1 --> Trace["Trace<br/>metadata.model_variant"]
M2 --> Trace
M3 --> Trace
Trace --> Evals["Online evals<br/>same metric collection"]
Evals --> Dashboard["Dashboard split by model_variant<br/>quality · volume · latency"]
style Agent fill:#eef2ff,stroke:#6366f1
style Trace fill:#eef2ff,stroke:#6366f1
style Dashboard fill:#eef2ff,stroke:#6366f1
Here's the thing: the model name captured on an LLM span is useful for debugging, but it is often too provider-specific to analyze — and it lives on the span, not the trace. Promoting a stable model_variant to the trace gives every dashboard one clean, product-level dimension to break down, filter, and trend by, even if the underlying provider model ID changes.
This same pattern compares far more than models. Anything you can label on a trace — prompt versions, temperature, retrievers, tool sets — can be compared the exact same way. See Compare Any Parameter to repeat this guide for a different variable.
What You'll Build
By the end, you will have:
- A traced agent that records
model_variantandmodel_idon every trace. - A repeatable command to generate comparison traffic for each model variant.
- A metric collection that scores every variant with the same criteria.
- A dashboard that compares quality, trace volume, and latency across variants.
- A clear read of which model wins, not just on one lucky slice of traffic.

Prerequisites
You need a Confident AI project, a project API key, and credentials for whichever model provider your agent calls. For OpenAI-based examples, set OPENAI_API_KEY. For the Strands example, configure AWS credentials with access to the Bedrock model IDs you use.
Install the packages for the integration you are using:
python -m venv .venv
source .venv/bin/activate
pip install -U deepeval openai-agentspython -m venv .venv
source .venv/bin/activate
pip install -U deepeval langgraph langchain langchain-openainpm install deepeval ai @ai-sdk/openai
npm install -D tsxpython -m venv .venv
source .venv/bin/activate
pip install -U deepeval strands-agents opentelemetry-sdk opentelemetry-exporter-otlp-proto-httpThen configure your project and provider credentials for that same integration:
export CONFIDENT_API_KEY="confident_us..."
export OPENAI_API_KEY="sk-..."export CONFIDENT_API_KEY="confident_us..."
export OPENAI_API_KEY="sk-..."export CONFIDENT_API_KEY="confident_us..."
export OPENAI_API_KEY="sk-..."export CONFIDENT_API_KEY="confident_us..."
export AWS_REGION="us-east-1"
export AWS_PROFILE="your-aws-profile"For EU projects, point OpenTelemetry export to the EU endpoint:
export CONFIDENT_OTEL_URL="https://eu.otel.confident-ai.com"Set Up Tracing
Tracing is what feeds every dashboard in this guide. In three steps, you'll instrument the agent so each request emits a trace tagged with model_variant, attach the metric collection that scores every variant, and verify the data shape before building any widgets.
Instrument the Agent
The most important implementation detail is where you attach metadata. Add model_variant to the trace, not just the LLM span, because dashboards commonly aggregate at the trace level: average trace score, trace count, trace latency, and trace-level online eval results.
Create the traced agent
Create a small agent module that accepts a normalized model variant, resolves it to the provider model ID, runs the agent, and records both names on the current trace.
Each integration below emits the same dashboard keys:
model_variant,model_id,agent,agent_version,rollout, andenvironment.Use OpenAI Agents'
tracecontext to wrap the run, then callupdate_current_traceafter the agent returns.openai_agents_model_compare.py import os import sys from agents import Agent, Runner, add_trace_processor, trace from deepeval.openai_agents import DeepEvalTracingProcessor from deepeval.tracing import update_current_trace add_trace_processor(DeepEvalTracingProcessor()) MODEL_MAP = {"gpt-4o-mini": "gpt-4o-mini", "gpt-4o": "gpt-4o", "gpt-4.1": "gpt-4.1"} def run_agent(user_input: str, model_variant: str) -> str: model_id = MODEL_MAP[model_variant] agent = Agent( name="Support Agent", instructions="Answer support questions clearly and safely.", model=model_id, ) with trace(workflow_name="support-agent"): output = Runner.run_sync(agent, user_input).final_output update_current_trace( input=user_input, output=output, metric_collection="Agent Quality", metadata={ "agent": "support-agent", "agent_version": os.getenv("AGENT_VERSION", "v2"), "model_variant": model_variant, "model_id": model_id, "rollout": os.getenv("ROLLOUT_NAME", "model-comparison"), "environment": os.getenv("APP_ENV", "production"), }, ) return output if __name__ == "__main__": print(run_agent(sys.argv[2], sys.argv[1]))LangGraph uses the Confident AI callback handler. Put
metric_collectionandmetadataon the handler so they apply to the root trace created by the graph invocation.langgraph_model_compare.py import os import sys from langchain.agents import create_agent from langchain_openai import ChatOpenAI from deepeval.integrations.langchain import CallbackHandler MODEL_MAP = {"gpt-4o-mini": "gpt-4o-mini", "gpt-4o": "gpt-4o", "gpt-4.1": "gpt-4.1"} def run_agent(user_input: str, model_variant: str) -> str: model_id = MODEL_MAP[model_variant] agent = create_agent( model=ChatOpenAI(model=model_id), tools=[], system_prompt="Answer support questions clearly and safely.", ) result = agent.invoke( input={"messages": [{"role": "user", "content": user_input}]}, config={ "callbacks": [ CallbackHandler( name="support-agent", metric_collection="Agent Quality", metadata={ "agent": "support-agent", "agent_version": os.getenv("AGENT_VERSION", "v2"), "model_variant": model_variant, "model_id": model_id, "rollout": os.getenv("ROLLOUT_NAME", "model-comparison"), "environment": os.getenv("APP_ENV", "production"), }, ) ] }, ) return result["messages"][-1].content if __name__ == "__main__": print(run_agent(sys.argv[2], sys.argv[1]))For the Vercel AI SDK, configure the Confident AI tracer once, then wrap each generation in
setTracingContext. The model variant is passed both to the model selector and to trace metadata.vercel-ai-model-compare.ts import { generateText } from "ai"; import { openai } from "@ai-sdk/openai"; import { configureAiSdkTracing } from "deepeval"; import { setTracingContext } from "deepeval/tracing"; const tracer = configureAiSdkTracing({ environment: process.env.APP_ENV ?? "production", name: "support-agent", }); const modelMap: Record<string, string> = { "gpt-4o-mini": "gpt-4o-mini", "gpt-4o": "gpt-4o", "gpt-4.1": "gpt-4.1", }; export async function runAgent(input: string, modelVariant: string) { const modelId = modelMap[modelVariant]; return setTracingContext( { metricCollection: "Agent Quality", metadata: { agent: "support-agent", agent_version: process.env.AGENT_VERSION ?? "v2", model_variant: modelVariant, model_id: modelId, rollout: process.env.ROLLOUT_NAME ?? "model-comparison", environment: process.env.APP_ENV ?? "production", }, }, async () => { const { text } = await generateText({ model: openai(modelId), prompt: input, experimental_telemetry: { isEnabled: true, tracer }, }); return text; }, ); } if (import.meta.url === `file://${process.argv[1]}`) { const [variant, ...rest] = process.argv.slice(2); runAgent(rest.join(" "), variant).then((text) => console.log(text)); }For Strands, call
instrument_strandsonce at startup. Strands captures the LLM and tool spans automatically, whileupdate_current_traceadds the normalized comparison metadata to the trace.strands_model_compare.py import os import sys from deepeval.integrations.strands import instrument_strands from deepeval.tracing import observe, update_current_trace from strands import Agent instrument_strands(name="support-agent", environment=os.getenv("APP_ENV", "production")) MODEL_MAP = { "nova-lite": "us.amazon.nova-lite-v1:0", "nova-pro": "us.amazon.nova-pro-v1:0", "claude-sonnet": "us.anthropic.claude-3-5-sonnet-20241022-v2:0", } @observe(name="support-agent") def run_agent(user_input: str, model_variant: str) -> str: model_id = MODEL_MAP[model_variant] output = str(Agent(model=model_id)(user_input)) update_current_trace( input=user_input, output=output, metric_collection="Agent Quality", metadata={ "agent": "support-agent", "agent_version": os.getenv("AGENT_VERSION", "v2"), "model_variant": model_variant, "model_id": model_id, "rollout": os.getenv("ROLLOUT_NAME", "model-comparison"), "environment": os.getenv("APP_ENV", "production"), }, ) return output if __name__ == "__main__": print(run_agent(sys.argv[2], sys.argv[1]))Metadata keys can be any string you want —
model_variantandmodel_idare just the ones we use for this example. Here,model_variantis the short, human-readable label you compare on, andmodel_idis the exact provider value kept for auditability, even if it is noisy. Name your keys whatever is most useful for you.Run a local trace
Send one request per variant to confirm traces reach Confident AI with the right metadata. Each script takes the variant and the input as positional arguments.
Run OpenAI Agents traces export CONFIDENT_TRACE_FLUSH=1 export AGENT_VERSION="v2" export ROLLOUT_NAME="model-comparison-smoke-test" export APP_ENV="production" for model in gpt-4o-mini gpt-4o gpt-4.1; do python openai_agents_model_compare.py "$model" \ "A customer says their invoice doubled after upgrading. Explain what to check first." doneRun LangGraph traces export CONFIDENT_TRACE_FLUSH=1 export AGENT_VERSION="v2" export ROLLOUT_NAME="model-comparison-smoke-test" export APP_ENV="production" for model in gpt-4o-mini gpt-4o gpt-4.1; do python langgraph_model_compare.py "$model" \ "A customer says their invoice doubled after upgrading. Explain what to check first." doneRun Vercel AI SDK traces export AGENT_VERSION="v2" export ROLLOUT_NAME="model-comparison-smoke-test" export APP_ENV="production" for model in gpt-4o-mini gpt-4o gpt-4.1; do npx tsx vercel-ai-model-compare.ts "$model" \ "A customer says their invoice doubled after upgrading. Explain what to check first." doneRun Strands traces export AGENT_VERSION="v2" export ROLLOUT_NAME="model-comparison-smoke-test" export APP_ENV="production" for model in nova-lite nova-pro claude-sonnet; do python strands_model_compare.py "$model" \ "A customer says their invoice doubled after upgrading. Explain what to check first." doneTraces appear in the Observatory as soon as the agent runs Done ✅. You now have at least one trace per model variant.
Create Metrics
Use the same metric collection for every model variant so each is scored against identical criteria. Your project's evaluation model — the LLM judge — is shared across every collection, so the judge itself is already consistent. The trap is scoring gpt-4o-mini and gpt-4o with different collections: you would be trending scores from two different rubrics, so the dashboard is no longer an apples-to-apples comparison.
Create a metric collection
Open Project > Metrics > Collections, create a collection named
Agent Quality, and add trace-level metrics that match the agent's job.Create the metric collection that every model variant will use For a support agent, a strong starting collection is:
- Task Completion for whether the answer solved the user's request.
- Answer Relevancy for whether the response stayed focused.
- A custom G-Eval metric for your product-specific standard, such as "support policy compliance" or "escalation quality".
Attach the collection in code
The earlier examples attach
Agent Qualityin the integration-specific trace context. That means every trace receives the same online eval collection, even though the exact API differs by framework.OpenAI Agents sets the trace-level collection with
update_current_trace:openai_agents_model_compare.py update_current_trace( input=user_input, output=output, metric_collection="Agent Quality", )LangGraph sets the trace-level collection on
CallbackHandler:langgraph_model_compare.py CallbackHandler( name="support-agent", metric_collection="Agent Quality", metadata={"model_variant": model_variant, "model_id": model_id}, )The Vercel AI SDK example sets the trace-level collection inside
setTracingContext:vercel-ai-model-compare.ts await setTracingContext( { metricCollection: "Agent Quality", metadata: { model_variant: modelVariant, model_id: modelId }, }, async () => { return generateText({ model: openai(modelId), prompt: input, experimental_telemetry: { isEnabled: true, tracer }, }); }, );Strands sets the trace-level collection with
update_current_trace(or oninstrument_strands(metric_collection=...)):strands_model_compare.py update_current_trace( input=user_input, output=output, metric_collection="Agent Quality", )If you prefer not to set the collection in code, configure Evaluation Rules in Project Settings. Use a rule filter such as
metadata.agent = support-agentormetadata.rollout = model-comparison, then apply the sameAgent Qualitycollection to matching traces.Generate enough scored traces
Dashboards are only useful once there is enough data to compare. Run each variant across a few prompts so the metric collection scores a batch of traces.
Generate comparison traffic export CONFIDENT_TRACE_FLUSH=1 prompts=( "A customer cannot access invoices after changing teams. Help them troubleshoot." "Summarize why a trial user should upgrade, but do not mention unavailable features." "The integration failed with an OAuth callback error. Explain the likely cause." "A user asks for a refund after annual renewal. Give a careful support response." ) for model in gpt-4o-mini gpt-4o gpt-4.1; do for prompt in "${prompts[@]}"; do python openai_agents_model_compare.py "$model" "$prompt" done doneGenerate comparison traffic export CONFIDENT_TRACE_FLUSH=1 prompts=( "A customer cannot access invoices after changing teams. Help them troubleshoot." "Summarize why a trial user should upgrade, but do not mention unavailable features." "The integration failed with an OAuth callback error. Explain the likely cause." "A user asks for a refund after annual renewal. Give a careful support response." ) for model in gpt-4o-mini gpt-4o gpt-4.1; do for prompt in "${prompts[@]}"; do python langgraph_model_compare.py "$model" "$prompt" done doneGenerate comparison traffic prompts=( "A customer cannot access invoices after changing teams. Help them troubleshoot." "Summarize why a trial user should upgrade, but do not mention unavailable features." "The integration failed with an OAuth callback error. Explain the likely cause." "A user asks for a refund after annual renewal. Give a careful support response." ) for model in gpt-4o-mini gpt-4o gpt-4.1; do for prompt in "${prompts[@]}"; do npx tsx vercel-ai-model-compare.ts "$model" "$prompt" done doneGenerate comparison traffic prompts=( "A customer cannot access invoices after changing teams. Help them troubleshoot." "Summarize why a trial user should upgrade, but do not mention unavailable features." "The integration failed with an OAuth callback error. Explain the likely cause." "A user asks for a refund after annual renewal. Give a careful support response." ) for model in nova-lite nova-pro claude-sonnet; do for prompt in "${prompts[@]}"; do python strands_model_compare.py "$model" "$prompt" done done
Verify the Traces
Before building dashboards, verify that the data shape is right. It is much easier to fix metadata and metric-collection names before you create five widgets around them.
Open the Observatory
In Confident AI, go to Observatory and filter for your agent or rollout:
metadata.agent = support-agentmetadata.rollout = model-comparisonmetadata.model_variant = gpt-4ofor OpenAI-based examples, ormetadata.model_variant = nova-profor Strands
Inspect one trace
Open a trace and confirm four things:
- The trace input and output are populated.
- The trace metadata includes
model_variant,model_id,agent_version, androllout. - The LLM span captured the provider model details from your integration.
- The trace has online eval results from
Agent Quality, or shows a clear metric error you can fix.

Online eval scores appear on the trace after ingestion
Create the Dashboard
The traces and scores from the previous step feed the dashboard. Build it either way — pick Platform to click through the Confident AI UI, or CLI to run a reproducible script against the Dashboards API. Your choice sticks across every step below, so you only pick once.
Create the dashboard
In Confident AI, create the dashboard from the sidebar:
- Open Dashboards.
- Click New Dashboard.
- Set Name to
Model Variant Comparison. - Set Description to
Compares support-agent quality, volume, and latency by metadata.model_variant. - Keep Private off to share it with the project, or on for a personal draft.
- Click Create.
Set your credentials, then create an empty dashboard and capture its ID for the next steps:
Create the dashboard export CONFIDENT_API_KEY="confident_us..." export CONFIDENT_API_BASE="https://api.confident-ai.com" export DASHBOARD_ID="$( curl -sS -X POST "$CONFIDENT_API_BASE/v1/dashboards" \ -H "CONFIDENT_API_KEY: $CONFIDENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Model Variant Comparison", "description": "Compares support-agent quality, volume, and latency by metadata.model_variant.", "private": false }' \ | python -c 'import json,sys; print(json.load(sys.stdin)["data"]["id"])' )" echo "Created dashboard: $DASHBOARD_ID"
Create a dashboard Add quality by model
Click Add widget and create a time-series widget that breaks down quality by
model_variant:Setting Value Widget name Average quality by modelShape Time series Display Line Mode Breakdown Data model Metric Data Belongs to Trace Metric collection Agent QualityAggregation Average score Filter metadata.agent = support-agentDimension Metadata Metadata key model_variantTop K Top 10 This is the main comparison chart: which model scores higher over time under the same metric collection?
Add one line per variant. Each line filters to
support-agentand onemodel_variant:Add the quality widget curl -sS -X POST "$CONFIDENT_API_BASE/v1/dashboards/$DASHBOARD_ID/widgets" \ -H "CONFIDENT_API_KEY: $CONFIDENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Average quality by model", "type": "LINE", "unit": "SCORE", "mode": "TIME_SERIES", "lines": [ { "name": "gpt-4o-mini", "color": "BLUE", "dataModel": "METRIC_DATA", "aggregation": "AVG_SCORE", "extraQueryParams": { "category": "TRACE", "metricName": "Task Completion" }, "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o-mini" } ] } ] } }, { "name": "gpt-4o", "color": "EMERALD", "dataModel": "METRIC_DATA", "aggregation": "AVG_SCORE", "extraQueryParams": { "category": "TRACE", "metricName": "Task Completion" }, "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o" } ] } ] } }, { "name": "gpt-4.1", "color": "VIOLET", "dataModel": "METRIC_DATA", "aggregation": "AVG_SCORE", "extraQueryParams": { "category": "TRACE", "metricName": "Task Completion" }, "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4.1" } ] } ] } } ] }'Add trace volume
Add a second time-series widget for traffic volume, so you don't over-trust a model that only handled a few easy requests:
Setting Value Widget name Trace volume by modelShape Time series Display Stacked bar Mode Breakdown Data model Trace Aggregation Count Filter metadata.agent = support-agentDimension Metadata Metadata key model_variantTop K Top 10 Add the volume widget curl -sS -X POST "$CONFIDENT_API_BASE/v1/dashboards/$DASHBOARD_ID/widgets" \ -H "CONFIDENT_API_KEY: $CONFIDENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Trace volume by model", "type": "STACKED_BAR", "unit": "COUNT", "mode": "TIME_SERIES", "lines": [ { "name": "gpt-4o-mini", "color": "BLUE", "dataModel": "TRACE", "aggregation": "COUNT", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o-mini" } ] } ] } }, { "name": "gpt-4o", "color": "EMERALD", "dataModel": "TRACE", "aggregation": "COUNT", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o" } ] } ] } }, { "name": "gpt-4.1", "color": "VIOLET", "dataModel": "TRACE", "aggregation": "COUNT", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4.1" } ] } ] } } ] }'Add P90 latency
Add a latency widget so the quality winner isn't judged on quality alone:
Setting Value Widget name P90 latency by modelShape Time series Display Line Mode Breakdown Data model Trace Aggregation P90 latency Filter metadata.agent = support-agentDimension Metadata Metadata key model_variantTop K Top 10 For model-call latency instead of whole-trace latency, switch the data model to Span, choose the LLM span type, and keep the same
model_variantbreakdown.Add the latency widget curl -sS -X POST "$CONFIDENT_API_BASE/v1/dashboards/$DASHBOARD_ID/widgets" \ -H "CONFIDENT_API_KEY: $CONFIDENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "P90 latency by model", "type": "LINE", "unit": "MILLISECONDS", "mode": "TIME_SERIES", "lines": [ { "name": "gpt-4o-mini", "color": "BLUE", "dataModel": "TRACE", "aggregation": "P90_LATENCY", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o-mini" } ] } ] } }, { "name": "gpt-4o", "color": "EMERALD", "dataModel": "TRACE", "aggregation": "P90_LATENCY", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4o" } ] } ] } }, { "name": "gpt-4.1", "color": "VIOLET", "dataModel": "TRACE", "aggregation": "P90_LATENCY", "filters": { "operator": "AND", "groups": [ { "operator": "AND", "filters": [ { "category": "Metadata", "condition": "Is", "key": "agent", "value": "support-agent" }, { "category": "Metadata", "condition": "Is", "key": "model_variant", "value": "gpt-4.1" } ] } ] } } ] }'Verify the dashboard
Set the shared dashboard date range to Last 7 days or Last 30 days, then confirm all three widgets break down by
model_variant.Done ✅. You now have a dashboard that compares quality, volume, and latency by model.
Fetch the dashboard to confirm all three widgets were saved:
Fetch the dashboard curl -sS "$CONFIDENT_API_BASE/v1/dashboards/$DASHBOARD_ID" \ -H "CONFIDENT_API_KEY: $CONFIDENT_API_KEY"Done ✅. You now have a dashboard that compares quality, volume, and latency by model.

Preview of the dashboard
Interpret Results
A model that scores higher is only the better choice if it also handled enough traffic to trust and kept latency acceptable. Read the three widgets together:
- Quality: Is the candidate's average score higher over a meaningful date range?
- Volume: Does each variant have enough traces to trust the result? Low-volume variants can win by chance.
- Latency: Is P90 latency still acceptable for your product?
Compare Any Parameter
Here's the key insight: nothing in this guide is actually model-specific. model_variant is just the metadata key every dashboard breaks down by. Swap it for any variable you want to A/B and the exact same workflow — one agent, one metric collection, three widgets — still applies. You're not comparing models, you're comparing whatever you label on the trace.
To compare something else, repeat the guide and change only two things:
- The metadata key you attach on the trace. Log
prompt_version(ortemperature,retriever, ...) instead of, or alongside,model_variant. - The dimension each widget breaks down by. Point the same dashboard filters at the new key.
Everything else stays identical. The new key is attached exactly like model_variant — as trace metadata:
update_current_trace(
input=user_input,
output=output,
metric_collection="Agent Quality",
metadata={
"agent": "support-agent",
"prompt_version": prompt_variant, # the dimension you're now comparing
},
)Common parameters teams compare this way:
- Prompt versions —
prompt_version: v3vsv4. - Decoding settings —
temperature: 0.2vs0.7. - Retrieval strategy —
retriever: bm25vshybrid, orchunk_size: 512vs1024. - Tool sets —
toolset: minimalvsfull. - Agent versions —
agent_version: v2vsv3.
Chart Any Measure
And just like the breakdown dimension is swappable, so is the measure each widget plots. This guide charts quality (AVG_SCORE), trace volume (COUNT), and P90 latency (P90_LATENCY) — but that's only three of many. Add a line with a different aggregation and you have a new comparison from the exact same traffic.
Measures you can break down by any dimension:
- Quality —
AVG_SCORE,PASS_RATE,FAILURE_RATE, orAVG_RATINGfor any metric in your collection. - Latency —
AVG_LATENCY,P50_LATENCY,P90_LATENCY,P99_LATENCY. - Cost & tokens —
TOTAL_COST,AVG_COST,AVG_COST_PER_USER,INPUT_TOKENS,OUTPUT_TOKENS,TOTAL_TOKENS. - Volume & users —
COUNT,UNIQUE_USERS,UNIQUE_THREADS. - Reliability —
ERROR_COUNT,ERROR_RATE.
Best Practices
These are optional deep-dives once the core comparison is working.
- Compare one thing at a time. If the prompt, tools, retriever, and model all change at once, the dashboard cannot tell you what caused the difference.
- Keep metadata names consistent. Dashboards depend on exact metadata keys, so do not alternate between
model,model_name, andmodel_variant. - Separate product labels from provider IDs. Use
model_variantfor the decision people understand andmodel_idfor exact reproducibility. - Use enough traffic before deciding. Low-volume variants can look better or worse by chance. Compare over a stable time range.
- Watch quality and operations together. A model with a higher score but much worse latency may not be the better choice.
What to Track
The dashboard depends on consistent metadata. Start with these keys:
model_variant— the comparison dimension, such asnova-liteorclaude-sonnet.model_id— the exact provider model ID used for the request.agent— the stable application or agent name, such assupport-agent.agent_version— the deployed agent version.rollout— the rollout, canary, or A/B test name.environment— production, staging, development, or testing.
Keep metadata values boring and predictable. model_variant="nova-pro" is easier to query than model_variant="Nova Pro - July canary (fast)". Put temporary rollout context in rollout, not in the model name.
Rollout Patterns
Three common ways to route traffic across models while comparing them:
- Shadow compare — send production traffic to the current model and run a copy through candidate models off the user-facing path. Log shadow traces with
rollout=shadow-model-compare. High signal, but every request may call multiple models. - Canary release — send a small percentage of real traffic to the candidate and label it
rollout=canary-v3. The simplest production rollout; watch trace volume, since a 5% canary looks noisy until it has enough traffic. - Segment routing — route a model to a specific segment such as internal users, one tenant, or one task type, and add metadata for that segment. Useful when the best model depends on the request.
Troubleshooting
My dashboard has no model_variant breakdown.
Open a trace and check whether metadata.model_variant exists on the trace.
If it only appears on an LLM span, move the value to update_current_trace.
Dashboards can only break down trace-level data by metadata that exists on
the trace.
Online eval scores are missing.
Confirm that the metric collection name in code exactly matches the
collection name in Confident AI. Then check that the trace has the
parameters required by the metrics, usually input and output for
referenceless trace-level metrics.
One model looks much better but has tiny traffic.
Add a trace-count widget next to the quality widget and compare over a longer time range. Low-volume variants can win by chance, especially if the router sent them easier requests.
The raw provider model ID keeps changing.
Keep model_variant stable and put the exact provider value in model_id.
The dashboard should usually break down by model_variant, while model_id
is there for debugging and audit trails.
Next Steps
Use this setup to compare model variants under one agent, then roll out the winner once quality, volume, and latency all look healthy.
OpenAI Agents
Trace OpenAI Agents workflows with agent, LLM, tool, handoff, and guardrail spans.
LangGraph
Trace LangGraph agents with callback handlers, trace metadata, and online evals.
Vercel AI SDK
Instrument AI SDK generations with Confident AI tracing and trace context.
Strands Agents
Instrument Strands agents with OpenTelemetry, online evals, and trace metadata.
Dashboards
Build widgets from metric data, traces, filters, and metadata breakdowns.
Online Evaluations
Score traces and spans as production traffic is ingested.
Metadata
Add metadata to traces, spans, and threads for filtering and analysis.
Last updated on