Loading...

Interview question, debug an AI agent that is only sometimes wrong

The hardest bugs in production AI never throw an error. The request returns 200 OK, the dashboard stays green, and the answer is quietly, confidently wrong. This is how production teams see inside a Large Language Model system, and how to answer the observability interview like someone who has actually done it.

A user says "sometimes it hallucinates." There is no stack trace, no error, no way to reproduce it. This is the full map of AI observability. Why it differs from normal monitoring, and how traces become the unit of debugging. How OpenTelemetry captures a Large Language Model (LLM) request, how quality is measured on live traffic, and how a real incident gets investigated end to end.

The bug that never throws an error

Here is the report you will get. "Sometimes it works." "Sometimes it hallucinates." "Sometimes it ignores the document I gave it." No stack trace. No error code. No steps to reproduce. You ask the user for the exact input, you paste it in yourself, and it answers perfectly. The bug refuses to show up when you are watching.

This is the part of AI engineering that catches people off guard. In normal software, a bug is a broken thing. Something throws, something returns the wrong status, a test goes red, and you follow the trail. In an AI system, the request succeeds. The Large Language Model (LLM) returns a fluent, confident, well-formatted answer, and the answer is wrong. Your monitoring sees a 200 OK and a healthy latency and moves on. The failure and the success look identical from the outside.

Three things make these bugs hard, and they are worth naming up front because the rest of this post is really about defeating each one.

The discipline that beats all three is called observability, and for AI it means something more specific than dashboards. It means recording enough about every single request that you can answer a question you did not think to ask in advance. Months later, from data you already have, you can ask why one answer ignored the policy document. This app has companion pieces on the full production AI stack and on what breaks when a million people use your AI app. This post zooms all the way into one layer of both, how you see inside the box.

Traditional monitoring answers "is the system up and fast." AI observability answers "was the answer any good." A system can be perfectly up, perfectly fast, and perfectly wrong. Only the second question catches that.

A Tuesday at a company that shipped an agent

Picture a mid-size company that added an AI support agent to its product. It answers customer questions from the company help centre. It can look up an order, check a refund policy, and draft a reply. It launched two months ago. The demo was great. Leadership loved it. It handles thousands of conversations a day now.

A support lead forwards a thread to engineering. A customer was told their order qualified for a refund. It did not. The agent had stated the wrong policy with total confidence. Below that email are three more like it from the past week that nobody connected until now.

An engineer picks it up. First move, reproduce it. He copies the customer question into the agent. It answers correctly. He tries again. Correct again. He cannot make it fail. The only record he has is the final text the agent sent the customer, stored in the conversations table. He has the wrong answer. He does not have the prompt that produced it, the documents it retrieved, or which model version was serving at that hour. He is holding the output of a function with none of its inputs.

He checks the dashboards. Latency is healthy. Error rate is zero. The model provider status page is green. Every signal the team has says the system is perfectly fine, and yet it is telling customers things that are false. There is nothing to grep, nothing to bisect, nothing to step through.

By Thursday it reaches leadership as a trust problem, which is what it is. The question in the room is simple and the team cannot answer it. How often is this happening, and why. Nobody knows, because nobody recorded the one thing that would tell them. That gap, the distance between knowing the agent said something wrong and being able to say exactly why, is the thing an AI observability platform exists to close.

This story is a composite, but the shape is common. Almost every team that ships an LLM feature without observability hits this exact wall within a few months, and the fix is never a clever prompt. It is the boring infrastructure that records what happened.

The interview question

This scenario shows up in Senior, Staff, and Principal interviews because it separates people who have shipped AI from people who have only prototyped it. The question usually arrives like this.

Users report that your AI agent is "sometimes wrong." It hallucinates, or ignores retrieved context, but only occasionally, and you cannot reproduce it. There are no errors. How do you design observability for this system so you can detect, debug, and prevent these failures?

What the interviewer is actually testing

They are not looking for a list of tools. They are checking whether you understand why this problem is different from anything in normal backend work, and whether you have a mental model for making a probabilistic system debuggable. A strong answer moves through four moves.

What a weak answer saysWhat a strong answer says
"I would add logging.""I would trace every request as nested spans, capturing the prompt, retrieved chunks, model version, tokens, and cost, because the final output alone cannot explain the failure."
"I would monitor latency and errors.""Latency and errors are necessary but blind to a wrong answer. I would add online evaluations that score a sample of live traffic for groundedness and helpfulness."
"I would test the prompt more before shipping.""Offline evals gate the release, but the model can change under me in production, so I also score live traffic and alert on drift."
"I would look at the logs when it happens.""I would pin every version so a quality drop maps to a change, and turn each confirmed failure into a regression test so it cannot recur."

The traps

Why AI observability is a different animal

Observability is an old idea. The classic version has three signals, and every backend engineer knows them.

This is Application Performance Monitoring (APM), and it is genuinely good at what it does. It just answers the wrong question for AI.

APM was built for a world where correctness is settled in code and caught by tests. The service either returned the right value or it threw. Nobody built a metric for the response was fluent but false, because in normal software that state does not exist. An LLM lives in exactly that state. It fails by being confidently wrong while returning success.

Why logging the final output is not enough

The instinct is to log the answer. It is also not enough, and it is worth being precise about why. An LLM answer is the output of a long chain. A system prompt, the user message, some retrieved documents, maybe a few tool calls, a specific model version, and a sampling setting. When the answer is wrong, the answer text tells you nothing about which link broke. Was the wrong document retrieved? Was the right document retrieved and then ignored? Did a tool return stale data? Did the model version change overnight? The output is the same shape in every one of those cases. You need the inputs, all of them, recorded together.

DimensionTraditional observabilityAI observability
Unit of failureAn exception or error statusA wrong or ungrounded answer that returns success
How you detect itError rate, alerts on 5xxQuality scores, groundedness, drift, user feedback
ReproducibilityDeterministic, re-run the inputNon-deterministic, must replay the captured request
What you recordStatus, latency, stack tracePrompt, retrieved chunks, model version, tokens, cost, plus a quality score
What healthy meansUp and fastUp, fast, and correct
What changes under youYour code, on your deploysThe model itself, silently, on the provider schedule

That last row is the one people miss. In normal software, nothing changes unless you change it. In AI, the provider can update the model behind the same name and your behaviour shifts with no deploy on your side. Observability is how you notice, and traditional monitoring has no place to even look.

The trace is the unit of debugging

Here is the single most important shift. In AI systems, you stop debugging with log lines and start debugging with traces. A trace is the complete recorded path of one request. It is made of spans, and a span is one timed operation inside that request, a model call, a retrieval, a tool execution, a guardrail check. Each span records when it started, how long it took, and a set of attributes, which for AI are the useful part, the model name, the token counts, the cost.

Spans nest. The top span is the whole request. Under it sit the steps, and under those sit their sub-steps. A retrieval span might contain a vector-search span. A model call sits next to the tool call it decided to make. That tree is the shape of what the agent actually did, reconstructed after the fact from data, and it is the thing you open when something goes wrong.

Three ideas hold this together, and they are the same three whether you are tracing a web request or an agent.

The full request lifecycle, as spans

A single agent answer is not one model call. It is a small pipeline, and every stage becomes a span. Here is the shape of a typical Retrieval Augmented Generation (RAG) agent request.

Read that top to bottom and you can see the whole decision. Which documents came back. Whether the tool succeeded. How long the model took to start streaming. What it cost. When a customer gets a wrong policy, this is the record that tells you, in seconds, whether the policy document was even retrieved. Without it, you are the engineer from Tuesday, holding an output with no inputs.

Here is a useful test for whether your tracing is good enough. Pick any past answer a user complained about and ask whether you can see the exact prompt and the exact retrieved chunks for that specific request. If the answer is no, your observability is not done, no matter how many dashboards you have.

In the interview, say this plainly. The trace is the unit of debugging, and if you cannot pull the exact prompt and retrieved chunks for one specific bad answer, nothing else you built matters.

OpenTelemetry, the standard that makes this portable

You could invent your own trace format. Do not. There is an open standard called OpenTelemetry, usually shortened to OTel, and it is the thing the rest of the industry has agreed on. It gives you libraries to create spans, a defined shape for what a span is, and a wire format called OTLP, the OpenTelemetry Protocol, to ship them. The payoff is simple. Instrument your code once, and you can send the data to any backend that speaks OTLP. You are not married to one vendor.

Mechanically, you wrap each meaningful operation in a span. You start a span before a model call, set attributes on it as you learn them, and end it when the call returns. The library handles the trace ID, the parent links, and context propagation for you, so a span created deep inside a helper still attaches to the right request.

GenAI semantic conventions

A span is only useful if a tool can understand it, and that needs shared names. If your team calls it model_name, mine calls it llm, and a third calls it engine, no dashboard can chart across all three. OpenTelemetry solves this with semantic conventions, agreed attribute names for common operations. There is now a GenAI set specifically for AI. gen_ai.request.model for the model you asked for, gen_ai.usage.input_tokens and gen_ai.usage.output_tokens for token counts, gen_ai.response.model for the version that actually answered. Use them. It means any OTel-aware backend understands your traces with no custom parsing, and it means the token and cost math works without you writing it.

Record the response model as well as the requested one. You ask for a model by a friendly name, but the provider serves a specific dated version behind it, and that version can change. gen_ai.response.model is often the single attribute that solves a quality-dropped-and-we-shipped-nothing mystery.

One more thing OTel gets right. It separates instrumentation from where the data goes. Your app emits spans to a collector, a small service that receives OTLP, and the collector is where you batch, sample, redact personal data, and fan the data out to storage. Your application code does not need to know or care which backend you use, which means you can change backends without touching the agent.

The observability platform, component by component

Put the pieces together and you get a platform. It is not one product. It is a pipeline that starts inside your running app and ends at the places engineers actually look. Here is the whole thing, then each part.

The important design property is the join. Because one trace ID threads through every stage, a spike on the cost dashboard, a drop on the quality dashboard, and a specific bad answer all point back to the same trace. You can move from quality fell at 2pm to "here are the twenty requests that caused it" without guessing.

In the interview, the join is the detail that marks a senior answer. One trace ID threading metrics, logs, and spans is how you go from quality fell at 2pm to the exact requests that caused it, and most candidates never think to mention it.

Retrieval, tools, and multi-agent chains

The three most common AI failures each live in a specific place in the trace. Learn to read those places and most debugging becomes fast. Start with the most common complaint of all, that it ignored the document you gave it.

Retrieval, where it ignored the context lives

In a Retrieval Augmented Generation (RAG) system, an answer is only as good as what got retrieved and whether the model used it. There are two very different failures hiding behind the same complaint, and the trace tells them apart instantly. Either the right document was not retrieved, which is a retrieval bug, or the right document was retrieved and the model ignored it, which is a grounding bug. The fixes are opposite. One is about your index and your search; the other is about your prompt and your model. Guessing which one you have, without the trace, wastes days.

So the retrieval span has to record what came back, not just that it ran. Log the query, the top-k chunk IDs, their similarity scores, and ideally the chunk text or a hash of it. Now was the policy even retrieved is a five-second lookup instead of an argument.

Tool execution, where stale or wrong data enters

Agents call tools, a database query, an API call, a search. A tool that returns wrong or stale data poisons everything downstream. The model will confidently build an answer on top of the bad input. Each tool call is a span, and it needs to record the arguments the model chose, the raw result, the latency, and whether it errored or retried.

When the answer is wrong, this chain lets you ask the right question in order. Did the model pick the wrong tool or the wrong arguments? Did the tool return bad data? Or did the tool return good data and the model still answered wrong? Three different bugs, three different owners, one trace to tell them apart.

Multi-agent, where the trace becomes essential

Once you have several agents handing work to each other, an orchestrator calling specialised sub-agents, the trace stops being nice to have and becomes the only way to understand anything. A single user request might fan out into a dozen model calls across several agents. The nested span tree is the map of who called whom, where the time went, and where the wrong turn happened.

Record the routing decision as a span attribute. In a multi-agent system, it sent the question to the wrong agent is a whole class of bug, and it is invisible unless you log which route was chosen and why.

Measuring quality when there is no error

Traces let you debug one request. They do not tell you how often the agent is wrong across thousands of requests, because nobody is reading every trace. For that you need to measure quality, continuously, on live traffic. This is the piece that has no equivalent in traditional monitoring, and it is where most teams are weakest.

The idea borrows from testing. Offline, before a release, you run a frozen set of known-good cases and score the new prompt or model against them, and that eval gates the release the way unit tests gate code. But offline evals cannot catch a model that changes under you in production, or a real user question you never thought to test. So you also score live traffic. You take a sample of real requests, run automated scorers over them, and write the scores back next to the trace.

How you score an answer

An LLM judge is useful but not free of judgement. It can be wrong, and it can be biased toward longer or more confident answers. Validate the judge against human ratings on a sample before you trust its number, and keep a human in the loop on the cases that matter. A judge you never checked is just a second opinion you did not verify.

The reason this matters so much is quality drift. A provider updates the model behind the same name. Your retrieval corpus grows and the good chunk falls out of the top-k. Neither event throws an error. Neither shows on a latency chart. The only thing that catches a slow slide in correctness is scoring live traffic over time and alerting when the score falls. That alert is the difference between finding out from your own dashboard and finding out from an angry customer three weeks later.

In the interview, this is the line that lands. Quality is measured, not asserted, so you score a sample of live traffic and alert on drift, because the model can get worse with no error and no deploy on your side.

Versioning, cost, and the silent model swap

When quality drops, the first question is always "what changed." You can only answer it if everything that can change is pinned to a version and recorded on the trace. In an AI system, more things change than you might expect.

Here is the failure this prevents, and it is the one that makes senior engineers nod in an interview. Quality drops. You check your deploys. Nothing shipped. Your code is byte for byte what it was last week. The cause is that the provider rolled the model forward under the same name, and your behaviour moved with it. If your traces pin the response model version, this shows up immediately as a change in one attribute. If they do not, you will spend days looking for a bug in code that never changed.

Token accounting and cost attribution

Every model call span records input and output tokens, and from tokens you get cost. Summed up, this is how you see spend per request, per user, per tenant, and per feature. It sounds like a finance concern, but it is an observability one, because cost is a signal. A request burning ten times the usual tokens is often a bug. A retrieval that stuffed too much into the context, an agent stuck in a loop, a prompt that ballooned. Cost attribution, tying every token back to who spent it, is what lets you find the one runaway tenant or the one feature quietly costing more than the rest of the product combined.

Token count is also a quality signal in disguise. A sudden jump in output tokens on a feature can mean the model started rambling, and a drop in retrieved tokens can mean your index stopped returning good chunks. Watch the token charts next to the quality charts, not just the cost report.

A real investigation, start to finish

Now put it together on the Tuesday incident, but this time assume the team built the observability first. Watch how the same problem goes from unsolvable to routine.

Step by step, this is what the on-call engineer does.

Notice what made this possible. Every step read from the trace of one real request. The prompt, the chunks, the tool result, the model version, all recorded. Without that record, steps three through six are pure speculation, and the incident stays open for a week. With it, root cause took one engineer twenty minutes.

Replay, rollback, and closing the loop

Finding the bug is not the end. A Staff engineer is judged on what happens next. Can you fix it safely, prove the fix, and guarantee it never comes back. Three mechanisms do this, and they all lean on the trace you already captured.

Replay

A production system is not reproducible on your laptop, but a captured trace is. Replay means re-running a real failing request against a new prompt or model version, using the exact frozen inputs from its trace, the same prompt, the same retrieved context, the same tool results. You are comparing versions on the real request that failed, not a made-up one. This is how you know a fix actually fixes the reported case, and how you compare a candidate model to the current one before you trust it.

Rollback, canary, and kill switches

When something regresses, you need to change it back fast. That means three habits. Ship changes as a canary deployment, to a small slice of traffic first, watching the quality and cost scores on that slice, expanding only if they hold and rolling back automatically if they slide. Keep a feature flag on every model, prompt, and capability, so you can turn it off in seconds without a deploy. And keep the previous version warm, so rollback is a switch, not a rebuild.

The most useful sentence during an AI incident is turn it off. If turning a bad model off needs a code change and a deploy, your incident is measured in hours. If it is a flag, it is measured in seconds. Build the flag before you need it.

Regression testing, so it never recurs

The last step is the one that compounds. Every confirmed production failure becomes a permanent test. You take the captured trace, freeze the input and the retrieved context, attach the known-good answer, and add it to your golden evaluation set. Now the release gate re-runs that exact case on every future change. A bug that reached a customer once can never ship again, because a specific test would go red first.

This is what turns an observability platform from a debugging tool into a system that gets more reliable over time. Every incident makes the eval suite smarter. The failures teach it. A team that does this ships fewer repeat bugs every quarter, not because they got lucky, but because the loop is closed.