- What separates signal from noise in production traces
- The review practice: a daily smoke check and a weekly error-analysis session
- Filtering traces by errors, latency outliers, evaluation scores, and session behavior from the CLI and programmatically
- Turning what you find into a failure taxonomy that drives your next evals
Follow Along with Sample Traces
You can run every query in this guide against any Phoenix project that has traces in it. To follow along with sample traces that cover every technique (hard failures, latency outliers, evaluation scores, and multi-turn sessions), load the prepared sample traces:-
Install and launch the Phoenix server.
-
Install the Phoenix CLI and point
pxat your server and project. -
Download the sample traces in the form of two JSONL files: the spans and their evaluations. This is the same shape Phoenix uses when you export your own spans, so nested fields (like span
events) and timestamps arrive already typed. -
Load the sample traces into Phoenix. This logs the spans and their evaluation labels into the
high-signal-demoproject so they appear to have happened in the last 12 hours.- Python
- TypeScript
- Save this file as
data-importer.py:
- Run it:
-
Confirm it landed. Open the Phoenix UI at http://localhost:6006, select the
high-signal-demoproject and select “all spans” to see ~100 traces including several withERRORstatus, a few slow outliers, and session groupings.
Finding the signal despite the noise
The most common mistake teams make is treating observability as a dashboard problem when it’s really an analysis problem. Generic aggregate metrics like an overall “helpfulness” score or a global pass rate create a false sense of measurement and control. It doesn’t matter which way the number moves if nobody can say what it means for users. Evals and observablity expert Hamel Husain calls error analysis “the most important activity in evals”, and observes that successful teams spend the majority of their development effort looking at data rather than building automated checks. So before you start building a metrics dashboard, you need to find out which traces matter. While sorts, scores, and queries help you understand traces and filters find them, they never replace reading them. High-signal traces come from five sources:- Hard failures. Exceptions, tool-call errors, timeouts, malformed outputs. It is the cheapest signal to query and the shallowest. An LLM application can be deeply broken without ever throwing an exception.
- Soft failures. Traces where evaluation scores flag a quality problem: a hallucinated answer, an irrelevant retrieval, a failed task.
- Outliers. Traces at the extremes of any metric: latency, token count, tool-call count, response length. Both extremes are interesting: the slow tail reveals retry loops and runaway agents; the suspiciously fast tail reveals silent failures like empty retrievals.
- Human signals. Negative feedback, support escalations, sessions where the user rephrases the same question three times. A user retry is an eval score the user computed for you.
- Novelty. Inputs unlike anything you’ve categorized before. New usage patterns are where your next failure mode is hiding.
The Discipline of timely Trace Reviews
Production trace review is all about discipline. If you can handle a daily checkin and a weekly standup, you can handle the regular reviews traces require. This process consists of a daily smoke check and a weekly error analysis session.Daily smoke check (minutes)
Glance at the metrics dashboard in your Phoenix server. Select your “high-signal-demo” project from the dropdown at the top of the page and look for anything alarming. Pay attention to spikes and climbing numbers in the following tiles:- Trace Latency
- Cost (not included in the sample traces)
- LLM spans with errors
- Tool spans with errors
Weekly error-analysis session (an hour or two)
Once a week, pull a deliberately mixed sample of traces and read them with open-ended notes. This is where improvement is made real. You’ll learn how in the last section of this guide.Teams that only do the daily check end up dashboard-watching. Teams that only do deep dives get surprised by fires. You need both, as they are complementary. Smoke-check anomalies tell you where to oversample in the weekly review.
How to perform the Daily Check: Finding Hard Failures
Hard failures are queryable with zero setup. Phoenix records span status, exception events, and error messages on every trace. Triage them visually in the Phoenix UI or straight from the terminal with thepx CLI. (Prefer code, e.g. for monitoring scripts? The same queries run through the Python and TypeScript clients.)
- UI
- CLI (px)
- Open the Phoenix UI at http://localhost:6006 and select your project (
high-signal-demo). - You will see a spans table. Click the “Traces” tab at the top of the table, next to “Spans”.
- Type a condition into the filter bar:
status_code == 'ERROR'. The table narrows to errored spans only. - Add a clause to focus on a span kind:
status_code == 'ERROR' and span_kind == 'LLM'for model-call failures, or'TOOL'for tool errors. - Click any row to open the trace, then select the failing span to read its exception (
exception.message/exception.stacktrace) under the “Events” tab.
How to read error traces
Find the first failure, not the loudest one. In multi-step agent traces, upstream errors cascade. A malformed tool output at step 2 produces a confused plan at step 4 and a wrong answer at step 9. Note the first point where the trace went wrong; fixing it usually dissolves the downstream symptoms. Collect and group before you fix. A hundred error traces might be three actual problems. Resist debugging trace-by-trace; collect, categorize, count in the weekly session.Finding Performance Outliers
Latency and cost problems rarely announce themselves in averages. A healthy p50 can hide a p99 where agents loop, contexts balloon, and users give up. Sort by extremes and read both tails:- Slow tail: retry storms, agents stuck in tool-call loops, oversized retrieved contexts, sequential calls that should be parallel.
- Fast tail: short-circuits and silent failures, empty retrieval results, guardrails firing when they shouldn’t, models returning refusals or stubs.
- UI
- CLI
- Python
- TypeScript
Go to your project’s dashboard. The “Trace Latency” panel plots the percentile lines (P50 through P99) over time, so you can read both tails at a glance: watch the P99 line for the slow tail, and the P50 dipping toward zero for the suspiciously fast tail. Toggle individual percentiles in the legend to isolate the tail you’re reading. The dashboard tells you when a tail moved; use the CLI or Python tabs to pull the actual outlier traces to read.
Finding Quality Issues with Evaluations
Evaluation results attached to traces let you query for soft failures the way you query for exceptions. Treat eval results as exploration clues, not verdicts:- Review failures as well as passes. Failing labels show you problems; passing labels show you whether your evaluator is calibrated. If you only ever read failures, you’ll never notice your judge passing garbage.
- Prefer binary and categorical judgments to numeric scales. “Did the assistant hand off to a human when it should have: yes/no” is actionable. “Helpfulness: 4.2” is not. Arize research testing LLM judges on continuous ranges found numeric score evals unreliable, with small prompt changes producing wildly different results, and a 2025 re-test on newer models confirmed that binary and categorical judgments remain the most stable (original study, follow-up). Phoenix’s built-in evaluators emit categorical labels for exactly this reason.
- A 100% pass rate is a warning sign. It usually means your evals aren’t stressing the system. A 70% pass rate often indicates an eval that’s actually testing something.
- CLI
- Python
- TypeScript
Session Signals: Multi-Turn Failures
Single-trace filters miss a whole class of problems that only exist across turns: the user who rephrases three times, the conversation that derails halfway, the agent that loses context it had two turns ago. Phoenix sessions group traces into conversations so you can read the interaction the way the user experienced it. Session-level signals worth querying for:- Sessions containing any error trace
- Unusually long sessions (turn count as an outlier metric often portrays a user fighting the system)
- Sessions with negative feedback on any turn
- CLI
- Python
- TypeScript
The Weekly Session: Finding Failure Clusters
Individual bad traces can be anecdotal, edge cases. Clusters help you see what to prioritize. This clustering method comes from qualitative research:- Open coding. In qualitative research, “coding” means annotating data with labels, not programing. Pull a sample of traces and write free-form notes on anything wrong with each one. Don’t pre-define categories before looking at the traces, or you’ll miss the failure modes unique to your application.
- Axial coding. Group the notes around shared themes (the “axes” the method is named for) producing a failure taxonomy: distinct, named categories. You can do this yourself like grouping socks after doing the laundry, but an LLM is often faster and just as good.
- Count. Tally failures per category. This step creates a prioritized roadmap. In one of Hamel Husain’s client engagements, three categories accounted for over 60% of all problems.
- Iterate to saturation. Keep sampling until new traces stop revealing new categories. Rule of thumb: review at least 100 traces to start; once ~20 consecutive traces turn up nothing new, you’re saturated.
sample.py and run it from the terminal with python sample.py.
df is a spans dataframe pulled with SpanQuery, and df["model"] is the column you stratify on
Embedding clustering (advanced, optional). Embed the message content (usually the user inputs) and cluster it to reveal the natural groupings in your traffic, then sample from every group. Note that this is a different kind of clustering from the failure taxonomy above:
- Axial coding groups failures, and rarely needs a formal algorithm.
- Embedding clustering groups inputs, so you can sample across the real shape of your traffic and cross-reference input clusters against error rates and eval labels.
- Embed the user inputs. An embedding model converts each input into a long vector of numbers, positioned so that semantically similar inputs land near each other. This turns “which inputs resemble each other?” into a geometry question.
- Reduce with UMAP. Raw embeddings have over a thousand dimensions, and at that scale distances stop being informative. Everything is roughly equally far from everything else. UMAP compresses the vectors to 5–25 dimensions while preserving which points are neighbors, which is exactly what the next step needs.
- Cluster with HDBSCAN, which finds the dense regions of that space and calls each one a cluster. Unlike k-means, it doesn’t make you guess the number of clusters before you’ve seen the data, and it doesn’t force every point into a group: inputs that fit nowhere get labeled noise (
-1). Don’t discard them. An input that resembles nothing else in your traffic is your novelty signal, found for free. - Label each cluster with an LLM using representative examples, because “cluster 3” tells you nothing until you know it means “billing-dispute questions.”
BERTopic packages this exact pipeline if you’d rather not assemble it yourself.
- Python
- TypeScript
This optional pipeline isn’t covered by the base install.
- First add the extra libraries (HDBSCAN ships in scikit-learn ≥ 1.3):
- Then set an embedding provider key:
- Save the following as pipeline.py:
- Run the script with
Phoenix doesn’t include a built-in embedding projector. The workflow above shows the full DIY approach: embed, reduce, cluster, cross-reference against error rates. This portability is a feature. You control the embedding model and clustering algorithm.
Putting It Together: The Triage Loop
The full weekly loop, end to end:- Pull a mixed sample. All hard failures since last review, both latency tails, the lowest (and a few highest) eval scores, sessions with negative feedback or high turn counts, plus a stratified random slice so you don’t develop tunnel vision.
- Annotate, open-coding style. Free-form notes, first failure first. Phoenix annotations keep notes attached to the trace where the next reviewer can find them.
- Update the taxonomy and re-count. New failure modes get new categories; recurring ones get their tallies bumped.
- Prioritize by frequency × impact and fix from the top.
- Convert each confirmed failure mode into an evaluator and where possible, a code assertion, an LLM-as-judge with validated binary outputs where not.
- Python
- TypeScript
Next Steps
- Convert your top failure categories into automated evaluators and run them as online evals
- Set up annotation queues so domain experts as well as engineers can review edge cases
- Track failure-category counts over time: a category trending up is a regression even if no individual eval fires
- Revisit the full loop after every significant prompt, model, or retrieval change
Further Reading
The methodology in this guide draws on practitioner research in error analysis and evaluation:- A Field Guide to Rapidly Improving AI Products by Hamel Husain
- AI Evals FAQ: Why is error analysis so important? by Hamel Husain
- AI Evals FAQ: How can I efficiently sample production traces for review? by Hamel Husain
- Why You Should Not Use Numeric Evals for LLM as a Judge by Arize AI research on why categorical judgments beat numeric scores
- Testing Binary vs. Score Evals on the Latest Models by 2025 follow-up confirming the findings hold on newer models

