Get Started with AI Agent Tracing
See inside your AI agents in under 5 minutes. Add comprehensive tracing to understand what your agents are doing, why they’re failing, and how to make them better.
This guide walks you through adding Handit.ai tracing to your AI agents. You’ll capture every LLM call, tool execution, and operation with complete inputs, outputs, timing, and error details.
What you’ll build: Complete observability for your AI agent with automatic tracking of all LLM nodes and tools, capturing the full execution flow from user request to final response.
Quick Overview
Here’s what we’ll accomplish:
Get Your Integration Token
Obtain your API key from Settings > Integrations to connect your code to Handit.ai
Install & Configure
Set up the SDK and connect to Handit.ai with your integration token
Add Tracing
Wrap your agent functions to automatically capture execution details
See Results
View complete traces, debug issues, and optimize performance
Get Your Integration Token
Before installing the SDK, you need to get your integration token to connect your code to Handit.ai.
Step 1: Navigate to Settings
- In your Handit.ai Dashboard , click on “Settings” in the navigation menu
- Go to the “Integrations” section
Step 2: Generate Your Token
- Find the SDK Integration section
- Copy your integration token - this is your API key for the SDK
- Keep it secure - treat this like a password
Security Note: Your integration token gives access to your Handit.ai account. Never commit it to version control or share it publicly. Always use environment variables in production.
What you’ll use this token for:
- Authenticating your SDK with Handit.ai
- Sending trace data from your application
- Connecting your agent runs to your dashboard
Pro tip: Copy this token now and save it as an environment variable. You’ll need it in the next step when configuring the SDK.
Install the SDK
Python
pip install -U "handit-sdk>=1.16.0"
Configure Your Integration Token
Now let’s configure the SDK with the integration token you obtained from the dashboard.
Python
Best Practice: Create a dedicated service file to centralize your tracker configuration.
First, create a handit_service.py
file to initialize the tracker:
# handit_service.py
from handit import HanditTracker
tracker = HanditTracker()
tracker.config(api_key="your-integration-token-here")
Then import the tracker in your agent code:
from handit_service import tracker
Pro tip: Use environment variables for your integration token: tracker.config(api_key=os.getenv("HANDIT_INTEGRATION_TOKEN"))
Add Tracing to Your Agent
Now for the fun part! Let’s add tracing to capture everything your agent does.
Step 1: Trace Your Main Agent Function
Start by wrapping your main agent function—this is the entry point that handles user requests and orchestrates the entire workflow.
Python
Use the @tracker.start_agent_tracing()
decorator on your main agent function:
from handit_service import tracker
@tracker.start_agent_tracing()
async def process_message(self, user_message: str, conversation_history: list):
# Process the user message and generate a response
try:
# Analyze the message intent
intent = await self.analyze_intent(user_message)
# Generate response based on intent
response = await self.generate_response(intent, conversation_history)
return {
"response": response,
"intent": intent,
"status": "success"
}
except Exception as e:
return {
"error": str(e),
"status": "error"
}
This decorator automatically captures the complete execution flow, timing, inputs, outputs, and any errors that occur during your agent’s execution.
Step 2: Trace Individual Components
Next, add tracing to your individual LLM calls, tools, and functions. Choose the method that best fits your use case:
Python
Key Tracing Methods:
-
Basic Tracing (
start_tracing
,track_node
,end_tracing
)- Use these for simple, direct tracing of your agent’s workflow
- Perfect for getting started and basic monitoring
- Provides complete visibility of inputs, outputs, and timing
-
Advanced Tracing (
trace_agent_node
,track_model
,track_tool
)- Use these for more complex scenarios and reusable components
- Great for tracking specific LLM models or custom tools
- Enables detailed monitoring of individual components
Best Practice: Start with basic tracing, then add advanced methods as needed for specific components.
Choose the right method for your use case:
# Method 1: Using the three main tracing functions
# Start a new trace session
tracing_response = tracker.start_tracing(
agent_name="your-agent-name" # The name of your AI Application
)
execution_id = tracing_response.get("executionId") # Unique ID for this trace session
# Track individual operations
tracker.track_node(
input=user_message, # The input data for the operation
output=response, # The result of the operation
node_name="response_generator", # Unique identifier for this operation
agent_name="your-agent-name", # The name of your AI Application
node_type="llm", # Type of operation ("llm" or "tool")
execution_id=execution_id # Links this operation to the current trace session
)
# End the trace session
tracker.end_tracing(
execution_id=execution_id, # The ID of the trace session to end
agent_name="your-agent-name" # Must match the name used in start_tracing
)
# Method 2: Using trace_agent_node_func for async functions
# This is useful for one-off async function calls that need tracing
result = await tracker.trace_agent_node_func(
search_documents, # func: The async function to trace
query="customer support", # *args: Positional arguments for the function
limit=5, # **kwargs: Keyword arguments for the function
key="document-search" # key: Unique identifier for this node in analytics
)
# Method 3: Using trace_agent_node decorator for async functions
# This is useful for reusable async functions that need tracing
@tracker.trace_agent_node("vector-search") # agent_node_id: Unique identifier for this node
async def search_similar_documents(query: str, top_k: int = 5):
# Your vector search logic here
return results
# Method 4: Using trace_agent_node decorator for sync functions
# This is useful for reusable sync functions that need tracing
@tracker.trace_agent_node("text-processor") # agent_node_id: Unique identifier for this node
def process_text(text: str, language: str = "en"):
# Your text processing logic here
return processed_text
# Method 5: Tracking an LLM model
# This wraps a language model to track all its interactions
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(
model_name="gpt-4", # model: The LLM model to track
temperature=0.7
)
tracked_llm = tracker.track_model(
llm, # model: The model instance to track
"gpt4-chat" # model_id: Unique identifier for this model
)
# Method 6: Tracking a tool
# This wraps a tool to track all its executions
from langchain.tools import Tool
def search_database(query: str):
# Database search logic
return results
search_tool = Tool(
name="database-search", # tool: The tool to track
func=search_database
)
tracked_tool = tracker.track_tool(
search_tool, # tool: The tool instance to track
"db-search" # tool_id: Unique identifier for this tool
)
When to use each method:
start_tracing
,track_node
,end_tracing
- The three main functions for tracing your AI applicationtrace_agent_node_func
- One-off async function calls you need to tracetrace_agent_node
decorator - Reusable functions you want to trace every timetrack_model
- LLM models where you want to capture all interactionstrack_tool
- Tools, RAG systems, and API calls you want to monitor
Key principle: The node_name
is a unique identifier you give to each operation that you want to track in your application. Think of it like a label that helps you identify and track what each part of your system is doing.
Best Practices for Production
Secure Configuration
- Store your API key as an environment variable, never in code
- Use a single initialization point for the tracker across your application
Smart Wrapping Strategy
- Always wrap your main agent function (the entry point)
- Wrap all important nodes in your agent pipeline (LLMs, tools, key functions)
- Don’t wrap library functions, setup code, or utility functions
- Focus on business logic that you want to observe and debug
Error Handling
- Let errors propagate naturally through your code
- Handit.ai automatically captures error details, stack traces, and context
- Don’t try to handle tracing errors - the SDK is designed to be fail-safe
Performance Considerations
- Use async/await patterns for better performance
- Keep your node functions focused and efficient
- The tracing overhead is minimal but measure if you have high-throughput requirements
Production tip: Start with tracing your main workflows, then gradually add more detailed tracing as needed. Too much tracing can create noise—focus on what matters most for debugging and optimization.
Complete Example: Invoice Processing Agent
Let’s see how this all comes together with a real-world example. This invoice processing agent demonstrates all the concepts in action.
Python
Step 1: Set up your environment and API key:
# setup.py
import os
from dotenv import load_dotenv
from handit import HanditTracker
# Load environment variables from .env file
load_dotenv()
# Initialize the tracker with your API key
tracker = HanditTracker()
tracker.config(api_key=os.getenv("HANDIT_API_KEY"))
# Export the tracker for use in other files
__all__ = ['tracker']
Step 2: Here’s the complete invoice processing agent:
"""
Invoice Processing Agent with Handit.ai Tracing
This example demonstrates how to use Handit.ai for comprehensive tracing of an AI agent.
Key concepts:
1. Use start_tracing() to begin a trace session
2. Use track_node() to record individual operations
3. Use end_tracing() to complete the trace session
Each operation should have a unique node_name for proper tracking.
"""
from typing import Dict, List, Optional
from datetime import datetime
import json
from setup import tracker # Import the configured tracker
class InvoiceProcessor:
"""
A class to process and analyze invoices using AI.
This agent uses vector search to find similar invoices and LLM to generate insights.
"""
def __init__(self, vector_store, llm_model):
"""
Initialize the processor with required services.
Args:
vector_store: A vector database instance for similarity search
llm_model: An LLM model instance for generating insights
"""
self.vector_store = vector_store
self.llm_model = llm_model
async def process_invoice(self, invoice_data: Dict) -> Dict:
"""
Main method to process an invoice. This is the entry point for the agent.
Args:
invoice_data: Dictionary containing invoice information
Required fields: invoice_number, amount, date, vendor
Returns:
Dict containing processing results and recommendations
"""
# Start a new trace session
tracing_response = tracker.start_tracing(
agent_name="invoice_processor" # Identifies this agent in the Handit.ai dashboard
)
execution_id = tracing_response.get("executionId") # Unique ID for this trace session
try:
# Step 1: Validate the input data
validated_data = await self._validate_invoice(invoice_data)
# Track validation
tracker.track_node(
input=invoice_data,
output=validated_data,
node_name="invoice_validation",
agent_name="invoice_processor",
node_type="tool",
execution_id=execution_id
)
# Step 2: Find similar historical invoices
similar_invoices = await self._search_similar_invoices(validated_data)
# Track search
tracker.track_node(
input=validated_data,
output=similar_invoices,
node_name="similar_invoice_search",
agent_name="invoice_processor",
node_type="tool",
execution_id=execution_id
)
# Step 3: Analyze patterns in similar invoices
analysis = await self._analyze_invoice_patterns(similar_invoices)
# Track analysis
tracker.track_node(
input=similar_invoices,
output=analysis,
node_name="pattern_analysis",
agent_name="invoice_processor",
node_type="tool",
execution_id=execution_id
)
# Step 4: Generate recommendations using LLM
response = await self._generate_recommendations({
"invoice": validated_data,
"similar_invoices": similar_invoices,
"analysis": analysis
})
# Track LLM response
tracker.track_node(
input={
"invoice": validated_data,
"similar_invoices": similar_invoices,
"analysis": analysis
},
output=response,
node_name="llm_recommendations",
agent_name="invoice_processor",
node_type="llm",
execution_id=execution_id
)
# Return successful response with timestamp
result = {
"status": "success",
"data": response,
"timestamp": datetime.now().isoformat()
}
# Track final result
tracker.track_node(
input=invoice_data,
output=result,
node_name="final_response",
agent_name="invoice_processor",
node_type="tool",
execution_id=execution_id
)
return result
except Exception as e:
# Track error
error_result = {
"status": "error",
"error": str(e),
"timestamp": datetime.now().isoformat()
}
tracker.track_node(
input=invoice_data,
output=error_result,
node_name="error_handling",
agent_name="invoice_processor",
node_type="tool",
execution_id=execution_id
)
return error_result
finally:
# End the trace session
tracker.end_tracing(
execution_id=execution_id,
agent_name="invoice_processor"
)
async def _validate_invoice(self, invoice_data: Dict) -> Dict:
"""Validate the invoice data structure and required fields."""
required_fields = ["invoice_number", "amount", "date", "vendor"]
for field in required_fields:
if field not in invoice_data:
raise ValueError(f"Missing required field: {field}")
return invoice_data
async def _search_similar_invoices(self, invoice_data: Dict) -> List[Dict]:
"""Search for similar invoices in the vector store."""
query = f"Invoice from {invoice_data['vendor']} for {invoice_data['amount']}"
results = await self.vector_store.similarity_search(
query,
k=5,
filter={"vendor": invoice_data["vendor"]}
)
return [doc.metadata for doc in results]
async def _analyze_invoice_patterns(self, similar_invoices: List[Dict]) -> Dict:
"""Analyze patterns in similar invoices to identify trends."""
total_amount = sum(inv["amount"] for inv in similar_invoices)
avg_amount = total_amount / len(similar_invoices)
return {
"average_amount": avg_amount,
"frequency": len(similar_invoices),
"trend": "increasing" if avg_amount > total_amount / 2 else "decreasing"
}
async def _generate_recommendations(self, context: Dict) -> Dict:
"""Generate recommendations using the LLM model."""
prompt = f"""
Analyze this invoice and provide recommendations:
Invoice: {json.dumps(context['invoice'])}
Similar Invoices: {json.dumps(context['similar_invoices'])}
Analysis: {json.dumps(context['analysis'])}
"""
response = await self.llm_model.generate(prompt)
return {
"recommendations": response,
"confidence_score": 0.95
}
What’s Next?
Congratulations! 🎉 You now have complete observability into your AI agent. Every LLM call, tool execution, and operation is being captured with full context.
View Your Results in the Dashboard:
Now you can:
- Debug issues faster - See exactly where and why your agent fails
- Optimize performance - Identify slow operations and bottlenecks
- Understand behavior - View complete execution flows and decision paths
- Monitor production - Get alerts when things go wrong
Ready to explore more?
- Learn about Advanced Tracing Features for comprehensive monitoring
- Explore Agent Tracing for end-to-end workflow tracking
- Check LLM Node Tracing for AI model optimization
Your AI agents are no longer black boxes—they’re fully observable, debuggable, and optimizable systems.