Category: Uncategorized

  • Agentic Design Pattern: Self Play

    
        """
    Pattern 069: Self-Play & Self-Improvement
    
    Description:
        Self-Play & Self-Improvement enables agents to autonomously improve their capabilities
        by competing or collaborating with themselves. Inspired by techniques like AlphaGo's
        self-play training, this pattern allows agents to generate their own training data,
        discover new strategies, and continuously refine their performance without external
        supervision. The agent plays both sides of a game, problem, or scenario, learning
        from each iteration to progressively improve.
        
        This pattern is particularly powerful for domains where supervised training data is
        scarce, expensive, or impossible to obtain. Through self-play, agents can explore
        vast strategy spaces, discover novel solutions, and achieve superhuman performance
        by learning from billions of self-generated experiences.
    
    Components:
        1. Strategy Generator: Creates candidate strategies/policies
        2. Self-Play Engine: Simulates interactions with itself
        3. Performance Evaluator: Measures strategy effectiveness
        4. Curriculum Generator: Creates progressively harder challenges
        5. Strategy Selector: Chooses best strategies to keep
        6. Meta-Learner: Learns how to improve the learning process
        7. Checkpoint Manager: Stores and manages strategy versions
    
    Architecture:
        ```
        Initial Strategy
            ↓
        ┌─────────────────────────┐
        │   Self-Play Loop        │
        │                         │
        │  Strategy A vs Strategy B│
        │         ↓               │
        │    Play Game/Task       │
        │         ↓               │
        │   Evaluate Performance  │
        │         ↓               │
        │   Update Strategies     │
        │         ↓               │
        │  Generate Curriculum    │
        │         ↓               │
        │   Select Best           │
        └─────────────────────────┘
             ↓ (iterate)
        Improved Strategy
        ```
    
    Use Cases:
        - Game AI development (chess, Go, poker)
        - Negotiation and bargaining agents
        - Creative content generation
        - Code optimization and debugging
        - Theorem proving and mathematical reasoning
        - Strategic planning and decision-making
    
    Advantages:
        - No need for labeled training data
        - Discovers novel strategies humans haven't found
        - Continuously improves without external input
        - Adapts to changing environments
        - Scales with compute rather than data
        - Explores diverse solution spaces
    
    LangChain Implementation:
        Uses ChatOpenAI for strategy generation and evaluation. Demonstrates
        self-play loops, performance tracking, curriculum generation, and
        iterative improvement through competition.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from dataclasses import dataclass, field
    from enum import Enum
    import time
    import random
    from collections import deque
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class TaskType(Enum):
        """Types of self-play tasks."""
        GAME = "game"
        PROBLEM_SOLVING = "problem_solving"
        NEGOTIATION = "negotiation"
        CREATIVE = "creative"
        OPTIMIZATION = "optimization"
    
    
    class StrategyQuality(Enum):
        """Quality levels of strategies."""
        NOVICE = "novice"
        INTERMEDIATE = "intermediate"
        ADVANCED = "advanced"
        EXPERT = "expert"
        SUPERHUMAN = "superhuman"
    
    
    @dataclass
    class Strategy:
        """Represents a strategy/policy."""
        strategy_id: str
        name: str
        description: str
        version: int
        quality: StrategyQuality
        win_rate: float = 0.0
        games_played: int = 0
        elo_rating: float = 1000.0  # Elo rating system
        created_at: float = field(default_factory=time.time)
    
    
    @dataclass
    class GameResult:
        """Result of a self-play game."""
        game_id: str
        strategy_a: Strategy
        strategy_b: Strategy
        winner: Optional[str]  # strategy_id or None for draw
        score_a: float
        score_b: float
        moves: List[str]
        insights: List[str] = field(default_factory=list)
    
    
    @dataclass
    class TrainingExample:
        """Training example generated from self-play."""
        example_id: str
        situation: str
        action: str
        outcome: str
        reward: float
        strategy_used: str
    
    
    @dataclass
    class CurriculumLevel:
        """Difficulty level in curriculum."""
        level: int
        name: str
        description: str
        complexity: float  # 0.0 to 1.0
        prerequisite_levels: List[int] = field(default_factory=list)
    
    
    @dataclass
    class ImprovementMetrics:
        """Metrics tracking improvement over time."""
        iteration: int
        best_strategy_quality: StrategyQuality
        average_elo: float
        total_games: int
        novel_strategies_found: int
        improvement_rate: float  # % improvement per iteration
    
    
    class SelfPlayAgent:
        """
        Agent that improves through self-play and self-generated curriculum.
        """
        
        def __init__(
            self,
            task_type: TaskType = TaskType.PROBLEM_SOLVING,
            initial_strategies: int = 3,
            elo_k_factor: int = 32
        ):
            """
            Initialize self-play agent.
            
            Args:
                task_type: Type of task for self-play
                initial_strategies: Number of initial strategies to generate
                elo_k_factor: K-factor for Elo rating updates
            """
            self.task_type = task_type
            self.elo_k_factor = elo_k_factor
            
            # LLMs for different tasks
            self.strategy_generator = ChatOpenAI(temperature=0.8, model="gpt-3.5-turbo")
            self.game_master = ChatOpenAI(temperature=0.5, model="gpt-3.5-turbo")
            self.evaluator = ChatOpenAI(temperature=0.2, model="gpt-3.5-turbo")
            self.meta_learner = ChatOpenAI(temperature=0.6, model="gpt-3.5-turbo")
            
            self.parser = StrOutputParser()
            
            # Strategy pool
            self.strategies: Dict[str, Strategy] = {}
            self.strategy_history: List[Strategy] = []
            
            # Training data
            self.training_examples: deque = deque(maxlen=1000)
            self.game_history: List[GameResult] = []
            
            # Curriculum
            self.curriculum_levels: List[CurriculumLevel] = []
            self.current_level = 0
            
            # Metrics
            self.improvement_history: List[ImprovementMetrics] = []
            self.iteration = 0
            
            # Initialize
            self._initialize_strategies(initial_strategies)
            self._initialize_curriculum()
        
        def _initialize_strategies(self, count: int):
            """Initialize starting strategies."""
            print(f"Initializing {count} starting strategies...")
            
            for i in range(count):
                strategy = Strategy(
                    strategy_id=f"strat_{i+1}",
                    name=f"Strategy {i+1}",
                    description=f"Initial strategy variant {i+1}",
                    version=1,
                    quality=StrategyQuality.NOVICE
                )
                self.strategies[strategy.strategy_id] = strategy
                print(f"  Created: {strategy.name}")
        
        def _initialize_curriculum(self):
            """Initialize learning curriculum."""
            self.curriculum_levels = [
                CurriculumLevel(1, "Basics", "Learn fundamental concepts", 0.2),
                CurriculumLevel(2, "Intermediate", "Apply concepts in simple scenarios", 0.4, [1]),
                CurriculumLevel(3, "Advanced", "Handle complex situations", 0.6, [2]),
                CurriculumLevel(4, "Expert", "Master edge cases and optimization", 0.8, [3]),
                CurriculumLevel(5, "Creative", "Discover novel strategies", 1.0, [4]),
            ]
        
        def generate_new_strategy(self, parent_strategies: List[Strategy]) -> Strategy:
            """
            Generate new strategy by learning from existing ones.
            
            Args:
                parent_strategies: Strategies to learn from
                
            Returns:
                New improved strategy
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a strategy generator. Create a new improved strategy by learning 
    from existing strategies. Combine their strengths and address their weaknesses.
    
    Task Type: {task_type}
    Current Level: {level}
    
    Create a strategy with:
    1. Name
    2. Description (key principles and tactics)
    3. Novel insights or improvements
    
    Format:
    Name: [strategy name]
    Description: [detailed description]
    Insights: [key innovations]"""),
                ("user", """Parent Strategies:
    {parent_strategies}
    
    Generate an improved strategy:""")
            ])
            
            chain = prompt | self.strategy_generator | self.parser
            
            try:
                parent_desc = "\n".join([
                    f"- {s.name} (Win Rate: {s.win_rate:.2f}, Games: {s.games_played}): {s.description}"
                    for s in parent_strategies
                ])
                
                level = self.curriculum_levels[min(self.current_level, len(self.curriculum_levels)-1)]
                
                result = chain.invoke({
                    "task_type": self.task_type.value,
                    "level": level.name,
                    "parent_strategies": parent_desc
                })
                
                # Parse result
                name = f"Strategy Gen{len(self.strategies)+1}"
                description = ""
                
                for line in result.split('\n'):
                    if line.startswith("Name:"):
                        name = line.replace("Name:", "").strip()
                    elif line.startswith("Description:"):
                        desc_lines = []
                        for next_line in result.split('\n')[result.split('\n').index(line):]:
                            if next_line.startswith("Insights:"):
                                break
                            desc_lines.append(next_line.replace("Description:", "").strip())
                        description = " ".join([d for d in desc_lines if d])
                
                if not description:
                    description = result.split('\n')[0]
                
                # Determine quality based on parent strategies
                avg_quality_idx = sum([list(StrategyQuality).index(s.quality) for s in parent_strategies]) // len(parent_strategies)
                quality = list(StrategyQuality)[min(avg_quality_idx + 1, len(StrategyQuality) - 1)]
                
                strategy = Strategy(
                    strategy_id=f"strat_{len(self.strategies)+1}",
                    name=name,
                    description=description,
                    version=1,
                    quality=quality,
                    elo_rating=sum(s.elo_rating for s in parent_strategies) / len(parent_strategies)
                )
                
                return strategy
                
            except Exception as e:
                print(f"Error generating strategy: {e}")
                # Fallback: create simple variant
                return Strategy(
                    strategy_id=f"strat_{len(self.strategies)+1}",
                    name=f"Strategy Variant {len(self.strategies)+1}",
                    description="Evolved strategy",
                    version=1,
                    quality=StrategyQuality.INTERMEDIATE
                )
        
        def play_game(
            self,
            strategy_a: Strategy,
            strategy_b: Strategy,
            scenario: str
        ) -> GameResult:
            """
            Simulate a game between two strategies.
            
            Args:
                strategy_a: First strategy
                strategy_b: Second strategy
                scenario: Game scenario/problem
                
            Returns:
                Game result with winner and insights
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a game master. Simulate a match between two strategies.
    
    Strategy A: {strategy_a_name}
    {strategy_a_desc}
    
    Strategy B: {strategy_b_name}
    {strategy_b_desc}
    
    Scenario: {scenario}
    
    Simulate the match and provide:
    Winner: [A/B/Draw]
    Score A: [0.0-1.0]
    Score B: [0.0-1.0]
    Key Moves: [list of 3-5 key moves/decisions]
    Insights: [what made the difference, what was learned]
    
    Format each on a new line."""),
                ("user", "Simulate the match:")
            ])
            
            chain = prompt | self.game_master | self.parser
            
            try:
                result = chain.invoke({
                    "strategy_a_name": strategy_a.name,
                    "strategy_a_desc": strategy_a.description,
                    "strategy_b_name": strategy_b.name,
                    "strategy_b_desc": strategy_b.description,
                    "scenario": scenario
                })
                
                # Parse result
                winner = None
                score_a = 0.5
                score_b = 0.5
                moves = []
                insights = []
                
                for line in result.split('\n'):
                    line = line.strip()
                    if line.startswith("Winner:"):
                        winner_str = line.replace("Winner:", "").strip().upper()
                        if 'A' in winner_str and 'DRAW' not in winner_str:
                            winner = strategy_a.strategy_id
                            score_a = 1.0
                            score_b = 0.0
                        elif 'B' in winner_str:
                            winner = strategy_b.strategy_id
                            score_a = 0.0
                            score_b = 1.0
                    elif line.startswith("Score A:"):
                        try:
                            score_a = float(line.replace("Score A:", "").strip())
                        except:
                            pass
                    elif line.startswith("Score B:"):
                        try:
                            score_b = float(line.replace("Score B:", "").strip())
                        except:
                            pass
                    elif line.startswith("Key Moves:"):
                        moves_str = line.replace("Key Moves:", "").strip()
                        moves = [m.strip() for m in moves_str.split(',')]
                    elif line.startswith("Insights:"):
                        insights_str = line.replace("Insights:", "").strip()
                        insights = [i.strip() for i in insights_str.split(',')]
                
                game_result = GameResult(
                    game_id=f"game_{len(self.game_history)+1}",
                    strategy_a=strategy_a,
                    strategy_b=strategy_b,
                    winner=winner,
                    score_a=score_a,
                    score_b=score_b,
                    moves=moves if moves else ["Move 1", "Move 2", "Move 3"],
                    insights=insights if insights else ["Strategies tested"]
                )
                
                return game_result
                
            except Exception as e:
                print(f"Error playing game: {e}")
                # Fallback: random winner
                winner = random.choice([strategy_a.strategy_id, strategy_b.strategy_id, None])
                return GameResult(
                    game_id=f"game_{len(self.game_history)+1}",
                    strategy_a=strategy_a,
                    strategy_b=strategy_b,
                    winner=winner,
                    score_a=0.5 if winner is None else (1.0 if winner == strategy_a.strategy_id else 0.0),
                    score_b=0.5 if winner is None else (1.0 if winner == strategy_b.strategy_id else 0.0),
                    moves=["Simulated game"],
                    insights=["Game completed"]
                )
        
        def update_elo_ratings(self, game_result: GameResult):
            """
            Update Elo ratings based on game result.
            
            Args:
                game_result: Result of the game
            """
            strategy_a = game_result.strategy_a
            strategy_b = game_result.strategy_b
            
            # Expected scores
            expected_a = 1.0 / (1.0 + 10 ** ((strategy_b.elo_rating - strategy_a.elo_rating) / 400))
            expected_b = 1.0 / (1.0 + 10 ** ((strategy_a.elo_rating - strategy_b.elo_rating) / 400))
            
            # Actual scores
            actual_a = game_result.score_a
            actual_b = game_result.score_b
            
            # Update ratings
            strategy_a.elo_rating += self.elo_k_factor * (actual_a - expected_a)
            strategy_b.elo_rating += self.elo_k_factor * (actual_b - expected_b)
            
            # Update stats
            strategy_a.games_played += 1
            strategy_b.games_played += 1
            
            if game_result.winner == strategy_a.strategy_id:
                strategy_a.win_rate = (strategy_a.win_rate * (strategy_a.games_played - 1) + 1.0) / strategy_a.games_played
            else:
                strategy_a.win_rate = (strategy_a.win_rate * (strategy_a.games_played - 1)) / strategy_a.games_played
            
            if game_result.winner == strategy_b.strategy_id:
                strategy_b.win_rate = (strategy_b.win_rate * (strategy_b.games_played - 1) + 1.0) / strategy_b.games_played
            else:
                strategy_b.win_rate = (strategy_b.win_rate * (strategy_b.games_played - 1)) / strategy_b.games_played
        
        def generate_curriculum_scenario(self, level: CurriculumLevel) -> str:
            """
            Generate training scenario for current curriculum level.
            
            Args:
                level: Curriculum level
                
            Returns:
                Scenario description
            """
            scenarios = {
                1: "Solve a basic optimization problem with clear constraints.",
                2: "Handle a scenario with competing objectives and trade-offs.",
                3: "Manage complex multi-step problem with hidden information.",
                4: "Optimize performance in adversarial environment with uncertainty.",
                5: "Discover creative solution to novel problem never seen before.",
            }
            return scenarios.get(level.level, "General problem-solving scenario")
        
        def self_play_iteration(self, num_games: int = 5) -> ImprovementMetrics:
            """
            Run one iteration of self-play training.
            
            Args:
                num_games: Number of games to play this iteration
                
            Returns:
                Improvement metrics for this iteration
            """
            self.iteration += 1
            print(f"\n{'='*60}")
            print(f"Self-Play Iteration {self.iteration}")
            print(f"{'='*60}")
            
            # Get current curriculum level
            level = self.curriculum_levels[min(self.current_level, len(self.curriculum_levels)-1)]
            print(f"Curriculum Level: {level.name} (Complexity: {level.complexity})")
            
            # Play games
            print(f"\nPlaying {num_games} games...")
            iteration_games = []
            
            strategies_list = list(self.strategies.values())
            
            for i in range(num_games):
                # Select two random strategies
                if len(strategies_list)  10:
                worst_strategy = min(self.strategies.values(), key=lambda s: s.elo_rating)
                del self.strategies[worst_strategy.strategy_id]
                print(f"  Pruned: {worst_strategy.name}")
            
            # Update curriculum if ready
            if self.iteration % 5 == 0 and self.current_level  0:
                prev_avg_elo = self.improvement_history[-1].average_elo
                improvement_rate = ((avg_elo - prev_avg_elo) / prev_avg_elo) * 100 if prev_avg_elo > 0 else 0.0
            
            metrics = ImprovementMetrics(
                iteration=self.iteration,
                best_strategy_quality=best_strategy.quality,
                average_elo=avg_elo,
                total_games=len(self.game_history),
                novel_strategies_found=len(self.strategy_history),
                improvement_rate=improvement_rate
            )
            
            self.improvement_history.append(metrics)
            
            print(f"\n--- Iteration {self.iteration} Metrics ---")
            print(f"Best Strategy: {best_strategy.name} (Elo: {best_strategy.elo_rating:.0f})")
            print(f"Average Elo: {avg_elo:.0f}")
            print(f"Total Strategies: {len(self.strategies)}")
            print(f"Improvement Rate: {improvement_rate:+.2f}%")
            
            return metrics
        
        def train(self, iterations: int = 10, games_per_iteration: int = 5):
            """
            Run full self-play training.
            
            Args:
                iterations: Number of training iterations
                games_per_iteration: Games to play per iteration
            """
            print("="*60)
            print("STARTING SELF-PLAY TRAINING")
            print("="*60)
            print(f"Task Type: {self.task_type.value}")
            print(f"Iterations: {iterations}")
            print(f"Games per Iteration: {games_per_iteration}")
            
            for i in range(iterations):
                self.self_play_iteration(games_per_iteration)
            
            print("\n" + "="*60)
            print("TRAINING COMPLETE")
            print("="*60)
            
            # Final summary
            best = max(self.strategies.values(), key=lambda s: s.elo_rating)
            print(f"\nBest Strategy: {best.name}")
            print(f"  Quality: {best.quality.value}")
            print(f"  Elo Rating: {best.elo_rating:.0f}")
            print(f"  Win Rate: {best.win_rate:.2%}")
            print(f"  Games Played: {best.games_played}")
            
            print(f"\nTotal Games: {len(self.game_history)}")
            print(f"Strategies Discovered: {len(self.strategy_history)}")
            print(f"Current Pool Size: {len(self.strategies)}")
            print(f"Final Curriculum Level: {self.curriculum_levels[self.current_level].name}")
    
    
    def demonstrate_self_play():
        """Demonstrate Self-Play & Self-Improvement pattern."""
        
        print("="*80)
        print("SELF-PLAY & SELF-IMPROVEMENT - DEMONSTRATION")
        print("="*80)
        
        # Test 1: Problem-solving self-play
        print("\n" + "="*80)
        print("TEST 1: Problem-Solving Self-Play (Short Training)")
        print("="*80)
        
        agent1 = SelfPlayAgent(
            task_type=TaskType.PROBLEM_SOLVING,
            initial_strategies=3
        )
        
        agent1.train(iterations=5, games_per_iteration=3)
        
        print("\n--- Strategy Evolution ---")
        for i, strategy in enumerate(agent1.strategy_history[:5], 1):
            print(f"{i}. {strategy.name}")
            print(f"   Quality: {strategy.quality.value}, Elo: {strategy.elo_rating:.0f}")
        
        # Test 2: Track improvement metrics
        print("\n" + "="*80)
        print("TEST 2: Improvement Metrics Analysis")
        print("="*80)
        
        print("\nIteration-by-Iteration Improvement:")
        print(f"{'Iter':<6} {'Avg Elo':<10} {'Best Quality':<15} {'Games':<8} {'Improvement':<12}")
        print("-" * 65)
        for metrics in agent1.improvement_history:
            print(f"{metrics.iteration:<6} {metrics.average_elo:<10.0f} "
                  f"{metrics.best_strategy_quality.value:<15} {metrics.total_games:+10.2f}%")
        
        # Test 3: Strategy comparison
        print("\n" + "="*80)
        print("TEST 3: Final Strategy Rankings")
        print("="*80)
        
        ranked_strategies = sorted(agent1.strategies.values(), key=lambda s: s.elo_rating, reverse=True)
        
        print(f"\n{'Rank':<6} {'Strategy':<25} {'Elo':<8} {'Win Rate':<10} {'Games':<8}")
        print("-" * 65)
        for i, strategy in enumerate(ranked_strategies, 1):
            print(f"{i:<6} {strategy.name:<25} {strategy.elo_rating:<8.0f} "
                  f"{strategy.win_rate:<10.1%} {strategy.games_played:<8}")
        
        # Test 4: Game insights
        print("\n" + "="*80)
        print("TEST 4: Notable Games and Insights")
        print("="*80)
        
        print("\nSample Games:")
        for game in agent1.game_history[-3:]:
            print(f"\n{game.game_id}: {game.strategy_a.name} vs {game.strategy_b.name}")
            winner_name = "Draw"
            if game.winner == game.strategy_a.strategy_id:
                winner_name = game.strategy_a.name
            elif game.winner == game.strategy_b.strategy_id:
                winner_name = game.strategy_b.name
            print(f"  Winner: {winner_name}")
            print(f"  Scores: {game.score_a:.2f} - {game.score_b:.2f}")
            if game.insights:
                print(f"  Insights: {', '.join(game.insights[:2])}")
        
        # Test 5: Different task type
        print("\n" + "="*80)
        print("TEST 5: Creative Task Self-Play")
        print("="*80)
        
        agent2 = SelfPlayAgent(
            task_type=TaskType.CREATIVE,
            initial_strategies=2
        )
        
        print("\nRunning creative task training...")
        agent2.train(iterations=3, games_per_iteration=2)
        
        print("\n--- Creative Strategies Evolved ---")
        best_creative = max(agent2.strategies.values(), key=lambda s: s.elo_rating)
        print(f"Best Strategy: {best_creative.name}")
        print(f"Description: {best_creative.description[:200]}...")
        
        # Summary
        print("\n" + "="*80)
        print("SUMMARY: Self-Play & Self-Improvement")
        print("="*80)
        print("""
    The Self-Play & Self-Improvement pattern demonstrates autonomous learning:
    
    Key Features Demonstrated:
    1. Strategy Generation: Creates new strategies from successful parents
    2. Self-Play Engine: Simulates competitions between strategies
    3. Performance Evaluation: Uses Elo ratings to track strategy strength
    4. Curriculum Learning: Progressively increases task difficulty
    5. Strategy Evolution: Generates improved strategies over time
    6. Strategy Pruning: Removes weak strategies to maintain quality pool
    7. Improvement Tracking: Monitors learning progress with metrics
    
    Benefits:
    • No labeled training data required
    • Discovers novel strategies through exploration
    • Continuously improves without human supervision
    • Adapts to changing task requirements
    • Scales with computational resources
    • Explores diverse solution spaces
    • Learns from billions of self-generated examples
    
    Use Cases:
    • Game AI (chess, Go, poker, StarCraft)
    • Negotiation and bargaining agents
    • Creative content generation (writing, art, music)
    • Code optimization and refactoring
    • Mathematical theorem proving
    • Strategic planning and decision-making
    • Competitive debate and argumentation
    • Resource allocation and scheduling
    
    Comparison with Supervised Learning:
    ┌─────────────────────┬────────────────────┬─────────────────────┐
    │ Aspect              │ Supervised         │ Self-Play           │
    ├─────────────────────┼────────────────────┼─────────────────────┤
    │ Training Data       │ Labeled examples   │ Self-generated      │
    │ Data Requirements   │ Large labeled sets │ Minimal initial     │
    │ Novel Strategies    │ Limited            │ Unlimited discovery │
    │ Adaptation          │ Retraining needed  │ Continuous          │
    │ Human Expertise     │ Required           │ Not required        │
    │ Scaling             │ Data-limited       │ Compute-limited     │
    │ Performance Ceiling │ Human-level        │ Can exceed humans   │
    └─────────────────────┴────────────────────┴─────────────────────┘
    
    Real-World Successes:
    • AlphaGo: Defeated world champion through self-play
    • AlphaZero: Mastered chess, shogi, and Go with no human data
    • OpenAI Five: Achieved professional-level Dota 2 play
    • GPT models: Improved through self-generated reasoning
    • AlphaCode: Competitive programming through self-play
    
    LangChain Implementation Notes:
    • Multiple specialized LLMs for different roles
    • Strategy generation through evolutionary approaches
    • Game simulation using LLM as game master
    • Elo rating system for objective performance measurement
    • Curriculum learning with progressive difficulty
    • Strategy pool management with pruning
    • Comprehensive metrics tracking
    
    Production Considerations:
    • Implement efficient game/task simulation
    • Add checkpointing for long training runs
    • Implement distributed self-play across multiple workers
    • Add exploration bonuses to encourage diversity
    • Implement opponent sampling strategies (uniform, skill-based)
    • Add meta-learning to improve learning efficiency
    • Implement proper evaluation against external benchmarks
    • Add human evaluation for strategy interpretability
    • Monitor for strategy collapse or degenerate solutions
    • Implement safety checks for deployed strategies
    • Add transfer learning from related tasks
    • Implement strategy distillation for deployment efficiency
    
    Advanced Extensions:
    • Multi-agent self-play with teams
    • Hierarchical strategies with sub-strategies
    • Transfer learning across different tasks
    • Meta-self-play (learning how to self-play better)
    • Curiosity-driven exploration bonuses
    • Population-based training with diversity metrics
    • Adversarial self-play for robustness
    • Self-play with human-in-the-loop feedback
        """)
    
    
    if __name__ == "__main__":
        demonstrate_self_play()
    
        

    Agentic Design Pattern: Self Play

    Pattern 069: Self Play – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Self Play pattern implementation.


    Pattern Description

    Pattern 069: Self-Play & Self-Improvement

    Description: Self-Play & Self-Improvement enables agents to autonomously improve their capabilities by competing or collaborating with themselves. Inspired by techniques like AlphaGo’s self-play training, this pattern allows agents to generate their own training data, discover new strategies, and continuously refine their performance without external supervision. The agent plays both sides of a game, problem, or scenario, learning from each iteration to progressively improve.

    This pattern is particularly powerful for domains where supervised training data is
    scarce, expensive, or impossible to obtain. Through self-play, agents can explore
    vast strategy spaces, discover novel solutions, and achieve superhuman performance
    by learning from billions of self-generated experiences.
    

    Components: 1. Strategy Generator: Creates candidate strategies/policies 2. Self-Play Engine: Simulates interactions with itself 3. Performance Evaluator: Measures strategy effectiveness 4. Curriculum Generator: Creates progressively harder challenges 5. Strategy Selector: Chooses best strategies to keep 6. Meta-Learner: Learns how to improve the learning process 7. Checkpoint Manager: Stores and manages strategy versions

    Architecture: Initial Strategy ↓ ┌─────────────────────────┐ │ Self-Play Loop │ │ │ │ Strategy A vs Strategy B│ │ ↓ │ │ Play Game/Task │ │ ↓ │ │ Evaluate Performance │ │ ↓ │ │ Update Strategies │ │ ↓ │ │ Generate Curriculum │ │ ↓ │ │ Select Best │ └─────────────────────────┘ ↓ (iterate) Improved Strategy

    Use Cases: – Game AI development (chess, Go, poker) – Negotiation and bargaining agents – Creative content generation – Code optimization and debugging – Theorem proving and mathematical reasoning – Strategic planning and decision-making

    Advantages: – No need for labeled training data – Discovers novel strategies humans haven’t found – Continuously improves without external input – Adapts to changing environments – Scales with compute rather than data – Explores diverse solution spaces

    LangChain Implementation: Uses ChatOpenAI for strategy generation and evaluation. Demonstrates self-play loops, performance tracking, curriculum generation, and iterative improvement through competition.


    File Statistics

    • Total Lines: 817
    • Classes: 8
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 069: Self-Play & Self-Improvement
    
    Description:
        Self-Play & Self-Improvement enables agents to autonomously improve their capabilities
        by competing or collaborating with themselves. Inspired by techniques like AlphaGo's
        self-play training, this pattern allows agents to generate their own training data,
        discover new strategies, and continuously refine their performance without external
        supervision. The agent plays both sides of a game, problem, or scenario, learning
        from each iteration to progressively improve.
    
        This pattern is particularly powerful for domains where supervised training data is
        scarce, expensive, or impossible to obtain. Through self-play, agents can explore
        vast strategy spaces, discover novel solutions, and achieve superhuman performance
        by learning from billions of self-generated experiences.
    
    Components:
        1. Strategy Generator: Creates candidate strategies/policies
        2. Self-Play Engine: Simulates interactions with itself
        3. Performance Evaluator: Measures strategy effectiveness
        4. Curriculum Generator: Creates progressively harder challenges
        5. Strategy Selector: Chooses best strategies to keep
        6. Meta-Learner: Learns how to improve the learning process
        7. Checkpoint Manager: Stores and manages strategy versions
    
    Architecture:
        ```
        Initial Strategy
            ↓
        ┌─────────────────────────┐
        │   Self-Play Loop        │
        │                         │
        │  Strategy A vs Strategy B│
        │         ↓               │
        │    Play Game/Task       │
        │         ↓               │
        │   Evaluate Performance  │
        │         ↓               │
        │   Update Strategies     │
        │         ↓               │
        │  Generate Curriculum    │
        │         ↓               │
        │   Select Best           │
        └─────────────────────────┘
             ↓ (iterate)
        Improved Strategy
        ```
    
    Use Cases:
        - Game AI development (chess, Go, poker)
        - Negotiation and bargaining agents
        - Creative content generation
        - Code optimization and debugging
        - Theorem proving and mathematical reasoning
        - Strategic planning and decision-making
    
    Advantages:
        - No need for labeled training data
        - Discovers novel strategies humans haven't found
        - Continuously improves without external input
        - Adapts to changing environments
        - Scales with compute rather than data
        - Explores diverse solution spaces
    
    LangChain Implementation:
        Uses ChatOpenAI for strategy generation and evaluation. Demonstrates
        self-play loops, performance tracking, curriculum generation, and
        iterative improvement through competition.
    """
    

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


    Import Statements

    Standard Library Imports

    import collections.deque
    import dataclasses.dataclass
    import dataclasses.field
    import enum.Enum
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import random
    import time
    import typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Tuple
    

    Purpose: Core Python functionality: – collections.deque: Specialized container datatypes – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – 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) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: TaskType

    Lines 87-93

    Inherits from: Enum

    Purpose:

    Types of self-play tasks.
    

    Class: StrategyQuality

    Lines 96-102

    Inherits from: Enum

    Purpose:

    Quality levels of strategies.
    

    Class: Strategy

    Lines 106-116

    Purpose:

    Represents a strategy/policy.
    

    Class: GameResult

    Lines 120-129

    Purpose:

    Result of a self-play game.
    

    Class: TrainingExample

    Lines 133-140

    Purpose:

    Training example generated from self-play.
    

    Class: CurriculumLevel

    Lines 144-150

    Purpose:

    Difficulty level in curriculum.
    

    Class: ImprovementMetrics

    Lines 154-161

    Purpose:

    Metrics tracking improvement over time.
    

    Class: SelfPlayAgent

    Lines 164-636

    Purpose:

    Agent that improves through self-play and self-generated curriculum.
    

    Methods:

    __init__(self, task_type, initial_strategies, elo_k_factor)

    Lines 169-212

    Purpose:

    Initialize self-play agent.
    
    Args:
        task_type: Type of task for self-play
        initial_strategies: Number of initial strategies to generate
        elo_k_factor: K-factor for Elo rating updates
    

    Parameters: – task_type: Task type – initial_strategies: Initial strategies – elo_k_factor: Elo k factor

    _initialize_strategies(self, count)

    Lines 214-227

    Purpose:

    Initialize starting strategies.
    

    Parameters: – count: Count

    _initialize_curriculum(self)

    Lines 229-237

    Purpose:

    Initialize learning curriculum.
    

    Parameters:

    generate_new_strategy(self, parent_strategies)

    Lines 239-329

    Purpose:

    Generate new strategy by learning from existing ones.
    
    Args:
        parent_strategies: Strategies to learn from
    
    Returns:
        New improved strategy
    

    Parameters: – parent_strategies: Parent strategies

    Returns: Strategy

    play_game(self, strategy_a, strategy_b, scenario)

    Lines 331-443

    Purpose:

    Simulate a game between two strategies.
    
    Args:
        strategy_a: First strategy
        strategy_b: Second strategy
        scenario: Game scenario/problem
    
    Returns:
        Game result with winner and insights
    

    Parameters: – strategy_a: Strategy a – strategy_b: Strategy b – scenario: Scenario

    Returns: GameResult

    update_elo_ratings(self, game_result)

    Lines 445-479

    Purpose:

    Update Elo ratings based on game result.
    
    Args:
        game_result: Result of the game
    

    Parameters: – game_result: Game result

    generate_curriculum_scenario(self, level)

    Lines 481-498

    Purpose:

    Generate training scenario for current curriculum level.
    
    Args:
        level: Curriculum level
    
    Returns:
        Scenario description
    

    Parameters: – level: Level

    Returns: str

    self_play_iteration(self, num_games)

    Lines 500-601

    Purpose:

    Run one iteration of self-play training.
    
    Args:
        num_games: Number of games to play this iteration
    
    Returns:
        Improvement metrics for this iteration
    

    Parameters: – num_games: Num games

    Returns: ImprovementMetrics

    train(self, iterations, games_per_iteration)

    Lines 603-636

    Purpose:

    Run full self-play training.
    
    Args:
        iterations: Number of training iterations
        games_per_iteration: Games to play per iteration
    

    Parameters: – iterations: Iterations – games_per_iteration: Games per iteration


    Functions

    demonstrate_self_play()

    Lines 639-812

    Purpose:

    Demonstrate Self-Play & Self-Improvement pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Self Play 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 068
    • Pattern 070

    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 Self Play 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: 069_self_play.py
    Total Lines: 817
    Pattern Category: AI Agent Design Pattern #069


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Instruction Following

    
        """
    Pattern 068: Instruction Following & Grounding
    
    Description:
        Instruction Following & Grounding enables agents to precisely understand and execute
        natural language instructions by breaking them down into structured, executable steps.
        This pattern goes beyond simple command parsing to deeply ground instructions in the
        agent's capabilities, environment constraints, and task context. The agent validates
        instructions for feasibility, disambiguates ambiguous commands, grounds abstract
        concepts in concrete actions, and provides execution verification.
        
        The pattern is crucial for building reliable agents that can follow complex,
        multi-step instructions while maintaining safety, handling edge cases, and providing
        transparent feedback about what it's doing and why.
    
    Components:
        1. Instruction Parser: Extracts intent, parameters, and constraints
        2. Action Grounder: Maps abstract instructions to concrete actions
        3. Feasibility Checker: Validates instruction executability
        4. Disambiguator: Resolves ambiguities through clarification
        5. Execution Planner: Creates step-by-step execution plan
        6. Execution Monitor: Tracks execution progress and validates outcomes
        7. Feedback Generator: Provides status updates and explanations
    
    Architecture:
        ```
        Natural Language Instruction
            ↓
        Instruction Parser → [Intent, Parameters, Constraints]
            ↓
        Disambiguator → [Clarified Instruction]
            ↓
        Action Grounder → [Concrete Actions]
            ↓
        Feasibility Checker → [Validation]
            ↓
        Execution Planner → [Execution Steps]
            ↓
        Execution Monitor → [Execute & Verify]
            ↓
        Feedback Generator → [Status & Results]
        ```
    
    Use Cases:
        - Robotic task execution from natural language
        - Virtual assistant command processing
        - Smart home automation
        - Software automation and scripting
        - Manufacturing and logistics instructions
        - Educational tutoring systems
    
    Advantages:
        - Handles ambiguous and underspecified instructions
        - Grounds abstract concepts in concrete actions
        - Validates feasibility before execution
        - Provides transparent execution process
        - Handles errors and edge cases gracefully
        - Learns from execution feedback
    
    LangChain Implementation:
        Uses ChatOpenAI for instruction parsing, grounding, and planning.
        Demonstrates instruction decomposition, parameter extraction, feasibility
        checking, execution planning, and verification.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from dataclasses import dataclass, field
    from enum import Enum
    import time
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class InstructionType(Enum):
        """Types of instructions."""
        ACTION = "action"  # Do something
        QUERY = "query"  # Find information
        CONDITION = "condition"  # If-then logic
        SEQUENCE = "sequence"  # Series of actions
        LOOP = "loop"  # Repeated actions
        CONSTRAINT = "constraint"  # With specific constraints
    
    
    class ActionStatus(Enum):
        """Status of action execution."""
        PENDING = "pending"
        IN_PROGRESS = "in_progress"
        COMPLETED = "completed"
        FAILED = "failed"
        BLOCKED = "blocked"
    
    
    class FeasibilityLevel(Enum):
        """Feasibility assessment levels."""
        FEASIBLE = "feasible"
        FEASIBLE_WITH_ASSUMPTIONS = "feasible_with_assumptions"
        PARTIALLY_FEASIBLE = "partially_feasible"
        INFEASIBLE = "infeasible"
        NEEDS_CLARIFICATION = "needs_clarification"
    
    
    @dataclass
    class Parameter:
        """Represents a parameter extracted from instruction."""
        name: str
        value: Any
        data_type: str
        is_optional: bool = False
        default_value: Any = None
    
    
    @dataclass
    class Constraint:
        """Represents a constraint on instruction execution."""
        constraint_type: str  # e.g., "time", "resource", "condition"
        description: str
        is_hard: bool = True  # Hard vs soft constraint
    
    
    @dataclass
    class ParsedInstruction:
        """Represents a parsed natural language instruction."""
        instruction_id: str
        original_text: str
        instruction_type: InstructionType
        intent: str  # The main goal
        parameters: List[Parameter]
        constraints: List[Constraint]
        dependencies: List[str] = field(default_factory=list)  # Other instruction IDs
        ambiguities: List[str] = field(default_factory=list)
    
    
    @dataclass
    class GroundedAction:
        """Represents a grounded, executable action."""
        action_id: str
        action_name: str
        parameters: Dict[str, Any]
        preconditions: List[str]
        effects: List[str]
        estimated_duration: float  # seconds
        
        def execute(self) -> Tuple[bool, str]:
            """Simulate action execution."""
            # In real implementation, this would execute actual actions
            print(f"  Executing: {self.action_name}({', '.join(f'{k}={v}' for k, v in self.parameters.items())})")
            time.sleep(0.1)  # Simulate execution time
            return True, f"Successfully executed {self.action_name}"
    
    
    @dataclass
    class ExecutionStep:
        """Represents a step in execution plan."""
        step_id: int
        action: GroundedAction
        status: ActionStatus
        start_time: Optional[float] = None
        end_time: Optional[float] = None
        result: Optional[str] = None
        error: Optional[str] = None
    
    
    @dataclass
    class FeasibilityAssessment:
        """Assessment of instruction feasibility."""
        level: FeasibilityLevel
        is_feasible: bool
        reasons: List[str]
        assumptions: List[str] = field(default_factory=list)
        clarifications_needed: List[str] = field(default_factory=list)
        alternatives: List[str] = field(default_factory=list)
    
    
    @dataclass
    class ExecutionResult:
        """Complete result of instruction execution."""
        instruction: ParsedInstruction
        feasibility: FeasibilityAssessment
        execution_steps: List[ExecutionStep]
        success: bool
        summary: str
        execution_time: float
    
    
    class InstructionFollowingAgent:
        """
        Agent that parses, grounds, and executes natural language instructions.
        """
        
        def __init__(self, agent_capabilities: Optional[List[str]] = None):
            """
            Initialize the instruction following agent.
            
            Args:
                agent_capabilities: List of actions the agent can perform
            """
            # Define agent capabilities
            self.capabilities = agent_capabilities or [
                "move_to",
                "pick_up",
                "put_down",
                "open",
                "close",
                "search",
                "read",
                "write",
                "calculate",
                "wait",
                "notify"
            ]
            
            # LLMs for different tasks
            self.parser = ChatOpenAI(temperature=0.1, model="gpt-3.5-turbo")
            self.grounder = ChatOpenAI(temperature=0.2, model="gpt-3.5-turbo")
            self.planner = ChatOpenAI(temperature=0.2, model="gpt-3.5-turbo")
            self.verifier = ChatOpenAI(temperature=0.1, model="gpt-3.5-turbo")
            
            self.str_parser = StrOutputParser()
            
            # Execution history
            self.execution_history: List[ExecutionResult] = []
        
        def parse_instruction(self, instruction_text: str) -> ParsedInstruction:
            """
            Parse natural language instruction into structured format.
            
            Args:
                instruction_text: Natural language instruction
                
            Returns:
                Parsed instruction with intent, parameters, and constraints
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an instruction parser. Extract structured information from natural language.
    
    Analyze the instruction and identify:
    1. Instruction Type (action/query/condition/sequence/loop/constraint)
    2. Intent (what is the goal?)
    3. Parameters (name, value, type, optional?)
    4. Constraints (time, resource, conditional constraints)
    5. Ambiguities (what is unclear or underspecified?)
    
    Format your response as:
    Type: [type]
    Intent: [main goal]
    Parameters: [name:value:type:optional, ...]
    Constraints: [type:description:hard/soft, ...]
    Ambiguities: [list of unclear aspects, or 'none']"""),
                ("user", "Parse this instruction: {instruction}")
            ])
            
            chain = prompt | self.parser | self.str_parser
            
            try:
                result = chain.invoke({"instruction": instruction_text})
                
                # Parse the structured output
                instruction_type = InstructionType.ACTION
                intent = ""
                parameters = []
                constraints = []
                ambiguities = []
                
                for line in result.split('\n'):
                    line = line.strip()
                    if line.startswith("Type:"):
                        type_str = line.replace("Type:", "").strip().lower()
                        for itype in InstructionType:
                            if itype.value in type_str:
                                instruction_type = itype
                                break
                    
                    elif line.startswith("Intent:"):
                        intent = line.replace("Intent:", "").strip()
                    
                    elif line.startswith("Parameters:"):
                        params_str = line.replace("Parameters:", "").strip()
                        if params_str.lower() != 'none':
                            # Parse format: name:value:type:optional
                            for param_str in params_str.split(','):
                                parts = [p.strip() for p in param_str.split(':')]
                                if len(parts) >= 3:
                                    param = Parameter(
                                        name=parts[0],
                                        value=parts[1],
                                        data_type=parts[2],
                                        is_optional=len(parts) > 3 and parts[3].lower() == 'optional'
                                    )
                                    parameters.append(param)
                    
                    elif line.startswith("Constraints:"):
                        const_str = line.replace("Constraints:", "").strip()
                        if const_str.lower() != 'none':
                            # Parse format: type:description:hard/soft
                            for c_str in const_str.split(','):
                                parts = [p.strip() for p in c_str.split(':')]
                                if len(parts) >= 2:
                                    constraint = Constraint(
                                        constraint_type=parts[0],
                                        description=parts[1] if len(parts) > 1 else "",
                                        is_hard=len(parts)  List[GroundedAction]:
            """
            Ground abstract instruction into concrete, executable actions.
            
            Args:
                parsed: Parsed instruction
                
            Returns:
                List of grounded actions
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an action grounder. Map abstract instructions to concrete actions.
    
    Agent Capabilities: {capabilities}
    
    For each instruction, specify:
    1. Action sequence (using available capabilities)
    2. Parameters for each action
    3. Preconditions (what must be true before)
    4. Effects (what changes after)
    5. Estimated duration (seconds)
    
    Format each action as:
    Action: [action_name]
    Parameters: [key1=value1, key2=value2, ...]
    Preconditions: [list]
    Effects: [list]
    Duration: [seconds]
    ---"""),
                ("user", """Ground this instruction:
    Intent: {intent}
    Type: {instruction_type}
    Parameters: {parameters}
    Constraints: {constraints}
    
    Provide concrete actions:""")
            ])
            
            chain = prompt | self.grounder | self.str_parser
            
            try:
                params_str = ", ".join([f"{p.name}={p.value}" for p in parsed.parameters])
                const_str = ", ".join([f"{c.constraint_type}: {c.description}" for c in parsed.constraints])
                
                result = chain.invoke({
                    "capabilities": ", ".join(self.capabilities),
                    "intent": parsed.intent,
                    "instruction_type": parsed.instruction_type.value,
                    "parameters": params_str if params_str else "none",
                    "constraints": const_str if const_str else "none"
                })
                
                # Parse grounded actions
                actions = []
                action_blocks = result.split('---')
                
                for i, block in enumerate(action_blocks):
                    if not block.strip():
                        continue
                    
                    action_name = ""
                    parameters = {}
                    preconditions = []
                    effects = []
                    duration = 1.0
                    
                    for line in block.split('\n'):
                        line = line.strip()
                        if line.startswith("Action:"):
                            action_name = line.replace("Action:", "").strip()
                        elif line.startswith("Parameters:"):
                            param_str = line.replace("Parameters:", "").strip()
                            if param_str.lower() != 'none':
                                for kv in param_str.split(','):
                                    if '=' in kv:
                                        k, v = kv.split('=', 1)
                                        parameters[k.strip()] = v.strip()
                        elif line.startswith("Preconditions:"):
                            pre_str = line.replace("Preconditions:", "").strip()
                            if pre_str.lower() != 'none':
                                preconditions = [p.strip() for p in pre_str.split(',')]
                        elif line.startswith("Effects:"):
                            eff_str = line.replace("Effects:", "").strip()
                            if eff_str.lower() != 'none':
                                effects = [e.strip() for e in eff_str.split(',')]
                        elif line.startswith("Duration:"):
                            try:
                                duration = float(line.replace("Duration:", "").strip())
                            except:
                                duration = 1.0
                    
                    if action_name:
                        action = GroundedAction(
                            action_id=f"action_{i+1}",
                            action_name=action_name,
                            parameters=parameters,
                            preconditions=preconditions,
                            effects=effects,
                            estimated_duration=duration
                        )
                        actions.append(action)
                
                return actions if actions else [
                    GroundedAction(
                        action_id="action_1",
                        action_name="execute_instruction",
                        parameters={"instruction": parsed.intent},
                        preconditions=[],
                        effects=["instruction executed"],
                        estimated_duration=1.0
                    )
                ]
                
            except Exception as e:
                print(f"Error grounding instruction: {e}")
                # Fallback: single generic action
                return [
                    GroundedAction(
                        action_id="action_1",
                        action_name="execute",
                        parameters={"instruction": parsed.intent},
                        preconditions=[],
                        effects=["completed"],
                        estimated_duration=1.0
                    )
                ]
        
        def check_feasibility(
            self,
            parsed: ParsedInstruction,
            grounded_actions: List[GroundedAction]
        ) -> FeasibilityAssessment:
            """
            Check if instruction is feasible to execute.
            
            Args:
                parsed: Parsed instruction
                grounded_actions: Grounded actions
                
            Returns:
                Feasibility assessment
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a feasibility checker. Assess if instructions can be executed.
    
    Agent Capabilities: {capabilities}
    
    Consider:
    1. Are all required actions within agent capabilities?
    2. Are parameters complete and valid?
    3. Are constraints satisfiable?
    4. Are preconditions likely to be met?
    5. Are there any safety concerns?
    
    Provide:
    Feasibility: [feasible/feasible_with_assumptions/partially_feasible/infeasible/needs_clarification]
    Reasons: [list of reasons for assessment]
    Assumptions: [assumptions made, if any]
    Clarifications: [what needs clarification, if any]
    Alternatives: [alternative approaches, if any]"""),
                ("user", """Assess feasibility:
    Intent: {intent}
    Actions: {actions}
    Constraints: {constraints}
    Ambiguities: {ambiguities}
    
    Provide assessment:""")
            ])
            
            chain = prompt | self.verifier | self.str_parser
            
            try:
                actions_str = ", ".join([a.action_name for a in grounded_actions])
                const_str = ", ".join([c.description for c in parsed.constraints])
                amb_str = ", ".join(parsed.ambiguities) if parsed.ambiguities else "none"
                
                result = chain.invoke({
                    "capabilities": ", ".join(self.capabilities),
                    "intent": parsed.intent,
                    "actions": actions_str,
                    "constraints": const_str if const_str else "none",
                    "ambiguities": amb_str
                })
                
                # Parse assessment
                level = FeasibilityLevel.FEASIBLE
                reasons = []
                assumptions = []
                clarifications = []
                alternatives = []
                
                for line in result.split('\n'):
                    line = line.strip()
                    if line.startswith("Feasibility:"):
                        level_str = line.replace("Feasibility:", "").strip().lower()
                        for flevel in FeasibilityLevel:
                            if flevel.value in level_str:
                                level = flevel
                                break
                    elif line.startswith("Reasons:"):
                        reasons_str = line.replace("Reasons:", "").strip()
                        if reasons_str.lower() != 'none':
                            reasons = [r.strip() for r in reasons_str.split(',')]
                    elif line.startswith("Assumptions:"):
                        ass_str = line.replace("Assumptions:", "").strip()
                        if ass_str.lower() != 'none':
                            assumptions = [a.strip() for a in ass_str.split(',')]
                    elif line.startswith("Clarifications:"):
                        clar_str = line.replace("Clarifications:", "").strip()
                        if clar_str.lower() != 'none':
                            clarifications = [c.strip() for c in clar_str.split(',')]
                    elif line.startswith("Alternatives:"):
                        alt_str = line.replace("Alternatives:", "").strip()
                        if alt_str.lower() != 'none':
                            alternatives = [a.strip() for a in alt_str.split(',')]
                
                is_feasible = level in [FeasibilityLevel.FEASIBLE, FeasibilityLevel.FEASIBLE_WITH_ASSUMPTIONS]
                
                return FeasibilityAssessment(
                    level=level,
                    is_feasible=is_feasible,
                    reasons=reasons if reasons else ["Assessment completed"],
                    assumptions=assumptions,
                    clarifications_needed=clarifications,
                    alternatives=alternatives
                )
                
            except Exception as e:
                print(f"Error checking feasibility: {e}")
                # Fallback: assume feasible
                return FeasibilityAssessment(
                    level=FeasibilityLevel.FEASIBLE,
                    is_feasible=True,
                    reasons=["Default feasibility check"]
                )
        
        def execute_instruction(
            self,
            instruction_text: str,
            dry_run: bool = False
        ) -> ExecutionResult:
            """
            Execute a natural language instruction end-to-end.
            
            Args:
                instruction_text: Natural language instruction
                dry_run: If True, plan but don't execute
                
            Returns:
                Complete execution result
            """
            start_time = time.time()
            
            print(f"\n{'='*60}")
            print(f"Instruction: {instruction_text}")
            print(f"{'='*60}\n")
            
            # Step 1: Parse
            print("Step 1: Parsing instruction...")
            parsed = self.parse_instruction(instruction_text)
            print(f"  → Intent: {parsed.intent}")
            print(f"  → Type: {parsed.instruction_type.value}")
            print(f"  → Parameters: {len(parsed.parameters)}")
            if parsed.ambiguities:
                print(f"  → Ambiguities: {', '.join(parsed.ambiguities)}")
            
            # Step 2: Ground
            print("\nStep 2: Grounding to concrete actions...")
            grounded_actions = self.ground_instruction(parsed)
            print(f"  → Generated {len(grounded_actions)} actions:")
            for action in grounded_actions:
                print(f"    • {action.action_name}({', '.join(f'{k}={v}' for k, v in action.parameters.items())})")
            
            # Step 3: Check feasibility
            print("\nStep 3: Checking feasibility...")
            feasibility = self.check_feasibility(parsed, grounded_actions)
            print(f"  → Feasibility: {feasibility.level.value}")
            print(f"  → Is Feasible: {feasibility.is_feasible}")
            if feasibility.assumptions:
                print(f"  → Assumptions: {', '.join(feasibility.assumptions)}")
            if feasibility.clarifications_needed:
                print(f"  → Needs Clarification: {', '.join(feasibility.clarifications_needed)}")
            
            # Step 4: Execute (if feasible and not dry run)
            execution_steps = []
            success = True
            
            if feasibility.is_feasible and not dry_run:
                print("\nStep 4: Executing actions...")
                for i, action in enumerate(grounded_actions, 1):
                    step = ExecutionStep(
                        step_id=i,
                        action=action,
                        status=ActionStatus.IN_PROGRESS,
                        start_time=time.time()
                    )
                    
                    try:
                        exec_success, result = action.execute()
                        step.status = ActionStatus.COMPLETED if exec_success else ActionStatus.FAILED
                        step.result = result
                        step.end_time = time.time()
                        print(f"    ✓ Step {i} completed")
                    except Exception as e:
                        step.status = ActionStatus.FAILED
                        step.error = str(e)
                        step.end_time = time.time()
                        success = False
                        print(f"    ✗ Step {i} failed: {e}")
                    
                    execution_steps.append(step)
            else:
                print("\nStep 4: Execution skipped (dry run or infeasible)")
                for i, action in enumerate(grounded_actions, 1):
                    step = ExecutionStep(
                        step_id=i,
                        action=action,
                        status=ActionStatus.PENDING
                    )
                    execution_steps.append(step)
            
            # Generate summary
            execution_time = time.time() - start_time
            
            if dry_run:
                summary = f"Dry run completed. {len(grounded_actions)} actions planned."
            elif not feasibility.is_feasible:
                summary = f"Instruction not feasible: {feasibility.level.value}"
            elif success:
                summary = f"Successfully executed {len(execution_steps)} actions."
            else:
                failed_steps = sum(1 for s in execution_steps if s.status == ActionStatus.FAILED)
                summary = f"Execution completed with {failed_steps} failures out of {len(execution_steps)} steps."
            
            result = ExecutionResult(
                instruction=parsed,
                feasibility=feasibility,
                execution_steps=execution_steps,
                success=success,
                summary=summary,
                execution_time=execution_time
            )
            
            self.execution_history.append(result)
            
            return result
    
    
    def demonstrate_instruction_following():
        """Demonstrate Instruction Following & Grounding pattern."""
        
        print("="*80)
        print("INSTRUCTION FOLLOWING & GROUNDING - DEMONSTRATION")
        print("="*80)
        
        agent = InstructionFollowingAgent()
        
        # Test 1: Simple action instruction
        print("\n" + "="*80)
        print("TEST 1: Simple Action Instruction")
        print("="*80)
        
        result1 = agent.execute_instruction("Pick up the red box and put it on the table.")
        
        print("\n--- EXECUTION RESULT ---")
        print(f"Success: {result1.success}")
        print(f"Summary: {result1.summary}")
        print(f"Execution Time: {result1.execution_time:.2f}s")
        print(f"Actions Executed: {len(result1.execution_steps)}")
        
        # Test 2: Instruction with constraints
        print("\n" + "="*80)
        print("TEST 2: Instruction with Constraints")
        print("="*80)
        
        result2 = agent.execute_instruction(
            "Search for documents about machine learning, but only look in the research folder "
            "and complete within 5 seconds."
        )
        
        print("\n--- PARSED INSTRUCTION ---")
        print(f"Intent: {result2.instruction.intent}")
        print(f"Type: {result2.instruction.instruction_type.value}")
        print(f"Parameters: {[(p.name, p.value) for p in result2.instruction.parameters]}")
        print(f"Constraints: {[(c.constraint_type, c.description) for c in result2.instruction.constraints]}")
        
        print("\n--- GROUNDED ACTIONS ---")
        for step in result2.execution_steps:
            print(f"Step {step.step_id}: {step.action.action_name}")
            print(f"  Parameters: {step.action.parameters}")
            print(f"  Preconditions: {step.action.preconditions}")
            print(f"  Effects: {step.action.effects}")
            print(f"  Status: {step.status.value}")
        
        # Test 3: Ambiguous instruction (dry run)
        print("\n" + "="*80)
        print("TEST 3: Ambiguous Instruction (Dry Run)")
        print("="*80)
        
        result3 = agent.execute_instruction(
            "Move the thing over there.",
            dry_run=True
        )
        
        print("\n--- AMBIGUITY ANALYSIS ---")
        if result3.instruction.ambiguities:
            print(f"Identified Ambiguities:")
            for amb in result3.instruction.ambiguities:
                print(f"  • {amb}")
        
        print("\n--- FEASIBILITY ASSESSMENT ---")
        print(f"Level: {result3.feasibility.level.value}")
        print(f"Is Feasible: {result3.feasibility.is_feasible}")
        if result3.feasibility.clarifications_needed:
            print(f"Clarifications Needed:")
            for clar in result3.feasibility.clarifications_needed:
                print(f"  • {clar}")
        if result3.feasibility.alternatives:
            print(f"Suggested Alternatives:")
            for alt in result3.feasibility.alternatives:
                print(f"  • {alt}")
        
        # Test 4: Complex multi-step instruction
        print("\n" + "="*80)
        print("TEST 4: Complex Multi-Step Instruction")
        print("="*80)
        
        result4 = agent.execute_instruction(
            "Open the file named 'report.txt', read its contents, calculate the sum of all numbers, "
            "and write the result to 'summary.txt'."
        )
        
        print("\n--- EXECUTION PLAN ---")
        total_duration = sum(step.action.estimated_duration for step in result4.execution_steps)
        print(f"Total Steps: {len(result4.execution_steps)}")
        print(f"Estimated Duration: {total_duration:.1f}s")
        print("\nStep-by-Step Plan:")
        for step in result4.execution_steps:
            print(f"  {step.step_id}. {step.action.action_name}")
            print(f"     Duration: {step.action.estimated_duration}s")
            print(f"     Effects: {', '.join(step.action.effects)}")
        
        print("\n--- EXECUTION SUMMARY ---")
        print(f"Status: {'✓ Success' if result4.success else '✗ Failed'}")
        print(f"Summary: {result4.summary}")
        
        # Test 5: Conditional instruction
        print("\n" + "="*80)
        print("TEST 5: Conditional Instruction")
        print("="*80)
        
        result5 = agent.execute_instruction(
            "If the temperature is above 25 degrees, open the window, otherwise turn on the heater.",
            dry_run=True
        )
        
        print("\n--- INSTRUCTION ANALYSIS ---")
        print(f"Type: {result5.instruction.instruction_type.value}")
        print(f"Intent: {result5.instruction.intent}")
        print(f"\nGrounded Actions: {len(result5.execution_steps)}")
        for step in result5.execution_steps:
            print(f"  • {step.action.action_name}: {step.action.parameters}")
        
        # Summary
        print("\n" + "="*80)
        print("SUMMARY: Instruction Following & Grounding")
        print("="*80)
        print(f"""
    The Instruction Following & Grounding pattern demonstrates precise instruction execution:
    
    Key Features Demonstrated:
    1. Instruction Parsing: Extracts intent, parameters, and constraints from natural language
    2. Action Grounding: Maps abstract instructions to concrete executable actions
    3. Feasibility Checking: Validates executability before attempting execution
    4. Ambiguity Detection: Identifies unclear or underspecified aspects
    5. Constraint Handling: Respects time, resource, and conditional constraints
    6. Execution Planning: Creates step-by-step execution plans with preconditions
    7. Execution Monitoring: Tracks progress and validates outcomes
    8. Transparent Feedback: Provides clear status updates and explanations
    
    Benefits:
    • Handles complex, multi-step instructions reliably
    • Grounds abstract concepts in concrete actions
    • Validates feasibility before execution
    • Identifies and resolves ambiguities
    • Provides transparent execution process
    • Handles errors gracefully with clear feedback
    • Respects constraints and preconditions
    • Learns from execution history
    
    Use Cases:
    • Robotic task execution from natural language commands
    • Virtual assistant command processing
    • Smart home automation with complex rules
    • Software automation and scripting from descriptions
    • Manufacturing instructions with safety constraints
    • Educational tutoring with adaptive instruction following
    • Accessibility tools for users with disabilities
    
    Comparison with Simple Command Parsing:
    ┌───────────────────────┬──────────────────┬─────────────────────┐
    │ Aspect                │ Simple Parsing   │ Full Grounding      │
    ├───────────────────────┼──────────────────┼─────────────────────┤
    │ Ambiguity Handling    │ Minimal          │ Comprehensive       │
    │ Constraint Validation │ None             │ Full validation     │
    │ Feasibility Checking  │ None             │ Pre-execution       │
    │ Action Grounding      │ Direct mapping   │ Context-aware       │
    │ Error Handling        │ Basic            │ Graceful with plans │
    │ Transparency          │ Low              │ High                │
    │ Complexity Handling   │ Limited          │ Excellent           │
    └───────────────────────┴──────────────────┴─────────────────────┘
    
    LangChain Implementation Notes:
    • Multiple specialized LLMs for parsing, grounding, and verification
    • Structured output parsing for instruction components
    • Feasibility assessment before execution
    • Step-by-step execution with monitoring
    • Execution history for learning and debugging
    • Dry run mode for planning without execution
    • Clear error messages and alternative suggestions
    
    Production Considerations:
    • Implement actual action execution layer (not simulated)
    • Add safety checks and permission systems
    • Implement rollback mechanisms for failed operations
    • Add interactive clarification for ambiguous instructions
    • Implement learning from user corrections
    • Add execution visualization for transparency
    • Implement cost estimation for resource-intensive operations
    • Add monitoring and logging for audit trails
    • Implement timeout and resource limit enforcement
    • Add support for cancellation and pause/resume
    • Implement state persistence for long-running tasks
    • Add multi-user coordination for shared resources
    
    Advanced Extensions:
    • Natural language feedback and explanation generation
    • Learning user preferences and implicit constraints
    • Proactive error prevention through predictive analysis
    • Multi-modal instruction understanding (speech, gesture, vision)
    • Collaborative instruction refinement with humans
    • Transfer learning from demonstration
    • Uncertainty-aware execution with confidence intervals
    • Hierarchical instruction decomposition for complex tasks
        """)
    
    
    if __name__ == "__main__":
        demonstrate_instruction_following()
    
        

    Agentic Design Pattern: Instruction Following

    Pattern 068: Instruction Following – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Instruction Following pattern implementation.


    Pattern Description

    Pattern 068: Instruction Following & Grounding

    Description: Instruction Following & Grounding enables agents to precisely understand and execute natural language instructions by breaking them down into structured, executable steps. This pattern goes beyond simple command parsing to deeply ground instructions in the agent’s capabilities, environment constraints, and task context. The agent validates instructions for feasibility, disambiguates ambiguous commands, grounds abstract concepts in concrete actions, and provides execution verification.

    The pattern is crucial for building reliable agents that can follow complex,
    multi-step instructions while maintaining safety, handling edge cases, and providing
    transparent feedback about what it's doing and why.
    

    Components: 1. Instruction Parser: Extracts intent, parameters, and constraints 2. Action Grounder: Maps abstract instructions to concrete actions 3. Feasibility Checker: Validates instruction executability 4. Disambiguator: Resolves ambiguities through clarification 5. Execution Planner: Creates step-by-step execution plan 6. Execution Monitor: Tracks execution progress and validates outcomes 7. Feedback Generator: Provides status updates and explanations

    Architecture: Natural Language Instruction ↓ Instruction Parser → [Intent, Parameters, Constraints] ↓ Disambiguator → [Clarified Instruction] ↓ Action Grounder → [Concrete Actions] ↓ Feasibility Checker → [Validation] ↓ Execution Planner → [Execution Steps] ↓ Execution Monitor → [Execute & Verify] ↓ Feedback Generator → [Status & Results]

    Use Cases: – Robotic task execution from natural language – Virtual assistant command processing – Smart home automation – Software automation and scripting – Manufacturing and logistics instructions – Educational tutoring systems

    Advantages: – Handles ambiguous and underspecified instructions – Grounds abstract concepts in concrete actions – Validates feasibility before execution – Provides transparent execution process – Handles errors and edge cases gracefully – Learns from execution feedback

    LangChain Implementation: Uses ChatOpenAI for instruction parsing, grounding, and planning. Demonstrates instruction decomposition, parameter extraction, feasibility checking, execution planning, and verification.


    File Statistics

    • Total Lines: 889
    • Classes: 11
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 068: Instruction Following & Grounding
    
    Description:
        Instruction Following & Grounding enables agents to precisely understand and execute
        natural language instructions by breaking them down into structured, executable steps.
        This pattern goes beyond simple command parsing to deeply ground instructions in the
        agent's capabilities, environment constraints, and task context. The agent validates
        instructions for feasibility, disambiguates ambiguous commands, grounds abstract
        concepts in concrete actions, and provides execution verification.
    
        The pattern is crucial for building reliable agents that can follow complex,
        multi-step instructions while maintaining safety, handling edge cases, and providing
        transparent feedback about what it's doing and why.
    
    Components:
        1. Instruction Parser: Extracts intent, parameters, and constraints
        2. Action Grounder: Maps abstract instructions to concrete actions
        3. Feasibility Checker: Validates instruction executability
        4. Disambiguator: Resolves ambiguities through clarification
        5. Execution Planner: Creates step-by-step execution plan
        6. Execution Monitor: Tracks execution progress and validates outcomes
        7. Feedback Generator: Provides status updates and explanations
    
    Architecture:
        ```
        Natural Language Instruction
            ↓
        Instruction Parser → [Intent, Parameters, Constraints]
            ↓
        Disambiguator → [Clarified Instruction]
            ↓
        Action Grounder → [Concrete Actions]
            ↓
        Feasibility Checker → [Validation]
            ↓
        Execution Planner → [Execution Steps]
            ↓
        Execution Monitor → [Execute & Verify]
            ↓
        Feedback Generator → [Status & Results]
        ```
    
    Use Cases:
        - Robotic task execution from natural language
        - Virtual assistant command processing
        - Smart home automation
        - Software automation and scripting
        - Manufacturing and logistics instructions
        - Educational tutoring systems
    
    Advantages:
        - Handles ambiguous and underspecified instructions
        - Grounds abstract concepts in concrete actions
        - Validates feasibility before execution
        - Provides transparent execution process
        - Handles errors and edge cases gracefully
        - Learns from execution feedback
    
    LangChain Implementation:
        Uses ChatOpenAI for instruction parsing, grounding, and planning.
        Demonstrates instruction decomposition, parameter extraction, feasibility
        checking, execution planning, and verification.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import enum.Enum
    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
    import typing.Tuple
    

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – enum.Enum: Enumeration support – 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) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: InstructionType

    Lines 80-87

    Inherits from: Enum

    Purpose:

    Types of instructions.
    

    Class: ActionStatus

    Lines 90-96

    Inherits from: Enum

    Purpose:

    Status of action execution.
    

    Class: FeasibilityLevel

    Lines 99-105

    Inherits from: Enum

    Purpose:

    Feasibility assessment levels.
    

    Class: Parameter

    Lines 109-115

    Purpose:

    Represents a parameter extracted from instruction.
    

    Class: Constraint

    Lines 119-123

    Purpose:

    Represents a constraint on instruction execution.
    

    Class: ParsedInstruction

    Lines 127-136

    Purpose:

    Represents a parsed natural language instruction.
    

    Class: GroundedAction

    Lines 140-154

    Purpose:

    Represents a grounded, executable action.
    

    Methods:

    execute(self)

    Lines 149-154

    Purpose:

    Simulate action execution.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D609C50>


    Class: ExecutionStep

    Lines 158-166

    Purpose:

    Represents a step in execution plan.
    

    Class: FeasibilityAssessment

    Lines 170-177

    Purpose:

    Assessment of instruction feasibility.
    

    Class: ExecutionResult

    Lines 181-188

    Purpose:

    Complete result of instruction execution.
    

    Class: InstructionFollowingAgent

    Lines 191-685

    Purpose:

    Agent that parses, grounds, and executes natural language instructions.
    

    Methods:

    __init__(self, agent_capabilities)

    Lines 196-227

    Purpose:

    Initialize the instruction following agent.
    
    Args:
        agent_capabilities: List of actions the agent can perform
    

    Parameters: – agent_capabilities: Agent capabilities

    parse_instruction(self, instruction_text)

    Lines 229-338

    Purpose:

    Parse natural language instruction into structured format.
    
    Args:
        instruction_text: Natural language instruction
    
    Returns:
        Parsed instruction with intent, parameters, and constraints
    

    Parameters: – instruction_text: Instruction text

    Returns: ParsedInstruction

    ground_instruction(self, parsed)

    Lines 340-465

    Purpose:

    Ground abstract instruction into concrete, executable actions.
    
    Args:
        parsed: Parsed instruction
    
    Returns:
        List of grounded actions
    

    Parameters: – parsed: Parsed

    Returns: <ast.Subscript object at 0x000001A06D77FED0>

    check_feasibility(self, parsed, grounded_actions)

    Lines 467-574

    Purpose:

    Check if instruction is feasible to execute.
    
    Args:
        parsed: Parsed instruction
        grounded_actions: Grounded actions
    
    Returns:
        Feasibility assessment
    

    Parameters: – parsed: Parsed – grounded_actions: Grounded actions

    Returns: FeasibilityAssessment

    execute_instruction(self, instruction_text, dry_run)

    Lines 576-685

    Purpose:

    Execute a natural language instruction end-to-end.
    
    Args:
        instruction_text: Natural language instruction
        dry_run: If True, plan but don't execute
    
    Returns:
        Complete execution result
    

    Parameters: – instruction_text: Instruction text – dry_run: Dry run

    Returns: ExecutionResult


    Functions

    demonstrate_instruction_following()

    Lines 688-884

    Purpose:

    Demonstrate Instruction Following & Grounding pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Instruction Following 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 067
    • Pattern 069

    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 Instruction Following 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: 068_instruction_following.py
    Total Lines: 889
    Pattern Category: AI Agent Design Pattern #068


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Agentic Rag

    
        """
    Pattern 067: Agentic RAG (Advanced)
    
    Description:
        Agentic RAG extends basic Retrieval-Augmented Generation with autonomous decision-making
        capabilities. Unlike simple RAG that retrieves once and generates, Agentic RAG employs
        sophisticated query planning, multi-hop reasoning, dynamic retrieval strategies, and source
        validation. The agent can decompose complex queries, determine when and what to retrieve,
        route queries to appropriate sources, validate retrieved information, and iteratively
        refine its understanding through multiple retrieval-reasoning cycles.
        
        This pattern transforms RAG from a passive information lookup into an active research
        process where the agent strategically explores knowledge bases, identifies gaps, verifies
        information across sources, and constructs well-grounded answers through deliberate
        reasoning chains.
    
    Components:
        1. Query Analyzer: Decomposes complex questions into sub-queries
        2. Query Router: Routes queries to appropriate retrieval sources
        3. Multi-Hop Reasoner: Follows chains of reasoning across retrieved documents
        4. Source Validator: Validates credibility and relevance of sources
        5. Retrieval Planner: Decides what to retrieve and when
        6. Answer Synthesizer: Combines information from multiple sources
        7. Confidence Estimator: Assesses answer quality and completeness
    
    Architecture:
        ```
        User Query
            ↓
        Query Analyzer → [Sub-queries]
            ↓
        Query Router → [Route to sources]
            ↓
        Retrieval Planner → [Iterative retrieval strategy]
            ↓
        Multi-Hop Reasoner → [Follow reasoning chains]
            ↓
        Source Validator → [Validate & filter]
            ↓
        Answer Synthesizer → [Combine & generate]
            ↓
        Confidence Estimator → [Quality check]
            ↓
        Final Answer (or iterate)
        ```
    
    Use Cases:
        - Complex research questions requiring multi-source synthesis
        - Scientific literature review and analysis
        - Legal document analysis with precedent lookup
        - Technical troubleshooting requiring multiple knowledge sources
        - Fact-checking and claim verification across sources
        - Exploratory data analysis with iterative refinement
    
    Advantages:
        - Handles complex, multi-part questions effectively
        - Validates information across multiple sources
        - Provides transparent reasoning chains
        - Adapts retrieval strategy based on findings
        - Reduces hallucination through source grounding
        - Identifies and fills knowledge gaps
    
    LangChain Implementation:
        Uses ChatOpenAI for LLM operations, custom query planning, routing logic,
        and iterative retrieval-reasoning loops. Demonstrates query decomposition,
        multi-source retrieval, validation chains, and answer synthesis.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Set, Tuple
    from dataclasses import dataclass, field
    from enum import Enum
    import time
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class QueryType(Enum):
        """Types of queries for routing."""
        FACTUAL = "factual"
        COMPARATIVE = "comparative"
        PROCEDURAL = "procedural"
        CAUSAL = "causal"
        EXPLORATORY = "exploratory"
    
    
    class SourceType(Enum):
        """Types of retrieval sources."""
        DOCUMENTS = "documents"
        WEB = "web"
        DATABASE = "database"
        API = "api"
        KNOWLEDGE_GRAPH = "knowledge_graph"
    
    
    class ConfidenceLevel(Enum):
        """Confidence in answer quality."""
        HIGH = "high"
        MEDIUM = "medium"
        LOW = "low"
        INSUFFICIENT = "insufficient"
    
    
    @dataclass
    class SubQuery:
        """Represents a decomposed sub-query."""
        query_id: str
        text: str
        query_type: QueryType
        dependencies: List[str] = field(default_factory=list)  # Other query IDs this depends on
        priority: int = 1
    
    
    @dataclass
    class RetrievalSource:
        """Represents a source for retrieval."""
        source_id: str
        source_type: SourceType
        name: str
        relevance_score: float = 0.0
        
        def retrieve(self, query: str) -> List[str]:
            """Simulate retrieval from this source."""
            # In real implementation, this would access actual data sources
            if self.source_type == SourceType.DOCUMENTS:
                return [
                    f"[{self.name}] Document excerpt about: {query}",
                    f"[{self.name}] Additional context: {query}"
                ]
            elif self.source_type == SourceType.WEB:
                return [f"[{self.name}] Web search result for: {query}"]
            elif self.source_type == SourceType.DATABASE:
                return [f"[{self.name}] Database record matching: {query}"]
            else:
                return [f"[{self.name}] Information about: {query}"]
    
    
    @dataclass
    class RetrievedDocument:
        """Represents a retrieved document with metadata."""
        doc_id: str
        content: str
        source: RetrievalSource
        relevance_score: float
        credibility_score: float = 0.0
        timestamp: float = field(default_factory=time.time)
    
    
    @dataclass
    class ReasoningStep:
        """Represents a step in multi-hop reasoning."""
        step_id: int
        question: str
        retrieved_docs: List[RetrievedDocument]
        reasoning: str
        answer: str
        confidence: float
    
    
    @dataclass
    class AgenticRAGResult:
        """Complete result from Agentic RAG."""
        query: str
        sub_queries: List[SubQuery]
        reasoning_chain: List[ReasoningStep]
        final_answer: str
        sources_used: List[RetrievalSource]
        confidence: ConfidenceLevel
        gaps_identified: List[str] = field(default_factory=list)
    
    
    class AgenticRAGAgent:
        """
        Advanced RAG agent with query planning, multi-hop reasoning, and validation.
        """
        
        def __init__(
            self,
            max_hops: int = 3,
            min_confidence: float = 0.7,
            temperature: float = 0.3
        ):
            """
            Initialize the Agentic RAG agent.
            
            Args:
                max_hops: Maximum number of retrieval-reasoning hops
                min_confidence: Minimum confidence threshold for answer
                temperature: Temperature for LLM generation
            """
            self.max_hops = max_hops
            self.min_confidence = min_confidence
            
            # Different LLMs for different tasks
            self.query_analyzer = ChatOpenAI(temperature=0.2, model="gpt-3.5-turbo")
            self.router = ChatOpenAI(temperature=0.1, model="gpt-3.5-turbo")
            self.reasoner = ChatOpenAI(temperature=temperature, model="gpt-3.5-turbo")
            self.validator = ChatOpenAI(temperature=0.1, model="gpt-3.5-turbo")
            self.synthesizer = ChatOpenAI(temperature=0.4, model="gpt-3.5-turbo")
            
            # Available sources (in real implementation, these would be actual data sources)
            self.sources = [
                RetrievalSource("src1", SourceType.DOCUMENTS, "Technical Documentation"),
                RetrievalSource("src2", SourceType.WEB, "Web Search"),
                RetrievalSource("src3", SourceType.DATABASE, "Knowledge Database"),
                RetrievalSource("src4", SourceType.KNOWLEDGE_GRAPH, "Entity Graph"),
            ]
            
            self.parser = StrOutputParser()
        
        def decompose_query(self, query: str) -> List[SubQuery]:
            """
            Decompose complex query into sub-queries.
            
            Args:
                query: The complex user query
                
            Returns:
                List of sub-queries with dependencies
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a query analyzer. Decompose complex questions into sub-queries.
    For each sub-query, identify:
    1. The specific question
    2. The type (factual, comparative, procedural, causal, exploratory)
    3. Dependencies on other sub-queries
    4. Priority (1=highest, 3=lowest)
    
    Format each sub-query as:
    Q[id]: [question] | Type: [type] | Depends: [comma-separated ids or 'none'] | Priority: [1-3]"""),
                ("user", "Decompose this query: {query}")
            ])
            
            chain = prompt | self.query_analyzer | self.parser
            
            try:
                result = chain.invoke({"query": query})
                
                # Parse the result into SubQuery objects
                sub_queries = []
                lines = [line.strip() for line in result.split('\n') if line.strip().startswith('Q')]
                
                for line in lines:
                    try:
                        # Parse format: Q1: What is X? | Type: factual | Depends: none | Priority: 1
                        parts = line.split('|')
                        if len(parts) >= 4:
                            # Extract query ID and text
                            q_part = parts[0].strip()
                            q_id = q_part.split(':')[0].strip()
                            q_text = ':'.join(q_part.split(':')[1:]).strip()
                            
                            # Extract type
                            type_str = parts[1].split(':')[1].strip().lower()
                            q_type = QueryType(type_str) if type_str in [t.value for t in QueryType] else QueryType.FACTUAL
                            
                            # Extract dependencies
                            deps_str = parts[2].split(':')[1].strip().lower()
                            dependencies = [] if deps_str == 'none' else [d.strip() for d in deps_str.split(',')]
                            
                            # Extract priority
                            priority = int(parts[3].split(':')[1].strip())
                            
                            sub_queries.append(SubQuery(
                                query_id=q_id,
                                text=q_text,
                                query_type=q_type,
                                dependencies=dependencies,
                                priority=priority
                            ))
                    except Exception as e:
                        print(f"Warning: Failed to parse sub-query line: {line}")
                        continue
                
                # If parsing failed, create a single sub-query
                if not sub_queries:
                    sub_queries = [SubQuery(
                        query_id="Q1",
                        text=query,
                        query_type=QueryType.EXPLORATORY,
                        dependencies=[],
                        priority=1
                    )]
                
                return sub_queries
                
            except Exception as e:
                print(f"Error in query decomposition: {e}")
                # Fallback to single query
                return [SubQuery(
                    query_id="Q1",
                    text=query,
                    query_type=QueryType.EXPLORATORY,
                    dependencies=[],
                    priority=1
                )]
        
        def route_query(self, sub_query: SubQuery) -> List[RetrievalSource]:
            """
            Route sub-query to appropriate sources.
            
            Args:
                sub_query: The sub-query to route
                
            Returns:
                Ranked list of sources to query
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a query router. Given a query and available sources, 
    rank which sources are most relevant (1-5 scale).
    
    Available sources:
    - Technical Documentation (detailed technical specs)
    - Web Search (general information, current events)
    - Knowledge Database (structured facts and entities)
    - Entity Graph (relationships between concepts)
    
    Format: [Source Name]: [score] - [brief reason]"""),
                ("user", "Query: {query}\nQuery Type: {query_type}\nRank the sources:")
            ])
            
            chain = prompt | self.router | self.parser
            
            try:
                result = chain.invoke({
                    "query": sub_query.text,
                    "query_type": sub_query.query_type.value
                })
                
                # Parse relevance scores and update sources
                scored_sources = []
                for source in self.sources:
                    if source.name in result:
                        # Extract score (look for patterns like "4" or "4/5")
                        import re
                        pattern = rf"{re.escape(source.name)}[:\s]+(\d+)"
                        match = re.search(pattern, result)
                        if match:
                            score = float(match.group(1)) / 5.0  # Normalize to 0-1
                            source.relevance_score = score
                            scored_sources.append(source)
                
                # Sort by relevance and return top sources
                scored_sources.sort(key=lambda x: x.relevance_score, reverse=True)
                return scored_sources[:2] if scored_sources else self.sources[:2]
                
            except Exception as e:
                print(f"Error in query routing: {e}")
                # Default to all sources
                return self.sources[:2]
        
        def retrieve_documents(
            self,
            sub_query: SubQuery,
            sources: List[RetrievalSource]
        ) -> List[RetrievedDocument]:
            """
            Retrieve documents from sources.
            
            Args:
                sub_query: The query to retrieve for
                sources: Sources to retrieve from
                
            Returns:
                List of retrieved documents
            """
            documents = []
            
            for source in sources:
                # Retrieve from source (simulated)
                contents = source.retrieve(sub_query.text)
                
                for i, content in enumerate(contents):
                    doc = RetrievedDocument(
                        doc_id=f"{source.source_id}_doc_{i}",
                        content=content,
                        source=source,
                        relevance_score=source.relevance_score * (1.0 - i * 0.1)  # Decay for lower results
                    )
                    documents.append(doc)
            
            # Sort by relevance
            documents.sort(key=lambda x: x.relevance_score, reverse=True)
            return documents
        
        def validate_sources(self, documents: List[RetrievedDocument]) -> List[RetrievedDocument]:
            """
            Validate credibility and consistency of retrieved documents.
            
            Args:
                documents: Documents to validate
                
            Returns:
                Validated documents with credibility scores
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a source validator. Assess the credibility of information.
    Consider:
    1. Source authority
    2. Information consistency across sources
    3. Specificity and detail level
    4. Potential biases
    
    Rate credibility 0.0-1.0 for each source and briefly explain."""),
                ("user", "Validate these sources:\n{sources}")
            ])
            
            chain = prompt | self.validator | self.parser
            
            try:
                # Prepare sources text
                sources_text = "\n".join([
                    f"- {doc.source.name}: {doc.content[:200]}..."
                    for doc in documents[:3]  # Validate top 3
                ])
                
                result = chain.invoke({"sources": sources_text})
                
                # Parse credibility scores (simple heuristic)
                for doc in documents:
                    # Higher credibility for documentation and databases
                    if doc.source.source_type in [SourceType.DOCUMENTS, SourceType.DATABASE]:
                        doc.credibility_score = 0.9
                    elif doc.source.source_type == SourceType.KNOWLEDGE_GRAPH:
                        doc.credibility_score = 0.85
                    else:
                        doc.credibility_score = 0.7
                
                # Filter low credibility documents
                validated_docs = [doc for doc in documents if doc.credibility_score >= 0.6]
                return validated_docs
                
            except Exception as e:
                print(f"Error in source validation: {e}")
                # Default: assign medium credibility to all
                for doc in documents:
                    doc.credibility_score = 0.75
                return documents
        
        def multi_hop_reasoning(
            self,
            sub_queries: List[SubQuery],
            context: Dict[str, str] = None
        ) -> List[ReasoningStep]:
            """
            Perform multi-hop reasoning across sub-queries.
            
            Args:
                sub_queries: Sub-queries to reason over
                context: Context from previous reasoning steps
                
            Returns:
                List of reasoning steps
            """
            reasoning_steps = []
            context = context or {}
            
            # Sort by priority and dependencies
            sorted_queries = sorted(sub_queries, key=lambda q: (q.priority, len(q.dependencies)))
            
            for step_id, sub_query in enumerate(sorted_queries, 1):
                # Check if dependencies are met
                if sub_query.dependencies:
                    missing_deps = [dep for dep in sub_query.dependencies if dep not in context]
                    if missing_deps:
                        print(f"Warning: Skipping {sub_query.query_id} due to missing dependencies: {missing_deps}")
                        continue
                
                # Route and retrieve
                sources = self.route_query(sub_query)
                documents = self.retrieve_documents(sub_query, sources)
                validated_docs = self.validate_sources(documents)
                
                # Build context from previous answers
                prev_context = "\n".join([
                    f"{qid}: {ans}"
                    for qid, ans in context.items()
                ])
                
                # Reason over retrieved documents
                prompt = ChatPromptTemplate.from_messages([
                    ("system", """You are a reasoning agent. Answer the question using retrieved documents.
    Explain your reasoning step-by-step and provide a clear answer.
    If information is insufficient, state what's missing."""),
                    ("user", """Previous context:
    {prev_context}
    
    Current question: {question}
    
    Retrieved information:
    {documents}
    
    Provide:
    1. Your reasoning
    2. Your answer
    3. Confidence (0.0-1.0)
    
    Format:
    Reasoning: [your reasoning]
    Answer: [your answer]
    Confidence: [0.0-1.0]""")
                ])
                
                chain = prompt | self.reasoner | self.parser
                
                try:
                    docs_text = "\n".join([
                        f"- [{doc.source.name}] {doc.content}"
                        for doc in validated_docs[:3]
                    ])
                    
                    result = chain.invoke({
                        "prev_context": prev_context if prev_context else "None",
                        "question": sub_query.text,
                        "documents": docs_text
                    })
                    
                    # Parse reasoning, answer, and confidence
                    reasoning = ""
                    answer = ""
                    confidence = 0.5
                    
                    for line in result.split('\n'):
                        if line.startswith("Reasoning:"):
                            reasoning = line.replace("Reasoning:", "").strip()
                        elif line.startswith("Answer:"):
                            answer = line.replace("Answer:", "").strip()
                        elif line.startswith("Confidence:"):
                            try:
                                confidence = float(line.replace("Confidence:", "").strip())
                            except:
                                confidence = 0.5
                    
                    if not answer:
                        answer = result.split('\n')[0]  # Fallback to first line
                    
                    reasoning_step = ReasoningStep(
                        step_id=step_id,
                        question=sub_query.text,
                        retrieved_docs=validated_docs,
                        reasoning=reasoning if reasoning else "Analyzed retrieved documents.",
                        answer=answer,
                        confidence=confidence
                    )
                    
                    reasoning_steps.append(reasoning_step)
                    context[sub_query.query_id] = answer
                    
                except Exception as e:
                    print(f"Error in reasoning for {sub_query.query_id}: {e}")
                    continue
            
            return reasoning_steps
        
        def synthesize_answer(
            self,
            query: str,
            reasoning_steps: List[ReasoningStep]
        ) -> Tuple[str, ConfidenceLevel, List[str]]:
            """
            Synthesize final answer from reasoning steps.
            
            Args:
                query: Original user query
                reasoning_steps: All reasoning steps
                
            Returns:
                Tuple of (final answer, confidence level, identified gaps)
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an answer synthesizer. Combine insights from multiple reasoning steps
    into a comprehensive, coherent answer. Identify any gaps in knowledge.
    
    Provide:
    1. Synthesized answer
    2. Overall confidence (high/medium/low/insufficient)
    3. Any gaps or limitations
    
    Format:
    Answer: [comprehensive answer]
    Confidence: [high/medium/low/insufficient]
    Gaps: [identified gaps or 'none']"""),
                ("user", """Original query: {query}
    
    Reasoning steps:
    {steps}
    
    Synthesize the final answer:""")
            ])
            
            chain = prompt | self.synthesizer | self.parser
            
            try:
                steps_text = "\n".join([
                    f"Step {step.step_id}: {step.question}\n  Reasoning: {step.reasoning}\n  Answer: {step.answer}\n  Confidence: {step.confidence}"
                    for step in reasoning_steps
                ])
                
                result = chain.invoke({
                    "query": query,
                    "steps": steps_text
                })
                
                # Parse result
                answer = ""
                confidence = ConfidenceLevel.MEDIUM
                gaps = []
                
                for line in result.split('\n'):
                    if line.startswith("Answer:"):
                        answer = line.replace("Answer:", "").strip()
                        # Continue reading multi-line answer
                        answer_lines = [answer]
                        for next_line in result.split('\n')[result.split('\n').index(line)+1:]:
                            if next_line.startswith("Confidence:") or next_line.startswith("Gaps:"):
                                break
                            if next_line.strip():
                                answer_lines.append(next_line.strip())
                        answer = " ".join(answer_lines)
                    elif line.startswith("Confidence:"):
                        conf_str = line.replace("Confidence:", "").strip().lower()
                        if "high" in conf_str:
                            confidence = ConfidenceLevel.HIGH
                        elif "low" in conf_str:
                            confidence = ConfidenceLevel.LOW
                        elif "insufficient" in conf_str:
                            confidence = ConfidenceLevel.INSUFFICIENT
                        else:
                            confidence = ConfidenceLevel.MEDIUM
                    elif line.startswith("Gaps:"):
                        gaps_str = line.replace("Gaps:", "").strip()
                        if gaps_str.lower() not in ['none', 'no gaps', '']:
                            gaps = [g.strip() for g in gaps_str.split(',')]
                
                if not answer:
                    answer = result.split('\n')[0]  # Fallback
                
                return answer, confidence, gaps
                
            except Exception as e:
                print(f"Error in answer synthesis: {e}")
                # Fallback: combine all reasoning step answers
                combined = " ".join([step.answer for step in reasoning_steps])
                avg_confidence = sum(step.confidence for step in reasoning_steps) / len(reasoning_steps) if reasoning_steps else 0.5
                
                if avg_confidence >= 0.8:
                    conf_level = ConfidenceLevel.HIGH
                elif avg_confidence >= 0.6:
                    conf_level = ConfidenceLevel.MEDIUM
                elif avg_confidence >= 0.4:
                    conf_level = ConfidenceLevel.LOW
                else:
                    conf_level = ConfidenceLevel.INSUFFICIENT
                
                return combined, conf_level, ["Error in synthesis process"]
        
        def query(self, user_query: str) -> AgenticRAGResult:
            """
            Process a query through the full Agentic RAG pipeline.
            
            Args:
                user_query: The user's question
                
            Returns:
                Complete Agentic RAG result with reasoning chain
            """
            print(f"\n{'='*60}")
            print(f"Processing Query: {user_query}")
            print(f"{'='*60}\n")
            
            # Step 1: Decompose query
            print("Step 1: Decomposing query...")
            sub_queries = self.decompose_query(user_query)
            print(f"  → Generated {len(sub_queries)} sub-queries")
            for sq in sub_queries:
                print(f"    • {sq.query_id}: {sq.text} (Type: {sq.query_type.value}, Priority: {sq.priority})")
            
            # Step 2: Multi-hop reasoning
            print("\nStep 2: Multi-hop reasoning...")
            reasoning_steps = self.multi_hop_reasoning(sub_queries)
            print(f"  → Completed {len(reasoning_steps)} reasoning steps")
            
            # Step 3: Synthesize answer
            print("\nStep 3: Synthesizing final answer...")
            final_answer, confidence, gaps = self.synthesize_answer(user_query, reasoning_steps)
            print(f"  → Confidence: {confidence.value}")
            
            # Collect all sources used
            sources_used = set()
            for step in reasoning_steps:
                for doc in step.retrieved_docs:
                    sources_used.add(doc.source)
            
            return AgenticRAGResult(
                query=user_query,
                sub_queries=sub_queries,
                reasoning_chain=reasoning_steps,
                final_answer=final_answer,
                sources_used=list(sources_used),
                confidence=confidence,
                gaps_identified=gaps
            )
    
    
    def demonstrate_agentic_rag():
        """Demonstrate Agentic RAG pattern with various scenarios."""
        
        print("="*80)
        print("AGENTIC RAG (ADVANCED) - DEMONSTRATION")
        print("="*80)
        
        agent = AgenticRAGAgent(max_hops=3, min_confidence=0.7)
        
        # Test 1: Multi-part complex query
        print("\n" + "="*80)
        print("TEST 1: Complex Multi-Part Query")
        print("="*80)
        
        result1 = agent.query(
            "What is the difference between machine learning and deep learning, "
            "and which one should I use for image classification?"
        )
        
        print("\n--- RESULT ---")
        print(f"Final Answer: {result1.final_answer}")
        print(f"Confidence: {result1.confidence.value}")
        print(f"Sources Used: {[s.name for s in result1.sources_used]}")
        print(f"Sub-queries: {len(result1.sub_queries)}")
        print(f"Reasoning Steps: {len(result1.reasoning_chain)}")
        if result1.gaps_identified:
            print(f"Gaps Identified: {', '.join(result1.gaps_identified)}")
        
        # Test 2: Causal query requiring multi-hop reasoning
        print("\n" + "="*80)
        print("TEST 2: Causal Query with Multi-Hop Reasoning")
        print("="*80)
        
        result2 = agent.query(
            "Why do neural networks need activation functions, and what happens "
            "if we remove them?"
        )
        
        print("\n--- REASONING CHAIN ---")
        for step in result2.reasoning_chain:
            print(f"\nStep {step.step_id}: {step.question}")
            print(f"  Reasoning: {step.reasoning}")
            print(f"  Answer: {step.answer}")
            print(f"  Confidence: {step.confidence:.2f}")
            print(f"  Sources: {[doc.source.name for doc in step.retrieved_docs[:2]]}")
        
        print("\n--- FINAL ANSWER ---")
        print(result2.final_answer)
        print(f"Overall Confidence: {result2.confidence.value}")
        
        # Test 3: Comparative analysis
        print("\n" + "="*80)
        print("TEST 3: Comparative Analysis Query")
        print("="*80)
        
        result3 = agent.query(
            "Compare the advantages and disadvantages of SQL vs NoSQL databases "
            "for a high-traffic social media application."
        )
        
        print("\n--- QUERY DECOMPOSITION ---")
        for sq in result3.sub_queries:
            print(f"{sq.query_id}: {sq.text}")
            print(f"  Type: {sq.query_type.value}")
            print(f"  Priority: {sq.priority}")
            if sq.dependencies:
                print(f"  Depends on: {', '.join(sq.dependencies)}")
        
        print("\n--- FINAL ANSWER ---")
        print(result3.final_answer)
        print(f"\nSources consulted: {[s.name for s in result3.sources_used]}")
        print(f"Confidence: {result3.confidence.value}")
        
        # Summary
        print("\n" + "="*80)
        print("SUMMARY: Agentic RAG (Advanced)")
        print("="*80)
        print("""
    The Agentic RAG pattern demonstrates advanced information retrieval and reasoning:
    
    Key Features Demonstrated:
    1. Query Decomposition: Breaks complex queries into manageable sub-queries
    2. Intelligent Routing: Routes queries to appropriate specialized sources
    3. Multi-Hop Reasoning: Follows chains of logic across multiple retrieval steps
    4. Source Validation: Assesses credibility and consistency of information
    5. Answer Synthesis: Combines insights from multiple sources coherently
    6. Confidence Estimation: Provides transparency about answer quality
    7. Gap Identification: Identifies missing information or limitations
    
    Benefits:
    • Handles complex, multi-faceted questions effectively
    • Provides transparent reasoning chains for interpretability
    • Validates information across sources to reduce hallucination
    • Adapts retrieval strategy based on intermediate findings
    • Identifies knowledge gaps and uncertainty explicitly
    • Grounds answers in retrieved sources with citations
    
    Use Cases:
    • Research and literature review
    • Technical troubleshooting and diagnosis
    • Comparative analysis and decision support
    • Fact-checking and claim verification
    • Legal and regulatory document analysis
    • Medical diagnosis support (with appropriate validation)
    
    Comparison with Basic RAG:
    ┌─────────────────────┬──────────────────┬────────────────────┐
    │ Aspect              │ Basic RAG        │ Agentic RAG        │
    ├─────────────────────┼──────────────────┼────────────────────┤
    │ Query Processing    │ Single-shot      │ Decomposed         │
    │ Retrieval Strategy  │ One-time lookup  │ Iterative/adaptive │
    │ Reasoning           │ Direct answer    │ Multi-hop chains   │
    │ Source Handling     │ Single source    │ Multi-source       │
    │ Validation          │ Minimal          │ Comprehensive      │
    │ Transparency        │ Low              │ High               │
    │ Complexity Handling │ Limited          │ Excellent          │
    └─────────────────────┴──────────────────┴────────────────────┘
    
    LangChain Implementation Notes:
    • Multiple specialized LLMs for different tasks (analysis, routing, reasoning)
    • Custom query decomposition and dependency resolution
    • Source routing based on query type and content
    • Iterative retrieval-reasoning loops with context accumulation
    • Validation chains for credibility assessment
    • Synthesis prompts for coherent answer generation
    • Confidence estimation through multi-model assessment
    
    Production Considerations:
    • Implement actual vector stores and retrieval systems
    • Add caching for repeated queries and sub-queries
    • Implement proper source credibility scoring
    • Add cost controls for multi-hop operations
    • Monitor and limit reasoning depth to prevent loops
    • Implement proper error handling and fallbacks
    • Add human-in-the-loop for critical decisions
    • Track and optimize retrieval quality metrics
    
    Advanced Extensions:
    • Adaptive retrieval depth based on query complexity
    • Cross-lingual retrieval and reasoning
    • Temporal reasoning for time-sensitive queries
    • Graph-based knowledge integration
    • Active learning from user feedback
    • Explanation generation for reasoning steps
    • Multi-modal retrieval (text, images, structured data)
        """)
    
    
    if __name__ == "__main__":
        demonstrate_agentic_rag()
    
        

    Agentic Design Pattern: Agentic Rag

    Pattern 067: Agentic Rag – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Agentic Rag pattern implementation.


    Pattern Description

    Pattern 067: Agentic RAG (Advanced)

    Description: Agentic RAG extends basic Retrieval-Augmented Generation with autonomous decision-making capabilities. Unlike simple RAG that retrieves once and generates, Agentic RAG employs sophisticated query planning, multi-hop reasoning, dynamic retrieval strategies, and source validation. The agent can decompose complex queries, determine when and what to retrieve, route queries to appropriate sources, validate retrieved information, and iteratively refine its understanding through multiple retrieval-reasoning cycles.

    This pattern transforms RAG from a passive information lookup into an active research
    process where the agent strategically explores knowledge bases, identifies gaps, verifies
    information across sources, and constructs well-grounded answers through deliberate
    reasoning chains.
    

    Components: 1. Query Analyzer: Decomposes complex questions into sub-queries 2. Query Router: Routes queries to appropriate retrieval sources 3. Multi-Hop Reasoner: Follows chains of reasoning across retrieved documents 4. Source Validator: Validates credibility and relevance of sources 5. Retrieval Planner: Decides what to retrieve and when 6. Answer Synthesizer: Combines information from multiple sources 7. Confidence Estimator: Assesses answer quality and completeness

    Architecture: User Query ↓ Query Analyzer → [Sub-queries] ↓ Query Router → [Route to sources] ↓ Retrieval Planner → [Iterative retrieval strategy] ↓ Multi-Hop Reasoner → [Follow reasoning chains] ↓ Source Validator → [Validate & filter] ↓ Answer Synthesizer → [Combine & generate] ↓ Confidence Estimator → [Quality check] ↓ Final Answer (or iterate)

    Use Cases: – Complex research questions requiring multi-source synthesis – Scientific literature review and analysis – Legal document analysis with precedent lookup – Technical troubleshooting requiring multiple knowledge sources – Fact-checking and claim verification across sources – Exploratory data analysis with iterative refinement

    Advantages: – Handles complex, multi-part questions effectively – Validates information across multiple sources – Provides transparent reasoning chains – Adapts retrieval strategy based on findings – Reduces hallucination through source grounding – Identifies and fills knowledge gaps

    LangChain Implementation: Uses ChatOpenAI for LLM operations, custom query planning, routing logic, and iterative retrieval-reasoning loops. Demonstrates query decomposition, multi-source retrieval, validation chains, and answer synthesis.


    File Statistics

    • Total Lines: 860
    • Classes: 9
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 067: Agentic RAG (Advanced)
    
    Description:
        Agentic RAG extends basic Retrieval-Augmented Generation with autonomous decision-making
        capabilities. Unlike simple RAG that retrieves once and generates, Agentic RAG employs
        sophisticated query planning, multi-hop reasoning, dynamic retrieval strategies, and source
        validation. The agent can decompose complex queries, determine when and what to retrieve,
        route queries to appropriate sources, validate retrieved information, and iteratively
        refine its understanding through multiple retrieval-reasoning cycles.
    
        This pattern transforms RAG from a passive information lookup into an active research
        process where the agent strategically explores knowledge bases, identifies gaps, verifies
        information across sources, and constructs well-grounded answers through deliberate
        reasoning chains.
    
    Components:
        1. Query Analyzer: Decomposes complex questions into sub-queries
        2. Query Router: Routes queries to appropriate retrieval sources
        3. Multi-Hop Reasoner: Follows chains of reasoning across retrieved documents
        4. Source Validator: Validates credibility and relevance of sources
        5. Retrieval Planner: Decides what to retrieve and when
        6. Answer Synthesizer: Combines information from multiple sources
        7. Confidence Estimator: Assesses answer quality and completeness
    
    Architecture:
        ```
        User Query
            ↓
        Query Analyzer → [Sub-queries]
            ↓
        Query Router → [Route to sources]
            ↓
        Retrieval Planner → [Iterative retrieval strategy]
            ↓
        Multi-Hop Reasoner → [Follow reasoning chains]
            ↓
        Source Validator → [Validate & filter]
            ↓
        Answer Synthesizer → [Combine & generate]
            ↓
        Confidence Estimator → [Quality check]
            ↓
        Final Answer (or iterate)
        ```
    
    Use Cases:
        - Complex research questions requiring multi-source synthesis
        - Scientific literature review and analysis
        - Legal document analysis with precedent lookup
        - Technical troubleshooting requiring multiple knowledge sources
        - Fact-checking and claim verification across sources
        - Exploratory data analysis with iterative refinement
    
    Advantages:
        - Handles complex, multi-part questions effectively
        - Validates information across multiple sources
        - Provides transparent reasoning chains
        - Adapts retrieval strategy based on findings
        - Reduces hallucination through source grounding
        - Identifies and fills knowledge gaps
    
    LangChain Implementation:
        Uses ChatOpenAI for LLM operations, custom query planning, routing logic,
        and iterative retrieval-reasoning loops. Demonstrates query decomposition,
        multi-source retrieval, validation chains, and answer synthesis.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import enum.Enum
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import re
    import time
    import typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Set
    import typing.Tuple
    

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – re: Regular expression 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) – typing.Set: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: QueryType

    Lines 83-89

    Inherits from: Enum

    Purpose:

    Types of queries for routing.
    

    Class: SourceType

    Lines 92-98

    Inherits from: Enum

    Purpose:

    Types of retrieval sources.
    

    Class: ConfidenceLevel

    Lines 101-106

    Inherits from: Enum

    Purpose:

    Confidence in answer quality.
    

    Class: SubQuery

    Lines 110-116

    Purpose:

    Represents a decomposed sub-query.
    

    Class: RetrievalSource

    Lines 120-140

    Purpose:

    Represents a source for retrieval.
    

    Methods:

    retrieve(self, query)

    Lines 127-140

    Purpose:

    Simulate retrieval from this source.
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D749090>


    Class: RetrievedDocument

    Lines 144-151

    Purpose:

    Represents a retrieved document with metadata.
    

    Class: ReasoningStep

    Lines 155-162

    Purpose:

    Represents a step in multi-hop reasoning.
    

    Class: AgenticRAGResult

    Lines 166-174

    Purpose:

    Complete result from Agentic RAG.
    

    Class: AgenticRAGAgent

    Lines 177-707

    Purpose:

    Advanced RAG agent with query planning, multi-hop reasoning, and validation.
    

    Methods:

    __init__(self, max_hops, min_confidence, temperature)

    Lines 182-214

    Purpose:

    Initialize the Agentic RAG agent.
    
    Args:
        max_hops: Maximum number of retrieval-reasoning hops
        min_confidence: Minimum confidence threshold for answer
        temperature: Temperature for LLM generation
    

    Parameters: – max_hops: Max hops – min_confidence: Min confidence – temperature: Sampling temperature (0-1)

    decompose_query(self, query)

    Lines 216-301

    Purpose:

    Decompose complex query into sub-queries.
    
    Args:
        query: The complex user query
    
    Returns:
        List of sub-queries with dependencies
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D7F5DD0>

    route_query(self, sub_query)

    Lines 303-355

    Purpose:

    Route sub-query to appropriate sources.
    
    Args:
        sub_query: The sub-query to route
    
    Returns:
        Ranked list of sources to query
    

    Parameters: – sub_query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D73A950>

    retrieve_documents(self, sub_query, sources)

    Lines 357-389

    Purpose:

    Retrieve documents from sources.
    
    Args:
        sub_query: The query to retrieve for
        sources: Sources to retrieve from
    
    Returns:
        List of retrieved documents
    

    Parameters: – sub_query: Input query or question – sources: Sources

    Returns: <ast.Subscript object at 0x000001A06D606550>

    validate_sources(self, documents)

    Lines 391-443

    Purpose:

    Validate credibility and consistency of retrieved documents.
    
    Args:
        documents: Documents to validate
    
    Returns:
        Validated documents with credibility scores
    

    Parameters: – documents: Documents

    Returns: <ast.Subscript object at 0x000001A06D65A5D0>

    multi_hop_reasoning(self, sub_queries, context)

    Lines 445-558

    Purpose:

    Perform multi-hop reasoning across sub-queries.
    
    Args:
        sub_queries: Sub-queries to reason over
        context: Context from previous reasoning steps
    
    Returns:
        List of reasoning steps
    

    Parameters: – sub_queries: Sub queries – context: Context

    Returns: <ast.Subscript object at 0x000001A06D15DDD0>

    synthesize_answer(self, query, reasoning_steps)

    Lines 560-660

    Purpose:

    Synthesize final answer from reasoning steps.
    
    Args:
        query: Original user query
        reasoning_steps: All reasoning steps
    
    Returns:
        Tuple of (final answer, confidence level, identified gaps)
    

    Parameters: – query: Input query or question – reasoning_steps: Reasoning steps

    Returns: <ast.Subscript object at 0x000001A06D7E6510>

    query(self, user_query)

    Lines 662-707

    Purpose:

    Process a query through the full Agentic RAG pipeline.
    
    Args:
        user_query: The user's question
    
    Returns:
        Complete Agentic RAG result with reasoning chain
    

    Parameters: – user_query: Input query or question

    Returns: AgenticRAGResult


    Functions

    demonstrate_agentic_rag()

    Lines 710-855

    Purpose:

    Demonstrate Agentic RAG pattern with various scenarios.
    

    Key Concepts Summary

    Pattern Overview

    The Agentic Rag 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 066
    • Pattern 068

    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 Agentic Rag 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: 067_agentic_rag.py
    Total Lines: 860
    Pattern Category: AI Agent Design Pattern #067


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Embodied Agent

    
        """
    Pattern 066: Embodied Agent
    
    Description:
        Embodied agents have a physical or virtual body that exists in an environment,
        with sensors for perception and actuators for action. Embodiment provides grounded
        understanding of the world through sensorimotor interaction, spatial reasoning,
        and physical constraints. This pattern bridges abstract reasoning with physical reality.
    
    Components:
        1. Sensory System: Vision, hearing, touch, proprioception
        2. Motor System: Movement, manipulation, locomotion
        3. Body Schema: Internal model of body and capabilities
        4. Spatial Reasoning: Navigation, object relations, distance estimation
        5. Affordance Detection: Understanding what actions are possible
        6. Sensorimotor Integration: Coordinating perception and action
    
    Use Cases:
        - Robotics (physical embodiment)
        - Virtual agents in simulations
        - Embodied AI assistants
        - Game NPCs with physical presence
        - Virtual reality interactions
        - Drone navigation
    
    LangChain Implementation:
        Implements embodied cognition using LLM-based spatial reasoning,
        action planning with physical constraints, and grounded perception.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    import math
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class SensorType(Enum):
        """Types of sensors"""
        VISION = "vision"
        HEARING = "hearing"
        TOUCH = "touch"
        PROPRIOCEPTION = "proprioception"  # Body position
        DISTANCE = "distance"  # Range sensor
    
    
    class ActionType(Enum):
        """Types of actions"""
        MOVE = "move"
        TURN = "turn"
        GRASP = "grasp"
        RELEASE = "release"
        LOOK = "look"
        SPEAK = "speak"
    
    
    @dataclass
    class Position:
        """3D position"""
        x: float
        y: float
        z: float = 0.0
        
        def distance_to(self, other: 'Position') -> float:
            """Calculate Euclidean distance"""
            return math.sqrt(
                (self.x - other.x)**2 +
                (self.y - other.y)**2 +
                (self.z - other.z)**2
            )
        
        def __str__(self) -> str:
            if self.z == 0:
                return f"({self.x:.1f}, {self.y:.1f})"
            return f"({self.x:.1f}, {self.y:.1f}, {self.z:.1f})"
    
    
    @dataclass
    class Perception:
        """Sensory perception"""
        sensor: SensorType
        data: Any
        timestamp: float = field(default_factory=time.time)
        confidence: float = 1.0
    
    
    @dataclass
    class PhysicalObject:
        """Object in environment"""
        object_id: str
        name: str
        position: Position
        size: Tuple[float, float, float]  # width, height, depth
        properties: Dict[str, Any] = field(default_factory=dict)
        
        def is_reachable(self, agent_position: Position, reach_distance: float) -> bool:
            """Check if object is within reach"""
            return agent_position.distance_to(self.position)  Perception:
            """Perceive environment through sensors"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are processing sensory information from a {sensor.value} sensor.
    
    Current body state:
    - Position: {self.body.position}
    - Orientation: {self.body.orientation}°
    
    Extract relevant information from the environment description."""),
                ("user", "Environment: {environment}\n\nWhat do you perceive?")
            ])
            
            chain = prompt | self.cognition | StrOutputParser()
            perception_data = chain.invoke({"environment": environment_description})
            
            perception = Perception(
                sensor=sensor,
                data=perception_data,
                confidence=0.85
            )
            
            self.perceptions.append(perception)
            return perception
        
        def identify_affordances(
            self,
            obj: PhysicalObject
        ) -> List[str]:
            """Identify what actions are possible with object"""
            
            # Check physical constraints
            is_reachable = obj.is_reachable(self.body.position, self.reach_distance)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Identify affordances (possible actions) for this object.
    
    Affordances are action possibilities based on physical properties.
    
    Consider:
    1. Object properties
    2. Physical reachability
    3. Agent capabilities
    4. Current state"""),
                ("user", """Object: {name}
    Size: {size}
    Properties: {properties}
    Reachable: {reachable}
    Agent holding: {holding}
    
    What actions are possible?""")
            ])
            
            chain = prompt | self.spatial_reasoner | StrOutputParser()
            affordances_text = chain.invoke({
                "name": obj.name,
                "size": f"{obj.size[0]}×{obj.size[1]}×{obj.size[2]}",
                "properties": str(obj.properties),
                "reachable": "Yes" if is_reachable else "No",
                "holding": self.body.holding_object or "Nothing"
            })
            
            # Parse affordances
            affordances = []
            for line in affordances_text.split('\n'):
                line = line.strip()
                if line and (line.startswith('-') or line.startswith('•')):
                    action = line.lstrip('-•').strip()
                    if action:
                        affordances.append(action)
            
            return affordances
        
        def plan_navigation(
            self,
            target_position: Position,
            obstacles: List[PhysicalObject]
        ) -> List[Dict[str, Any]]:
            """Plan navigation path considering obstacles"""
            
            current = self.body.position
            distance = current.distance_to(target_position)
            
            # Build obstacle description
            obstacle_desc = ""
            if obstacles:
                obstacle_desc = "Obstacles:\n"
                for obs in obstacles:
                    dist = current.distance_to(obs.position)
                    obstacle_desc += f"- {obs.name} at {obs.position} (distance: {dist:.1f}m)\n"
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Plan a navigation path from current position to target.
    
    Consider:
    1. Obstacles to avoid
    2. Efficient path
    3. Physical constraints (can't move through walls)
    
    Provide step-by-step movement plan."""),
                ("user", """Current position: {current}
    Target position: {target}
    Distance: {distance:.1f}m
    
    {obstacles}
    
    Navigation plan:""")
            ])
            
            chain = prompt | self.spatial_reasoner | StrOutputParser()
            plan_text = chain.invoke({
                "current": str(current),
                "target": str(target_position),
                "distance": distance,
                "obstacles": obstacle_desc
            })
            
            # Parse plan into actions
            actions = []
            for i, line in enumerate(plan_text.split('\n'), 1):
                line = line.strip()
                if line and (line[0].isdigit() or line.startswith('-')):
                    action_text = line.lstrip('0123456789.-) ').strip()
                    if action_text:
                        actions.append({
                            "step": i,
                            "action": "move",
                            "description": action_text
                        })
            
            return actions
        
        def execute_action(
            self,
            action_type: ActionType,
            parameters: Dict[str, Any]
        ) -> Dict[str, Any]:
            """Execute physical action"""
            
            result = {
                "action": action_type.value,
                "success": False,
                "message": ""
            }
            
            if action_type == ActionType.MOVE:
                # Simulate movement
                target = parameters.get("target_position")
                if target:
                    distance = self.body.position.distance_to(target)
                    time_needed = distance / self.move_speed
                    
                    # Simple movement (in reality would be more complex)
                    self.body.position = target
                    self.body.energy_level -= 0.1 * distance
                    
                    result["success"] = True
                    result["message"] = f"Moved to {target} ({time_needed:.1f}s)"
            
            elif action_type == ActionType.GRASP:
                object_id = parameters.get("object_id")
                if object_id in self.perceived_objects:
                    obj = self.perceived_objects[object_id]
                    
                    if obj.is_reachable(self.body.position, self.reach_distance):
                        if not self.body.holding_object:
                            self.body.holding_object = object_id
                            result["success"] = True
                            result["message"] = f"Grasped {obj.name}"
                        else:
                            result["message"] = f"Already holding {self.body.holding_object}"
                    else:
                        result["message"] = f"{obj.name} is out of reach"
                else:
                    result["message"] = "Object not found"
            
            elif action_type == ActionType.RELEASE:
                if self.body.holding_object:
                    released = self.body.holding_object
                    self.body.holding_object = None
                    result["success"] = True
                    result["message"] = f"Released {released}"
                else:
                    result["message"] = "Not holding anything"
            
            return result
        
        def ground_understanding(
            self,
            abstract_instruction: str
        ) -> str:
            """Ground abstract instruction in physical terms"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""Translate abstract instruction into concrete physical actions.
    
    Current embodied state:
    - Position: {self.body.position}
    - Holding: {self.body.holding_object or 'nothing'}
    - Energy: {self.body.energy_level:.0%}
    
    Known objects:
    {self._get_objects_description()}
    
    Provide specific physical actions with spatial details."""),
                ("user", "Instruction: {instruction}\n\nPhysical actions:")
            ])
            
            chain = prompt | self.cognition | StrOutputParser()
            return chain.invoke({"instruction": abstract_instruction})
        
        def _get_objects_description(self) -> str:
            """Get description of perceived objects"""
            if not self.perceived_objects:
                return "No objects perceived yet"
            
            desc = ""
            for obj in self.perceived_objects.values():
                dist = self.body.position.distance_to(obj.position)
                desc += f"- {obj.name} at {obj.position} (distance: {dist:.1f}m)\n"
            return desc
    
    
    def demonstrate_embodied_agent():
        """Demonstrate Embodied Agent pattern"""
        
        print("=" * 80)
        print("PATTERN 066: EMBODIED AGENT DEMONSTRATION")
        print("=" * 80)
        print("\nPhysical embodiment: sensors, actuators, spatial reasoning\n")
        
        # Test 1: Perception and spatial awareness
        print("\n" + "=" * 80)
        print("TEST 1: Perception and Spatial Awareness")
        print("=" * 80)
        
        agent = EmbodiedAgent(
            initial_position=Position(0, 0, 0),
            reach_distance=1.5,
            move_speed=1.0
        )
        
        print(f"\n🤖 Agent initialized at position {agent.body.position}")
        
        # Perceive environment
        environment = """
        You are in a room. To your left (at -2, 0) is a table with a cup on it.
        Straight ahead (at 0, 3) is a door. To your right (at 2, 0) is a chair.
        """
        
        print(f"\n👁️  Perceiving environment...")
        perception = agent.perceive(SensorType.VISION, environment)
        print(f"   Sensor: {perception.sensor.value}")
        print(f"   Perceived: {perception.data[:150]}...")
        
        # Add objects to agent's knowledge
        agent.perceived_objects["table"] = PhysicalObject(
            "table", "table", Position(-2, 0, 0), (1.0, 0.8, 0.1)
        )
        agent.perceived_objects["cup"] = PhysicalObject(
            "cup", "cup", Position(-2, 0, 0.8), (0.1, 0.15, 0.1),
            properties={"containable": True, "graspable": True}
        )
        agent.perceived_objects["door"] = PhysicalObject(
            "door", "door", Position(0, 3, 0), (1.0, 2.0, 0.1)
        )
        
        print(f"\n📍 Objects in environment:")
        for obj_id, obj in agent.perceived_objects.items():
            distance = agent.body.position.distance_to(obj.position)
            reachable = "✓" if distance  5:
            print(f"      ... ({len(nav_plan) - 5} more steps)")
        
        # Test 4: Action execution
        print("\n" + "=" * 80)
        print("TEST 4: Physical Action Execution")
        print("=" * 80)
        
        print(f"\n🎮 Executing actions:\n")
        
        # Move to table
        print(f"   Action 1: Move to table")
        result1 = agent.execute_action(
            ActionType.MOVE,
            {"target_position": Position(-2, 0, 0)}
        )
        print(f"      Result: {result1['message']}")
        print(f"      New position: {agent.body.position}")
        
        # Try to grasp cup
        print(f"\n   Action 2: Grasp cup")
        result2 = agent.execute_action(
            ActionType.GRASP,
            {"object_id": "cup"}
        )
        print(f"      Result: {result2['message']}")
        print(f"      Holding: {agent.body.holding_object}")
        
        # Try to grasp something else (should fail)
        print(f"\n   Action 3: Try to grasp door (should fail)")
        result3 = agent.execute_action(
            ActionType.GRASP,
            {"object_id": "door"}
        )
        print(f"      Result: {result3['message']}")
        
        # Release cup
        print(f"\n   Action 4: Release cup")
        result4 = agent.execute_action(ActionType.RELEASE, {})
        print(f"      Result: {result4['message']}")
        print(f"      Holding: {agent.body.holding_object or 'nothing'}")
        
        # Test 5: Grounding abstract instructions
        print("\n" + "=" * 80)
        print("TEST 5: Grounding Abstract Instructions")
        print("=" * 80)
        
        agent2 = EmbodiedAgent(initial_position=Position(0, 0, 0))
        agent2.perceived_objects = {
            "book": PhysicalObject("book", "book", Position(1, 1, 0.5), (0.2, 0.3, 0.05)),
            "shelf": PhysicalObject("shelf", "shelf", Position(3, 0, 1.0), (1.0, 2.0, 0.3))
        }
        
        instructions = [
            "Get the book",
            "Put it on the shelf",
            "Return to starting position"
        ]
        
        print(f"\n📝 Grounding abstract instructions in physical terms:\n")
        
        for instruction in instructions:
            print(f"   Abstract: '{instruction}'")
            grounded = agent2.ground_understanding(instruction)
            print(f"   Grounded:")
            for line in grounded.split('\n')[:3]:
                if line.strip():
                    print(f"      {line.strip()}")
            print()
        
        # Summary
        print("\n" + "=" * 80)
        print("EMBODIED AGENT PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Grounded Understanding: Physical basis for abstract concepts
    2. Spatial Reasoning: Navigate and manipulate in 3D space
    3. Action Constraints: Realistic physical limitations
    4. Sensorimotor Integration: Coordinated perception-action
    5. Natural Interaction: Physical presence enables natural tasks
    
    Embodiment Components:
    
    1. Sensory System:
       - Vision: Object detection, scene understanding
       - Hearing: Sound localization, speech
       - Touch: Contact, texture, force
       - Proprioception: Body position, posture
       - Distance: Range sensing, depth
    
    2. Motor System:
       - Locomotion: Walking, rolling, flying
       - Manipulation: Grasping, moving objects
       - Gestures: Pointing, waving
       - Facial expressions (if humanoid)
       - Tool use
    
    3. Body Schema:
       - Size and shape awareness
       - Reach distance
       - Movement capabilities
       - Center of mass
       - Degrees of freedom
    
    4. Spatial Representation:
       - Egocentric: Relative to body ("to my left")
       - Allocentric: World-centered ("north of the tree")
       - Route knowledge: Paths between places
       - Survey knowledge: Map-like representation
    
    5. Affordances:
       - Graspable: Can be picked up
       - Sittable: Can sit on
       - Pushable: Can be moved
       - Openable: Can be opened
       - Context-dependent
    
    6. Physical Constraints:
       - Reachability: Within arm's reach
       - Traversability: Can move through
       - Stability: Won't fall over
       - Energy: Battery/fuel limitations
       - Payload: Weight capacity
    
    Grounding Language in Action:
    - "Pick up": Approach + reach + grasp + lift
    - "Put on": Move + position + align + release
    - "Go to": Plan path + navigate + avoid obstacles
    - "Look at": Turn head/body + focus vision
    
    Use Cases:
    - Physical Robotics:
      - Warehouse robots
      - Service robots
      - Manufacturing robots
      - Exploration rovers
    
    - Virtual Embodiment:
      - Game characters
      - Virtual assistants with avatars
      - Simulation agents
      - Training environments
    
    - Human-Robot Interaction:
      - Collaborative robots (cobots)
      - Social robots
      - Assistive robots
      - Telepresence
    
    Challenges:
    1. Sensor Noise: Imperfect perception
    2. Motor Control: Precise actuation
    3. Real-time Processing: Fast reactions needed
    4. Sim-to-Real Transfer: Simulations differ from reality
    5. Safety: Physical harm risks
    6. Complexity: Many degrees of freedom
    
    Best Practices:
    1. Start with simplified sensing
    2. Use world models for planning
    3. Implement safety constraints
    4. Test in simulation first
    5. Incremental complexity
    6. Fallback behaviors
    7. Human oversight for learning
    
    Production Considerations:
    - Sensor calibration and maintenance
    - Safety zones and limits
    - Emergency stop mechanisms
    - Predictable failure modes
    - Logging for debugging
    - Remote monitoring
    - Regular testing
    
    Spatial Reasoning Tasks:
    - Object localization
    - Path planning
    - Obstacle avoidance
    - Reachability analysis
    - Collision detection
    - Distance estimation
    - Orientation tracking
    
    Integration with AI:
    - Vision: Computer vision, object detection
    - Planning: Motion planning, SLAM
    - Control: PID, MPC, RL
    - Learning: Imitation learning, RL
    - Language: Instruction grounding
    
    Comparison with Related Patterns:
    - vs. Abstract Agent: Physical vs symbolic
    - vs. Disembodied: Grounded vs abstract
    - vs. Simulated: Real physics vs approximation
    - vs. Teleoperated: Autonomous vs controlled
    
    Advanced Techniques:
    1. Visual Servoing: Vision-guided control
    2. Force Control: Manipulation with force feedback
    3. Dynamic Balance: Maintaining stability
    4. Compliant Motion: Adaptive to contact
    5. Multi-Modal Fusion: Combine sensor types
    
    Embodied Cognition Theory:
    - Mind is shaped by body
    - Cognition is for action
    - Environment as external memory
    - Situated activity
    - Sensorimotor contingencies
    
    Research Directions:
    - Sim-to-real transfer
    - Learning from demonstration
    - Embodied language understanding
    - Social embodiment
    - Morphological computation
    
    Design Principles:
    1. Sense-Plan-Act cycle
    2. Reactive behaviors for safety
    3. Deliberative planning for complex tasks
    4. Hierarchical control
    5. Graceful degradation
    
    Testing Approaches:
    - Unit tests for each component
    - Integration tests for behaviors
    - Simulation before deployment
    - Staged real-world testing
    - Safety-critical validation
    
    The Embodied Agent pattern grounds AI systems in physical
    reality through sensors, actuators, and spatial reasoning,
    enabling natural interaction with the physical world.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_embodied_agent()
    
        

    Agentic Design Pattern: Embodied Agent

    Pattern 066: Embodied Agent – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Embodied Agent pattern implementation.


    Pattern Description

    Pattern 066: Embodied Agent

    Description: Embodied agents have a physical or virtual body that exists in an environment, with sensors for perception and actuators for action. Embodiment provides grounded understanding of the world through sensorimotor interaction, spatial reasoning, and physical constraints. This pattern bridges abstract reasoning with physical reality.

    Components: 1. Sensory System: Vision, hearing, touch, proprioception 2. Motor System: Movement, manipulation, locomotion 3. Body Schema: Internal model of body and capabilities 4. Spatial Reasoning: Navigation, object relations, distance estimation 5. Affordance Detection: Understanding what actions are possible 6. Sensorimotor Integration: Coordinating perception and action

    Use Cases: – Robotics (physical embodiment) – Virtual agents in simulations – Embodied AI assistants – Game NPCs with physical presence – Virtual reality interactions – Drone navigation

    LangChain Implementation: Implements embodied cognition using LLM-based spatial reasoning, action planning with physical constraints, and grounded perception.


    File Statistics

    • Total Lines: 712
    • Classes: 7
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 066: Embodied Agent
    
    Description:
        Embodied agents have a physical or virtual body that exists in an environment,
        with sensors for perception and actuators for action. Embodiment provides grounded
        understanding of the world through sensorimotor interaction, spatial reasoning,
        and physical constraints. This pattern bridges abstract reasoning with physical reality.
    
    Components:
        1. Sensory System: Vision, hearing, touch, proprioception
        2. Motor System: Movement, manipulation, locomotion
        3. Body Schema: Internal model of body and capabilities
        4. Spatial Reasoning: Navigation, object relations, distance estimation
        5. Affordance Detection: Understanding what actions are possible
        6. Sensorimotor Integration: Coordinating perception and action
    
    Use Cases:
        - Robotics (physical embodiment)
        - Virtual agents in simulations
        - Embodied AI assistants
        - Game NPCs with physical presence
        - Virtual reality interactions
        - Drone navigation
    
    LangChain Implementation:
        Implements embodied cognition using LLM-based spatial reasoning,
        action planning with physical constraints, and grounded perception.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import datetime.datetime
    import enum.Enum
    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
    import typing.Tuple
    

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – 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) – typing.Tuple: 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 math
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: SensorType

    Lines 47-53

    Inherits from: Enum

    Purpose:

    Types of sensors
    

    Class: ActionType

    Lines 56-63

    Inherits from: Enum

    Purpose:

    Types of actions
    

    Class: Position

    Lines 67-84

    Purpose:

    3D position
    

    Methods:

    distance_to(self, other)

    Lines 73-79

    Purpose:

    Calculate Euclidean distance
    

    Parameters: – other: Other

    Returns: float

    __str__(self)

    Lines 81-84

    Purpose: Str operation.

    Parameters:

    Returns: str


    Class: Perception

    Lines 88-93

    Purpose:

    Sensory perception
    

    Class: PhysicalObject

    Lines 97-107

    Purpose:

    Object in environment
    

    Methods:

    is_reachable(self, agent_position, reach_distance)

    Lines 105-107

    Purpose:

    Check if object is within reach
    

    Parameters: – agent_position: Agent position – reach_distance: Reach distance

    Returns: bool


    Class: BodyState

    Lines 111-117

    Purpose:

    State of agent's body
    

    Class: EmbodiedAgent

    Lines 120-383

    Purpose:

    Embodied agent with physical presence.
    
    Features:
    1. Spatial awareness and navigation
    2. Object interaction with constraints
    3. Grounded perception
    4. Physical action planning
    5. Affordance recognition
    

    Methods:

    __init__(self, initial_position, reach_distance, move_speed)

    Lines 132-156

    Purpose: Initialize the class instance with required parameters.

    Parameters: – initial_position: Initial position – reach_distance: Reach distance – move_speed: Move speed

    perceive(self, sensor, environment_description)

    Lines 158-186

    Purpose:

    Perceive environment through sensors
    

    Parameters: – sensor: Sensor – environment_description: Environment description

    Returns: Perception

    identify_affordances(self, obj)

    Lines 188-234

    Purpose:

    Identify what actions are possible with object
    

    Parameters: – obj: Obj

    Returns: <ast.Subscript object at 0x000001A06D660BD0>

    plan_navigation(self, target_position, obstacles)

    Lines 236-293

    Purpose:

    Plan navigation path considering obstacles
    

    Parameters: – target_position: Target position – obstacles: Obstacles

    Returns: <ast.Subscript object at 0x000001A06D16F510>

    execute_action(self, action_type, parameters)

    Lines 295-348

    Purpose:

    Execute physical action
    

    Parameters: – action_type: Action type – parameters: Parameters

    Returns: <ast.Subscript object at 0x000001A06D6A9C50>

    ground_understanding(self, abstract_instruction)

    Lines 350-372

    Purpose:

    Ground abstract instruction in physical terms
    

    Parameters: – abstract_instruction: Abstract instruction

    Returns: str

    _get_objects_description(self)

    Lines 374-383

    Purpose:

    Get description of perceived objects
    

    Parameters:

    Returns: str


    Functions

    demonstrate_embodied_agent()

    Lines 386-707

    Purpose:

    Demonstrate Embodied Agent pattern
    

    Key Concepts Summary

    Pattern Overview

    The Embodied Agent 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 065
    • Pattern 067

    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 Embodied Agent 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: 066_embodied_agent.py
    Total Lines: 712
    Pattern Category: AI Agent Design Pattern #066


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Social Agent

    
        """
    Pattern 065: Social Agent Patterns
    
    Description:
        Social agents possess social intelligence - the ability to understand and
        interact with humans in socially appropriate ways. This includes theory of
        mind (understanding others' mental states), empathy, politeness, social norms,
        and collaborative communication. These capabilities enable more natural and
        effective human-AI interaction.
    
    Components:
        1. Theory of Mind: Understanding others' beliefs, desires, intentions
        2. Empathy Module: Recognizing and responding to emotions
        3. Politeness Engine: Socially appropriate communication
        4. Social Norm Awareness: Understanding context-appropriate behavior
        5. Collaborative Communication: Turn-taking, clarification, common ground
        6. Personality Model: Consistent behavioral traits
    
    Use Cases:
        - Virtual assistants and companions
        - Customer service chatbots
        - Educational tutors
        - Healthcare support agents
        - Social robots and NPCs
        - Collaborative work assistants
    
    LangChain Implementation:
        Implements social intelligence using LLM-based mental state modeling,
        emotion recognition, politeness strategies, and collaborative dialogue.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class EmotionType(Enum):
        """Types of emotions"""
        JOY = "joy"
        SADNESS = "sadness"
        ANGER = "anger"
        FEAR = "fear"
        SURPRISE = "surprise"
        DISGUST = "disgust"
        NEUTRAL = "neutral"
        EXCITEMENT = "excitement"
        FRUSTRATION = "frustration"
        CONFUSION = "confusion"
    
    
    class PolitenessLevel(Enum):
        """Levels of politeness"""
        FORMAL = "formal"
        POLITE = "polite"
        CASUAL = "casual"
        INFORMAL = "informal"
    
    
    @dataclass
    class MentalState:
        """Representation of someone's mental state"""
        beliefs: Dict[str, Any] = field(default_factory=dict)
        desires: List[str] = field(default_factory=list)
        intentions: List[str] = field(default_factory=list)
        emotions: Dict[EmotionType, float] = field(default_factory=dict)
        
        def to_text(self) -> str:
            text = "Mental State:\n"
            if self.beliefs:
                text += f"  Beliefs: {self.beliefs}\n"
            if self.desires:
                text += f"  Desires: {', '.join(self.desires)}\n"
            if self.intentions:
                text += f"  Intentions: {', '.join(self.intentions)}\n"
            if self.emotions:
                emotions_str = ", ".join(f"{e.value}={v:.2f}" for e, v in self.emotions.items())
                text += f"  Emotions: {emotions_str}\n"
            return text
    
    
    @dataclass
    class SocialContext:
        """Context for social interaction"""
        relationship: str  # e.g., "stranger", "friend", "professional"
        setting: str  # e.g., "formal meeting", "casual chat"
        cultural_context: Optional[str] = None
        power_dynamic: Optional[str] = None  # e.g., "equal", "superior", "subordinate"
    
    
    @dataclass
    class ConversationTurn:
        """Single turn in conversation"""
        speaker: str
        utterance: str
        emotion: Optional[EmotionType] = None
        intention: Optional[str] = None
        timestamp: float = field(default_factory=time.time)
    
    
    class SocialAgent:
        """
        Socially intelligent agent.
        
        Features:
        1. Theory of mind modeling
        2. Emotion recognition and empathy
        3. Politeness and social appropriateness
        4. Collaborative communication
        5. Personality consistency
        """
        
        def __init__(
            self,
            personality: str = "helpful and friendly",
            default_politeness: PolitenessLevel = PolitenessLevel.POLITE
        ):
            self.personality = personality
            self.default_politeness = default_politeness
            self.conversation_history: List[ConversationTurn] = []
            
            # Theory of mind model
            self.user_mental_state = MentalState()
            
            # LLM for social reasoning
            self.social_reasoner = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.4
            )
            
            # LLM for empathetic response
            self.empathy_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.6
            )
        
        def recognize_emotion(self, text: str) -> Tuple[EmotionType, float]:
            """Recognize emotion in user's message"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Analyze the emotion in the following text.
    
    Identify:
    1. Primary emotion (joy, sadness, anger, fear, surprise, disgust, neutral, excitement, frustration, confusion)
    2. Intensity (0.0 to 1.0)
    
    Format: emotion,intensity
    Example: joy,0.8"""),
                ("user", "{text}")
            ])
            
            chain = prompt | self.social_reasoner | StrOutputParser()
            
            try:
                result = chain.invoke({"text": text})
                parts = result.strip().split(',')
                
                emotion_str = parts[0].strip().lower()
                intensity = float(parts[1].strip()) if len(parts) > 1 else 0.5
                
                # Map to enum
                for emotion in EmotionType:
                    if emotion.value in emotion_str:
                        return emotion, intensity
                
                return EmotionType.NEUTRAL, intensity
            except:
                return EmotionType.NEUTRAL, 0.5
        
        def infer_mental_state(self, utterance: str, context: str = "") -> MentalState:
            """Infer user's mental state (theory of mind)"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Analyze what the user is thinking and feeling.
    
    Infer:
    1. Beliefs: What does the user believe?
    2. Desires: What does the user want?
    3. Intentions: What is the user trying to do?
    
    {context}
    
    Provide your analysis."""),
                ("user", "User said: {utterance}\n\nMental State:")
            ])
            
            chain = prompt | self.social_reasoner | StrOutputParser()
            analysis = chain.invoke({
                "utterance": utterance,
                "context": context
            })
            
            # Parse analysis (simplified)
            mental_state = MentalState()
            
            lines = analysis.split('\n')
            for line in lines:
                line_lower = line.lower()
                if 'belief' in line_lower or 'think' in line_lower:
                    mental_state.beliefs['inferred'] = line.strip()
                elif 'desire' in line_lower or 'want' in line_lower:
                    mental_state.desires.append(line.strip())
                elif 'intention' in line_lower or 'trying' in line_lower:
                    mental_state.intentions.append(line.strip())
            
            return mental_state
        
        def generate_empathetic_response(
            self,
            user_utterance: str,
            emotion: EmotionType,
            intensity: float
        ) -> str:
            """Generate empathetic response"""
            
            empathy_guidance = {
                EmotionType.JOY: "Share in their happiness and be enthusiastic",
                EmotionType.SADNESS: "Show compassion and support",
                EmotionType.ANGER: "Acknowledge their feelings and be calming",
                EmotionType.FEAR: "Be reassuring and supportive",
                EmotionType.FRUSTRATION: "Show understanding and offer help",
                EmotionType.CONFUSION: "Be patient and clarifying",
                EmotionType.EXCITEMENT: "Match their energy positively"
            }
            
            guidance = empathy_guidance.get(emotion, "Be supportive and understanding")
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are a {self.personality} assistant with high emotional intelligence.
    
    The user is experiencing {emotion.value} (intensity: {intensity:.1f}/1.0).
    
    Response strategy: {guidance}
    
    Show empathy by:
    1. Acknowledging their emotion
    2. Validating their feelings
    3. Responding appropriately
    4. Offering support if needed
    
    Be genuine and warm."""),
                ("user", "{utterance}")
            ])
            
            chain = prompt | self.empathy_model | StrOutputParser()
            return chain.invoke({"utterance": user_utterance})
        
        def adjust_politeness(
            self,
            message: str,
            target_politeness: PolitenessLevel,
            social_context: SocialContext
        ) -> str:
            """Adjust message politeness level"""
            
            politeness_guides = {
                PolitenessLevel.FORMAL: "Use formal language, titles, and respectful phrasing",
                PolitenessLevel.POLITE: "Use polite language with 'please' and 'thank you'",
                PolitenessLevel.CASUAL: "Use friendly, conversational language",
                PolitenessLevel.INFORMAL: "Use relaxed, casual language"
            }
            
            guide = politeness_guides[target_politeness]
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""Adjust the following message to be {target_politeness.value}.
    
    Context:
    - Relationship: {social_context.relationship}
    - Setting: {social_context.setting}
    
    Strategy: {guide}
    
    Maintain the core meaning while adjusting tone."""),
                ("user", "Original: {message}\n\nAdjusted:")
            ])
            
            chain = prompt | self.social_reasoner | StrOutputParser()
            return chain.invoke({"message": message})
        
        def collaborate(
            self,
            user_utterance: str,
            social_context: SocialContext
        ) -> str:
            """Collaborative response with social awareness"""
            
            # Recognize emotion
            emotion, intensity = self.recognize_emotion(user_utterance)
            
            # Update mental state
            self.user_mental_state = self.infer_mental_state(user_utterance)
            
            # Add to conversation history
            turn = ConversationTurn(
                speaker="user",
                utterance=user_utterance,
                emotion=emotion,
                intention=self.user_mental_state.intentions[0] if self.user_mental_state.intentions else None
            )
            self.conversation_history.append(turn)
            
            # Generate empathetic response
            if intensity > 0.6:  # Strong emotion
                response = self.generate_empathetic_response(user_utterance, emotion, intensity)
            else:
                # Standard response
                context = f"Relationship: {social_context.relationship}, Setting: {social_context.setting}"
                
                prompt = ChatPromptTemplate.from_messages([
                    ("system", f"""You are a {self.personality} assistant.
    
    Context: {context}
    
    User's mental state:
    {self.user_mental_state.to_text()}
    
    Respond in a socially appropriate way:
    1. Show understanding of their perspective
    2. Be collaborative and helpful
    3. Maintain conversation flow
    4. Ask clarifying questions if needed"""),
                    ("user", "{utterance}")
                ])
                
                chain = prompt | self.social_reasoner | StrOutputParser()
                response = chain.invoke({"utterance": user_utterance})
            
            # Adjust politeness
            response = self.adjust_politeness(response, self.default_politeness, social_context)
            
            # Add agent response to history
            self.conversation_history.append(ConversationTurn(
                speaker="agent",
                utterance=response
            ))
            
            return response
        
        def demonstrate_theory_of_mind(
            self,
            scenario: str
        ) -> Dict[str, Any]:
            """Demonstrate theory of mind reasoning"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Analyze this scenario using theory of mind.
    
    For each person in the scenario, identify:
    1. What they BELIEVE (their knowledge/beliefs)
    2. What they KNOW vs DON'T KNOW
    3. What they might THINK about the situation
    4. What they might DO next
    
    This requires understanding different perspectives."""),
                ("user", "Scenario: {scenario}\n\nTheory of Mind Analysis:")
            ])
            
            chain = prompt | self.social_reasoner | StrOutputParser()
            analysis = chain.invoke({"scenario": scenario})
            
            return {
                "scenario": scenario,
                "analysis": analysis
            }
    
    
    def demonstrate_social_agent():
        """Demonstrate Social Agent Patterns"""
        
        print("=" * 80)
        print("PATTERN 065: SOCIAL AGENT PATTERNS DEMONSTRATION")
        print("=" * 80)
        print("\nSocial intelligence: emotion, empathy, politeness, theory of mind\n")
        
        # Test 1: Emotion recognition and empathy
        print("\n" + "=" * 80)
        print("TEST 1: Emotion Recognition and Empathetic Response")
        print("=" * 80)
        
        agent = SocialAgent(
            personality="warm and understanding",
            default_politeness=PolitenessLevel.POLITE
        )
        
        emotional_messages = [
            ("I just got promoted at work! I can't believe it!", "joyful announcement"),
            ("I'm really struggling with this project. Nothing seems to work.", "frustrated"),
            ("I don't understand what you mean. This is confusing.", "confused"),
        ]
        
        social_ctx = SocialContext(
            relationship="friend",
            setting="casual chat"
        )
        
        for message, description in emotional_messages:
            print(f"\n📝 User ({description}): {message}")
            
            emotion, intensity = agent.recognize_emotion(message)
            print(f"   Detected: {emotion.value} (intensity: {intensity:.2f})")
            
            response = agent.collaborate(message, social_ctx)
            print(f"\n💬 Agent: {response}")
        
        # Test 2: Theory of mind
        print("\n" + "=" * 80)
        print("TEST 2: Theory of Mind Reasoning")
        print("=" * 80)
        
        agent2 = SocialAgent()
        
        scenario = """
        Sally puts her ball in the basket and leaves the room.
        While Sally is gone, Anne moves the ball from the basket to the box.
        Sally comes back to the room.
        Where will Sally look for her ball?
        """
        
        print(f"\n🧠 Classic Theory of Mind Test (Sally-Anne):")
        print(f"   {scenario.strip()}")
        
        tom_result = agent2.demonstrate_theory_of_mind(scenario)
        
        print(f"\n   Analysis:")
        for line in tom_result['analysis'].split('\n')[:8]:
            if line.strip():
                print(f"   {line}")
        
        # Test 3: Politeness adjustment
        print("\n" + "=" * 80)
        print("TEST 3: Politeness Level Adjustment")
        print("=" * 80)
        
        message = "I need the report by tomorrow."
        
        contexts = [
            (PolitenessLevel.FORMAL, SocialContext("subordinate to boss", "formal meeting")),
            (PolitenessLevel.POLITE, SocialContext("colleague", "office")),
            (PolitenessLevel.CASUAL, SocialContext("friend", "casual chat")),
        ]
        
        print(f"\n📄 Original message: '{message}'")
        print(f"\n   Adjusted for different contexts:\n")
        
        for politeness, context in contexts:
            adjusted = agent.adjust_politeness(message, politeness, context)
            print(f"   {politeness.value.upper()} ({context.relationship}):")
            print(f"      {adjusted}")
            print()
        
        # Test 4: Mental state inference
        print("\n" + "=" * 80)
        print("TEST 4: Mental State Inference")
        print("=" * 80)
        
        utterances = [
            "I've been trying to figure this out for hours but I'm not making progress.",
            "Maybe we should consider a different approach?",
            "I think I understand now, but could you clarify the last part?",
        ]
        
        print(f"\n🎭 Inferring mental states from utterances:\n")
        
        for utterance in utterances:
            print(f"   User: {utterance}")
            mental_state = agent.infer_mental_state(utterance)
            print(f"   Inferred State:")
            if mental_state.beliefs:
                print(f"      Beliefs: {mental_state.beliefs}")
            if mental_state.desires:
                print(f"      Desires: {mental_state.desires}")
            if mental_state.intentions:
                print(f"      Intentions: {mental_state.intentions}")
            print()
        
        # Test 5: Collaborative conversation
        print("\n" + "=" * 80)
        print("TEST 5: Collaborative Conversation")
        print("=" * 80)
        
        agent3 = SocialAgent(personality="helpful and patient tutor")
        
        conversation = [
            "I'm trying to learn Python but it's overwhelming.",
            "I've tried online tutorials but they move too fast.",
            "Maybe something more interactive would help?",
        ]
        
        context = SocialContext(
            relationship="student to tutor",
            setting="educational session"
        )
        
        print(f"\n🗣️  Collaborative conversation:\n")
        
        for user_msg in conversation:
            print(f"   Student: {user_msg}")
            response = agent3.collaborate(user_msg, context)
            print(f"   Tutor: {response}\n")
        
        print(f"   Conversation history: {len(agent3.conversation_history)} turns")
        
        # Summary
        print("\n" + "=" * 80)
        print("SOCIAL AGENT PATTERNS SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Natural Interaction: More human-like communication
    2. Emotional Intelligence: Recognize and respond to emotions
    3. Social Appropriateness: Context-aware behavior
    4. Better Understanding: Theory of mind for perspective-taking
    5. User Satisfaction: More pleasant and effective interactions
    
    Social Intelligence Components:
    
    1. Theory of Mind (ToM):
       - Understanding others' mental states
       - Beliefs, desires, intentions
       - Perspective-taking ability
       - Predicting behavior
       - Key for collaboration
    
    2. Emotion Recognition:
       - Detect emotions in text/speech
       - Intensity estimation
       - Multi-emotion handling
       - Context-sensitive
       - Real-time updates
    
    3. Empathy:
       - Emotional resonance
       - Appropriate responses
       - Validation of feelings
       - Supportive communication
       - Compassionate behavior
    
    4. Politeness Strategies:
       - Formal vs informal register
       - Indirect speech acts
       - Face-saving behaviors
       - Cultural sensitivity
       - Power dynamics awareness
    
    5. Social Norms:
       - Context-appropriate behavior
       - Turn-taking in conversation
       - Topic management
       - Disclosure appropriateness
       - Boundary respect
    
    6. Collaborative Communication:
       - Common ground building
       - Clarification requests
       - Confirmation checks
       - Shared understanding
       - Cooperative principles
    
    Emotion Categories:
    - Basic: Joy, sadness, anger, fear, surprise, disgust
    - Complex: Frustration, confusion, excitement, disappointment
    - Intensity: Weak, moderate, strong
    - Mixed: Multiple emotions simultaneously
    
    Politeness Levels:
    1. Formal: Official, respectful, distant
    2. Polite: Courteous, considerate
    3. Casual: Friendly, relaxed
    4. Informal: Very casual, intimate
    
    Theory of Mind Stages:
    1. Basic: Recognize that others have mental states
    2. Intermediate: Infer beliefs from behavior
    3. Advanced: Recursive reasoning (I think you think I think...)
    4. False Belief: Understand mistaken beliefs
    
    Social Contexts:
    - Relationship: Stranger, acquaintance, friend, family, professional
    - Setting: Formal meeting, casual chat, educational, healthcare
    - Power: Equal, superior, subordinate
    - Culture: Individualist vs collectivist, direct vs indirect
    
    Use Cases:
    - Virtual Assistants: Alexa, Siri, Google Assistant
    - Customer Service: Empathetic support bots
    - Education: Patient tutoring agents
    - Healthcare: Supportive health coaches
    - Gaming: Believable NPCs
    - Companionship: Social robots
    - Workplace: Collaborative assistants
    
    Challenges:
    1. Ambiguity: Emotions and intentions unclear
    2. Cultural Differences: Norms vary across cultures
    3. Sarcasm/Irony: Difficult to detect
    4. Context Dependency: Same words, different meanings
    5. Individual Differences: Personal preferences
    6. Privacy: Balancing personalization and privacy
    
    Best Practices:
    1. Start with emotion recognition
    2. Validate user's feelings
    3. Adjust tone to context
    4. Be consistent in personality
    5. Ask clarifying questions
    6. Respect boundaries
    7. Learn from interactions
    
    Production Considerations:
    - Real-time emotion detection
    - Personality consistency
    - Cultural adaptation
    - Privacy protection
    - Bias mitigation
    - Graceful degradation
    - User feedback integration
    
    Advanced Techniques:
    1. Multimodal Emotion Recognition
       - Text + voice + facial expressions
       - Improved accuracy
    
    2. Dynamic Personality
       - Adapt to user preferences
       - Context-dependent traits
    
    3. Cultural Adaptation
       - Learn cultural norms
       - Localization
    
    4. Conversation State Tracking
       - Maintain context
       - Track goals
    
    5. Proactive Empathy
       - Anticipate needs
       - Offer support preemptively
    
    Evaluation Metrics:
    - User Satisfaction: Ratings and feedback
    - Engagement: Conversation length, return rate
    - Task Success: Goal achievement
    - Emotion Recognition Accuracy: Precision/recall
    - Appropriateness: Human judgment
    - Trust: Willingness to share information
    
    Comparison with Related Patterns:
    - vs. Basic Chatbot: Social awareness vs scripted
    - vs. Task-Focused: Relationship vs transaction
    - vs. Rule-Based: Flexible vs rigid
    - vs. Reactive: Proactive social intelligence
    
    Integration with Other Patterns:
    - Memory: Remember user preferences and history
    - Personalization: Adapt to individual users
    - Multi-Agent: Social coordination
    - Active Learning: Learn from corrections
    
    Psychological Foundations:
    - Social Cognition: How we understand others
    - Emotional Intelligence: Perceive, use, understand, manage emotions
    - Pragmatics: Language use in context
    - Conversation Analysis: Structure of interaction
    
    Research Directions:
    - Artificial empathy effectiveness
    - Cultural intelligence in AI
    - Long-term relationship building
    - Ethical boundaries of social AI
    - Measuring social intelligence
    
    Design Guidelines:
    1. Personality: Consistent and appropriate
    2. Boundaries: Clear role definition
    3. Transparency: Honest about AI nature
    4. Respect: User autonomy and feelings
    5. Safety: Protect vulnerable users
    6. Inclusivity: Diverse users and contexts
    
    The Social Agent pattern enables AI systems to interact with
    humans in more natural, appropriate, and satisfying ways through
    social intelligence, emotional awareness, and collaborative communication.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_social_agent()
    
        

    Agentic Design Pattern: Social Agent

    Pattern 065: Social Agent – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Social Agent pattern implementation.


    Pattern Description

    Pattern 065: Social Agent Patterns

    Description: Social agents possess social intelligence – the ability to understand and interact with humans in socially appropriate ways. This includes theory of mind (understanding others’ mental states), empathy, politeness, social norms, and collaborative communication. These capabilities enable more natural and effective human-AI interaction.

    Components: 1. Theory of Mind: Understanding others’ beliefs, desires, intentions 2. Empathy Module: Recognizing and responding to emotions 3. Politeness Engine: Socially appropriate communication 4. Social Norm Awareness: Understanding context-appropriate behavior 5. Collaborative Communication: Turn-taking, clarification, common ground 6. Personality Model: Consistent behavioral traits

    Use Cases: – Virtual assistants and companions – Customer service chatbots – Educational tutors – Healthcare support agents – Social robots and NPCs – Collaborative work assistants

    LangChain Implementation: Implements social intelligence using LLM-based mental state modeling, emotion recognition, politeness strategies, and collaborative dialogue.


    File Statistics

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

    Module Documentation

    """
    Pattern 065: Social Agent Patterns
    
    Description:
        Social agents possess social intelligence - the ability to understand and
        interact with humans in socially appropriate ways. This includes theory of
        mind (understanding others' mental states), empathy, politeness, social norms,
        and collaborative communication. These capabilities enable more natural and
        effective human-AI interaction.
    
    Components:
        1. Theory of Mind: Understanding others' beliefs, desires, intentions
        2. Empathy Module: Recognizing and responding to emotions
        3. Politeness Engine: Socially appropriate communication
        4. Social Norm Awareness: Understanding context-appropriate behavior
        5. Collaborative Communication: Turn-taking, clarification, common ground
        6. Personality Model: Consistent behavioral traits
    
    Use Cases:
        - Virtual assistants and companions
        - Customer service chatbots
        - Educational tutors
        - Healthcare support agents
        - Social robots and NPCs
        - Collaborative work assistants
    
    LangChain Implementation:
        Implements social intelligence using LLM-based mental state modeling,
        emotion recognition, politeness strategies, and collaborative dialogue.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import datetime.datetime
    import enum.Enum
    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
    import typing.Tuple
    

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – 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) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: EmotionType

    Lines 47-58

    Inherits from: Enum

    Purpose:

    Types of emotions
    

    Class: PolitenessLevel

    Lines 61-66

    Inherits from: Enum

    Purpose:

    Levels of politeness
    

    Class: MentalState

    Lines 70-88

    Purpose:

    Representation of someone's mental state
    

    Methods:

    to_text(self)

    Lines 77-88

    Purpose: To Text operation.

    Parameters:

    Returns: str


    Class: SocialContext

    Lines 92-97

    Purpose:

    Context for social interaction
    

    Class: ConversationTurn

    Lines 101-107

    Purpose:

    Single turn in conversation
    

    Class: SocialAgent

    Lines 110-374

    Purpose:

    Socially intelligent agent.
    
    Features:
    1. Theory of mind modeling
    2. Emotion recognition and empathy
    3. Politeness and social appropriateness
    4. Collaborative communication
    5. Personality consistency
    

    Methods:

    __init__(self, personality, default_politeness)

    Lines 122-144

    Purpose: Initialize the class instance with required parameters.

    Parameters: – personality: Personality – default_politeness: Default politeness

    recognize_emotion(self, text)

    Lines 146-177

    Purpose:

    Recognize emotion in user's message
    

    Parameters: – text: Text

    Returns: <ast.Subscript object at 0x000001A06D6299D0>

    infer_mental_state(self, utterance, context)

    Lines 179-215

    Purpose:

    Infer user's mental state (theory of mind)
    

    Parameters: – utterance: Utterance – context: Context

    Returns: MentalState

    generate_empathetic_response(self, user_utterance, emotion, intensity)

    Lines 217-255

    Purpose:

    Generate empathetic response
    

    Parameters: – user_utterance: User utterance – emotion: Emotion – intensity: Intensity

    Returns: str

    adjust_politeness(self, message, target_politeness, social_context)

    Lines 257-288

    Purpose:

    Adjust message politeness level
    

    Parameters: – message: Message – target_politeness: Target politeness – social_context: Social context

    Returns: str

    collaborate(self, user_utterance, social_context)

    Lines 290-347

    Purpose:

    Collaborative response with social awareness
    

    Parameters: – user_utterance: User utterance – social_context: Social context

    Returns: str

    demonstrate_theory_of_mind(self, scenario)

    Lines 349-374

    Purpose:

    Demonstrate theory of mind reasoning
    

    Parameters: – scenario: Scenario

    Returns: <ast.Subscript object at 0x000001A06D75A050>


    Functions

    demonstrate_social_agent()

    Lines 377-693

    Purpose:

    Demonstrate Social Agent Patterns
    

    Key Concepts Summary

    Pattern Overview

    The Social Agent 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 064
    • Pattern 066

    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 Social Agent 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: 065_social_agent.py
    Total Lines: 698
    Pattern Category: AI Agent Design Pattern #065


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Continual Learning

    
        """
    Pattern 064: Continual Learning
    
    Description:
        Continual Learning (also called Lifelong Learning) enables agents to learn
        continuously from new experiences without forgetting previously acquired
        knowledge. This addresses the "catastrophic forgetting" problem where neural
        networks forget old tasks when learning new ones.
    
    Components:
        1. Experience Buffer: Stores past experiences
        2. Consolidation: Strengthens important memories
        3. Regularization: Prevents overwriting old knowledge
        4. Knowledge Integration: Merges new with existing knowledge
        5. Forgetting Prevention: Various strategies to retain knowledge
        6. Performance Monitoring: Tracks across all tasks
    
    Use Cases:
        - Long-running personal assistants
        - Adaptive recommendation systems
        - Evolving domain knowledge
        - Multi-task learning scenarios
        - Personalization over time
        - Dynamic environment adaptation
    
    LangChain Implementation:
        Implements continual learning using experience replay, knowledge consolidation,
        and regularization techniques to prevent catastrophic forgetting.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    import random
    from collections import deque
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class ForgettingStrategy(Enum):
        """Strategies to prevent catastrophic forgetting"""
        EXPERIENCE_REPLAY = "experience_replay"  # Replay old experiences
        REGULARIZATION = "regularization"  # Constrain changes
        PROGRESSIVE_NETWORKS = "progressive_networks"  # Add new capacity
        KNOWLEDGE_DISTILLATION = "knowledge_distillation"  # Transfer knowledge
        CONSOLIDATION = "consolidation"  # Strengthen important knowledge
    
    
    class TaskPriority(Enum):
        """Priority levels for tasks"""
        CRITICAL = "critical"  # Must never forget
        HIGH = "high"  # Important
        MEDIUM = "medium"  # Moderate importance
        LOW = "low"  # Can forget if needed
    
    
    @dataclass
    class Task:
        """Learning task"""
        task_id: str
        name: str
        description: str
        domain: str
        examples: List[Tuple[str, str]] = field(default_factory=list)  # (input, output) pairs
        priority: TaskPriority = TaskPriority.MEDIUM
        importance_score: float = 1.0
        
        def add_example(self, input_text: str, output_text: str):
            """Add training example"""
            self.examples.append((input_text, output_text))
    
    
    @dataclass
    class Experience:
        """Single learning experience"""
        experience_id: str
        task_id: str
        input_data: str
        output_data: str
        timestamp: float = field(default_factory=time.time)
        importance: float = 1.0
        rehearsal_count: int = 0
    
    
    @dataclass
    class PerformanceMetric:
        """Performance on a task"""
        task_id: str
        task_name: str
        accuracy: float
        timestamp: float
        num_samples: int
    
    
    class ContinualLearner:
        """
        Continual learning agent.
        
        Features:
        1. Learn from new tasks continuously
        2. Prevent catastrophic forgetting
        3. Selective memory consolidation
        4. Experience replay
        5. Performance monitoring
        """
        
        def __init__(
            self,
            buffer_size: int = 100,
            forgetting_strategy: ForgettingStrategy = ForgettingStrategy.EXPERIENCE_REPLAY,
            consolidation_frequency: int = 10
        ):
            self.buffer_size = buffer_size
            self.forgetting_strategy = forgetting_strategy
            self.consolidation_frequency = consolidation_frequency
            
            # Experience buffer (ring buffer)
            self.experience_buffer: deque = deque(maxlen=buffer_size)
            
            # Task registry
            self.tasks: Dict[str, Task] = {}
            
            # Knowledge base (consolidated knowledge)
            self.knowledge_base: Dict[str, str] = {}
            
            # Performance history
            self.performance_history: List[PerformanceMetric] = []
            
            # Learner LLM
            self.learner = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.3
            )
            
            # Consolidator LLM
            self.consolidator = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.2
            )
            
            # Learning counter for consolidation
            self.learning_steps = 0
        
        def register_task(self, task: Task):
            """Register a new task"""
            self.tasks[task.task_id] = task
            print(f"   ✓ Registered task: {task.name} (priority: {task.priority.value})")
        
        def learn(
            self,
            task_id: str,
            input_data: str,
            output_data: str,
            importance: float = 1.0
        ):
            """Learn from a single example"""
            
            # Store experience
            experience = Experience(
                experience_id=f"exp_{len(self.experience_buffer)}",
                task_id=task_id,
                input_data=input_data,
                output_data=output_data,
                importance=importance
            )
            
            self.experience_buffer.append(experience)
            
            # Increment learning counter
            self.learning_steps += 1
            
            # Apply forgetting prevention strategy
            if self.forgetting_strategy == ForgettingStrategy.EXPERIENCE_REPLAY:
                self._experience_replay()
            
            # Consolidate periodically
            if self.learning_steps % self.consolidation_frequency == 0:
                self._consolidate_knowledge()
        
        def _experience_replay(self):
            """Replay past experiences to prevent forgetting"""
            
            if len(self.experience_buffer)  0.7
            ]
            
            # Sample from different tasks
            task_samples = {}
            for exp in self.experience_buffer:
                if exp.task_id not in task_samples:
                    task_samples[exp.task_id] = []
                task_samples[exp.task_id].append(exp)
            
            # Take one from each task
            replay_set = []
            for task_id, exps in task_samples.items():
                if exps:
                    # Prefer important experiences
                    exps_sorted = sorted(exps, key=lambda e: e.importance, reverse=True)
                    replay_set.append(exps_sorted[0])
                    exps_sorted[0].rehearsal_count += 1
            
            return replay_set
        
        def _consolidate_knowledge(self):
            """Consolidate experiences into knowledge base"""
            
            print(f"\n   🔄 Consolidating knowledge (step {self.learning_steps})...")
            
            if len(self.experience_buffer)  str:
            """Make prediction for a task"""
            
            if task_id not in self.tasks:
                return "Unknown task"
            
            task = self.tasks[task_id]
            
            # Build context
            context = f"Task: {task.name}\n"
            context += f"Description: {task.description}\n\n"
            
            # Add consolidated knowledge if available
            if use_consolidation and task_id in self.knowledge_base:
                context += f"Learned Knowledge:\n{self.knowledge_base[task_id]}\n\n"
            
            # Add relevant experiences
            relevant_experiences = [
                exp for exp in self.experience_buffer
                if exp.task_id == task_id
            ]
            
            if relevant_experiences:
                context += "Recent Examples:\n"
                for exp in relevant_experiences[-3:]:  # Last 3
                    context += f"Input: {exp.input_data}\n"
                    context += f"Output: {exp.output_data}\n\n"
            
            # Make prediction
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are making a prediction based on learned knowledge and examples.
    
    {context}
    
    Use the learned knowledge and examples to make an accurate prediction."""),
                ("user", "Input: {input}\n\nOutput:")
            ])
            
            chain = prompt | self.learner | StrOutputParser()
            prediction = chain.invoke({
                "context": context,
                "input": input_data
            })
            
            return prediction
        
        def evaluate(
            self,
            task_id: str,
            test_cases: List[Tuple[str, str]]
        ) -> PerformanceMetric:
            """Evaluate performance on a task"""
            
            if task_id not in self.tasks:
                return PerformanceMetric(task_id, "Unknown", 0.0, time.time(), 0)
            
            task = self.tasks[task_id]
            
            correct = 0
            total = len(test_cases)
            
            for input_data, expected_output in test_cases:
                prediction = self.predict(task_id, input_data)
                
                # Simple match (in practice, would use better comparison)
                if expected_output.lower() in prediction.lower():
                    correct += 1
            
            accuracy = correct / total if total > 0 else 0.0
            
            metric = PerformanceMetric(
                task_id=task_id,
                task_name=task.name,
                accuracy=accuracy,
                timestamp=time.time(),
                num_samples=total
            )
            
            self.performance_history.append(metric)
            
            return metric
        
        def get_forgetting_metrics(self) -> Dict[str, Any]:
            """Calculate forgetting metrics"""
            
            if len(self.performance_history) < 2:
                return {"status": "insufficient data"}
            
            # Group by task
            task_performance = {}
            for metric in self.performance_history:
                if metric.task_id not in task_performance:
                    task_performance[metric.task_id] = []
                task_performance[metric.task_id].append(metric)
            
            # Calculate forgetting
            forgetting_scores = {}
            for task_id, metrics in task_performance.items():
                if len(metrics) < 2:
                    continue
                
                # Compare first and last
                initial_acc = metrics[0].accuracy
                latest_acc = metrics[-1].accuracy
                
                forgetting = initial_acc - latest_acc
                forgetting_scores[task_id] = {
                    "task": metrics[0].task_name,
                    "initial_accuracy": initial_acc,
                    "latest_accuracy": latest_acc,
                    "forgetting": forgetting,
                    "status": "retained" if forgetting = perf1.accuracy - 0.1:
            print(f"   ✅ No catastrophic forgetting!")
        else:
            print(f"   ⚠️  Some forgetting detected")
        
        print("\n📚 Learning Task 3: Summarization")
        summary_examples = [
            ("AI is transforming healthcare through diagnosis and treatment.", "AI in healthcare"),
            ("Climate change requires immediate global action.", "Climate action needed"),
        ]
        
        for input_text, output_text in summary_examples:
            learner.learn("summarization", input_text, output_text, importance=0.9)
        
        # Test 2: Knowledge consolidation
        print("\n" + "=" * 80)
        print("TEST 2: Knowledge Consolidation")
        print("=" * 80)
        
        print(f"\n📊 Knowledge Base Status:")
        print(f"   Tasks Learned: {len(learner.tasks)}")
        print(f"   Experiences Stored: {len(learner.experience_buffer)}")
        print(f"   Consolidated Knowledge: {len(learner.knowledge_base)} tasks")
        
        if learner.knowledge_base:
            print(f"\n   Consolidated Knowledge Samples:")
            for task_id, knowledge in list(learner.knowledge_base.items())[:2]:
                task_name = learner.tasks[task_id].name
                print(f"\n   {task_name}:")
                lines = knowledge.split('\n')[:3]
                for line in lines:
                    if line.strip():
                        print(f"      {line}")
        
        # Test 3: Long-term retention
        print("\n" + "=" * 80)
        print("TEST 3: Long-term Retention Test")
        print("=" * 80)
        
        # Continue learning more examples
        print("\n📚 Learning additional examples...")
        
        for i in range(5):
            learner.learn(
                "translation",
                f"Example {i}",
                f"Ejemplo {i}",
                importance=0.5
            )
        
        # Re-evaluate all tasks
        print(f"\n📊 Final Performance Across All Tasks:")
        
        all_tests = {
            "sentiment": sentiment_test,
            "translation": [("Hello", "Hola"), ("Thank you", "Gracias")],
            "summarization": [("Brief text", "summary")]
        }
        
        for task_id, test_cases in all_tests.items():
            if task_id in learner.tasks:
                perf = learner.evaluate(task_id, test_cases)
                print(f"   {learner.tasks[task_id].name}: {perf.accuracy:.1%}")
        
        # Test 4: Forgetting analysis
        print("\n" + "=" * 80)
        print("TEST 4: Forgetting Analysis")
        print("=" * 80)
        
        forgetting_metrics = learner.get_forgetting_metrics()
        
        if "status" not in forgetting_metrics:
            print(f"\n📈 Forgetting Metrics:")
            for task_id, metrics in forgetting_metrics.items():
                print(f"\n   {metrics['task']}:")
                print(f"      Initial: {metrics['initial_accuracy']:.1%}")
                print(f"      Latest: {metrics['latest_accuracy']:.1%}")
                print(f"      Change: {metrics['forgetting']:+.1%}")
                print(f"      Status: {metrics['status']}")
        
        # Test 5: Experience buffer analysis
        print("\n" + "=" * 80)
        print("TEST 5: Experience Buffer Analysis")
        print("=" * 80)
        
        print(f"\n🗂️  Experience Buffer Status:")
        print(f"   Capacity: {learner.buffer_size}")
        print(f"   Current Size: {len(learner.experience_buffer)}")
        print(f"   Utilization: {len(learner.experience_buffer)/learner.buffer_size:.1%}")
        
        # Task distribution
        task_distribution = {}
        for exp in learner.experience_buffer:
            task_distribution[exp.task_id] = task_distribution.get(exp.task_id, 0) + 1
        
        print(f"\n   Task Distribution:")
        for task_id, count in task_distribution.items():
            task_name = learner.tasks[task_id].name
            percentage = count / len(learner.experience_buffer) * 100
            print(f"      {task_name}: {count} ({percentage:.1f}%)")
        
        # Rehearsal statistics
        rehearsal_counts = [exp.rehearsal_count for exp in learner.experience_buffer]
        if rehearsal_counts:
            avg_rehearsal = sum(rehearsal_counts) / len(rehearsal_counts)
            max_rehearsal = max(rehearsal_counts)
            print(f"\n   Rehearsal Statistics:")
            print(f"      Average: {avg_rehearsal:.1f}")
            print(f"      Maximum: {max_rehearsal}")
        
        # Summary
        print("\n" + "=" * 80)
        print("CONTINUAL LEARNING PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Lifelong Learning: Continuously acquire new knowledge
    2. No Forgetting: Retain previously learned tasks
    3. Efficient Memory: Consolidate experiences
    4. Adaptability: Adjust to new domains
    5. Scalability: Handle many tasks over time
    
    The Catastrophic Forgetting Problem:
    - Neural networks tend to forget old tasks when learning new ones
    - Old knowledge is overwritten by new knowledge
    - Performance on previous tasks degrades dramatically
    - Critical challenge for continual learning systems
    
    Forgetting Prevention Strategies:
    
    1. Experience Replay:
       - Store subset of old experiences
       - Replay while learning new tasks
       - Maintains performance on old tasks
       - Buffer management is key
    
    2. Regularization:
       - Constrain parameter changes
       - Protect important weights
       - Elastic Weight Consolidation (EWC)
       - Synaptic Intelligence
    
    3. Progressive Networks:
       - Add new capacity for new tasks
       - Keep old networks frozen
       - Lateral connections for transfer
       - No forgetting by design
    
    4. Knowledge Distillation:
       - Transfer knowledge to new model
       - Old model as teacher
       - Soft targets preserve knowledge
       - Compression friendly
    
    5. Memory Consolidation:
       - Identify important experiences
       - Strengthen critical knowledge
       - Hierarchical organization
       - Gradual integration
    
    Experience Buffer Management:
    - Fixed size (ring buffer)
    - Reservoir sampling
    - Priority-based retention
    - Diversity sampling
    - Importance weighting
    
    Consolidation Process:
    1. Group related experiences
    2. Extract general patterns
    3. Compress into knowledge
    4. Store in knowledge base
    5. Use for future predictions
    
    Task Priority:
    - Critical: Never forget (safety, core capabilities)
    - High: Important to retain
    - Medium: Moderate importance
    - Low: Can forget under pressure
    
    Use Cases:
    - Personal Assistants: Learn user preferences over time
    - Recommendation Systems: Adapt to changing tastes
    - Chatbots: Expand knowledge continuously
    - Robotics: Learn new skills without forgetting old
    - Adaptive Systems: Evolve with environment
    - Multi-task Learning: Master many tasks
    
    Challenges:
    1. Memory Limitations: Can't store everything
    2. Task Interference: Tasks conflict
    3. Generalization: New tasks differ from old
    4. Forgetting Measurement: How to evaluate?
    5. Computational Cost: Replay and consolidation
    6. Task Boundaries: When is a task new?
    
    Best Practices:
    1. Use experience replay
    2. Consolidate regularly
    3. Priority-based retention
    4. Monitor all task performance
    5. Adjust buffer size appropriately
    6. Combine multiple strategies
    7. Test for forgetting continuously
    
    Production Considerations:
    - Storage requirements for experiences
    - Replay frequency and cost
    - Consolidation timing
    - Performance monitoring
    - Graceful degradation
    - User feedback integration
    - Model versioning
    
    Metrics to Track:
    - Forward Transfer: New task learning speed
    - Backward Transfer: Impact on old tasks
    - Forgetting: Performance degradation
    - Memory Efficiency: Knowledge per byte
    - Learning Speed: Time to acquire new task
    
    Advanced Techniques:
    1. Meta-Learning: Learn how to learn continually
    2. Compositional Learning: Reuse components
    3. Modular Networks: Task-specific modules
    4. Attention Mechanisms: Focus on relevant knowledge
    5. Hierarchical Memory: Multi-level organization
    
    Comparison with Related Patterns:
    - vs. Meta-Learning: Long-term vs quick adaptation
    - vs. Transfer Learning: Continuous vs one-shot
    - vs. Multi-Task: Sequential vs simultaneous
    - vs. Few-Shot: Many tasks vs few examples
    
    Integration with Other Patterns:
    - Memory Patterns: Foundation for continual learning
    - Meta-Learning: Quick adaptation to new tasks
    - Active Learning: What to learn next?
    - Curriculum Learning: Order of learning tasks
    
    Biological Inspiration:
    - Complementary Learning Systems
    - Systems Consolidation
    - Hippocampus-Neocortex interaction
    - Sleep and memory replay
    
    Research Directions:
    - Scalable to thousands of tasks
    - Zero-shot continual learning
    - Compositional generalization
    - Causal continual learning
    - Fairness in continual learning
    
    The Continual Learning pattern enables agents to grow and
    adapt continuously while retaining all previously acquired
    knowledge - essential for long-lived, adaptive AI systems.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_continual_learning()
    
        

    Agentic Design Pattern: Continual Learning

    Pattern 064: Continual Learning – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Continual Learning pattern implementation.


    Pattern Description

    Pattern 064: Continual Learning

    Description: Continual Learning (also called Lifelong Learning) enables agents to learn continuously from new experiences without forgetting previously acquired knowledge. This addresses the “catastrophic forgetting” problem where neural networks forget old tasks when learning new ones.

    Components: 1. Experience Buffer: Stores past experiences 2. Consolidation: Strengthens important memories 3. Regularization: Prevents overwriting old knowledge 4. Knowledge Integration: Merges new with existing knowledge 5. Forgetting Prevention: Various strategies to retain knowledge 6. Performance Monitoring: Tracks across all tasks

    Use Cases: – Long-running personal assistants – Adaptive recommendation systems – Evolving domain knowledge – Multi-task learning scenarios – Personalization over time – Dynamic environment adaptation

    LangChain Implementation: Implements continual learning using experience replay, knowledge consolidation, and regularization techniques to prevent catastrophic forgetting.


    File Statistics

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

    Module Documentation

    """
    Pattern 064: Continual Learning
    
    Description:
        Continual Learning (also called Lifelong Learning) enables agents to learn
        continuously from new experiences without forgetting previously acquired
        knowledge. This addresses the "catastrophic forgetting" problem where neural
        networks forget old tasks when learning new ones.
    
    Components:
        1. Experience Buffer: Stores past experiences
        2. Consolidation: Strengthens important memories
        3. Regularization: Prevents overwriting old knowledge
        4. Knowledge Integration: Merges new with existing knowledge
        5. Forgetting Prevention: Various strategies to retain knowledge
        6. Performance Monitoring: Tracks across all tasks
    
    Use Cases:
        - Long-running personal assistants
        - Adaptive recommendation systems
        - Evolving domain knowledge
        - Multi-task learning scenarios
        - Personalization over time
        - Dynamic environment adaptation
    
    LangChain Implementation:
        Implements continual learning using experience replay, knowledge consolidation,
        and regularization techniques to prevent catastrophic forgetting.
    """
    

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


    Import Statements

    Standard Library Imports

    import collections.deque
    import dataclasses.dataclass
    import dataclasses.field
    import datetime.datetime
    import enum.Enum
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import random
    import time
    import typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Tuple
    

    Purpose: Core Python functionality: – collections.deque: Specialized container datatypes – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – 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) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: ForgettingStrategy

    Lines 48-54

    Inherits from: Enum

    Purpose:

    Strategies to prevent catastrophic forgetting
    

    Class: TaskPriority

    Lines 57-62

    Inherits from: Enum

    Purpose:

    Priority levels for tasks
    

    Class: Task

    Lines 66-78

    Purpose:

    Learning task
    

    Methods:

    add_example(self, input_text, output_text)

    Lines 76-78

    Purpose:

    Add training example
    

    Parameters: – input_text: Input data – output_text: Output text


    Class: Experience

    Lines 82-90

    Purpose:

    Single learning experience
    

    Class: PerformanceMetric

    Lines 94-100

    Purpose:

    Performance on a task
    

    Class: ContinualLearner

    Lines 103-389

    Purpose:

    Continual learning agent.
    
    Features:
    1. Learn from new tasks continuously
    2. Prevent catastrophic forgetting
    3. Selective memory consolidation
    4. Experience replay
    5. Performance monitoring
    

    Methods:

    __init__(self, buffer_size, forgetting_strategy, consolidation_frequency)

    Lines 115-150

    Purpose: Initialize the class instance with required parameters.

    Parameters: – buffer_size: Buffer size – forgetting_strategy: Forgetting strategy – consolidation_frequency: Consolidation frequency

    register_task(self, task)

    Lines 152-155

    Purpose:

    Register a new task
    

    Parameters: – task: Task

    learn(self, task_id, input_data, output_data, importance)

    Lines 157-186

    Purpose:

    Learn from a single example
    

    Parameters: – task_id: Task id – input_data: Input data – output_data: Output data – importance: Importance

    _experience_replay(self)

    Lines 188-219

    Purpose:

    Replay past experiences to prevent forgetting
    

    Parameters:

    _consolidate_knowledge(self)

    Lines 221-268

    Purpose:

    Consolidate experiences into knowledge base
    

    Parameters:

    predict(self, task_id, input_data, use_consolidation)

    Lines 270-319

    Purpose:

    Make prediction for a task
    

    Parameters: – task_id: Task id – input_data: Input data – use_consolidation: Use consolidation

    Returns: str

    evaluate(self, task_id, test_cases)

    Lines 321-355

    Purpose:

    Evaluate performance on a task
    

    Parameters: – task_id: Task id – test_cases: Test cases

    Returns: PerformanceMetric

    get_forgetting_metrics(self)

    Lines 357-389

    Purpose:

    Calculate forgetting metrics
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D72B750>


    Functions

    demonstrate_continual_learning()

    Lines 392-730

    Purpose:

    Demonstrate Continual Learning pattern
    

    Key Concepts Summary

    Pattern Overview

    The Continual Learning 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 063
    • Pattern 065

    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 Continual Learning 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: 064_continual_learning.py
    Total Lines: 735
    Pattern Category: AI Agent Design Pattern #064


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Causal Reasoning

    
        """
    Pattern 063: Causal Reasoning Agent
    
    Description:
        Causal Reasoning enables agents to understand cause-effect relationships,
        perform interventions, and reason about counterfactuals. Unlike correlation-based
        reasoning, causal reasoning distinguishes between "what happened" and "what would
        happen if we changed something", enabling better decision-making and explanation.
    
    Components:
        1. Causal Graph: Represents causal relationships between variables
        2. Structural Equations: Model mechanisms of causation
        3. Intervention Handler: Simulates "do" operations
        4. Counterfactual Reasoner: Answers "what if" questions
        5. Causal Discovery: Learns causal structure from data
        6. Effect Estimation: Quantifies causal effects
    
    Use Cases:
        - Root cause analysis
        - Decision-making under uncertainty
        - Policy evaluation ("what if we change X?")
        - Scientific reasoning and hypothesis testing
        - Fairness and bias analysis
        - Explanation generation
    
    LangChain Implementation:
        Implements causal reasoning using LLM-based causal graph construction,
        intervention simulation, and counterfactual query answering.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple, Set
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class CausalRelationType(Enum):
        """Types of causal relationships"""
        DIRECT = "direct"  # A → B
        INDIRECT = "indirect"  # A → C → B
        COMMON_CAUSE = "common_cause"  # A ← C → B
        COLLIDER = "collider"  # A → C ← B
        BIDIRECTIONAL = "bidirectional"  # A ↔ B
    
    
    @dataclass
    class CausalEdge:
        """Edge in causal graph"""
        cause: str
        effect: str
        strength: float = 1.0  # 0.0 to 1.0
        mechanism: Optional[str] = None
        
        def __str__(self) -> str:
            arrow = "→" if self.strength > 0 else "⊣"
            strength_str = f"({self.strength:.2f})" if self.strength != 1.0 else ""
            return f"{self.cause} {arrow} {self.effect} {strength_str}"
    
    
    @dataclass
    class CausalGraph:
        """Causal graph structure"""
        variables: Set[str] = field(default_factory=set)
        edges: List[CausalEdge] = field(default_factory=list)
        
        def add_edge(self, cause: str, effect: str, strength: float = 1.0, mechanism: str = None):
            """Add causal edge"""
            self.variables.add(cause)
            self.variables.add(effect)
            self.edges.append(CausalEdge(cause, effect, strength, mechanism))
        
        def get_parents(self, variable: str) -> List[str]:
            """Get direct causes of variable"""
            return [edge.cause for edge in self.edges if edge.effect == variable]
        
        def get_children(self, variable: str) -> List[str]:
            """Get direct effects of variable"""
            return [edge.effect for edge in self.edges if edge.cause == variable]
        
        def get_ancestors(self, variable: str) -> Set[str]:
            """Get all ancestors (transitive causes)"""
            ancestors = set()
            to_visit = self.get_parents(variable)
            
            while to_visit:
                parent = to_visit.pop()
                if parent not in ancestors:
                    ancestors.add(parent)
                    to_visit.extend(self.get_parents(parent))
            
            return ancestors
        
        def to_text(self) -> str:
            """Convert graph to text"""
            text = "Causal Graph:\n"
            text += f"Variables: {', '.join(sorted(self.variables))}\n\n"
            text += "Edges:\n"
            for edge in self.edges:
                text += f"  {edge}\n"
                if edge.mechanism:
                    text += f"    Mechanism: {edge.mechanism}\n"
            return text
    
    
    @dataclass
    class Intervention:
        """Causal intervention (do-operator)"""
        variable: str
        value: Any
        description: str
    
    
    @dataclass
    class CounterfactualQuery:
        """Counterfactual question"""
        observed_facts: Dict[str, Any]
        intervention: Intervention
        query_variable: str
        
        def to_text(self) -> str:
            facts_str = ", ".join(f"{k}={v}" for k, v in self.observed_facts.items())
            return f"Given {facts_str}, what if we {self.intervention.description}? What would {self.query_variable} be?"
    
    
    @dataclass
    class CausalAnswer:
        """Answer to causal query"""
        query: str
        answer: str
        confidence: float
        reasoning: str
        affected_variables: List[str] = field(default_factory=list)
    
    
    class CausalReasoningAgent:
        """
        Agent capable of causal reasoning.
        
        Features:
        1. Causal graph construction
        2. Intervention simulation
        3. Counterfactual reasoning
        4. Effect estimation
        5. Root cause analysis
        """
        
        def __init__(self, domain: str = "general"):
            self.domain = domain
            self.causal_graph = CausalGraph()
            
            # LLM for causal discovery
            self.discovery_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.3
            )
            
            # LLM for reasoning
            self.reasoning_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.2
            )
        
        def build_causal_graph(self, context: str) -> CausalGraph:
            """Construct causal graph from context"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are identifying causal relationships in {self.domain}.
    
    Analyze the context and identify:
    1. Variables (factors that can change)
    2. Causal relationships (A causes B)
    3. Mechanisms (how A causes B)
    
    Format your response as:
    VARIABLES:
    - variable1
    - variable2
    ...
    
    CAUSAL EDGES:
    variable1 → variable2 (strength: 0.0-1.0)
    Mechanism: explanation
    
    ..."""),
                ("user", "Context: {context}\n\nCausal Analysis:")
            ])
            
            chain = prompt | self.discovery_model | StrOutputParser()
            analysis = chain.invoke({"context": context})
            
            # Parse response
            graph = CausalGraph()
            lines = analysis.split('\n')
            
            section = None
            current_edge = None
            
            for line in lines:
                line = line.strip()
                
                if 'VARIABLES:' in line:
                    section = 'variables'
                elif 'CAUSAL EDGES:' in line or 'EDGES:' in line:
                    section = 'edges'
                elif section == 'variables' and line.startswith('-'):
                    var = line.lstrip('- ').strip()
                    if var:
                        graph.variables.add(var)
                elif section == 'edges' and ('→' in line or '->' in line):
                    # Parse edge
                    if '→' in line:
                        parts = line.split('→')
                    elif '->' in line:
                        parts = line.split('->')
                    else:
                        continue
                    
                    if len(parts) >= 2:
                        cause = parts[0].strip()
                        effect_part = parts[1].strip()
                        
                        # Extract effect and strength
                        effect = effect_part.split('(')[0].strip()
                        strength = 1.0
                        
                        if '(' in effect_part:
                            try:
                                strength_str = effect_part.split('(')[1].split(')')[0]
                                strength = float(''.join(c for c in strength_str if c.isdigit() or c == '.'))
                                if strength > 1.0:
                                    strength = strength / 100.0
                            except:
                                pass
                        
                        current_edge = CausalEdge(cause, effect, strength)
                        graph.edges.append(current_edge)
                        graph.variables.add(cause)
                        graph.variables.add(effect)
                elif section == 'edges' and 'mechanism:' in line.lower() and current_edge:
                    mechanism = line.split(':', 1)[1].strip()
                    current_edge.mechanism = mechanism
            
            self.causal_graph = graph
            return graph
        
        def do_intervention(
            self,
            intervention: Intervention,
            context: str
        ) -> CausalAnswer:
            """Simulate causal intervention (do-operator)"""
            
            # Build/use causal graph
            if not self.causal_graph.edges:
                self.build_causal_graph(context)
            
            # Find affected variables
            affected = {intervention.variable}
            affected.update(self.causal_graph.get_children(intervention.variable))
            
            # Recursively add downstream effects
            to_check = list(self.causal_graph.get_children(intervention.variable))
            while to_check:
                var = to_check.pop()
                children = self.causal_graph.get_children(var)
                for child in children:
                    if child not in affected:
                        affected.add(child)
                        to_check.append(child)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are performing causal intervention analysis in {self.domain}.
    
    Causal Graph:
    {self.causal_graph.to_text()}
    
    The intervention sets {intervention.variable} to {intervention.value}.
    This is a CAUSAL intervention (do-operator), not just observation.
    
    Analyze:
    1. What changes because of this intervention
    2. Which variables are affected and how
    3. What are the downstream effects
    
    Provide clear reasoning about the causal chain."""),
                ("user", """Context: {context}
    
    Intervention: {intervention}
    
    What are the causal effects?""")
            ])
            
            chain = prompt | self.reasoning_model | StrOutputParser()
            analysis = chain.invoke({
                "context": context,
                "intervention": intervention.description
            })
            
            return CausalAnswer(
                query=f"do({intervention.variable}={intervention.value})",
                answer=analysis,
                confidence=0.7,
                reasoning="Based on causal graph structure and domain knowledge",
                affected_variables=list(affected)
            )
        
        def answer_counterfactual(
            self,
            query: CounterfactualQuery,
            context: str
        ) -> CausalAnswer:
            """Answer counterfactual question"""
            
            # Counterfactuals require:
            # 1. What actually happened (observed facts)
            # 2. What we change (intervention)
            # 3. What we query (counterfactual outcome)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are answering counterfactual questions in {self.domain}.
    
    Counterfactual reasoning has three steps:
    1. Abduction: Infer what must be true given observations
    2. Action: Apply the intervention
    3. Prediction: Determine outcome in this counterfactual world
    
    Use the causal graph to reason about what would change."""),
                ("user", """Context: {context}
    
    Observed Facts: {facts}
    
    Counterfactual Intervention: {intervention}
    
    Question: What would {query_var} be in this counterfactual scenario?
    
    Answer:""")
            ])
            
            chain = prompt | self.reasoning_model | StrOutputParser()
            answer = chain.invoke({
                "context": context,
                "facts": str(query.observed_facts),
                "intervention": query.intervention.description,
                "query_var": query.query_variable
            })
            
            return CausalAnswer(
                query=query.to_text(),
                answer=answer,
                confidence=0.6,  # Counterfactuals are harder
                reasoning="Counterfactual reasoning via abduction-action-prediction"
            )
        
        def identify_root_cause(
            self,
            effect: str,
            context: str
        ) -> CausalAnswer:
            """Identify root cause of an effect"""
            
            if not self.causal_graph.edges:
                self.build_causal_graph(context)
            
            # Get all ancestors
            ancestors = self.causal_graph.get_ancestors(effect)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are performing root cause analysis in {self.domain}.
    
    Causal Graph:
    {self.causal_graph.to_text()}
    
    Identify the ROOT CAUSE(S) of the observed effect.
    Root causes are variables with:
    1. No upstream causes (or only external causes)
    2. Strong causal connection to the effect
    3. Actionable or controllable
    
    Trace back through the causal chain."""),
                ("user", """Context: {context}
    
    Observed Effect: {effect}
    
    Potential Causes: {ancestors}
    
    What is the root cause?""")
            ])
            
            chain = prompt | self.reasoning_model | StrOutputParser()
            analysis = chain.invoke({
                "context": context,
                "effect": effect,
                "ancestors": ", ".join(ancestors) if ancestors else "None identified"
            })
            
            return CausalAnswer(
                query=f"Root cause of: {effect}",
                answer=analysis,
                confidence=0.75,
                reasoning="Traced causal chain to root variables"
            )
    
    
    def demonstrate_causal_reasoning():
        """Demonstrate Causal Reasoning Agent pattern"""
        
        print("=" * 80)
        print("PATTERN 063: CAUSAL REASONING AGENT DEMONSTRATION")
        print("=" * 80)
        print("\nUnderstanding cause-effect relationships and counterfactuals\n")
        
        # Test 1: Building causal graph
        print("\n" + "=" * 80)
        print("TEST 1: Causal Graph Construction")
        print("=" * 80)
        
        agent = CausalReasoningAgent(domain="business")
        
        context1 = """
        A company has been experiencing declining sales. Analysis shows:
        - Marketing budget was reduced
        - Competitor launched new product
        - Customer satisfaction scores dropped
        - Website traffic decreased
        - Sales team morale is low
        The reduced marketing budget led to less advertising, which decreased website 
        traffic. The competitor's new product attracted customers, reducing market share.
        Lower sales affected team morale.
        """
        
        print(f"\n📊 Analyzing business scenario...")
        
        graph = agent.build_causal_graph(context1)
        
        print(f"\n🔗 Discovered Causal Graph:")
        print(f"   Variables: {len(graph.variables)}")
        print(f"   Edges: {len(graph.edges)}")
        print(f"\n{graph.to_text()}")
        
        # Test 2: Intervention analysis
        print("\n" + "=" * 80)
        print("TEST 2: Causal Intervention (do-operator)")
        print("=" * 80)
        
        intervention = Intervention(
            variable="marketing budget",
            value="increase by 50%",
            description="increase marketing budget by 50%"
        )
        
        print(f"\n🎯 Intervention: {intervention.description}")
        print(f"   Question: What causal effects will this have?")
        
        result = agent.do_intervention(intervention, context1)
        
        print(f"\n💡 Causal Effects:")
        print(f"   Affected Variables: {', '.join(result.affected_variables)}")
        print(f"\n   Analysis:")
        for line in result.answer.split('\n')[:10]:  # First 10 lines
            if line.strip():
                print(f"   {line}")
        
        # Test 3: Counterfactual reasoning
        print("\n" + "=" * 80)
        print("TEST 3: Counterfactual Reasoning")
        print("=" * 80)
        
        counterfactual = CounterfactualQuery(
            observed_facts={
                "marketing_budget": "reduced",
                "sales": "declined",
                "market_share": "lost"
            },
            intervention=Intervention(
                variable="marketing budget",
                value="maintained at original level",
                description="maintained marketing budget at original level"
            ),
            query_variable="sales"
        )
        
        print(f"\n❓ Counterfactual Question:")
        print(f"   {counterfactual.to_text()}")
        
        cf_result = agent.answer_counterfactual(counterfactual, context1)
        
        print(f"\n🔮 Counterfactual Answer:")
        print(f"   Confidence: {cf_result.confidence:.2%}")
        print(f"\n   Answer:")
        for line in cf_result.answer.split('\n')[:8]:
            if line.strip():
                print(f"   {line}")
        
        # Test 4: Root cause analysis
        print("\n" + "=" * 80)
        print("TEST 4: Root Cause Analysis")
        print("=" * 80)
        
        agent2 = CausalReasoningAgent(domain="software systems")
        
        context2 = """
        A web application is experiencing slow response times:
        - Database queries are taking longer
        - Server CPU usage is high
        - Memory usage increased
        - Number of active users grew
        - Cache hit rate decreased
        - Recent code deployment added complex queries
        
        The code deployment introduced inefficient queries, which increased
        database load. Growing user base put additional load on the system.
        High server load caused slower query execution. Poor cache performance
        meant more database hits.
        """
        
        print(f"\n🔍 Analyzing system performance issue...")
        
        agent2.build_causal_graph(context2)
        
        root_cause = agent2.identify_root_cause("slow response times", context2)
        
        print(f"\n🎯 Root Cause Analysis:")
        print(f"   Effect: slow response times")
        print(f"   Confidence: {root_cause.confidence:.2%}")
        print(f"\n   Root Cause(s):")
        for line in root_cause.answer.split('\n'):
            if line.strip():
                print(f"   {line}")
        
        # Test 5: Comparing correlation vs causation
        print("\n" + "=" * 80)
        print("TEST 5: Correlation vs Causation")
        print("=" * 80)
        
        agent3 = CausalReasoningAgent(domain="healthcare")
        
        context3 = """
        Study observations:
        - People who drink coffee have lower rates of heart disease
        - People who exercise regularly drink more coffee
        - People who exercise have better heart health
        - Coffee consumption and exercise are correlated
        - Income level affects both coffee drinking and exercise
        """
        
        print(f"\n📈 Analyzing correlation pattern...")
        
        graph3 = agent3.build_causal_graph(context3)
        print(f"\n   Causal Graph:")
        for edge in graph3.edges:
            print(f"   {edge}")
        
        # Compare two interventions
        print(f"\n   Comparing interventions:")
        
        int1 = Intervention("coffee consumption", "increase", "increase coffee consumption")
        int2 = Intervention("exercise", "increase", "increase exercise")
        
        result1 = agent3.do_intervention(int1, context3)
        result2 = agent3.do_intervention(int2, context3)
        
        print(f"\n   If we increase COFFEE consumption:")
        print(f"      Affected: {', '.join(result1.affected_variables)}")
        
        print(f"\n   If we increase EXERCISE:")
        print(f"      Affected: {', '.join(result2.affected_variables)}")
        
        print(f"\n   ⚠️  Causal vs Correlational insight:")
        print(f"      Correlation doesn't imply causation!")
        print(f"      The causal structure reveals true mechanisms.")
        
        # Summary
        print("\n" + "=" * 80)
        print("CAUSAL REASONING AGENT PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. True Understanding: Distinguish causation from correlation
    2. Better Decisions: Predict intervention outcomes
    3. Counterfactual Reasoning: Answer "what if" questions
    4. Root Cause Analysis: Find true sources of problems
    5. Robust Generalization: Transfer across contexts
    
    Causal Hierarchy (Pearl's Ladder):
    1. Association (Seeing): P(Y|X)
       - "What is?" (observation)
       - Correlations and patterns
       - Machine learning operates here
    
    2. Intervention (Doing): P(Y|do(X))
       - "What if?" (action)
       - Effects of interventions
       - Experiments and A/B tests
    
    3. Counterfactuals (Imagining): P(Y_x|X',Y')
       - "What if I had?" (retrospection)
       - Alternative histories
       - Requires full causal model
    
    Core Concepts:
    - Causal Graph: Directed graph of cause-effect
    - Structural Equations: Mechanisms of causation
    - do-Operator: Intervention (breaking incoming edges)
    - Backdoor Criterion: Identifying confounders
    - Frontdoor Criterion: Alternative identification
    - Counterfactual: Alternative possible worlds
    
    Types of Causal Questions:
    1. Association: "Are X and Y related?"
    2. Intervention: "What if we do X?"
    3. Counterfactual: "What if we had done X?"
    4. Effect Size: "How much does X affect Y?"
    5. Attribution: "Did X cause this Y?"
    6. Explanation: "Why did Y happen?"
    
    Causal Graph Patterns:
    - Chain: A → B → C (mediation)
    - Fork: A ← B → C (common cause/confounder)
    - Collider: A → B ← C (selection bias)
    - Confounding: A ← C → Y (spurious correlation)
    
    Intervention vs Observation:
    - Observation: P(Y|X=x) - passive seeing
    - Intervention: P(Y|do(X=x)) - active doing
    - Key difference: Intervention breaks incoming edges
    
    Use Cases:
    - Medicine: Treatment effect estimation
    - Policy: Evaluate policy interventions
    - Business: Understand driver of metrics
    - Science: Hypothesis testing
    - Fairness: Identify discrimination
    - Debugging: Root cause analysis
    - Legal: Attribution and liability
    
    Causal Discovery Methods:
    1. Constraint-Based: Test conditional independencies
    2. Score-Based: Search for best-fitting graph
    3. Functional: Exploit noise structure
    4. Hybrid: Combine approaches
    5. LLM-Based: Extract from text/knowledge
    
    Challenges:
    1. Unobserved Confounders: Hidden common causes
    2. Selection Bias: Collider conditioning
    3. Measurement Error: Imperfect variables
    4. Feedback Loops: Bidirectional causation
    5. Time Delays: Lag between cause and effect
    6. Context Dependence: Different mechanisms
    
    Best Practices:
    1. Draw causal graph explicitly
    2. Identify confounders
    3. Use randomization when possible
    4. Combine domain knowledge + data
    5. Test causal assumptions
    6. Estimate uncertainty
    7. Consider alternative graphs
    
    Production Considerations:
    - Causal graph versioning
    - Domain expert validation
    - A/B testing for verification
    - Sensitivity analysis
    - Handling edge cases
    - Explanation generation
    - Confidence calibration
    
    Advanced Techniques:
    1. Causal Effect Estimation
       - Instrumental variables
       - Difference-in-differences
       - Regression discontinuity
       - Propensity score matching
    
    2. Mediation Analysis
       - Direct vs indirect effects
       - Path analysis
    
    3. Time-Series Causality
       - Granger causality
       - Dynamic causal models
    
    4. Causal Reinforcement Learning
       - Credit assignment
       - Off-policy evaluation
    
    Comparison with Related Patterns:
    - vs. Correlation: Distinguishes cause from association
    - vs. Prediction: Explains mechanism, not just predicts
    - vs. Counterfactual (simple): Full causal framework
    - vs. Analogical: Causal structure vs similarity
    
    Integration with Other Patterns:
    - World Models: Causal = mechanism in world model
    - Planning: Causal graph guides planning
    - Explanation: Causal = better explanations
    - Reasoning: Foundation for robust reasoning
    
    Philosophical Note:
    Causal reasoning represents understanding of "why" not just
    "what". It's the difference between prediction (correlation)
    and understanding (causation). Essential for AGI.
    
    The Causal Reasoning Agent pattern enables agents to move
    beyond correlation to true causal understanding, supporting
    better decisions, explanations, and counterfactual reasoning.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_causal_reasoning()
    
        

    Agentic Design Pattern: Causal Reasoning

    Pattern 063: Causal Reasoning – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Causal Reasoning pattern implementation.


    Pattern Description

    Pattern 063: Causal Reasoning Agent

    Description: Causal Reasoning enables agents to understand cause-effect relationships, perform interventions, and reason about counterfactuals. Unlike correlation-based reasoning, causal reasoning distinguishes between “what happened” and “what would happen if we changed something”, enabling better decision-making and explanation.

    Components: 1. Causal Graph: Represents causal relationships between variables 2. Structural Equations: Model mechanisms of causation 3. Intervention Handler: Simulates “do” operations 4. Counterfactual Reasoner: Answers “what if” questions 5. Causal Discovery: Learns causal structure from data 6. Effect Estimation: Quantifies causal effects

    Use Cases: – Root cause analysis – Decision-making under uncertainty – Policy evaluation (“what if we change X?”) – Scientific reasoning and hypothesis testing – Fairness and bias analysis – Explanation generation

    LangChain Implementation: Implements causal reasoning using LLM-based causal graph construction, intervention simulation, and counterfactual query answering.


    File Statistics

    • Total Lines: 722
    • Classes: 7
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 063: Causal Reasoning Agent
    
    Description:
        Causal Reasoning enables agents to understand cause-effect relationships,
        perform interventions, and reason about counterfactuals. Unlike correlation-based
        reasoning, causal reasoning distinguishes between "what happened" and "what would
        happen if we changed something", enabling better decision-making and explanation.
    
    Components:
        1. Causal Graph: Represents causal relationships between variables
        2. Structural Equations: Model mechanisms of causation
        3. Intervention Handler: Simulates "do" operations
        4. Counterfactual Reasoner: Answers "what if" questions
        5. Causal Discovery: Learns causal structure from data
        6. Effect Estimation: Quantifies causal effects
    
    Use Cases:
        - Root cause analysis
        - Decision-making under uncertainty
        - Policy evaluation ("what if we change X?")
        - Scientific reasoning and hypothesis testing
        - Fairness and bias analysis
        - Explanation generation
    
    LangChain Implementation:
        Implements causal reasoning using LLM-based causal graph construction,
        intervention simulation, and counterfactual query answering.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import datetime.datetime
    import enum.Enum
    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
    import typing.Set
    import typing.Tuple
    

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – 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) – typing.Set: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: CausalRelationType

    Lines 46-52

    Inherits from: Enum

    Purpose:

    Types of causal relationships
    

    Class: CausalEdge

    Lines 56-66

    Purpose:

    Edge in causal graph
    

    Methods:

    __str__(self)

    Lines 63-66

    Purpose: Str operation.

    Parameters:

    Returns: str


    Class: CausalGraph

    Lines 70-111

    Purpose:

    Causal graph structure
    

    Methods:

    add_edge(self, cause, effect, strength, mechanism)

    Lines 75-79

    Purpose:

    Add causal edge
    

    Parameters: – cause: Cause – effect: Effect – strength: Strength – mechanism: Mechanism

    get_parents(self, variable)

    Lines 81-83

    Purpose:

    Get direct causes of variable
    

    Parameters: – variable: Variable

    Returns: <ast.Subscript object at 0x000001A06D68FD10>

    get_children(self, variable)

    Lines 85-87

    Purpose:

    Get direct effects of variable
    

    Parameters: – variable: Variable

    Returns: <ast.Subscript object at 0x000001A06D159310>

    get_ancestors(self, variable)

    Lines 89-100

    Purpose:

    Get all ancestors (transitive causes)
    

    Parameters: – variable: Variable

    Returns: <ast.Subscript object at 0x000001A06D15BA50>

    to_text(self)

    Lines 102-111

    Purpose:

    Convert graph to text
    

    Parameters:

    Returns: str


    Class: Intervention

    Lines 115-119

    Purpose:

    Causal intervention (do-operator)
    

    Class: CounterfactualQuery

    Lines 123-131

    Purpose:

    Counterfactual question
    

    Methods:

    to_text(self)

    Lines 129-131

    Purpose: To Text operation.

    Parameters:

    Returns: str


    Class: CausalAnswer

    Lines 135-141

    Purpose:

    Answer to causal query
    

    Class: CausalReasoningAgent

    Lines 144-410

    Purpose:

    Agent capable of causal reasoning.
    
    Features:
    1. Causal graph construction
    2. Intervention simulation
    3. Counterfactual reasoning
    4. Effect estimation
    5. Root cause analysis
    

    Methods:

    __init__(self, domain)

    Lines 156-170

    Purpose: Initialize the class instance with required parameters.

    Parameters: – domain: Domain

    build_causal_graph(self, context)

    Lines 172-253

    Purpose:

    Construct causal graph from context
    

    Parameters: – context: Context

    Returns: CausalGraph

    do_intervention(self, intervention, context)

    Lines 255-314

    Purpose:

    Simulate causal intervention (do-operator)
    

    Parameters: – intervention: Intervention – context: Context

    Returns: CausalAnswer

    answer_counterfactual(self, query, context)

    Lines 316-361

    Purpose:

    Answer counterfactual question
    

    Parameters: – query: Input query or question – context: Context

    Returns: CausalAnswer

    identify_root_cause(self, effect, context)

    Lines 363-410

    Purpose:

    Identify root cause of an effect
    

    Parameters: – effect: Effect – context: Context

    Returns: CausalAnswer


    Functions

    demonstrate_causal_reasoning()

    Lines 413-717

    Purpose:

    Demonstrate Causal Reasoning Agent pattern
    

    Key Concepts Summary

    Pattern Overview

    The Causal Reasoning 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 062
    • Pattern 064

    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 Causal Reasoning 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: 063_causal_reasoning.py
    Total Lines: 722
    Pattern Category: AI Agent Design Pattern #063


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: World Model

    
        """
    Pattern 062: World Model Learning
    
    Description:
        World Model Learning involves an agent building an internal model of its
        environment's dynamics. The agent learns to predict future states given
        current states and actions, enabling model-based planning, simulation,
        and what-if analysis without direct environment interaction.
    
    Components:
        1. State Representation: How the world is encoded
        2. Dynamics Model: Predicts next state from current state and action
        3. Reward Predictor: Estimates rewards for state-action pairs
        4. Experience Collection: Gathers real-world data
        5. Model Training: Updates predictions based on experience
        6. Planning Module: Uses model for decision-making
    
    Use Cases:
        - Robotics simulation and planning
        - Game AI with environment modeling
        - Strategic decision-making
        - Risk assessment and scenario planning
        - Sample-efficient reinforcement learning
        - Counterfactual reasoning
    
    LangChain Implementation:
        Implements world modeling using LLM-based state prediction, transition
        dynamics, and outcome forecasting for planning and decision-making.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    import random
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class StateType(Enum):
        """Types of state representations"""
        DISCRETE = "discrete"  # Finite states
        CONTINUOUS = "continuous"  # Continuous values
        HYBRID = "hybrid"  # Mix of both
        SYMBOLIC = "symbolic"  # Symbolic/linguistic
    
    
    @dataclass
    class State:
        """World state representation"""
        state_id: str
        description: str
        variables: Dict[str, Any] = field(default_factory=dict)
        timestamp: float = field(default_factory=time.time)
        
        def to_text(self) -> str:
            """Convert state to text"""
            text = f"State: {self.description}\n"
            if self.variables:
                text += "Variables:\n"
                for key, value in self.variables.items():
                    text += f"  - {key}: {value}\n"
            return text
    
    
    @dataclass
    class Action:
        """Action that can be taken"""
        action_id: str
        name: str
        parameters: Dict[str, Any] = field(default_factory=dict)
        
        def to_text(self) -> str:
            """Convert action to text"""
            text = f"Action: {self.name}"
            if self.parameters:
                params_str = ", ".join(f"{k}={v}" for k, v in self.parameters.items())
                text += f" ({params_str})"
            return text
    
    
    @dataclass
    class Transition:
        """State transition experience"""
        state: State
        action: Action
        next_state: State
        reward: float
        info: Dict[str, Any] = field(default_factory=dict)
    
    
    @dataclass
    class Prediction:
        """Model prediction"""
        predicted_state: State
        confidence: float
        predicted_reward: float
        alternative_outcomes: List[Tuple[State, float]] = field(default_factory=list)
    
    
    @dataclass
    class SimulationResult:
        """Result from simulation"""
        initial_state: State
        action_sequence: List[Action]
        predicted_trajectory: List[State]
        cumulative_reward: float
        success_probability: float
    
    
    class WorldModel:
        """
        World model for environment dynamics.
        
        Features:
        1. State transition prediction
        2. Reward prediction
        3. Experience-based learning
        4. Multi-step simulation
        5. Uncertainty estimation
        """
        
        def __init__(self, domain: str = "general"):
            self.domain = domain
            self.experiences: List[Transition] = []
            
            # Model for dynamics prediction
            self.dynamics_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.3
            )
            
            # Model for reward prediction
            self.reward_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.2
            )
            
            # Model for uncertainty estimation
            self.uncertainty_model = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.4
            )
        
        def add_experience(self, transition: Transition):
            """Add experience to model's memory"""
            self.experiences.append(transition)
        
        def _get_relevant_experiences(
            self,
            state: State,
            action: Action,
            k: int = 3
        ) -> List[Transition]:
            """Retrieve relevant past experiences"""
            
            if not self.experiences:
                return []
            
            # Simple relevance: recent experiences are more relevant
            # In production, would use embedding similarity
            return self.experiences[-k:] if len(self.experiences) >= k else self.experiences
        
        def predict_next_state(
            self,
            current_state: State,
            action: Action
        ) -> Prediction:
            """Predict next state given current state and action"""
            
            # Retrieve relevant experiences
            relevant = self._get_relevant_experiences(current_state, action)
            
            # Build context from experiences
            context = ""
            if relevant:
                context = "Past Similar Experiences:\n"
                for i, exp in enumerate(relevant, 1):
                    context += f"{i}. State: {exp.state.description}\n"
                    context += f"   Action: {exp.action.name}\n"
                    context += f"   Result: {exp.next_state.description}\n"
                    context += f"   Reward: {exp.reward}\n\n"
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are a world model for {self.domain}.
    
    Predict what happens when an action is taken in a given state.
    
    {context}
    
    Provide:
    1. Description of the resulting state
    2. Key variables that changed
    3. Confidence (0.0-1.0) in prediction"""),
                ("user", """Current State:
    {state}
    
    Action Taken:
    {action}
    
    Predicted Next State:""")
            ])
            
            chain = prompt | self.dynamics_model | StrOutputParser()
            prediction_text = chain.invoke({
                "state": current_state.to_text(),
                "action": action.to_text()
            })
            
            # Parse prediction
            lines = prediction_text.strip().split('\n')
            description = lines[0] if lines else "Unknown state"
            
            # Extract confidence if mentioned
            confidence = 0.7  # Default
            for line in lines:
                if 'confidence' in line.lower():
                    try:
                        confidence = float(''.join(c for c in line if c.isdigit() or c == '.'))
                        if confidence > 1.0:
                            confidence = confidence / 100.0
                        confidence = max(0.0, min(1.0, confidence))
                    except:
                        pass
            
            # Predict reward
            predicted_reward = self._predict_reward(current_state, action, description)
            
            predicted_state = State(
                state_id=f"predicted_{int(time.time())}",
                description=description,
                variables={}
            )
            
            return Prediction(
                predicted_state=predicted_state,
                confidence=confidence,
                predicted_reward=predicted_reward
            )
        
        def _predict_reward(
            self,
            state: State,
            action: Action,
            outcome: str
        ) -> float:
            """Predict reward for state-action-outcome"""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""Estimate the reward/value for an outcome in {self.domain}.
    
    Provide a number between -1.0 (very bad) and +1.0 (very good).
    Consider: goal achievement, efficiency, safety, side effects.
    
    Respond with just a number."""),
                ("user", """State: {state}
    Action: {action}
    Outcome: {outcome}
    
    Reward:""")
            ])
            
            chain = prompt | self.reward_model | StrOutputParser()
            
            try:
                reward_str = chain.invoke({
                    "state": state.description,
                    "action": action.name,
                    "outcome": outcome
                })
                reward = float(''.join(c for c in reward_str if c.isdigit() or c == '.' or c == '-'))
                return max(-1.0, min(1.0, reward))
            except:
                return 0.0
        
        def simulate_trajectory(
            self,
            initial_state: State,
            action_sequence: List[Action],
            max_steps: int = 10
        ) -> SimulationResult:
            """Simulate a trajectory through the world"""
            
            current_state = initial_state
            trajectory = [initial_state]
            cumulative_reward = 0.0
            confidences = []
            
            for i, action in enumerate(action_sequence[:max_steps]):
                # Predict next state
                prediction = self.predict_next_state(current_state, action)
                
                # Update trajectory
                trajectory.append(prediction.predicted_state)
                cumulative_reward += prediction.predicted_reward
                confidences.append(prediction.confidence)
                
                # Move to next state
                current_state = prediction.predicted_state
            
            # Success probability = average confidence
            success_prob = sum(confidences) / len(confidences) if confidences else 0.5
            
            return SimulationResult(
                initial_state=initial_state,
                action_sequence=action_sequence,
                predicted_trajectory=trajectory,
                cumulative_reward=cumulative_reward,
                success_probability=success_prob
            )
        
        def plan_with_model(
            self,
            current_state: State,
            goal: str,
            num_alternatives: int = 3
        ) -> List[SimulationResult]:
            """Use model for planning"""
            
            # Generate alternative action sequences
            planning_prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are planning in {self.domain}.
    
    Generate {num_alternatives} different action sequences to achieve the goal.
    
    For each plan, list 3-5 actions.
    
    Format:
    Plan 1:
    1. [action]
    2. [action]
    ...
    
    Plan 2:
    1. [action]
    ..."""),
                ("user", """Current State: {state}
    
    Goal: {goal}
    
    Action Plans:""")
            ])
            
            chain = planning_prompt | self.dynamics_model | StrOutputParser()
            plans_text = chain.invoke({
                "state": current_state.to_text(),
                "goal": goal
            })
            
            # Parse action sequences
            action_sequences = []
            current_sequence = []
            
            for line in plans_text.split('\n'):
                line = line.strip()
                
                if line.startswith('Plan'):
                    if current_sequence:
                        action_sequences.append(current_sequence)
                        current_sequence = []
                elif line and (line[0].isdigit() or line.startswith('-')):
                    action_name = line.lstrip('0123456789.-) ').strip()
                    if action_name:
                        action = Action(
                            action_id=f"action_{len(current_sequence)}",
                            name=action_name
                        )
                        current_sequence.append(action)
            
            if current_sequence:
                action_sequences.append(current_sequence)
            
            # Simulate each sequence
            simulations = []
            for seq in action_sequences[:num_alternatives]:
                if seq:  # Only simulate non-empty sequences
                    sim = self.simulate_trajectory(current_state, seq)
                    simulations.append(sim)
            
            # Sort by expected reward
            simulations.sort(key=lambda s: s.cumulative_reward, reverse=True)
            
            return simulations
    
    
    def demonstrate_world_model():
        """Demonstrate World Model Learning pattern"""
        
        print("=" * 80)
        print("PATTERN 062: WORLD MODEL LEARNING DEMONSTRATION")
        print("=" * 80)
        print("\nLearning environment dynamics for model-based planning\n")
        
        # Test 1: Building a world model from experience
        print("\n" + "=" * 80)
        print("TEST 1: Learning from Experience")
        print("=" * 80)
        
        world = WorldModel(domain="business strategy")
        
        # Add some experiences
        print("\n📚 Adding experiences to world model...")
        
        experiences = [
            Transition(
                state=State("s1", "Low market share, limited brand awareness"),
                action=Action("a1", "Launch aggressive marketing campaign"),
                next_state=State("s2", "Increased brand visibility, higher costs"),
                reward=0.3
            ),
            Transition(
                state=State("s2", "Increased brand visibility, higher costs"),
                action=Action("a2", "Optimize pricing strategy"),
                next_state=State("s3", "Improved market share, balanced budget"),
                reward=0.7
            ),
            Transition(
                state=State("s3", "Improved market share, balanced budget"),
                action=Action("a3", "Expand product line"),
                next_state=State("s4", "Diversified revenue, market leadership"),
                reward=0.9
            )
        ]
        
        for exp in experiences:
            world.add_experience(exp)
            print(f"   ✓ {exp.state.description} --[{exp.action.name}]--> {exp.next_state.description}")
        
        print(f"\n   Total experiences: {len(world.experiences)}")
        
        # Test 2: Predicting next state
        print("\n" + "=" * 80)
        print("TEST 2: State Prediction")
        print("=" * 80)
        
        current_state = State(
            "s_test",
            "New competitor entering market"
        )
        
        test_action = Action(
            "a_test",
            "Form strategic partnership"
        )
        
        print(f"\n🔮 Making prediction...")
        print(f"   Current: {current_state.description}")
        print(f"   Action: {test_action.name}")
        
        prediction = world.predict_next_state(current_state, test_action)
        
        print(f"\n✨ Predicted outcome:")
        print(f"   State: {prediction.predicted_state.description}")
        print(f"   Confidence: {prediction.confidence:.2f}")
        print(f"   Expected Reward: {prediction.predicted_reward:.2f}")
        
        # Test 3: Multi-step simulation
        print("\n" + "=" * 80)
        print("TEST 3: Trajectory Simulation")
        print("=" * 80)
        
        initial = State("s_start", "Startup with innovative product")
        
        actions = [
            Action("a1", "Secure seed funding"),
            Action("a2", "Build MVP"),
            Action("a3", "Beta testing with early adopters"),
            Action("a4", "Launch marketing campaign")
        ]
        
        print(f"\n🎬 Simulating trajectory...")
        print(f"   Initial State: {initial.description}")
        print(f"   Actions: {len(actions)}")
        
        simulation = world.simulate_trajectory(initial, actions)
        
        print(f"\n📊 Simulation Results:")
        print(f"   Success Probability: {simulation.success_probability:.2%}")
        print(f"   Cumulative Reward: {simulation.cumulative_reward:.2f}")
        print(f"\n   Trajectory:")
        for i, state in enumerate(simulation.predicted_trajectory):
            if i == 0:
                print(f"   0. [START] {state.description}")
            else:
                action = simulation.action_sequence[i-1]
                print(f"   {i}. --[{action.name}]--> {state.description}")
        
        # Test 4: Model-based planning
        print("\n" + "=" * 80)
        print("TEST 4: Model-Based Planning")
        print("=" * 80)
        
        planning_state = State(
            "s_plan",
            "Established company facing digital transformation"
        )
        
        goal = "Successfully transition to cloud-based operations"
        
        print(f"\n🎯 Planning Goal: {goal}")
        print(f"   Current State: {planning_state.description}")
        print(f"\n   Generating alternative plans...")
        
        plans = world.plan_with_model(planning_state, goal, num_alternatives=3)
        
        print(f"\n📋 Generated {len(plans)} alternative plans:\n")
        
        for i, plan in enumerate(plans, 1):
            print(f"   Plan {i}:")
            print(f"   Expected Reward: {plan.cumulative_reward:.2f}")
            print(f"   Success Probability: {plan.success_probability:.2%}")
            print(f"   Actions:")
            for j, action in enumerate(plan.action_sequence, 1):
                print(f"      {j}. {action.name}")
            print()
        
        if plans:
            print(f"   🏆 Recommended: Plan 1 (highest expected reward)")
        
        # Test 5: What-if analysis
        print("\n" + "=" * 80)
        print("TEST 5: What-If Analysis")
        print("=" * 80)
        
        world2 = WorldModel(domain="project management")
        
        project_state = State("proj", "Project behind schedule, team morale low")
        
        scenarios = [
            ("Hire additional developers", Action("a1", "Hire additional developers")),
            ("Reduce scope", Action("a2", "Reduce project scope")),
            ("Extend deadline", Action("a3", "Negotiate deadline extension"))
        ]
        
        print(f"\n🔍 Analyzing scenarios for: {project_state.description}\n")
        
        for scenario_name, action in scenarios:
            prediction = world2.predict_next_state(project_state, action)
            
            print(f"   Scenario: {scenario_name}")
            print(f"      Predicted outcome: {prediction.predicted_state.description}")
            print(f"      Expected reward: {prediction.predicted_reward:+.2f}")
            print(f"      Confidence: {prediction.confidence:.2%}")
            print()
        
        # Summary
        print("\n" + "=" * 80)
        print("WORLD MODEL LEARNING PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Sample Efficiency: Learn from limited experience
    2. Planning Capability: Simulate before acting
    3. Risk Assessment: Evaluate outcomes without execution
    4. What-If Analysis: Compare alternative scenarios
    5. Transfer Learning: Generalize across similar situations
    
    World Model Components:
    1. State Representation: How world is encoded
       - Discrete states (finite set)
       - Continuous states (real values)
       - Symbolic states (descriptions)
       - Hybrid (combination)
    
    2. Dynamics Model: Predicts transitions
       - Input: Current state + Action
       - Output: Next state prediction
       - Confidence estimation
    
    3. Reward Model: Estimates outcomes
       - Input: State + Action + Result
       - Output: Expected reward/value
       - Risk assessment
    
    4. Experience Buffer: Stores transitions
       - Real experiences
       - Simulated experiences
       - Relevance-based retrieval
    
    5. Planning Module: Uses model
       - Forward simulation
       - Trajectory optimization
       - Alternative comparison
    
    Learning Process:
    1. Collect Experience: Interact with environment
    2. Update Model: Learn from transitions
    3. Validate Predictions: Test accuracy
    4. Refine Model: Improve based on errors
    5. Plan Actions: Use model for decisions
    
    Model Types:
    1. Forward Model: State → Next State
       - Most common
       - Direct prediction
    
    2. Inverse Model: Current State + Next State → Action
       - Action discovery
       - Imitation learning
    
    3. Reward Model: State + Action → Reward
       - Value estimation
       - Policy evaluation
    
    4. Termination Model: State → Done?
       - Episode completion
       - Goal achievement
    
    Prediction Strategies:
    - Deterministic: Single prediction
    - Stochastic: Sample from distribution
    - Ensemble: Multiple models
    - Uncertainty-aware: Confidence bounds
    
    Use Cases:
    - Robotics: Simulate movements before execution
    - Game AI: Plan strategies by modeling game
    - Business: Scenario planning and forecasting
    - Healthcare: Treatment outcome prediction
    - Finance: Market behavior modeling
    - Education: Learning path optimization
    
    Planning with World Models:
    1. Model-Predictive Control (MPC)
       - Rolling horizon planning
       - Re-plan at each step
    
    2. Tree Search
       - Build search tree using model
       - Evaluate trajectories
    
    3. Trajectory Optimization
       - Find optimal action sequence
       - Gradient-based or sampling
    
    4. Policy Learning
       - Use model to improve policy
       - Dyna-style algorithms
    
    Challenges:
    1. Model Accuracy: Prediction errors compound
    2. Generalization: Unseen states
    3. Computational Cost: Simulation overhead
    4. Model Bias: Learned from limited data
    5. Non-stationarity: Environment changes
    
    Best Practices:
    1. Start with simple models
    2. Validate predictions frequently
    3. Use ensemble of models
    4. Estimate uncertainty
    5. Combine model-based and model-free
    6. Update model continuously
    7. Use for planning, not just prediction
    
    Production Considerations:
    - Model versioning and updates
    - Prediction latency requirements
    - Memory for experience buffer
    - Model accuracy metrics
    - Fallback to model-free
    - A/B testing model changes
    - Continuous validation
    
    Advanced Techniques:
    1. Latent World Models: Learn compressed representations
    2. Hierarchical Models: Multi-scale predictions
    3. Causal Models: Understand mechanisms
    4. Meta-Learning Models: Quick adaptation
    5. Uncertainty-Aware Models: Calibrated predictions
    
    Comparison with Related Patterns:
    - vs. Model-Free RL: More sample efficient
    - vs. Imitation Learning: Enables planning
    - vs. Heuristic Planning: Data-driven
    - vs. Simulation: Learned not programmed
    
    Integration with Other Patterns:
    - Curriculum Learning: Progressive model building
    - Meta-Learning: Quick model adaptation
    - Active Learning: Query informative states
    - Memory Patterns: Store experiences
    
    The World Model Learning pattern enables agents to learn
    environment dynamics from experience and use these models
    for efficient planning, simulation, and decision-making.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_world_model()
    
        

    Agentic Design Pattern: World Model

    Pattern 062: World Model – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the World Model pattern implementation.


    Pattern Description

    Pattern 062: World Model Learning

    Description: World Model Learning involves an agent building an internal model of its environment’s dynamics. The agent learns to predict future states given current states and actions, enabling model-based planning, simulation, and what-if analysis without direct environment interaction.

    Components: 1. State Representation: How the world is encoded 2. Dynamics Model: Predicts next state from current state and action 3. Reward Predictor: Estimates rewards for state-action pairs 4. Experience Collection: Gathers real-world data 5. Model Training: Updates predictions based on experience 6. Planning Module: Uses model for decision-making

    Use Cases: – Robotics simulation and planning – Game AI with environment modeling – Strategic decision-making – Risk assessment and scenario planning – Sample-efficient reinforcement learning – Counterfactual reasoning

    LangChain Implementation: Implements world modeling using LLM-based state prediction, transition dynamics, and outcome forecasting for planning and decision-making.


    File Statistics

    • Total Lines: 699
    • Classes: 7
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 062: World Model Learning
    
    Description:
        World Model Learning involves an agent building an internal model of its
        environment's dynamics. The agent learns to predict future states given
        current states and actions, enabling model-based planning, simulation,
        and what-if analysis without direct environment interaction.
    
    Components:
        1. State Representation: How the world is encoded
        2. Dynamics Model: Predicts next state from current state and action
        3. Reward Predictor: Estimates rewards for state-action pairs
        4. Experience Collection: Gathers real-world data
        5. Model Training: Updates predictions based on experience
        6. Planning Module: Uses model for decision-making
    
    Use Cases:
        - Robotics simulation and planning
        - Game AI with environment modeling
        - Strategic decision-making
        - Risk assessment and scenario planning
        - Sample-efficient reinforcement learning
        - Counterfactual reasoning
    
    LangChain Implementation:
        Implements world modeling using LLM-based state prediction, transition
        dynamics, and outcome forecasting for planning and decision-making.
    """
    

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


    Import Statements

    Standard Library Imports

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

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – 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) – typing.Tuple: 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
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: StateType

    Lines 47-52

    Inherits from: Enum

    Purpose:

    Types of state representations
    

    Class: State

    Lines 56-70

    Purpose:

    World state representation
    

    Methods:

    to_text(self)

    Lines 63-70

    Purpose:

    Convert state to text
    

    Parameters:

    Returns: str


    Class: Action

    Lines 74-86

    Purpose:

    Action that can be taken
    

    Methods:

    to_text(self)

    Lines 80-86

    Purpose:

    Convert action to text
    

    Parameters:

    Returns: str


    Class: Transition

    Lines 90-96

    Purpose:

    State transition experience
    

    Class: Prediction

    Lines 100-105

    Purpose:

    Model prediction
    

    Class: SimulationResult

    Lines 109-115

    Purpose:

    Result from simulation
    

    Class: WorldModel

    Lines 118-390

    Purpose:

    World model for environment dynamics.
    
    Features:
    1. State transition prediction
    2. Reward prediction
    3. Experience-based learning
    4. Multi-step simulation
    5. Uncertainty estimation
    

    Methods:

    __init__(self, domain)

    Lines 130-150

    Purpose: Initialize the class instance with required parameters.

    Parameters: – domain: Domain

    add_experience(self, transition)

    Lines 152-154

    Purpose:

    Add experience to model's memory
    

    Parameters: – transition: Transition

    _get_relevant_experiences(self, state, action, k)

    Lines 156-169

    Purpose:

    Retrieve relevant past experiences
    

    Parameters: – state: State – action: Action – k: K

    Returns: <ast.Subscript object at 0x000001A06D6601D0>

    predict_next_state(self, current_state, action)

    Lines 171-246

    Purpose:

    Predict next state given current state and action
    

    Parameters: – current_state: Current state – action: Action

    Returns: Prediction

    _predict_reward(self, state, action, outcome)

    Lines 248-281

    Purpose:

    Predict reward for state-action-outcome
    

    Parameters: – state: State – action: Action – outcome: Outcome

    Returns: float

    simulate_trajectory(self, initial_state, action_sequence, max_steps)

    Lines 283-317

    Purpose:

    Simulate a trajectory through the world
    

    Parameters: – initial_state: Initial state – action_sequence: Action sequence – max_steps: Max steps

    Returns: SimulationResult

    plan_with_model(self, current_state, goal, num_alternatives)

    Lines 319-390

    Purpose:

    Use model for planning
    

    Parameters: – current_state: Current state – goal: Goal – num_alternatives: Num alternatives

    Returns: <ast.Subscript object at 0x000001A06D625C10>


    Functions

    demonstrate_world_model()

    Lines 393-694

    Purpose:

    Demonstrate World Model Learning pattern
    

    Key Concepts Summary

    Pattern Overview

    The World Model 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 061
    • Pattern 063

    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 World Model 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: 062_world_model.py
    Total Lines: 699
    Pattern Category: AI Agent Design Pattern #062


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Diffusion Planning

    
        """
    Pattern 061: Diffusion-Based Planning
    
    Description:
        Diffusion-Based Planning uses diffusion models (inspired by diffusion processes
        in physics) for trajectory planning and action sequence generation. This approach
        can handle multimodal distributions and generate diverse, high-quality plans by
        iteratively denoising random noise into coherent action sequences.
    
    Components:
        1. Diffusion Process: Forward (add noise) and reverse (denoise)
        2. Action Sequence Generator: Creates trajectory proposals
        3. Denoising Steps: Iterative refinement of plans
        4. Scoring Function: Evaluates plan quality
        5. Sampling Strategy: Controls exploration-exploitation
    
    Use Cases:
        - Robotics motion planning
        - Game AI pathfinding
        - Multi-step action planning
        - Creative sequence generation
        - Trajectory optimization
        - Scenario planning
    
    LangChain Implementation:
        Implements diffusion-inspired planning using LLM-based iterative refinement,
        noise injection, and progressive denoising for plan generation.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    import random
    import math
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class NoiseLevel(Enum):
        """Noise levels for diffusion process"""
        HIGH = "high"  # Initial high noise
        MEDIUM = "medium"  # Mid-process
        LOW = "low"  # Near convergence
        MINIMAL = "minimal"  # Final refinement
    
    
    class SamplingStrategy(Enum):
        """Strategies for plan sampling"""
        DETERMINISTIC = "deterministic"  # Single best plan
        STOCHASTIC = "stochastic"  # Sample from distribution
        BEAM_SEARCH = "beam_search"  # Keep top-k candidates
        DIVERSE = "diverse"  # Maximize diversity
    
    
    @dataclass
    class ActionStep:
        """Single action in a sequence"""
        step_id: int
        action: str
        parameters: Dict[str, Any] = field(default_factory=dict)
        confidence: float = 1.0
        
        def add_noise(self, noise_level: float) -> 'ActionStep':
            """Add noise to action for diffusion"""
            # In real implementation, this would modify action parameters
            # Here we adjust confidence as proxy for noise
            noisy_confidence = max(0.1, self.confidence * (1 - noise_level * 0.5))
            return ActionStep(
                step_id=self.step_id,
                action=self.action,
                parameters=self.parameters.copy(),
                confidence=noisy_confidence
            )
    
    
    @dataclass
    class Plan:
        """Complete action plan"""
        plan_id: str
        goal: str
        steps: List[ActionStep]
        noise_level: float = 0.0
        quality_score: float = 0.0
        diversity_score: float = 0.0
        
        @property
        def num_steps(self) -> int:
            return len(self.steps)
        
        def to_text(self) -> str:
            """Convert plan to text representation"""
            text = f"Goal: {self.goal}\n\nSteps:\n"
            for step in self.steps:
                text += f"{step.step_id}. {step.action}\n"
            return text
    
    
    @dataclass
    class DiffusionResult:
        """Result from diffusion planning"""
        goal: str
        final_plans: List[Plan]
        diffusion_steps: int
        best_plan: Plan
        diversity_score: float
        total_time_ms: float
        
        def to_dict(self) -> Dict[str, Any]:
            return {
                "goal": self.goal[:80] + "...",
                "num_plans": len(self.final_plans),
                "best_plan_steps": self.best_plan.num_steps,
                "best_quality": f"{self.best_plan.quality_score:.2f}",
                "diversity": f"{self.diversity_score:.2f}",
                "diffusion_steps": self.diffusion_steps,
                "total_time_ms": f"{self.total_time_ms:.1f}"
            }
    
    
    class DiffusionPlanner:
        """
        Diffusion-based planning agent.
        
        Features:
        1. Iterative plan denoising
        2. Multi-modal plan generation
        3. Quality-guided refinement
        4. Diverse plan sampling
        5. Progressive improvement
        """
        
        def __init__(
            self,
            num_diffusion_steps: int = 5,
            num_candidates: int = 3,
            sampling_strategy: SamplingStrategy = SamplingStrategy.DIVERSE,
            temperature: float = 0.7
        ):
            self.num_diffusion_steps = num_diffusion_steps
            self.num_candidates = num_candidates
            self.sampling_strategy = sampling_strategy
            self.temperature = temperature
            
            # Planner LLM (higher temperature for creativity)
            self.planner = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=temperature
            )
            
            # Refiner LLM (lower temperature for quality)
            self.refiner = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.3
            )
            
            # Evaluator LLM
            self.evaluator = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.2
            )
        
        def _generate_noisy_plan(
            self,
            goal: str,
            noise_level: float,
            context: Optional[str] = None
        ) -> Plan:
            """Generate initial noisy plan"""
            
            # Higher noise = more randomness = more creative/diverse
            creativity_instruction = ""
            if noise_level > 0.7:
                creativity_instruction = "Be highly creative and explore unusual approaches."
            elif noise_level > 0.4:
                creativity_instruction = "Consider multiple different approaches."
            else:
                creativity_instruction = "Focus on practical, direct solutions."
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are generating an action plan to achieve a goal.
    
    {creativity_instruction}
    
    {context if context else ""}
    
    Generate a step-by-step plan. Format:
    1. [action step]
    2. [action step]
    ..."""),
                ("user", "Goal: {goal}\n\nPlan:")
            ])
            
            chain = prompt | self.planner | StrOutputParser()
            plan_text = chain.invoke({"goal": goal})
            
            # Parse steps
            steps = []
            for i, line in enumerate(plan_text.split('\n'), 1):
                line = line.strip()
                if line and (line[0].isdigit() or line.startswith('-')):
                    # Remove numbering
                    action = line.lstrip('0123456789.-) ').strip()
                    if action:
                        steps.append(ActionStep(
                            step_id=i,
                            action=action,
                            confidence=1.0 - noise_level * 0.3
                        ))
            
            return Plan(
                plan_id=f"plan_{random.randint(1000, 9999)}",
                goal=goal,
                steps=steps,
                noise_level=noise_level
            )
        
        def _denoise_plan(
            self,
            noisy_plan: Plan,
            target_noise_level: float,
            iteration: int
        ) -> Plan:
            """Denoise/refine a plan"""
            
            # Calculate denoising strength
            denoise_strength = noisy_plan.noise_level - target_noise_level
            
            if denoise_strength  float:
            """Evaluate plan quality"""
            
            evaluation_prompt = ChatPromptTemplate.from_messages([
                ("system", """Evaluate this action plan on a scale of 0.0 to 1.0.
    
    Consider:
    - Completeness: Does it achieve the goal?
    - Feasibility: Is it realistic?
    - Efficiency: Minimal unnecessary steps?
    - Clarity: Clear and actionable?
    
    Respond with just a number between 0.0 and 1.0"""),
                ("user", "{plan}")
            ])
            
            chain = evaluation_prompt | self.evaluator | StrOutputParser()
            
            try:
                score_str = chain.invoke({"plan": plan.to_text()})
                score = float(''.join(c for c in score_str if c.isdigit() or c == '.'))
                return max(0.0, min(1.0, score))
            except:
                # Fallback: score based on step count and confidence
                avg_confidence = sum(s.confidence for s in plan.steps) / len(plan.steps) if plan.steps else 0
                step_score = min(1.0, len(plan.steps) / 5.0)  # Prefer 5-ish steps
                return (avg_confidence + step_score) / 2
        
        def _calculate_diversity(self, plans: List[Plan]) -> float:
            """Calculate diversity among plans"""
            
            if len(plans)  0 else 0.0
        
        def plan(
            self,
            goal: str,
            constraints: Optional[List[str]] = None
        ) -> DiffusionResult:
            """Generate plans using diffusion process"""
            
            start_time = time.time()
            
            print(f"\n🌊 Starting diffusion-based planning...")
            print(f"   Goal: {goal}")
            print(f"   Diffusion steps: {self.num_diffusion_steps}")
            print(f"   Candidates: {self.num_candidates}")
            
            # Build context from constraints
            context = ""
            if constraints:
                context = "Constraints:\n" + "\n".join(f"- {c}" for c in constraints)
            
            # Generate initial noisy plans
            print(f"\n📍 Step 0: Generating {self.num_candidates} initial noisy plans...")
            
            current_plans = []
            for i in range(self.num_candidates):
                # Start with high noise for diversity
                initial_noise = 0.8 + random.random() * 0.2  # 0.8-1.0
                plan = self._generate_noisy_plan(goal, initial_noise, context)
                current_plans.append(plan)
                print(f"   Plan {i+1}: {len(plan.steps)} steps (noise: {initial_noise:.2f})")
            
            # Diffusion process: progressively denoise
            for step in range(1, self.num_diffusion_steps + 1):
                # Calculate target noise level (decreasing)
                target_noise = max(0.0, 1.0 - (step / self.num_diffusion_steps))
                
                print(f"\n📍 Step {step}/{self.num_diffusion_steps}: Denoising to {target_noise:.2f}")
                
                # Denoise all plans
                denoised_plans = []
                for plan in current_plans:
                    denoised = self._denoise_plan(plan, target_noise, step)
                    denoised_plans.append(denoised)
                
                # Evaluate plans
                for plan in denoised_plans:
                    plan.quality_score = self._evaluate_plan(plan)
                
                # Select best plans based on strategy
                if self.sampling_strategy == SamplingStrategy.BEAM_SEARCH:
                    # Keep top-k by quality
                    denoised_plans.sort(key=lambda p: p.quality_score, reverse=True)
                    current_plans = denoised_plans[:self.num_candidates]
                    
                elif self.sampling_strategy == SamplingStrategy.DIVERSE:
                    # Balance quality and diversity
                    # Keep best plan + diverse alternatives
                    denoised_plans.sort(key=lambda p: p.quality_score, reverse=True)
                    current_plans = [denoised_plans[0]]  # Best plan
                    
                    # Add diverse plans
                    for plan in denoised_plans[1:]:
                        if len(current_plans) >= self.num_candidates:
                            break
                        # Check if sufficiently different
                        is_diverse = True
                        for existing in current_plans:
                            # Simple diversity check
                            common_actions = sum(
                                1 for s1 in plan.steps
                                for s2 in existing.steps
                                if s1.action[:30] == s2.action[:30]
                            )
                            if common_actions > len(plan.steps) * 0.7:
                                is_diverse = False
                                break
                        
                        if is_diverse:
                            current_plans.append(plan)
                
                else:  # DETERMINISTIC or STOCHASTIC
                    denoised_plans.sort(key=lambda p: p.quality_score, reverse=True)
                    current_plans = denoised_plans[:self.num_candidates]
                
                # Show progress
                for i, plan in enumerate(current_plans, 1):
                    print(f"   Plan {i}: Quality {plan.quality_score:.2f}, {len(plan.steps)} steps")
            
            # Final evaluation
            print(f"\n✅ Diffusion complete. Evaluating final plans...")
            
            for plan in current_plans:
                plan.quality_score = self._evaluate_plan(plan)
            
            # Calculate diversity
            diversity_score = self._calculate_diversity(current_plans)
            
            # Select best plan
            best_plan = max(current_plans, key=lambda p: p.quality_score)
            
            total_time_ms = (time.time() - start_time) * 1000
            
            return DiffusionResult(
                goal=goal,
                final_plans=current_plans,
                diffusion_steps=self.num_diffusion_steps,
                best_plan=best_plan,
                diversity_score=diversity_score,
                total_time_ms=total_time_ms
            )
    
    
    def demonstrate_diffusion_planning():
        """Demonstrate Diffusion-Based Planning pattern"""
        
        print("=" * 80)
        print("PATTERN 061: DIFFUSION-BASED PLANNING DEMONSTRATION")
        print("=" * 80)
        print("\nIterative plan refinement through diffusion process\n")
        
        # Test 1: Simple goal with diverse sampling
        print("\n" + "=" * 80)
        print("TEST 1: Diverse Plan Generation")
        print("=" * 80)
        
        planner1 = DiffusionPlanner(
            num_diffusion_steps=4,
            num_candidates=3,
            sampling_strategy=SamplingStrategy.DIVERSE,
            temperature=0.8
        )
        
        goal1 = "Plan a successful product launch"
        
        result1 = planner1.plan(goal1)
        
        print(f"\n📊 Results:")
        print(f"   Plans Generated: {len(result1.final_plans)}")
        print(f"   Diversity Score: {result1.diversity_score:.2f}")
        print(f"   Total Time: {result1.total_time_ms:.1f}ms")
        
        print(f"\n🏆 Best Plan (Quality: {result1.best_plan.quality_score:.2f}):")
        for step in result1.best_plan.steps:
            print(f"   {step.step_id}. {step.action}")
        
        print(f"\n🔄 Alternative Plans:")
        for i, plan in enumerate(result1.final_plans[1:], 2):
            print(f"\n   Plan {i} (Quality: {plan.quality_score:.2f}):")
            for step in plan.steps[:3]:
                print(f"      {step.step_id}. {step.action}")
            if len(plan.steps) > 3:
                print(f"      ... ({len(plan.steps) - 3} more steps)")
        
        # Test 2: Constrained planning
        print("\n" + "=" * 80)
        print("TEST 2: Constrained Diffusion Planning")
        print("=" * 80)
        
        planner2 = DiffusionPlanner(
            num_diffusion_steps=5,
            num_candidates=2,
            sampling_strategy=SamplingStrategy.BEAM_SEARCH
        )
        
        goal2 = "Organize a team-building event"
        constraints2 = [
            "Budget limited to $1000",
            "Must be completed within 2 weeks",
            "Remote team across different time zones"
        ]
        
        print(f"\n🎯 Goal: {goal2}")
        print(f"📋 Constraints:")
        for constraint in constraints2:
            print(f"   - {constraint}")
        
        result2 = planner2.plan(goal2, constraints=constraints2)
        
        print(f"\n🏆 Optimized Plan (Quality: {result2.best_plan.quality_score:.2f}):")
        for step in result2.best_plan.steps:
            print(f"   {step.step_id}. {step.action}")
        
        # Test 3: Comparing diffusion steps
        print("\n" + "=" * 80)
        print("TEST 3: Impact of Diffusion Steps")
        print("=" * 80)
        
        goal3 = "Improve customer satisfaction"
        
        for num_steps in [2, 4, 6]:
            print(f"\n   Testing with {num_steps} diffusion steps...")
            
            planner = DiffusionPlanner(
                num_diffusion_steps=num_steps,
                num_candidates=2,
                sampling_strategy=SamplingStrategy.DIVERSE
            )
            
            result = planner.plan(goal3)
            
            print(f"      Best Quality: {result.best_plan.quality_score:.2f}")
            print(f"      Diversity: {result.diversity_score:.2f}")
            print(f"      Time: {result.total_time_ms:.1f}ms")
            print(f"      Steps in plan: {result.best_plan.num_steps}")
        
        # Summary
        print("\n" + "=" * 80)
        print("DIFFUSION-BASED PLANNING PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Multi-Modal Plans: Generates diverse alternative plans
    2. Progressive Refinement: Iterative quality improvement
    3. Exploration-Exploitation: Balances creativity and quality
    4. Robustness: Multiple candidate solutions
    5. Flexible Control: Adjustable diffusion process
    
    Diffusion Process:
    1. Forward Process: Add noise (generate diverse initial plans)
    2. Reverse Process: Denoise (iteratively refine plans)
    3. Multiple Steps: Progressive quality improvement
    4. Sampling: Control exploration vs exploitation
    
    Components:
    - Noise Injection: Creates initial diversity
    - Denoising: Iterative refinement
    - Quality Scoring: Evaluates plan effectiveness
    - Diversity Measurement: Ensures variety
    - Selection Strategy: Chooses best candidates
    
    Sampling Strategies:
    1. Deterministic: Single best path
       - Highest quality guarantee
       - No diversity
    
    2. Stochastic: Sample from distribution
       - Controlled randomness
       - Balanced exploration
    
    3. Beam Search: Keep top-k candidates
       - Quality-focused
       - Limited diversity
    
    4. Diverse: Maximize variety
       - Multiple alternatives
       - Creative solutions
    
    Noise Schedules:
    - High Noise (1.0 → 0.7): Initial creativity
    - Medium Noise (0.7 → 0.4): Refinement begins
    - Low Noise (0.4 → 0.1): Convergence
    - Minimal Noise (0.1 → 0.0): Final polish
    
    Use Cases:
    - Robotics: Motion planning, trajectory optimization
    - Game AI: Pathfinding, strategy generation
    - Planning: Project planning, resource allocation
    - Creative Tasks: Story generation, design
    - Optimization: Multi-objective planning
    - Scenario Analysis: Alternative futures
    
    Best Practices:
    1. More steps = higher quality (but slower)
    2. More candidates = more diversity
    3. Appropriate noise schedule
    4. Quality-diversity balance
    5. Constraint incorporation
    6. Iterative evaluation
    7. Early stopping when converged
    
    Production Considerations:
    - Computational cost (multiple LLM calls)
    - Caching intermediate results
    - Parallel plan generation
    - Quality thresholds
    - Timeout handling
    - Plan validation
    - User preference learning
    
    Comparison with Related Patterns:
    - vs. Tree-of-Thoughts: Continuous vs discrete
    - vs. Beam Search: Diffusion vs greedy
    - vs. Monte Carlo: Systematic vs random
    - vs. Evolutionary: Denoising vs mutation
    
    Advanced Techniques:
    1. Conditional Diffusion: Constraint-aware
    2. Guided Diffusion: Quality-guided sampling
    3. Classifier-Free: Balance guidance
    4. Latent Diffusion: Work in abstract space
    5. Cascaded: Multi-resolution planning
    
    Research Inspiration:
    - Denoising Diffusion Probabilistic Models (DDPM)
    - Score-based Generative Models
    - Diffusion for Planning (Diffuser)
    - Planning with Diffusion for Flexible Behavior
    
    The Diffusion-Based Planning pattern provides a powerful framework
    for generating diverse, high-quality plans through iterative
    refinement inspired by diffusion processes in physics.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_diffusion_planning()
    
        

    Agentic Design Pattern: Diffusion Planning

    Pattern 061: Diffusion Planning – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Diffusion Planning pattern implementation.


    Pattern Description

    Pattern 061: Diffusion-Based Planning

    Description: Diffusion-Based Planning uses diffusion models (inspired by diffusion processes in physics) for trajectory planning and action sequence generation. This approach can handle multimodal distributions and generate diverse, high-quality plans by iteratively denoising random noise into coherent action sequences.

    Components: 1. Diffusion Process: Forward (add noise) and reverse (denoise) 2. Action Sequence Generator: Creates trajectory proposals 3. Denoising Steps: Iterative refinement of plans 4. Scoring Function: Evaluates plan quality 5. Sampling Strategy: Controls exploration-exploitation

    Use Cases: – Robotics motion planning – Game AI pathfinding – Multi-step action planning – Creative sequence generation – Trajectory optimization – Scenario planning

    LangChain Implementation: Implements diffusion-inspired planning using LLM-based iterative refinement, noise injection, and progressive denoising for plan generation.


    File Statistics

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

    Module Documentation

    """
    Pattern 061: Diffusion-Based Planning
    
    Description:
        Diffusion-Based Planning uses diffusion models (inspired by diffusion processes
        in physics) for trajectory planning and action sequence generation. This approach
        can handle multimodal distributions and generate diverse, high-quality plans by
        iteratively denoising random noise into coherent action sequences.
    
    Components:
        1. Diffusion Process: Forward (add noise) and reverse (denoise)
        2. Action Sequence Generator: Creates trajectory proposals
        3. Denoising Steps: Iterative refinement of plans
        4. Scoring Function: Evaluates plan quality
        5. Sampling Strategy: Controls exploration-exploitation
    
    Use Cases:
        - Robotics motion planning
        - Game AI pathfinding
        - Multi-step action planning
        - Creative sequence generation
        - Trajectory optimization
        - Scenario planning
    
    LangChain Implementation:
        Implements diffusion-inspired planning using LLM-based iterative refinement,
        noise injection, and progressive denoising for plan generation.
    """
    

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


    Import Statements

    Standard Library Imports

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

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – 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) – typing.Tuple: 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 math
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: NoiseLevel

    Lines 47-52

    Inherits from: Enum

    Purpose:

    Noise levels for diffusion process
    

    Class: SamplingStrategy

    Lines 55-60

    Inherits from: Enum

    Purpose:

    Strategies for plan sampling
    

    Class: ActionStep

    Lines 64-81

    Purpose:

    Single action in a sequence
    

    Methods:

    add_noise(self, noise_level)

    Lines 71-81

    Purpose:

    Add noise to action for diffusion
    

    Parameters: – noise_level: Noise level

    Returns: ActionStep


    Class: Plan

    Lines 85-103

    Purpose:

    Complete action plan
    

    Methods:

    num_steps(self)

    Lines 95-96

    Decorators: property

    Purpose: Num Steps operation.

    Parameters:

    Returns: int

    to_text(self)

    Lines 98-103

    Purpose:

    Convert plan to text representation
    

    Parameters:

    Returns: str


    Class: DiffusionResult

    Lines 107-125

    Purpose:

    Result from diffusion planning
    

    Methods:

    to_dict(self)

    Lines 116-125

    Purpose: To Dict operation.

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D79BDD0>


    Class: DiffusionPlanner

    Lines 128-440

    Purpose:

    Diffusion-based planning agent.
    
    Features:
    1. Iterative plan denoising
    2. Multi-modal plan generation
    3. Quality-guided refinement
    4. Diverse plan sampling
    5. Progressive improvement
    

    Methods:

    __init__(self, num_diffusion_steps, num_candidates, sampling_strategy, temperature)

    Lines 140-168

    Purpose: Initialize the class instance with required parameters.

    Parameters: – num_diffusion_steps: Num diffusion steps – num_candidates: Num candidates – sampling_strategy: Sampling strategy – temperature: Sampling temperature (0-1)

    _generate_noisy_plan(self, goal, noise_level, context)

    Lines 170-223

    Purpose:

    Generate initial noisy plan
    

    Parameters: – goal: Goal – noise_level: Noise level – context: Context

    Returns: Plan

    _denoise_plan(self, noisy_plan, target_noise_level, iteration)

    Lines 225-286

    Purpose:

    Denoise/refine a plan
    

    Parameters: – noisy_plan: Noisy plan – target_noise_level: Target noise level – iteration: Iteration

    Returns: Plan

    _evaluate_plan(self, plan)

    Lines 288-314

    Purpose:

    Evaluate plan quality
    

    Parameters: – plan: Plan

    Returns: float

    _calculate_diversity(self, plans)

    Lines 316-330

    Purpose:

    Calculate diversity among plans
    

    Parameters: – plans: Plans

    Returns: float

    plan(self, goal, constraints)

    Lines 332-440

    Purpose:

    Generate plans using diffusion process
    

    Parameters: – goal: Goal – constraints: Constraints

    Returns: DiffusionResult


    Functions

    demonstrate_diffusion_planning()

    Lines 443-632

    Purpose:

    Demonstrate Diffusion-Based Planning pattern
    

    Key Concepts Summary

    Pattern Overview

    The Diffusion Planning 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 060
    • Pattern 062

    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 Diffusion Planning 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: 061_diffusion_planning.py
    Total Lines: 637
    Pattern Category: AI Agent Design Pattern #061


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Curriculum Learning

    
        """
    Pattern 060: Curriculum Learning
    
    Description:
        Curriculum Learning structures the learning process by presenting training
        examples in a meaningful order, from simple to complex. Like human education,
        this progressive approach helps agents build foundational skills before
        tackling harder problems, leading to faster learning and better performance.
    
    Components:
        1. Difficulty Scorer: Evaluates task complexity
        2. Curriculum Designer: Orders tasks by difficulty
        3. Pacing Strategy: Determines progression speed
        4. Performance Monitor: Tracks learning progress
        5. Adaptive Scheduler: Adjusts curriculum based on performance
    
    Use Cases:
        - Training complex reasoning agents
        - Gradual skill acquisition
        - Hierarchical task learning
        - Educational tutoring systems
        - Reinforcement learning with sparse rewards
        - Knowledge scaffolding
    
    LangChain Implementation:
        Implements curriculum learning by ordering prompts and examples from
        simple to complex, adapting progression based on agent performance.
    """
    
    import os
    import time
    from typing import Dict, Any, List, Optional, Tuple, Callable
    from dataclasses import dataclass, field
    from datetime import datetime
    from enum import Enum
    import statistics
    import random
    
    from dotenv import load_dotenv
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    load_dotenv()
    
    
    class DifficultyLevel(Enum):
        """Difficulty levels for curriculum"""
        TRIVIAL = 1
        EASY = 2
        MEDIUM = 3
        HARD = 4
        EXPERT = 5
    
    
    class PacingStrategy(Enum):
        """Strategies for curriculum pacing"""
        FIXED = "fixed"  # Predetermined progression
        ADAPTIVE = "adaptive"  # Based on performance
        MIXED = "mixed"  # Combine easy and hard
        SPIRAL = "spiral"  # Revisit with increasing complexity
        COMPETENCY_BASED = "competency_based"  # Advance when mastered
    
    
    @dataclass
    class LearningExample:
        """A single learning example"""
        example_id: str
        task_type: str
        input_text: str
        expected_output: str
        difficulty: DifficultyLevel
        prerequisites: List[str] = field(default_factory=list)
        concepts: List[str] = field(default_factory=list)
        metadata: Dict[str, Any] = field(default_factory=dict)
        
        def is_prerequisite_met(self, mastered_examples: Set[str]) -> bool:
            """Check if prerequisites are satisfied"""
            return all(prereq in mastered_examples for prereq in self.prerequisites)
    
    
    @dataclass
    class CurriculumStage:
        """A stage in the curriculum"""
        stage_id: int
        stage_name: str
        difficulty_range: Tuple[DifficultyLevel, DifficultyLevel]
        examples: List[LearningExample]
        mastery_threshold: float = 0.8
        
        @property
        def num_examples(self) -> int:
            return len(self.examples)
    
    
    @dataclass
    class PerformanceRecord:
        """Record of performance on an example"""
        example_id: str
        attempt_number: int
        correct: bool
        response: str
        timestamp: datetime
        time_taken_ms: float
    
    
    @dataclass
    class CurriculumResult:
        """Result from curriculum learning"""
        stages_completed: int
        total_examples: int
        examples_mastered: int
        overall_accuracy: float
        learning_curve: List[Tuple[int, float]]  # (example_num, accuracy)
        time_per_stage: List[float]
        final_performance: float
        total_time_ms: float
        
        def to_dict(self) -> Dict[str, Any]:
            return {
                "stages_completed": self.stages_completed,
                "examples_mastered": f"{self.examples_mastered}/{self.total_examples}",
                "overall_accuracy": f"{self.overall_accuracy:.1%}",
                "final_performance": f"{self.final_performance:.1%}",
                "total_time_ms": f"{self.total_time_ms:.1f}"
            }
    
    
    class CurriculumLearner:
        """
        Agent that learns through curriculum progression.
        
        Features:
        1. Difficulty-ordered example presentation
        2. Performance-based pacing
        3. Mastery tracking
        4. Adaptive progression
        5. Skill scaffolding
        """
        
        def __init__(
            self,
            pacing_strategy: PacingStrategy = PacingStrategy.ADAPTIVE,
            mastery_threshold: float = 0.8,
            temperature: float = 0.7
        ):
            self.pacing_strategy = pacing_strategy
            self.mastery_threshold = mastery_threshold
            self.temperature = temperature
            
            # Learner LLM
            self.learner = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=temperature
            )
            
            # Difficulty assessor
            self.assessor = ChatOpenAI(
                model_name="gpt-3.5-turbo",
                temperature=0.2
            )
            
            # Learning state
            self.mastered_examples: Set[str] = set()
            self.performance_history: List[PerformanceRecord] = []
            self.current_stage: int = 0
            
            # Learning statistics
            self.learning_curve: List[Tuple[int, float]] = []
        
        def _assess_difficulty(self, example: LearningExample) -> DifficultyLevel:
            """Assess difficulty of an example"""
            
            # If already labeled, use that
            if example.difficulty != DifficultyLevel.MEDIUM:
                return example.difficulty
            
            # Otherwise, assess automatically
            assessment_prompt = ChatPromptTemplate.from_messages([
                ("system", """Rate the difficulty of this learning task on a scale:
    1 - Trivial (very simple, obvious answer)
    2 - Easy (straightforward, basic reasoning)
    3 - Medium (requires some thought)
    4 - Hard (complex, multiple steps)
    5 - Expert (very challenging, advanced concepts)
    
    Respond with just the number."""),
                ("user", "Task: {input}\nExpected: {output}\n\nDifficulty:")
            ])
            
            chain = assessment_prompt | self.assessor | StrOutputParser()
            
            try:
                rating = chain.invoke({
                    "input": example.input_text,
                    "output": example.expected_output
                })
                
                difficulty_num = int(''.join(c for c in rating if c.isdigit()))
                return DifficultyLevel(min(max(difficulty_num, 1), 5))
            except:
                return DifficultyLevel.MEDIUM
        
        def _create_curriculum(
            self,
            examples: List[LearningExample]
        ) -> List[CurriculumStage]:
            """Create curriculum stages from examples"""
            
            # Assess difficulties if needed
            for example in examples:
                if example.difficulty == DifficultyLevel.MEDIUM and not example.metadata.get("assessed"):
                    example.difficulty = self._assess_difficulty(example)
                    example.metadata["assessed"] = True
            
            # Group by difficulty
            stages_dict: Dict[DifficultyLevel, List[LearningExample]] = {}
            for example in examples:
                if example.difficulty not in stages_dict:
                    stages_dict[example.difficulty] = []
                stages_dict[example.difficulty].append(example)
            
            # Create stages
            stages = []
            for difficulty in sorted(stages_dict.keys(), key=lambda x: x.value):
                stage = CurriculumStage(
                    stage_id=len(stages),
                    stage_name=f"Stage {len(stages)+1}: {difficulty.name}",
                    difficulty_range=(difficulty, difficulty),
                    examples=stages_dict[difficulty],
                    mastery_threshold=self.mastery_threshold
                )
                stages.append(stage)
            
            return stages
        
        def _attempt_example(
            self,
            example: LearningExample,
            previous_examples: List[LearningExample]
        ) -> PerformanceRecord:
            """Attempt to solve an example"""
            
            start_time = time.time()
            
            # Build context from previous examples
            context = ""
            if previous_examples:
                context = "Previous examples:\n" + "\n".join([
                    f"Input: {ex.input_text}\nOutput: {ex.expected_output}"
                    for ex in previous_examples[-3:]  # Last 3 examples
                ]) + "\n\n"
            
            # Generate response
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are learning to solve tasks step by step.
    
    {context}Now solve this task:"""),
                ("user", "{input}")
            ])
            
            chain = prompt | self.learner | StrOutputParser()
            response = chain.invoke({
                "context": context,
                "input": example.input_text
            })
            
            time_taken_ms = (time.time() - start_time) * 1000
            
            # Check correctness (simplified)
            response_normalized = response.strip().lower()
            expected_normalized = example.expected_output.strip().lower()
            
            # Flexible matching
            correct = (
                response_normalized == expected_normalized or
                expected_normalized in response_normalized or
                response_normalized in expected_normalized
            )
            
            # Count attempts
            attempt_number = len([
                r for r in self.performance_history
                if r.example_id == example.example_id
            ]) + 1
            
            record = PerformanceRecord(
                example_id=example.example_id,
                attempt_number=attempt_number,
                correct=correct,
                response=response,
                timestamp=datetime.now(),
                time_taken_ms=time_taken_ms
            )
            
            self.performance_history.append(record)
            
            return record
        
        def _calculate_stage_performance(
            self,
            stage: CurriculumStage
        ) -> float:
            """Calculate performance on current stage"""
            
            stage_example_ids = {ex.example_id for ex in stage.examples}
            
            recent_records = [
                r for r in self.performance_history[-20:]  # Last 20 attempts
                if r.example_id in stage_example_ids
            ]
            
            if not recent_records:
                return 0.0
            
            correct_count = sum(1 for r in recent_records if r.correct)
            return correct_count / len(recent_records)
        
        def _should_advance(
            self,
            stage: CurriculumStage,
            attempts_on_stage: int
        ) -> bool:
            """Determine if should advance to next stage"""
            
            if self.pacing_strategy == PacingStrategy.FIXED:
                # Fixed progression after N attempts
                return attempts_on_stage >= len(stage.examples) * 2
            
            elif self.pacing_strategy == PacingStrategy.ADAPTIVE:
                # Advance when performance exceeds threshold
                performance = self._calculate_stage_performance(stage)
                return performance >= stage.mastery_threshold and attempts_on_stage >= len(stage.examples)
            
            elif self.pacing_strategy == PacingStrategy.COMPETENCY_BASED:
                # Advance when all examples mastered
                stage_example_ids = {ex.example_id for ex in stage.examples}
                mastered_in_stage = stage_example_ids & self.mastered_examples
                return len(mastered_in_stage) >= len(stage_example_ids) * stage.mastery_threshold
            
            else:
                # Default: adaptive
                performance = self._calculate_stage_performance(stage)
                return performance >= stage.mastery_threshold
        
        def learn_curriculum(
            self,
            examples: List[LearningExample],
            max_attempts_per_stage: int = 20
        ) -> CurriculumResult:
            """Learn through curriculum progression"""
            
            start_time = time.time()
            
            print(f"\n📚 Starting curriculum learning with {len(examples)} examples...")
            
            # Create curriculum
            stages = self._create_curriculum(examples)
            
            print(f"\n🎯 Curriculum Structure:")
            for stage in stages:
                print(f"   {stage.stage_name}: {stage.num_examples} examples")
            
            # Learn through stages
            time_per_stage = []
            
            for stage_idx, stage in enumerate(stages):
                stage_start = time.time()
                
                print(f"\n📖 Stage {stage_idx + 1}/{len(stages)}: {stage.stage_name}")
                print(f"   Examples: {stage.num_examples}")
                print(f"   Mastery Threshold: {stage.mastery_threshold:.0%}")
                
                attempts_on_stage = 0
                previous_examples = []
                
                # Learn examples in this stage
                while attempts_on_stage  float:
            """Calculate accuracy over recent attempts"""
            
            if not self.performance_history:
                return 0.0
            
            recent = self.performance_history[-window:]
            correct = sum(1 for r in recent if r.correct)
            return correct / len(recent)
    
    
    def demonstrate_curriculum_learning():
        """Demonstrate Curriculum Learning pattern"""
        
        print("=" * 80)
        print("PATTERN 060: CURRICULUM LEARNING DEMONSTRATION")
        print("=" * 80)
        print("\nProgressive learning from simple to complex\n")
        
        # Create learning examples with varying difficulty
        examples = [
            # Trivial examples
            LearningExample("ex1", "math", "What is 2 + 2?", "4", DifficultyLevel.TRIVIAL),
            LearningExample("ex2", "math", "What is 5 + 3?", "8", DifficultyLevel.TRIVIAL),
            LearningExample("ex3", "math", "What is 10 - 6?", "4", DifficultyLevel.TRIVIAL),
            
            # Easy examples
            LearningExample("ex4", "math", "What is 12 + 15?", "27", DifficultyLevel.EASY, prerequisites=["ex1"]),
            LearningExample("ex5", "math", "What is 25 - 8?", "17", DifficultyLevel.EASY, prerequisites=["ex3"]),
            LearningExample("ex6", "math", "What is 7 times 3?", "21", DifficultyLevel.EASY),
            
            # Medium examples
            LearningExample("ex7", "math", "What is 15 times 4?", "60", DifficultyLevel.MEDIUM, prerequisites=["ex6"]),
            LearningExample("ex8", "math", "What is 100 divided by 4?", "25", DifficultyLevel.MEDIUM),
            LearningExample("ex9", "math", "What is (5 + 3) times 2?", "16", DifficultyLevel.MEDIUM, prerequisites=["ex4", "ex6"]),
            
            # Hard examples
            LearningExample("ex10", "math", "What is 25% of 80?", "20", DifficultyLevel.HARD, prerequisites=["ex8"]),
            LearningExample("ex11", "math", "If x + 5 = 12, what is x?", "7", DifficultyLevel.HARD, prerequisites=["ex4"]),
        ]
        
        # Test 1: Adaptive curriculum
        print("\n" + "=" * 80)
        print("TEST 1: Adaptive Curriculum Learning")
        print("=" * 80)
        
        learner1 = CurriculumLearner(
            pacing_strategy=PacingStrategy.ADAPTIVE,
            mastery_threshold=0.7
        )
        
        result1 = learner1.learn_curriculum(examples, max_attempts_per_stage=15)
        
        print(f"\n📊 Learning Results:")
        print(f"   Stages Completed: {result1.stages_completed}")
        print(f"   Examples Mastered: {result1.examples_mastered}/{result1.total_examples}")
        print(f"   Overall Accuracy: {result1.overall_accuracy:.1%}")
        print(f"   Final Performance: {result1.final_performance:.1%}")
        print(f"   Total Time: {result1.total_time_ms/1000:.1f}s")
        
        print(f"\n📈 Learning Curve (samples):")
        for i in range(0, len(result1.learning_curve), max(1, len(result1.learning_curve)//5)):
            attempt_num, accuracy = result1.learning_curve[i]
            print(f"   Attempt {attempt_num}: {accuracy:.1%} accuracy")
        
        print(f"\n⏱️  Time per Stage:")
        for i, stage_time in enumerate(result1.time_per_stage, 1):
            print(f"   Stage {i}: {stage_time:.1f}s")
        
        # Test 2: Fixed pacing
        print("\n" + "=" * 80)
        print("TEST 2: Fixed Pacing Curriculum")
        print("=" * 80)
        
        learner2 = CurriculumLearner(
            pacing_strategy=PacingStrategy.FIXED,
            mastery_threshold=0.6
        )
        
        # Use subset for faster demo
        subset_examples = examples[:8]
        
        result2 = learner2.learn_curriculum(subset_examples, max_attempts_per_stage=10)
        
        print(f"\n📊 Fixed Pacing Results:")
        print(f"   Stages: {result2.stages_completed}")
        print(f"   Mastered: {result2.examples_mastered}/{result2.total_examples}")
        print(f"   Accuracy: {result2.overall_accuracy:.1%}")
        
        # Test 3: Learning curve comparison
        print("\n" + "=" * 80)
        print("TEST 3: Learning Progress Visualization")
        print("=" * 80)
        
        print(f"\n📈 Performance Improvement:")
        curve = result1.learning_curve
        
        # Show improvement
        if len(curve) >= 3:
            early_perf = statistics.mean([acc for _, acc in curve[:3]])
            mid_perf = statistics.mean([acc for _, acc in curve[len(curve)//2:len(curve)//2+3]])
            late_perf = statistics.mean([acc for _, acc in curve[-3:]])
            
            print(f"   Early (first 3): {early_perf:.1%}")
            print(f"   Middle: {mid_perf:.1%}")
            print(f"   Late (last 3): {late_perf:.1%}")
            print(f"   Improvement: +{(late_perf - early_perf):.1%}")
        
        # Summary
        print("\n" + "=" * 80)
        print("CURRICULUM LEARNING PATTERN SUMMARY")
        print("=" * 80)
        print("""
    Key Benefits:
    1. Faster Learning: Build on foundations progressively
    2. Better Performance: Master basics before advanced topics
    3. Reduced Frustration: Appropriate difficulty at each stage
    4. Stable Training: Avoid overwhelming with complexity
    5. Improved Retention: Scaffold knowledge systematically
    
    Curriculum Design:
    1. Difficulty Ordering: Simple → Complex
    2. Prerequisite Structure: Build dependencies
    3. Concept Progression: Foundational → Advanced
    4. Skill Scaffolding: Support skill development
    5. Spiral Learning: Revisit concepts with depth
    
    Difficulty Assessment:
    - Automatic: LLM-based complexity scoring
    - Manual: Expert labeling
    - Hybrid: Combine both approaches
    - Dynamic: Adjust based on learner performance
    - Multi-dimensional: Consider multiple factors
    
    Pacing Strategies:
    1. Fixed: Predetermined progression
       - Simple, predictable
       - May not fit all learners
    
    2. Adaptive: Performance-based
       - Personalized pace
       - More complex logic
    
    3. Mixed: Interleave difficulties
       - Maintain engagement
       - Prevent boredom
    
    4. Spiral: Revisit with complexity
       - Reinforce learning
       - Deepen understanding
    
    5. Competency-Based: Master before advance
       - Ensure readiness
       - May be slower
    
    Curriculum Stages:
    1. Foundation: Basic concepts
    2. Development: Core skills
    3. Integration: Combine concepts
    4. Application: Real problems
    5. Mastery: Advanced challenges
    
    Performance Monitoring:
    - Accuracy tracking
    - Time per example
    - Mastery identification
    - Learning curve analysis
    - Plateau detection
    
    Advancement Criteria:
    - Accuracy threshold (e.g., 80%)
    - Minimum attempts per stage
    - Mastery of N examples
    - Time-based progression
    - Adaptive combinations
    
    Use Cases:
    - Educational tutoring systems
    - Agent skill development
    - Reinforcement learning
    - Language learning
    - Technical training
    - Complex task acquisition
    
    Best Practices:
    1. Clear difficulty progression
    2. Prerequisite tracking
    3. Performance monitoring
    4. Adaptive pacing
    5. Regular assessment
    6. Sufficient practice
    7. Gradual complexity increase
    
    Production Considerations:
    - Curriculum versioning
    - Learner state persistence
    - Performance analytics
    - A/B testing curricula
    - Difficulty calibration
    - Prerequisite validation
    - Scalability
    
    Comparison with Related Patterns:
    - vs. Random: Structured vs unordered
    - vs. Hard Example Mining: Systematic vs selection
    - vs. Meta-Learning: Progression vs strategy learning
    - vs. Active Learning: Curriculum vs query strategy
    
    Challenges:
    1. Difficulty assessment accuracy
    2. Optimal ordering
    3. Individual differences
    4. Curriculum generalization
    5. Pacing calibration
    
    Research Findings:
    - 30-50% faster learning vs random
    - Better final performance
    - Improved generalization
    - Reduced training instability
    - Context-dependent effectiveness
    
    The Curriculum Learning pattern enables efficient learning through
    strategic ordering of examples from simple to complex, mirroring
    effective human educational approaches.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_curriculum_learning()
    
        

    Agentic Design Pattern: Curriculum Learning

    Pattern 060: Curriculum Learning – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Curriculum Learning pattern implementation.


    Pattern Description

    Pattern 060: Curriculum Learning

    Description: Curriculum Learning structures the learning process by presenting training examples in a meaningful order, from simple to complex. Like human education, this progressive approach helps agents build foundational skills before tackling harder problems, leading to faster learning and better performance.

    Components: 1. Difficulty Scorer: Evaluates task complexity 2. Curriculum Designer: Orders tasks by difficulty 3. Pacing Strategy: Determines progression speed 4. Performance Monitor: Tracks learning progress 5. Adaptive Scheduler: Adjusts curriculum based on performance

    Use Cases: – Training complex reasoning agents – Gradual skill acquisition – Hierarchical task learning – Educational tutoring systems – Reinforcement learning with sparse rewards – Knowledge scaffolding

    LangChain Implementation: Implements curriculum learning by ordering prompts and examples from simple to complex, adapting progression based on agent performance.


    File Statistics

    • Total Lines: 666
    • Classes: 7
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 060: Curriculum Learning
    
    Description:
        Curriculum Learning structures the learning process by presenting training
        examples in a meaningful order, from simple to complex. Like human education,
        this progressive approach helps agents build foundational skills before
        tackling harder problems, leading to faster learning and better performance.
    
    Components:
        1. Difficulty Scorer: Evaluates task complexity
        2. Curriculum Designer: Orders tasks by difficulty
        3. Pacing Strategy: Determines progression speed
        4. Performance Monitor: Tracks learning progress
        5. Adaptive Scheduler: Adjusts curriculum based on performance
    
    Use Cases:
        - Training complex reasoning agents
        - Gradual skill acquisition
        - Hierarchical task learning
        - Educational tutoring systems
        - Reinforcement learning with sparse rewards
        - Knowledge scaffolding
    
    LangChain Implementation:
        Implements curriculum learning by ordering prompts and examples from
        simple to complex, adapting progression based on agent performance.
    """
    

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


    Import Statements

    Standard Library Imports

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

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – time: Time-related functions – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Callable: 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) – typing.Tuple: 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 statistics
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: DifficultyLevel

    Lines 47-53

    Inherits from: Enum

    Purpose:

    Difficulty levels for curriculum
    

    Class: PacingStrategy

    Lines 56-62

    Inherits from: Enum

    Purpose:

    Strategies for curriculum pacing
    

    Class: LearningExample

    Lines 66-79

    Purpose:

    A single learning example
    

    Methods:

    is_prerequisite_met(self, mastered_examples)

    Lines 77-79

    Purpose:

    Check if prerequisites are satisfied
    

    Parameters: – mastered_examples: Mastered examples

    Returns: bool


    Class: CurriculumStage

    Lines 83-93

    Purpose:

    A stage in the curriculum
    

    Methods:

    num_examples(self)

    Lines 92-93

    Decorators: property

    Purpose: Num Examples operation.

    Parameters:

    Returns: int


    Class: PerformanceRecord

    Lines 97-104

    Purpose:

    Record of performance on an example
    

    Class: CurriculumResult

    Lines 108-126

    Purpose:

    Result from curriculum learning
    

    Methods:

    to_dict(self)

    Lines 119-126

    Purpose: To Dict operation.

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D700050>


    Class: CurriculumLearner

    Lines 129-444

    Purpose:

    Agent that learns through curriculum progression.
    
    Features:
    1. Difficulty-ordered example presentation
    2. Performance-based pacing
    3. Mastery tracking
    4. Adaptive progression
    5. Skill scaffolding
    

    Methods:

    __init__(self, pacing_strategy, mastery_threshold, temperature)

    Lines 141-169

    Purpose: Initialize the class instance with required parameters.

    Parameters: – pacing_strategy: Pacing strategy – mastery_threshold: Mastery threshold – temperature: Sampling temperature (0-1)

    _assess_difficulty(self, example)

    Lines 171-202

    Purpose:

    Assess difficulty of an example
    

    Parameters: – example: Example

    Returns: DifficultyLevel

    _create_curriculum(self, examples)

    Lines 204-235

    Purpose:

    Create curriculum stages from examples
    

    Parameters: – examples: Examples

    Returns: <ast.Subscript object at 0x000001A06D1683D0>

    _attempt_example(self, example, previous_examples)

    Lines 237-298

    Purpose:

    Attempt to solve an example
    

    Parameters: – example: Example – previous_examples: Previous examples

    Returns: PerformanceRecord

    _calculate_stage_performance(self, stage)

    Lines 300-317

    Purpose:

    Calculate performance on current stage
    

    Parameters: – stage: Stage

    Returns: float

    _should_advance(self, stage, attempts_on_stage)

    Lines 319-344

    Purpose:

    Determine if should advance to next stage
    

    Parameters: – stage: Stage – attempts_on_stage: Attempts on stage

    Returns: bool

    learn_curriculum(self, examples, max_attempts_per_stage)

    Lines 346-434

    Purpose:

    Learn through curriculum progression
    

    Parameters: – examples: Examples – max_attempts_per_stage: Max attempts per stage

    Returns: CurriculumResult

    _calculate_recent_accuracy(self, window)

    Lines 436-444

    Purpose:

    Calculate accuracy over recent attempts
    

    Parameters: – window: Window

    Returns: float


    Functions

    demonstrate_curriculum_learning()

    Lines 447-661

    Purpose:

    Demonstrate Curriculum Learning pattern
    

    Key Concepts Summary

    Pattern Overview

    The Curriculum Learning 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 059
    • Pattern 061

    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 Curriculum Learning 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: 060_curriculum_learning.py
    Total Lines: 666
    Pattern Category: AI Agent Design Pattern #060


    Generated from actual implementation code
    For educational and reference purposes