A practical guide for debugging common failures in production AI applications.
Use this checklist before changing prompts or models:
- Reproduce the issue with a fixed input and save a
trace_id. - Log full pipeline state: query, retrieved context, prompt, tool calls, output, latency.
- Compare failing trace with a known-good trace.
- Isolate one stage at a time (retrieval, prompt, tools, agent loop, generation).
- Measure before/after metrics for every fix.
- Add a regression test for the failure case.
- Capture one failing request with full artifacts.
- Verify retrieval quality first (wrong context causes downstream failures).
- Check prompt instructions and output constraints.
- Inspect tool argument payloads, errors, and timeout behavior.
- Review agent step history for loops or no-progress actions.
- Break down latency by stage and identify the slowest component.
- Re-run after fix and compare metrics against baseline.
The system retrieves weakly related chunks, so answers become generic or incorrect.
Chunking is suboptimal, embedding quality is weak, metadata filters are too strict, or query normalization is missing.
Tune chunk size/overlap, validate embedding model choice, improve query rewriting, and add reranking for top-k candidates.
Recall@kPrecision@k- retrieval hit rate on labeled queries
- average similarity score of top result
def debug_retrieval(query: str, retriever) -> None:
results = retriever.search(query, k=5)
print(f"query={query}")
for rank, row in enumerate(results, start=1):
print(f"{rank}. score={row['score']:.3f} id={row['id']} text={row['text'][:80]}")The model returns confident claims that are not supported by available context.
Generation is not grounded strongly enough, context is incomplete, or no verification step exists.
Use grounded prompts with citation requirements, add evidence checks, and return uncertainty when support is missing.
- faithfulness score
- unsupported-claim rate
- citation coverage (claims with source)
- user correction / escalation rate
def has_citation(answer: str) -> bool:
# Minimal check: expects references like [chunk-12]
return "[chunk-" in answerOutputs are inconsistent, off-format, or ignore important constraints.
Instructions are ambiguous, role hierarchy is unclear, or prompts are too long and noisy.
Use strict system prompts, short task instructions, explicit output schema, and prompt regression tests.
- format validity rate
- task success rate on eval set
- refusal correctness rate (for out-of-scope requests)
- output variance across repeated runs
def format_valid(output: dict) -> bool:
required = {"answer", "confidence"}
return required.issubset(output.keys())Tool calls fail, return malformed data, or timeout under load.
Arguments are invalid, schemas are weak, dependency services are unstable, or retry strategy is missing.
Validate tool inputs with strict schemas, add retries with backoff, set timeouts, and define fallbacks.
- tool success rate
- timeout rate
- invalid-argument rate
- fallback usage rate
def safe_tool_call(tool_fn, args: dict):
if "city" not in args:
return {"ok": False, "error": "missing city"}
try:
return {"ok": True, "data": tool_fn(**args)}
except Exception as exc:
return {"ok": False, "error": str(exc)}The agent repeats actions without progress or never reaches a stop condition.
Planning policy is weak, stop conditions are missing, and state tracking is incomplete.
Add max-step limits, no-progress detection, action deduplication, and explicit termination criteria.
- average steps per task
- completion rate
- repeated-action ratio
- forced-stop rate
def repeated_action_ratio(actions: list[str]) -> float:
if not actions:
return 0.0
repeats = sum(1 for i in range(1, len(actions)) if actions[i] == actions[i - 1])
return repeats / max(1, len(actions) - 1)Responses are slow or unstable, causing poor UX and higher infrastructure cost.
Large prompts, slow retrieval/tool dependencies, and no stage-level latency budgets.
Track per-stage latency, trim context, cache repeated work, and route simple tasks to smaller models.
- end-to-end latency (
p50,p95,p99) - per-stage latency (retrieval, LLM, tools)
- tokens per request
- timeout and retry counts
import time
def timed(label: str, fn, *args, **kwargs):
start = time.perf_counter()
out = fn(*args, **kwargs)
elapsed_ms = (time.perf_counter() - start) * 1000
print(f"{label}: {elapsed_ms:.1f}ms")
return outWhen multiple failures appear at once, debug in this order:
- retrieval quality
- prompt quality
- tool correctness
- agent loop behavior
- generation faithfulness
- performance and latency
This order usually finds the root cause faster than tuning prompts first.