AI Agent Debugging Tools: A Complete Guide
How to debug AI agents that produce unpredictable outputs.
Debugging AI agents is fundamentally different from debugging traditional software. When bugs are non-deterministic and reasoning is opaque, standard debugging approaches fall short. This guide covers the tools and techniques that actually work.
Common AI Agent Bugs
| Bug Type | Symptoms | Cause |
|---|---|---|
| Hallucination | Agent invents facts | No grounding data |
| Tool misuse | Wrong tool or wrong params | Poor tool descriptions |
| Infinite loop | Agent repeats actions | No termination condition |
| Context overflow | Agent forgets earlier context | Context too long |
| Prompt injection | Agent follows malicious instructions | No input filtering |
| Cost explosion | Unexpectedly high API bills | No budget controls |
| Inconsistent behavior | Same input, different output | Non-determinism |
| Tool timeout | Agent hangs waiting for tool | No timeout handling |
Debugging Toolkit
1. Conversation Inspector
class ConversationInspector:
"""Inspect and replay agent conversations step by step."""
async def inspect(self, conversation_id):
messages = await self.db.get_messages(conversation_id)
for i, msg in enumerate(messages):
print(f"\n{'='*60}")
print(f"Step {i+1} β {msg['role'].upper()}")
print(f"{'='*60}")
if msg["role"] == "assistant":
print(f"Content: {msg['content'][:200]}")
if msg.get("tool_calls"):
print(f"\nTool Calls:")
for call in msg["tool_calls"]:
print(f" β {call['name']}({call['arguments']})")
if msg.get("reasoning"):
print(f"\nReasoning: {msg['reasoning']}")
if msg.get("tokens"):
print(f"\nTokens: in={msg['tokens']['input']}, out={msg['tokens']['output']}")
print(f"Cost: β¬{msg['tokens']['cost']:.4f}")
elif msg["role"] == "tool":
print(f"Tool: {msg['name']}")
print(f"Result: {str(msg['content'])[:200]}")
print(f"Duration: {msg.get('duration_ms', '?')}ms")
else:
print(f"Content: {msg['content']}")
2. Replay Debugger
class ReplayDebugger:
"""Reproduce bugs by replaying conversations."""
async def replay(self, conversation_id, step=None):
"""Replay a conversation up to a specific step."""
messages = await self.db.get_messages(conversation_id)
if step:
messages = messages[:step]
# Recreate exact state
agent = await self.recreate_agent_state(conversation_id)
# Replay each step
for i, msg in enumerate(messages):
if msg["role"] == "user":
print(f"\n[User]: {msg['content']}")
# Get agent's response
response = await agent.process(msg["content"])
# Compare with original
original = messages[i+1] if i+1 < len(messages) else None
if original and original["content"] != response.content:
print(f"\nβ οΈ DIFFERENCE DETECTED:")
print(f" Original: {original['content'][:100]}")
print(f" Replay: {response.content[:100]}")
return response
3. Decision Tree Visualizer
class DecisionVisualizer:
"""Visualize the agent's decision-making process."""
def visualize(self, conversation):
tree = "Agent Decision Tree\n"
tree += "=" * 50 + "\n\n"
for step in conversation.steps:
indent = " " * step.depth
tree += f"{indent}β {step.action}\n"
if step.reasoning:
tree += f"{indent} Reason: {step.reasoning}\n"
if step.tool_call:
tree += f"{indent} Tool: {step.tool_call.name}\n"
tree += f"{indent} Input: {step.tool_call.args}\n"
tree += f"{indent} Output: {str(step.tool_call.result)[:100]}\n"
if step.error:
tree += f"{indent} β ERROR: {step.error}\n"
tree += "\n"
return tree
Debugging by Bug Type
Fixing Hallucinations
async def debug_hallucination(conversation_id):
"""Investigate why agent hallucinated."""
conv = await load_conversation(conversation_id)
# Check if grounding data was available
if not conv.retrieved_documents:
return "No documents retrieved β agent had no grounding data"
# Check if documents were in context
context_docs = conv.context.get("documents", [])
if len(context_docs) == 0:
return "Documents retrieved but not included in context"
# Check if documents were relevant
relevance_scores = await score_relevance(
conv.user_message, context_docs
)
if max(relevance_scores) < 0.3:
return "Retrieved documents were not relevant to question"
# Check if model ignored context
response_claims = extract_claims(conv.agent_response)
supported_claims = [c for c in response_claims if is_supported(c, context_docs)]
unsupported = [c for c in response_claims if c not in supported_claims]
return {
"diagnosis": "Model generated claims not supported by context",
"unsupported_claims": unsupported,
"fix": "Add stronger grounding instructions to system prompt",
}
Fixing Tool Misuse
async def debug_tool_misuse(conversation_id):
"""Investigate why agent used wrong tool or wrong parameters."""
conv = await load_conversation(conversation_id)
for call in conv.tool_calls:
print(f"\nTool: {call.name}")
print(f"Input: {call.arguments}")
print(f"Expected: {call.expected_arguments}")
# Check tool description clarity
tool = get_tool_definition(call.name)
print(f"Description: {tool.description}")
# Check if description is ambiguous
ambiguity_score = await check_ambiguity(tool.description)
if ambiguity_score > 0.5:
print(f"β οΈ Tool description is ambiguous")
# Check parameter schema
for param, value in call.arguments.items():
schema = tool.inputSchema.get("properties", {}).get(param)
if not schema:
print(f"β οΈ Parameter '{param}' not in schema")
elif not validate_type(value, schema):
print(f"β οΈ Type mismatch for '{param}'")
Fixing Infinite Loops
async def debug_loop(conversation_id):
"""Find why agent is stuck in a loop."""
conv = await load_conversation(conversation_id)
# Detect repeated actions
actions = [step.action_hash for step in conv.steps]
repeats = find_repeats(actions)
if repeats:
return {
"diagnosis": f"Agent repeated {repeats[0]} {len(repeats)} times",
"fix": "Add max_iterations limit and loop detection",
}
# Check if agent gets stuck on tool errors
tool_errors = [s for s in conv.steps if s.error]
if len(tool_errors) > 3:
return {
"diagnosis": "Agent stuck retrying failed tool",
"fix": "Add circuit breaker β stop after 3 failures",
}
Debugging Tools Comparison
| Tool | Best For | Price | Integration |
|---|---|---|---|
| LangSmith | LangChain agents | Free/β¬39+ | LangChain |
| Langfuse | Any LLM app | Free/SaaS | Any |
| Phoenix | Deep tracing | Free | Various |
| Helicone | OpenAI agents | Free/β¬29+ | OpenAI proxy |
| Sentry | Error tracking | Free/β¬26+ | Any |
| Custom inspector | Full control | Dev time | Your stack |
Logging for Debuggability
import structlog
logger = structlog.get_logger()
class DebugLogger:
async def log_agent_step(self, step):
logger.info(
"agent_step",
conversation_id=step.conversation_id,
step_number=step.number,
action=step.action,
input=step.input,
output=step.output,
duration_ms=step.duration_ms,
tokens_used=step.tokens,
cost=step.cost,
model=step.model,
# Debug-specific fields
_debug={
"prompt": step.full_prompt,
"raw_response": step.raw_response,
"tool_options": step.available_tools,
"selection_reason": step.selection_reasoning,
},
)
Testing as Debugging Prevention
Regression Tests for Bugs
# Every bug you fix should become a test
class TestBugFixes:
@pytest.mark.asyncio
async def test_bug_123_no_hallucination_on_empty_results(self):
"""Bug #123: Agent hallucinated when no results were found."""
result = await agent.process("Tell me about product XYZ")
assert "I couldn't find" in result or "not available" in result
assert "XYZ" not in result.content or result.content.count("XYZ") <= 1
@pytest.mark.asyncio
async def test_bug_456_tool_timeout_handled(self):
"""Bug #456: Agent hung when tool timed out."""
with patch("tools.search", side_effect=TimeoutError):
result = await agent.process("Search for AI news")
assert "couldn't complete" in result or "try again" in result
Production Debugging Checklist
When an issue is reported in production:
1. Find the conversation: Search by user_id, timestamp, or content
2. Replay the conversation: Use ReplayDebugger to reproduce
3. Inspect each step: Use ConversationInspector for detailed view
4. Check the model: Was the right model used? Same version?
5. Check the tools: Did tools return correct data?
6. Check the context: Was relevant information in the context?
7. Check for injection: Was the input adversarial?
8. Check costs: Were there unexpected token spikes?
9. Document the bug: What happened, why, and how to prevent
10. Write a regression test: Ensure it never happens again
Conclusion
Debugging AI agents requires specialized tools and approaches. By implementing comprehensive logging, conversation inspection, replay debugging, and regression testing, you can identify and fix even the most elusive AI agent bugs.
Learn More
- AI Agent Testing Framework
- AI Agent Observability
- AI Agent Monitoring Tools
- AI Agent Logging Best Practices
Find debugging and testing tools on SkillExchange.