Agentic Design Pattern: Trace Tracking

Written by

in


    """
Pattern 094: Trace/Lineage Tracking

Description:
    Trace and Lineage Tracking captures the complete execution path of an agent, including
    all decisions, actions, intermediate states, and data transformations. This pattern is
    essential for debugging complex agent behaviors, understanding how decisions were made,
    ensuring compliance, and optimizing performance. Comprehensive tracing enables
    reproducibility, auditing, and continuous improvement of AI systems.

    Trace tracking provides:
    - Complete execution history
    - Decision point visibility
    - Data lineage and provenance
    - Performance metrics per step
    - Error context and stack traces
    - Reproducibility information

Components:
    1. Trace Capture
       - Execution events (start, end, error)
       - Decision points and branches
       - Input/output at each step
       - Timestamps and durations
       - Resource consumption
       - Agent state snapshots

    2. Trace Storage
       - Structured trace format
       - Hierarchical relationships
       - Searchable metadata
       - Efficient retrieval
       - Long-term retention

    3. Trace Analysis
       - Path visualization
       - Performance analysis
       - Bottleneck identification
       - Error pattern detection
       - Comparison tools

    4. Trace Replay
       - Reproduce executions
       - Debug specific paths
       - Test alternative branches
       - Validate fixes
       - Training data generation

Use Cases:
    1. Debugging
       - Understand unexpected behavior
       - Reproduce production issues
       - Identify error causes
       - Validate fixes
       - Root cause analysis

    2. Performance Optimization
       - Identify bottlenecks
       - Measure step durations
       - Analyze resource usage
       - Compare implementations
       - Optimize hot paths

    3. Compliance & Auditing
       - Document decision making
       - Prove regulatory compliance
       - Audit trails for sensitive operations
       - Accountability tracking
       - Legal requirements

    4. Quality Assurance
       - Validate agent behavior
       - Ensure determinism
       - Track test coverage
       - Regression detection
       - Change impact analysis

    5. Agent Development
       - Understand agent flow
       - Optimize prompts
       - Refine decision logic
       - Test edge cases
       - Documentation generation

LangChain Implementation:
    LangChain supports tracing through:
    - LangSmith for comprehensive tracing
    - Custom callback handlers
    - OpenTelemetry integration
    - Structured logging
    - Trace context propagation

Key Features:
    1. Comprehensive Capture
       - Every agent step traced
       - Full context preservation
       - Hierarchical relationships
       - Performance metrics
       - Error details

    2. Structured Format
       - Consistent trace structure
       - Searchable attributes
       - Machine-readable format
       - Human-readable display
       - Standard schemas

    3. Powerful Analysis
       - Path visualization
       - Query capabilities
       - Statistical analysis
       - Comparison tools
       - Pattern detection

    4. Production Ready
       - Low overhead ( 1 year
       - Compression for old traces
       - Automatic cleanup

    3. Performance Impact
       - Target:  Dict[str, Any]:
        """Convert to dictionary"""
        data = asdict(self)
        data['event_type'] = self.event_type.value
        data['timestamp'] = self.timestamp.isoformat()
        return data


@dataclass
class Trace:
    """Complete trace of an agent execution"""
    trace_id: str
    start_time: datetime
    end_time: Optional[datetime] = None
    events: List[TraceEvent] = field(default_factory=list)
    status: str = "running"  # running, completed, failed
    metadata: Dict[str, Any] = field(default_factory=dict)
    
    @property
    def duration_ms(self) -> float:
        """Calculate total duration"""
        if self.end_time:
            return (self.end_time - self.start_time).total_seconds() * 1000
        return 0.0
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        return {
            'trace_id': self.trace_id,
            'start_time': self.start_time.isoformat(),
            'end_time': self.end_time.isoformat() if self.end_time else None,
            'duration_ms': self.duration_ms,
            'status': self.status,
            'event_count': len(self.events),
            'metadata': self.metadata,
            'events': [e.to_dict() for e in self.events]
        }


class TraceCollector:
    """
    Collects and manages traces for agent executions.
    
    Captures events, maintains relationships, and provides query capabilities.
    """
    
    def __init__(self):
        """Initialize trace collector"""
        self.traces: Dict[str, Trace] = {}
        self.current_trace_id: Optional[str] = None
        self.event_stack: List[str] = []  # Stack of event IDs for hierarchy
    
    def start_trace(self, metadata: Optional[Dict[str, Any]] = None) -> str:
        """
        Start a new trace.
        
        Args:
            metadata: Optional trace metadata
            
        Returns:
            Trace ID
        """
        trace_id = str(uuid.uuid4())
        trace = Trace(
            trace_id=trace_id,
            start_time=datetime.now(),
            metadata=metadata or {}
        )
        self.traces[trace_id] = trace
        self.current_trace_id = trace_id
        return trace_id
    
    def end_trace(self, trace_id: str, status: str = "completed"):
        """
        End a trace.
        
        Args:
            trace_id: Trace identifier
            status: Final status (completed, failed)
        """
        if trace_id in self.traces:
            trace = self.traces[trace_id]
            trace.end_time = datetime.now()
            trace.status = status
    
    def log_event(
        self,
        event_type: TraceEventType,
        name: str,
        input_data: Optional[Dict[str, Any]] = None,
        output_data: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
        error: Optional[str] = None,
        duration_ms: Optional[float] = None
    ) -> str:
        """
        Log a trace event.
        
        Args:
            event_type: Type of event
            name: Event name
            input_data: Input data
            output_data: Output data
            metadata: Additional metadata
            error: Error message if applicable
            duration_ms: Event duration
            
        Returns:
            Event ID
        """
        if not self.current_trace_id:
            return ""
        
        event_id = str(uuid.uuid4())
        parent_id = self.event_stack[-1] if self.event_stack else None
        
        event = TraceEvent(
            event_id=event_id,
            trace_id=self.current_trace_id,
            parent_id=parent_id,
            event_type=event_type,
            timestamp=datetime.now(),
            name=name,
            input_data=input_data or {},
            output_data=output_data or {},
            metadata=metadata or {},
            error=error,
            duration_ms=duration_ms
        )
        
        trace = self.traces[self.current_trace_id]
        trace.events.append(event)
        
        return event_id
    
    def start_span(self, name: str, event_type: TraceEventType) -> str:
        """
        Start a span (nested event).
        
        Args:
            name: Span name
            event_type: Event type
            
        Returns:
            Event ID
        """
        event_id = self.log_event(
            event_type=event_type,
            name=name,
            metadata={'span_start': True}
        )
        self.event_stack.append(event_id)
        return event_id
    
    def end_span(self, event_id: str, output_data: Optional[Dict[str, Any]] = None):
        """
        End a span.
        
        Args:
            event_id: Event ID to end
            output_data: Output data
        """
        if self.event_stack and self.event_stack[-1] == event_id:
            self.event_stack.pop()
            
            # Update event with output and duration
            if self.current_trace_id:
                trace = self.traces[self.current_trace_id]
                for event in trace.events:
                    if event.event_id == event_id:
                        if output_data:
                            event.output_data = output_data
                        # Calculate duration from event timestamp to now
                        event.duration_ms = (datetime.now() - event.timestamp).total_seconds() * 1000
                        break
    
    def get_trace(self, trace_id: str) -> Optional[Trace]:
        """Get trace by ID"""
        return self.traces.get(trace_id)
    
    def get_all_traces(self) -> List[Trace]:
        """Get all traces"""
        return list(self.traces.values())
    
    def clear_traces(self):
        """Clear all traces"""
        self.traces.clear()
        self.current_trace_id = None
        self.event_stack.clear()


class TraceAnalyzer:
    """
    Analyzes traces to extract insights and metrics.
    
    Provides visualization, statistics, and pattern detection.
    """
    
    def __init__(self):
        """Initialize analyzer"""
        pass
    
    def get_execution_path(self, trace: Trace) -> List[str]:
        """
        Get execution path as list of event names.
        
        Args:
            trace: Trace to analyze
            
        Returns:
            List of event names in order
        """
        return [event.name for event in trace.events]
    
    def get_performance_stats(self, trace: Trace) -> Dict[str, Any]:
        """
        Calculate performance statistics.
        
        Args:
            trace: Trace to analyze
            
        Returns:
            Performance statistics
        """
        events_with_duration = [e for e in trace.events if e.duration_ms]
        
        if not events_with_duration:
            return {
                'total_duration_ms': trace.duration_ms,
                'event_count': len(trace.events),
                'avg_event_duration_ms': 0,
                'max_event_duration_ms': 0,
                'min_event_duration_ms': 0
            }
        
        durations = [e.duration_ms for e in events_with_duration]
        
        return {
            'total_duration_ms': trace.duration_ms,
            'event_count': len(trace.events),
            'events_with_timing': len(events_with_duration),
            'avg_event_duration_ms': sum(durations) / len(durations),
            'max_event_duration_ms': max(durations),
            'min_event_duration_ms': min(durations),
            'total_event_time_ms': sum(durations)
        }
    
    def get_event_type_stats(self, trace: Trace) -> Dict[str, int]:
        """
        Count events by type.
        
        Args:
            trace: Trace to analyze
            
        Returns:
            Event counts by type
        """
        stats = {}
        for event in trace.events:
            event_type = event.event_type.value
            stats[event_type] = stats.get(event_type, 0) + 1
        return stats
    
    def get_error_summary(self, trace: Trace) -> List[Dict[str, Any]]:
        """
        Get summary of errors in trace.
        
        Args:
            trace: Trace to analyze
            
        Returns:
            List of error details
        """
        errors = []
        for event in trace.events:
            if event.error:
                errors.append({
                    'event_id': event.event_id,
                    'event_type': event.event_type.value,
                    'name': event.name,
                    'error': event.error,
                    'timestamp': event.timestamp.isoformat()
                })
        return errors
    
    def visualize_trace(self, trace: Trace) -> str:
        """
        Create text-based visualization of trace.
        
        Args:
            trace: Trace to visualize
            
        Returns:
            Formatted string visualization
        """
        lines = []
        lines.append(f"Trace: {trace.trace_id}")
        lines.append(f"Status: {trace.status}")
        lines.append(f"Duration: {trace.duration_ms:.2f}ms")
        lines.append(f"Events: {len(trace.events)}")
        lines.append("")
        lines.append("Execution Path:")
        
        # Build hierarchy
        event_map = {e.event_id: e for e in trace.events}
        root_events = [e for e in trace.events if e.parent_id is None]
        
        def format_event(event: TraceEvent, indent: int = 0) -> List[str]:
            """Format event with indentation"""
            result = []
            prefix = "  " * indent
            duration = f"({event.duration_ms:.1f}ms)" if event.duration_ms else ""
            error_mark = " ❌" if event.error else ""
            
            result.append(f"{prefix}├─ {event.event_type.value}: {event.name} {duration}{error_mark}")
            
            # Find children
            children = [e for e in trace.events if e.parent_id == event.event_id]
            for child in children:
                result.extend(format_event(child, indent + 1))
            
            return result
        
        for root in root_events:
            lines.extend(format_event(root))
        
        return "\n".join(lines)


class TracedAgent:
    """
    Example agent with comprehensive tracing.
    
    Demonstrates trace collection for all agent operations.
    """
    
    def __init__(self, collector: TraceCollector):
        """
        Initialize traced agent.
        
        Args:
            collector: Trace collector instance
        """
        self.collector = collector
        self.llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
    
    def process_query(self, query: str, metadata: Optional[Dict[str, Any]] = None) -> str:
        """
        Process query with full tracing.
        
        Args:
            query: User query
            metadata: Optional metadata
            
        Returns:
            Response
        """
        # Start trace
        trace_id = self.collector.start_trace(metadata={'query': query, **(metadata or {})})
        
        try:
            # Log agent start
            agent_span = self.collector.start_span("process_query", TraceEventType.AGENT_START)
            
            # Step 1: Analyze query
            analysis_span = self.collector.start_span("analyze_query", TraceEventType.DECISION)
            query_type = self._analyze_query(query)
            self.collector.end_span(analysis_span, {'query_type': query_type})
            
            # Step 2: Generate response
            llm_span = self.collector.start_span("llm_call", TraceEventType.LLM_START)
            response = self._call_llm(query)
            self.collector.end_span(llm_span, {'response': response[:100]})
            
            # End agent span
            self.collector.end_span(agent_span, {'response': response})
            
            # End trace successfully
            self.collector.end_trace(trace_id, "completed")
            
            return response
            
        except Exception as e:
            # Log error
            self.collector.log_event(
                event_type=TraceEventType.ERROR,
                name="agent_error",
                error=str(e),
                metadata={'exception_type': type(e).__name__}
            )
            
            # End trace with failure
            self.collector.end_trace(trace_id, "failed")
            
            raise
    
    def _analyze_query(self, query: str) -> str:
        """Analyze query type"""
        time.sleep(0.05)  # Simulate processing
        if '?' in query:
            return "question"
        else:
            return "statement"
    
    def _call_llm(self, query: str) -> str:
        """Call LLM"""
        prompt = ChatPromptTemplate.from_template("Answer concisely: {query}")
        chain = prompt | self.llm | StrOutputParser()
        return chain.invoke({"query": query})


def demonstrate_trace_tracking():
    """Demonstrate trace and lineage tracking"""
    print("=" * 80)
    print("TRACE/LINEAGE TRACKING DEMONSTRATION")
    print("=" * 80)
    
    # Example 1: Basic Trace Collection
    print("\n" + "=" * 80)
    print("EXAMPLE 1: Basic Trace Collection")
    print("=" * 80)
    
    collector = TraceCollector()
    
    # Start a trace
    trace_id = collector.start_trace(metadata={'user': 'demo_user', 'session': 'session_001'})
    print(f"\nStarted trace: {trace_id[:8]}...")
    
    # Log some events
    collector.log_event(
        event_type=TraceEventType.AGENT_START,
        name="agent_init",
        metadata={'agent_type': 'demo'}
    )
    
    collector.log_event(
        event_type=TraceEventType.LLM_START,
        name="llm_call_1",
        input_data={'prompt': 'What is AI?'},
        duration_ms=1250.5
    )
    
    collector.log_event(
        event_type=TraceEventType.LLM_END,
        name="llm_call_1",
        output_data={'response': 'AI is...'},
        duration_ms=1250.5
    )
    
    collector.end_trace(trace_id, "completed")
    
    trace = collector.get_trace(trace_id)
    print(f"\nTrace completed:")
    print(f"  Events: {len(trace.events)}")
    print(f"  Duration: {trace.duration_ms:.2f}ms")
    print(f"  Status: {trace.status}")
    
    # Example 2: Hierarchical Tracing
    print("\n" + "=" * 80)
    print("EXAMPLE 2: Hierarchical Tracing with Spans")
    print("=" * 80)
    
    collector.clear_traces()
    trace_id = collector.start_trace()
    
    # Parent span
    parent_span = collector.start_span("main_task", TraceEventType.AGENT_START)
    time.sleep(0.1)
    
    # Child span 1
    child_span1 = collector.start_span("subtask_1", TraceEventType.TOOL_START)
    time.sleep(0.05)
    collector.end_span(child_span1, {'result': 'success'})
    
    # Child span 2
    child_span2 = collector.start_span("subtask_2", TraceEventType.TOOL_START)
    time.sleep(0.03)
    collector.end_span(child_span2, {'result': 'success'})
    
    collector.end_span(parent_span, {'status': 'completed'})
    collector.end_trace(trace_id, "completed")
    
    trace = collector.get_trace(trace_id)
    print(f"\nHierarchical trace with {len(trace.events)} events:")
    for event in trace.events:
        indent = "  " if event.parent_id else ""
        duration = f" ({event.duration_ms:.1f}ms)" if event.duration_ms else ""
        print(f"{indent}{event.event_type.value}: {event.name}{duration}")
    
    # Example 3: Traced Agent Execution
    print("\n" + "=" * 80)
    print("EXAMPLE 3: Full Agent Execution with Tracing")
    print("=" * 80)
    
    collector.clear_traces()
    agent = TracedAgent(collector)
    
    print("\nExecuting query with tracing...")
    query = "What is machine learning?"
    response = agent.process_query(query, metadata={'priority': 'high'})
    
    print(f"\nResponse: {response[:100]}...")
    
    # Get the trace
    traces = collector.get_all_traces()
    if traces:
        trace = traces[0]
        print(f"\nTrace captured:")
        print(f"  Trace ID: {trace.trace_id[:8]}...")
        print(f"  Events: {len(trace.events)}")
        print(f"  Duration: {trace.duration_ms:.2f}ms")
        print(f"  Status: {trace.status}")
    
    # Example 4: Trace Analysis
    print("\n" + "=" * 80)
    print("EXAMPLE 4: Trace Analysis and Insights")
    print("=" * 80)
    
    analyzer = TraceAnalyzer()
    
    if traces:
        trace = traces[0]
        
        # Performance stats
        print("\nPerformance Statistics:")
        stats = analyzer.get_performance_stats(trace)
        for key, value in stats.items():
            if isinstance(value, float):
                print(f"  {key}: {value:.2f}")
            else:
                print(f"  {key}: {value}")
        
        # Event type stats
        print("\nEvent Type Distribution:")
        type_stats = analyzer.get_event_type_stats(trace)
        for event_type, count in sorted(type_stats.items()):
            print(f"  {event_type}: {count}")
        
        # Execution path
        print("\nExecution Path:")
        path = analyzer.get_execution_path(trace)
        for i, step in enumerate(path, 1):
            print(f"  {i}. {step}")
    
    # Example 5: Trace Visualization
    print("\n" + "=" * 80)
    print("EXAMPLE 5: Trace Visualization")
    print("=" * 80)
    
    if traces:
        visualization = analyzer.visualize_trace(traces[0])
        print("\n" + visualization)
    
    # Example 6: Error Tracing
    print("\n" + "=" * 80)
    print("EXAMPLE 6: Error Tracing and Debugging")
    print("=" * 80)
    
    collector.clear_traces()
    trace_id = collector.start_trace(metadata={'test': 'error_handling'})
    
    # Log successful operation
    collector.log_event(
        event_type=TraceEventType.AGENT_START,
        name="operation_start"
    )
    
    # Log error
    collector.log_event(
        event_type=TraceEventType.ERROR,
        name="operation_failed",
        error="Simulated error: Connection timeout",
        metadata={'error_code': 'TIMEOUT', 'retry_count': 3}
    )
    
    collector.end_trace(trace_id, "failed")
    
    trace = collector.get_trace(trace_id)
    errors = analyzer.get_error_summary(trace)
    
    print(f"\nTrace with errors:")
    print(f"  Status: {trace.status}")
    print(f"  Error count: {len(errors)}")
    
    if errors:
        print("\nError Details:")
        for error in errors:
            print(f"  Event: {error['name']}")
            print(f"  Type: {error['event_type']}")
            print(f"  Error: {error['error']}")
            print(f"  Time: {error['timestamp']}")
    
    # Example 7: Trace Export and Storage
    print("\n" + "=" * 80)
    print("EXAMPLE 7: Trace Export for Storage/Analysis")
    print("=" * 80)
    
    if traces:
        trace = traces[0]
        trace_dict = trace.to_dict()
        
        print("\nTrace exported to dictionary:")
        print(f"  Trace ID: {trace_dict['trace_id'][:8]}...")
        print(f"  Duration: {trace_dict['duration_ms']:.2f}ms")
        print(f"  Event count: {trace_dict['event_count']}")
        print(f"  Status: {trace_dict['status']}")
        
        # Simulate JSON export
        trace_json = json.dumps(trace_dict, indent=2, default=str)
        print(f"\nJSON size: {len(trace_json)} bytes")
        print(f"First 200 chars:\n{trace_json[:200]}...")
    
    # Summary
    print("\n" + "=" * 80)
    print("TRACE/LINEAGE TRACKING SUMMARY")
    print("=" * 80)
    print("""
Trace Tracking Benefits:
1. Complete Visibility: See every step of agent execution
2. Debugging Power: Reproduce and understand issues
3. Performance Insights: Identify bottlenecks
4. Compliance: Document decision-making
5. Reproducibility: Replay executions exactly
6. Optimization: Data-driven improvements

Key Components:
1. Trace Events
   - Agent lifecycle (start, end)
   - LLM calls (input, output, tokens, cost)
   - Tool invocations (name, args, results)
   - Decision points (conditions, branches)
   - State changes (before, after)
   - Errors (type, message, context)

2. Event Hierarchy
   - Parent-child relationships
   - Nested spans for complex operations
   - Call stack preservation
   - Context propagation
   - Timing at each level

3. Trace Metadata
   - User/session information
   - Request context
   - Environment details
   - Custom attributes
   - Tags for filtering

4. Analysis Capabilities
   - Performance profiling
   - Path visualization
   - Error pattern detection
   - Statistical analysis
   - Comparison tools

What to Trace:
1. Essential (Always)
   - All LLM calls
   - Tool invocations
   - Agent start/end
   - Errors and exceptions
   - Final results

2. Important (Most cases)
   - Decision points
   - State changes
   - Performance metrics
   - Resource usage
   - Intermediate results

3. Detailed (Development/Debug)
   - Full context
   - Internal calculations
   - Validation steps
   - Cache hits/misses
   - Detailed timing

Trace Structure:
```
Trace
├── trace_id (unique identifier)
├── start_time
├── end_time
├── status (running, completed, failed)
├── metadata (user, session, etc.)
└── events []
    ├── Event 1
    │   ├── event_id
    │   ├── parent_id
    │   ├── event_type
    │   ├── timestamp
    │   ├── duration_ms
    │   ├── input_data
    │   ├── output_data
    │   └── metadata
    └── Event 2
        └── ...
```

Best Practices:
1. Trace Organization
   - Unique trace IDs (UUID v4)
   - Clear event names
   - Consistent structure
   - Rich metadata
   - Logical grouping

2. Performance
   - Async trace writes
   - Batch uploads
   - Sampling in production
   - Compression for storage
   - Resource limits

3. Privacy
   - PII redaction
   - Sensitive data filtering
   - Access controls
   - Encryption at rest/transit
   - Retention policies

4. Storage
   - Hot: 7-30 days (fast access)
   - Warm: 31-90 days (slower)
   - Cold: 91-365 days (archive)
   - Automatic cleanup
   - Efficient formats

Production Deployment:
1. Sampling Strategy
   - Development: 100%
   - Staging: 20-50%
   - Production: 1-10% baseline
   - Errors: Always 100%
   - Important users: Higher rate

2. Performance Impact
   - Target: < 5% overhead
   - Measure actual impact
   - Optimize hot paths
   - Circuit breaker
   - Degradation handling

3. Integration
   - LangSmith for LangChain
   - OpenTelemetry standard
   - Custom backends
   - Log aggregation systems
   - APM tools integration

4. Analysis Workflow
   - Real-time monitoring
   - Periodic analysis
   - Alert on patterns
   - Dashboard visualization
   - Export for deep analysis

When to Use Tracing:
✓ All production AI systems
✓ Complex multi-step agents
✓ Debugging difficult issues
✓ Performance optimization
✓ Compliance requirements
✓ Quality assurance
✗ Simple single-shot calls (maybe)
✗ Privacy-critical scenarios (with redaction)

Common Use Cases:
1. Debugging
   - Why did agent fail?
   - What was the decision path?
   - What inputs caused issue?
   - How to reproduce?

2. Optimization
   - Which step is slowest?
   - Where are bottlenecks?
   - Cost breakdown by component
   - Cache effectiveness

3. Monitoring
   - Error rate trends
   - Performance degradation
   - Usage patterns
   - Resource consumption

4. Compliance
   - Decision documentation
   - Audit trails
   - Regulatory requirements
   - Accountability

Tools & Services:
- LangSmith: Native LangChain tracing
- OpenTelemetry: Standard observability
- Jaeger: Distributed tracing
- DataDog: APM and tracing
- Custom: Roll your own

ROI Analysis:
- Debug time: 50-90% reduction
- Issue resolution: 2-10x faster
- Performance gains: 20-50%
- Compliance: Critical for regulated industries
- Cost: Low overhead, high value
""")
    
    print("\n" + "=" * 80)
    print("Pattern 094 (Trace/Lineage Tracking) demonstration complete!")
    print("=" * 80)


if __name__ == "__main__":
    demonstrate_trace_tracking()

    

Agentic Design Pattern: Trace Tracking

Pattern 094: Trace Tracking – Line-by-Line Explanation

Overview

This document provides a detailed line-by-line explanation of the Trace Tracking pattern implementation.


Pattern Description

Pattern 094: Trace/Lineage Tracking

Description: Trace and Lineage Tracking captures the complete execution path of an agent, including all decisions, actions, intermediate states, and data transformations. This pattern is essential for debugging complex agent behaviors, understanding how decisions were made, ensuring compliance, and optimizing performance. Comprehensive tracing enables reproducibility, auditing, and continuous improvement of AI systems.

Trace tracking provides:
- Complete execution history
- Decision point visibility
- Data lineage and provenance
- Performance metrics per step
- Error context and stack traces
- Reproducibility information

Components: 1. Trace Capture – Execution events (start, end, error) – Decision points and branches – Input/output at each step – Timestamps and durations – Resource consumption – Agent state snapshots

2. Trace Storage
   - Structured trace format
   - Hierarchical relationships
   - Searchable metadata
   - Efficient retrieval
   - Long-term retention

3. Trace Analysis
   - Path visualization
   - Performance analysis
   - Bottleneck identification
   - Error pattern detection
   - Comparison tools

4. Trace Replay
   - Reproduce executions
   - Debug specific paths
   - Test alternative branches
   - Validate fixes
   - Training data generation

Use Cases: 1. Debugging – Understand unexpected behavior – Reproduce production issues – Identify error causes – Validate fixes – Root cause analysis

2. Performance Optimization
   - Identify bottlenecks
   - Measure step durations
   - Analyze resource usage
   - Compare implementations
   - Optimize hot paths

3. Compliance & Auditing
   - Document decision making
   - Prove regulatory compliance
   - Audit trails for sensitive operations
   - Accountability tracking
   - Legal requirements

4. Quality Assurance
   - Validate agent behavior
   - Ensure determinism
   - Track test coverage
   - Regression detection
   - Change impact analysis

5. Agent Development
   - Understand agent flow
   - Optimize prompts
   - Refine decision logic
   - Test edge cases
   - Documentation generation

LangChain Implementation: LangChain supports tracing through: – LangSmith for comprehensive tracing – Custom callback handlers – OpenTelemetry integration – Structured logging – Trace context propagation

Key Features: 1. Comprehensive Capture – Every agent step traced – Full context preservation – Hierarchical relationships – Performance metrics – Error details

2. Structured Format
   - Consistent trace structure
   - Searchable attributes
   - Machine-readable format
   - Human-readable display
   - Standard schemas

3. Powerful Analysis
   - Path visualization
   - Query capabilities
   - Statistical analysis
   - Comparison tools
   - Pattern detection

4. Production Ready
   - Low overhead (< 5% impact)
   - Efficient storage
   - Privacy controls
   - Sampling strategies
   - Scalable architecture

Best Practices: 1. What to Trace – All LLM calls (input, output, model, tokens) – Tool invocations (name, args, results) – Decision points (conditions, chosen branch) – State changes (before, after) – Errors (type, message, stack) – Performance (start, end, duration)

2. Trace Organization
   - Clear trace IDs (unique, sortable)
   - Parent-child relationships
   - Logical grouping (by session, user, task)
   - Consistent naming
   - Rich metadata

3. Performance Considerations
   - Sampling in production (1-10%)
   - Async trace writes
   - Compression for storage
   - Automatic rotation
   - Resource limits

4. Privacy & Security
   - PII redaction
   - Access controls
   - Encryption at rest
   - Retention policies
   - Compliance adherence

Trade-offs: Advantages: – Complete execution visibility – Powerful debugging capabilities – Compliance documentation – Performance insights – Reproducible executions – Historical analysis

Disadvantages:
- Storage overhead
- Performance impact (small)
- Complexity in setup
- Privacy concerns
- Analysis tools needed
- Maintenance overhead

Production Considerations: 1. Sampling Strategy – 100% for development – 10-20% for staging – 1-5% for production (baseline) – 100% for errors – Adaptive based on load

2. Storage Management
   - Hot storage: 7-30 days
   - Cold storage: 90-365 days
   - Archive: > 1 year
   - Compression for old traces
   - Automatic cleanup

3. Performance Impact
   - Target: < 5% overhead
   - Async writes preferred
   - Batch uploads
   - Local buffering
   - Circuit breaker

4. Privacy Compliance
   - Automatic PII detection
   - Redaction policies
   - Consent tracking
   - Data retention limits
   - Right to deletion

5. Analysis Tools
   - Web UI for browsing
   - Query interface
   - Visualization tools
   - Alert integration
   - Export capabilities

File Statistics

  • Total Lines: 1067
  • Classes: 6
  • Functions: 1
  • Constants: 0

Module Documentation

"""
Pattern 094: Trace/Lineage Tracking

Description:
    Trace and Lineage Tracking captures the complete execution path of an agent, including
    all decisions, actions, intermediate states, and data transformations. This pattern is
    essential for debugging complex agent behaviors, understanding how decisions were made,
    ensuring compliance, and optimizing performance. Comprehensive tracing enables
    reproducibility, auditing, and continuous improvement of AI systems.

    Trace tracking provides:
    - Complete execution history
    - Decision point visibility
    - Data lineage and provenance
    - Performance metrics per step
    - Error context and stack traces
    - Reproducibility information

Components:
    1. Trace Capture
       - Execution events (start, end, error)
       - Decision points and branches
       - Input/output at each step
       - Timestamps and durations
       - Resource consumption
       - Agent state snapshots

    2. Trace Storage
       - Structured trace format
       - Hierarchical relationships
       - Searchable metadata
       - Efficient retrieval
       - Long-term retention

    3. Trace Analysis
       - Path visualization
       - Performance analysis
       - Bottleneck identification
       - Error pattern detection
       - Comparison tools

    4. Trace Replay
       - Reproduce executions
       - Debug specific paths
       - Test alternative branches
       - Validate fixes
       - Training data generation

Use Cases:
    1. Debugging
       - Understand unexpected behavior
       - Reproduce production issues
       - Identify error causes
       - Validate fixes
       - Root cause analysis

    2. Performance Optimization
       - Identify bottlenecks
       - Measure step durations
       - Analyze resource usage
       - Compare implementations
       - Optimize hot paths

    3. Compliance & Auditing
       - Document decision making
       - Prove regulatory compliance
       - Audit trails for sensitive operations
       - Accountability tracking
       - Legal requirements

    4. Quality Assurance
       - Validate agent behavior
       - Ensure determinism
       - Track test coverage
       - Regression detection
       - Change impact analysis

    5. Agent Development
       - Understand agent flow
       - Optimize prompts
       - Refine decision logic
       - Test edge cases
       - Documentation generation

LangChain Implementation:
    LangChain supports tracing through:
    - LangSmith for comprehensive tracing
    - Custom callback handlers
    - OpenTelemetry integration
    - Structured logging
    - Trace context propagation

Key Features:
    1. Comprehensive Capture
       - Every agent step traced
       - Full context preservation
       - Hierarchical relationships
       - Performance metrics
       - Error details

    2. Structured Format
       - Consistent trace structure
       - Searchable attributes
       - Machine-readable format
       - Human-readable display
       - Standard schemas

    3. Powerful Analysis
       - Path visualization
       - Query capabilities
       - Statistical analysis
       - Comparison tools
       - Pattern detection

    4. Production Ready
       - Low overhead (< 5% impact)
       - Efficient storage
       - Privacy controls
       - Sampling strategies
       - Scalable architecture

Best Practices:
    1. What to Trace
       - All LLM calls (input, output, model, tokens)
       - Tool invocations (name, args, results)
       - Decision points (conditions, chosen branch)
       - State changes (before, after)
       - Errors (type, message, stack)
       - Performance (start, end, duration)

    2. Trace Organization
       - Clear trace IDs (unique, sortable)
       - Parent-child relationships
       - Logical grouping (by session, user, task)
       - Consistent naming
       - Rich metadata

    3. Performance Considerations
       - Sampling in production (1-10%)
       - Async trace writes
       - Compression for storage
       - Automatic rotation
       - Resource limits

    4. Privacy & Security
       - PII redaction
       - Access controls
       - Encryption at rest
       - Retention policies
       - Compliance adherence

Trade-offs:
    Advantages:
    - Complete execution visibility
    - Powerful debugging capabilities
    - Compliance documentation
    - Performance insights
    - Reproducible executions
    - Historical analysis

    Disadvantages:
    - Storage overhead
    - Performance impact (small)
    - Complexity in setup
    - Privacy concerns
    - Analysis tools needed
    - Maintenance overhead

Production Considerations:
    1. Sampling Strategy
       - 100% for development
       - 10-20% for staging
       - 1-5% for production (baseline)
       - 100% for errors
       - Adaptive based on load

    2. Storage Management
       - Hot storage: 7-30 days
       - Cold storage: 90-365 days
       - Archive: > 1 year
       - Compression for old traces
       - Automatic cleanup

    3. Performance Impact
       - Target: < 5% overhead
       - Async writes preferred
       - Batch uploads
       - Local buffering
       - Circuit breaker

    4. Privacy Compliance
       - Automatic PII detection
       - Redaction policies
       - Consent tracking
       - Data retention limits
       - Right to deletion

    5. Analysis Tools
       - Web UI for browsing
       - Query interface
       - Visualization tools
       - Alert integration
       - Export capabilities
"""

Purpose: This module implements the Trace Tracking pattern, providing a structured approach to agent design and implementation.


Import Statements

Standard Library Imports

import dataclasses.asdict
import dataclasses.dataclass
import dataclasses.field
import datetime.datetime
import enum.Enum
import json
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import os
import time
import typing.Any
import typing.Dict
import typing.List
import typing.Optional

Purpose: Core Python functionality: – dataclasses.asdict: Simplified class definitions with @dataclass – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – json: JSON serialization/deserialization – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – time: Time-related functions – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)

LangChain Imports

from langchain_openai.ChatOpenAI

Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows

Other Imports

from dotenv.load_dotenv
from uuid

Purpose: Additional dependencies for specialized functionality.


Classes

Class: TraceEventType

Lines 223-233

Inherits from: Enum

Purpose:

Types of trace events

Class: TraceEvent

Lines 237-258

Purpose:

Represents a single event in a trace

Methods:

to_dict(self)

Lines 253-258

Purpose:

Convert to dictionary

Parameters:

Returns: <ast.Subscript object at 0x000001A06D624550>


Class: Trace

Lines 262-289

Purpose:

Complete trace of an agent execution

Methods:

duration_ms(self)

Lines 272-276

Decorators: property

Purpose:

Calculate total duration

Parameters:

Returns: float

to_dict(self)

Lines 278-289

Purpose:

Convert to dictionary

Parameters:

Returns: <ast.Subscript object at 0x000001A06D645990>


Class: TraceCollector

Lines 292-441

Purpose:

Collects and manages traces for agent executions.

Captures events, maintains relationships, and provides query capabilities.

Methods:

__init__(self)

Lines 299-303

Purpose:

Initialize trace collector

Parameters:

start_trace(self, metadata)

Lines 305-323

Purpose:

Start a new trace.

Args:
    metadata: Optional trace metadata

Returns:
    Trace ID

Parameters: – metadata: Metadata

Returns: str

end_trace(self, trace_id, status)

Lines 325-336

Purpose:

End a trace.

Args:
    trace_id: Trace identifier
    status: Final status (completed, failed)

Parameters: – trace_id: Trace id – status: Status

log_event(self, event_type, name, input_data, output_data, metadata, error, duration_ms)

Lines 338-386

Purpose:

Log a trace event.

Args:
    event_type: Type of event
    name: Event name
    input_data: Input data
    output_data: Output data
    metadata: Additional metadata
    error: Error message if applicable
    duration_ms: Event duration

Returns:
    Event ID

Parameters: – event_type: Event type – name: Name – input_data: Input data – output_data: Output data – metadata: Metadata – error: Error – duration_ms: Duration ms

Returns: str

start_span(self, name, event_type)

Lines 388-405

Purpose:

Start a span (nested event).

Args:
    name: Span name
    event_type: Event type

Returns:
    Event ID

Parameters: – name: Name – event_type: Event type

Returns: str

end_span(self, event_id, output_data)

Lines 407-427

Purpose:

End a span.

Args:
    event_id: Event ID to end
    output_data: Output data

Parameters: – event_id: Event id – output_data: Output data

get_trace(self, trace_id)

Lines 429-431

Purpose:

Get trace by ID

Parameters: – trace_id: Trace id

Returns: <ast.Subscript object at 0x000001A06D7C9750>

get_all_traces(self)

Lines 433-435

Purpose:

Get all traces

Parameters:

Returns: <ast.Subscript object at 0x000001A06D7C85D0>

clear_traces(self)

Lines 437-441

Purpose:

Clear all traces

Parameters:


Class: TraceAnalyzer

Lines 444-579

Purpose:

Analyzes traces to extract insights and metrics.

Provides visualization, statistics, and pattern detection.

Methods:

__init__(self)

Lines 451-453

Purpose:

Initialize analyzer

Parameters:

get_execution_path(self, trace)

Lines 455-465

Purpose:

Get execution path as list of event names.

Args:
    trace: Trace to analyze

Returns:
    List of event names in order

Parameters: – trace: Trace

Returns: <ast.Subscript object at 0x000001A06D660DD0>

get_performance_stats(self, trace)

Lines 467-498

Purpose:

Calculate performance statistics.

Args:
    trace: Trace to analyze

Returns:
    Performance statistics

Parameters: – trace: Trace

Returns: <ast.Subscript object at 0x000001A06D888850>

get_event_type_stats(self, trace)

Lines 500-514

Purpose:

Count events by type.

Args:
    trace: Trace to analyze

Returns:
    Event counts by type

Parameters: – trace: Trace

Returns: <ast.Subscript object at 0x000001A06D88B6D0>

get_error_summary(self, trace)

Lines 516-536

Purpose:

Get summary of errors in trace.

Args:
    trace: Trace to analyze

Returns:
    List of error details

Parameters: – trace: Trace

Returns: <ast.Subscript object at 0x000001A06D75B110>

visualize_trace(self, trace)

Lines 538-579

Purpose:

Create text-based visualization of trace.

Args:
    trace: Trace to visualize

Returns:
    Formatted string visualization

Parameters: – trace: Trace

Returns: str


Class: TracedAgent

Lines 582-661

Purpose:

Example agent with comprehensive tracing.

Demonstrates trace collection for all agent operations.

Methods:

__init__(self, collector)

Lines 589-597

Purpose:

Initialize traced agent.

Args:
    collector: Trace collector instance

Parameters: – collector: Collector

process_query(self, query, metadata)

Lines 599-647

Purpose:

Process query with full tracing.

Args:
    query: User query
    metadata: Optional metadata

Returns:
    Response

Parameters: – query: Input query or question – metadata: Metadata

Returns: str

_analyze_query(self, query)

Lines 649-655

Purpose:

Analyze query type

Parameters: – query: Input query or question

Returns: str

_call_llm(self, query)

Lines 657-661

Purpose:

Call LLM

Parameters: – query: Input query or question

Returns: str


Functions

demonstrate_trace_tracking()

Lines 664-1062

Purpose:

Demonstrate trace and lineage tracking

Key Concepts Summary

Pattern Overview

The Trace Tracking pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.

Core Components

  1. Agent Class: Main implementation of the pattern logic
  2. Prompt Engineering: Carefully crafted prompts to guide LLM behavior
  3. Tool Integration: Optional tools for extended functionality
  4. Memory Management: State handling across interactions
  5. Output Parsing: Structured response processing

When to Use

✅ Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability

❌ Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)


Best Practices Demonstrated

1. Environment Configuration

  • ✅ Use .env files for API keys
  • ✅ Never hardcode credentials
  • ✅ Validate environment variables on startup

2. Error Handling

  • ✅ Graceful degradation on failures
  • ✅ Informative error messages
  • ✅ Logging for debugging

3. Prompt Engineering

  • ✅ Clear, specific instructions
  • ✅ Structured output formats
  • ✅ Few-shot examples when beneficial

4. Code Organization

  • ✅ Separation of concerns (class-based design)
  • ✅ Type hints for clarity
  • ✅ Docstrings for documentation

5. Performance Optimization

  • ✅ Appropriate temperature settings
  • ✅ Token budget management
  • ✅ Caching where applicable

LangChain Components Used

  • ChatOpenAI: OpenAI model interface
  • Chains: Sequential component composition

Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development


Execution Flow

1. Initialize agent with configuration
   ↓
2. Load environment and dependencies
   ↓
3. Prepare prompt with input data
   ↓
4. Invoke LLM through LangChain
   ↓
5. Parse and validate response
   ↓
6. Return formatted result

Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation


Performance Considerations

Computational Cost

  • LLM Calls: Primary cost factor (based on tokens)
  • Latency: Depends on model and input size
  • Memory: Minimal for most patterns

Optimization Strategies

  1. Prompt Optimization: Minimize tokens while maintaining clarity
  2. Caching: Cache repeated queries/responses
  3. Model Selection: Use smaller models when appropriate
  4. Batch Processing: Group similar requests
  5. Streaming: For long responses, stream tokens

Cost Estimates (Approximate)

  • GPT-3.5-turbo: ~$0.002 per request
  • GPT-4: ~$0.03 per request
  • GPT-4-turbo: ~$0.01 per request

Actual costs vary based on input/output token counts


Common Pitfalls to Avoid

1. Prompt Design Issues

❌ Vague or ambiguous instructions
✅ Clear, specific, testable prompts

2. Error Handling

❌ Ignoring API errors or rate limits
✅ Implement retry logic and graceful degradation

3. Token Management

❌ Exceeding model token limits
✅ Truncate inputs, chunk processing

4. Security

❌ Exposing API keys in code
✅ Use environment variables

5. Testing

❌ Only testing happy paths
✅ Test edge cases, failures, malformed inputs

6. Cost Management

❌ Unmonitored API usage
✅ Implement budgets, alerts, and monitoring


Further Improvements

Production Enhancements

  1. Monitoring: Add logging, metrics, tracing
  2. Validation: Input/output schema validation
  3. Rate Limiting: Implement request throttling
  4. Caching: Redis or in-memory cache for responses
  5. Async Support: Non-blocking operations for scalability

Feature Extensions

  1. Multi-Model Support: Switch between providers
  2. Fallback Models: Graceful degradation strategy
  3. Custom Tools: Domain-specific function calling
  4. Advanced Memory: Vector stores for long-term memory
  5. Evaluation: Automated testing and benchmarking

Code Quality

  1. Testing: Unit tests, integration tests
  2. Documentation: API docs, usage examples
  3. Type Safety: Stricter type hints
  4. Configuration: Externalized config files

Related Patterns

  • Pattern 093
  • Pattern 095

Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…


Summary

The Trace Tracking pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:

✅ Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns

🎯 Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability

📚 Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/


File: 094_trace_tracking.py
Total Lines: 1067
Pattern Category: AI Agent Design Pattern #094


Generated from actual implementation code
For educational and reference purposes

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *