Tag: ai

  • Agentic Design Pattern: Iterative Refinement

    
        """
    Pattern 024: Iterative Refinement
    
    Description:
        The Iterative Refinement pattern enables agents to progressively improve outputs
        through cycles of generation, evaluation, and refinement. The agent generates an
        initial solution, evaluates it against criteria, receives feedback, and refines
        the solution iteratively until quality thresholds are met or iteration limits reached.
    
    Components:
        - Generator: Creates initial and refined solutions
        - Evaluator: Assesses solution quality against criteria
        - Feedback Generator: Produces actionable improvement suggestions
        - Refinement Loop: Orchestrates iterative improvement cycles
        - Convergence Checker: Determines when to stop iterating
    
    Use Cases:
        - Content creation and editing
        - Code optimization and debugging
        - Design iteration and improvement
        - Answer quality enhancement
        - Complex problem solving
    
    LangChain Implementation:
        Uses separate LLM instances for generation and evaluation,
        implements feedback loops with structured criteria, and tracks
        improvement metrics across iterations.
    
    Key Features:
        - Multi-criteria evaluation
        - Targeted feedback generation
        - Iterative improvement tracking
        - Convergence detection
        - Quality metrics monitoring
    """
    
    import os
    from typing import List, Dict, Any, Optional, Callable
    from dataclasses import dataclass, field
    from enum import Enum
    from datetime import datetime
    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 EvaluationCriterion(Enum):
        """Criteria for evaluating solutions."""
        ACCURACY = "accuracy"
        COMPLETENESS = "completeness"
        CLARITY = "clarity"
        EFFICIENCY = "efficiency"
        CREATIVITY = "creativity"
        RELEVANCE = "relevance"
        COHERENCE = "coherence"
        STYLE = "style"
    
    
    @dataclass
    class EvaluationScore:
        """Score for a single criterion."""
        criterion: EvaluationCriterion
        score: float  # 0-10
        feedback: str
        
        def to_dict(self) -> Dict[str, Any]:
            """Convert to dictionary."""
            return {
                "criterion": self.criterion.value,
                "score": self.score,
                "feedback": self.feedback
            }
    
    
    @dataclass
    class Evaluation:
        """Complete evaluation of a solution."""
        scores: List[EvaluationScore]
        overall_score: float
        summary: str
        suggestions: List[str]
        timestamp: datetime = field(default_factory=datetime.now)
        
        def get_score(self, criterion: EvaluationCriterion) -> Optional[float]:
            """Get score for specific criterion."""
            for score in self.scores:
                if score.criterion == criterion:
                    return score.score
            return None
        
        def get_feedback(self, criterion: EvaluationCriterion) -> Optional[str]:
            """Get feedback for specific criterion."""
            for score in self.scores:
                if score.criterion == criterion:
                    return score.feedback
            return None
    
    
    @dataclass
    class Solution:
        """A solution with metadata."""
        content: str
        iteration: int
        evaluation: Optional[Evaluation] = None
        timestamp: datetime = field(default_factory=datetime.now)
    
    
    class SolutionGenerator:
        """
        Generates and refines solutions.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """
            Initialize solution generator.
            
            Args:
                model: LLM model to use
            """
            self.llm = ChatOpenAI(model=model, temperature=0.7)
        
        def generate_initial(
            self,
            task: str,
            requirements: Optional[str] = None
        ) -> Solution:
            """
            Generate initial solution.
            
            Args:
                task: Task description
                requirements: Optional requirements
                
            Returns:
                Initial solution
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an expert problem solver. Generate a high-quality
    solution to the given task."""),
                ("user", "{task}{requirements_str}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            requirements_str = f"\n\nRequirements:\n{requirements}" if requirements else ""
            
            content = chain.invoke({
                "task": task,
                "requirements_str": requirements_str
            })
            
            return Solution(
                content=content.strip(),
                iteration=0
            )
        
        def refine(
            self,
            task: str,
            current_solution: str,
            evaluation: Evaluation,
            iteration: int
        ) -> Solution:
            """
            Refine solution based on feedback.
            
            Args:
                task: Original task
                current_solution: Current solution
                evaluation: Evaluation with feedback
                iteration: Current iteration number
                
            Returns:
                Refined solution
            """
            # Format feedback
            feedback_items = [
                f"- {score.criterion.value}: {score.score}/10 - {score.feedback}"
                for score in evaluation.scores
            ]
            feedback_text = "\n".join(feedback_items)
            
            suggestions_text = "\n".join([
                f"- {suggestion}"
                for suggestion in evaluation.suggestions
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are refining a solution based on evaluation feedback.
    Improve the solution by addressing the feedback while maintaining its strengths."""),
                ("user", """Task: {task}
    
    Current Solution:
    {current_solution}
    
    Evaluation Feedback:
    {feedback_text}
    
    Suggestions for Improvement:
    {suggestions_text}
    
    Overall Score: {overall_score}/10
    
    Provide an improved version that addresses the feedback:""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            content = chain.invoke({
                "task": task,
                "current_solution": current_solution,
                "feedback_text": feedback_text,
                "suggestions_text": suggestions_text,
                "overall_score": evaluation.overall_score
            })
            
            return Solution(
                content=content.strip(),
                iteration=iteration
            )
    
    
    class SolutionEvaluator:
        """
        Evaluates solutions against criteria.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """
            Initialize solution evaluator.
            
            Args:
                model: LLM model to use
            """
            self.llm = ChatOpenAI(model=model, temperature=0.2)
        
        def evaluate(
            self,
            task: str,
            solution: Solution,
            criteria: List[EvaluationCriterion]
        ) -> Evaluation:
            """
            Evaluate solution against criteria.
            
            Args:
                task: Original task
                solution: Solution to evaluate
                criteria: Evaluation criteria
                
            Returns:
                Evaluation with scores and feedback
            """
            criteria_text = ", ".join([c.value for c in criteria])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an expert evaluator. Assess the solution against
    each criterion on a scale of 0-10, provide specific feedback, and suggest improvements."""),
                ("user", """Task: {task}
    
    Solution:
    {solution}
    
    Evaluation Criteria: {criteria_text}
    
    For each criterion, provide:
    1. Score (0-10)
    2. Specific feedback
    3. Suggestions for improvement
    
    Format your response as:
    CRITERION: [criterion name]
    SCORE: [0-10]
    FEEDBACK: [specific feedback]
    
    [Repeat for each criterion]
    
    OVERALL:
    SCORE: [average score]
    SUMMARY: [brief overall assessment]
    SUGGESTIONS:
    - [suggestion 1]
    - [suggestion 2]
    - [etc.]""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "task": task,
                "solution": solution.content,
                "criteria_text": criteria_text
            })
            
            # Parse evaluation
            return self._parse_evaluation(response, criteria)
        
        def _parse_evaluation(
            self,
            response: str,
            criteria: List[EvaluationCriterion]
        ) -> Evaluation:
            """Parse evaluation from LLM response."""
            lines = response.split("\n")
            
            scores = []
            overall_score = 0.0
            summary = ""
            suggestions = []
            
            current_criterion = None
            current_score = 0.0
            current_feedback = ""
            in_suggestions = False
            
            for line in lines:
                line = line.strip()
                
                if line.startswith("CRITERION:"):
                    if current_criterion:
                        scores.append(EvaluationScore(
                            criterion=current_criterion,
                            score=current_score,
                            feedback=current_feedback
                        ))
                    criterion_name = line.split(":", 1)[1].strip()
                    for c in criteria:
                        if c.value in criterion_name.lower():
                            current_criterion = c
                            break
                    current_feedback = ""
                    
                elif line.startswith("SCORE:"):
                    score_text = line.split(":", 1)[1].strip()
                    try:
                        if "/" in score_text:
                            current_score = float(score_text.split("/")[0])
                        else:
                            current_score = float(score_text)
                    except:
                        current_score = 5.0
                        
                elif line.startswith("FEEDBACK:"):
                    current_feedback = line.split(":", 1)[1].strip()
                    
                elif line.startswith("OVERALL:"):
                    if current_criterion:
                        scores.append(EvaluationScore(
                            criterion=current_criterion,
                            score=current_score,
                            feedback=current_feedback
                        ))
                        current_criterion = None
                        
                elif line.startswith("SUMMARY:"):
                    summary = line.split(":", 1)[1].strip()
                    
                elif line.startswith("SUGGESTIONS:"):
                    in_suggestions = True
                    
                elif in_suggestions and line.startswith("-"):
                    suggestion = line[1:].strip()
                    if suggestion:
                        suggestions.append(suggestion)
            
            # Add last criterion if exists
            if current_criterion:
                scores.append(EvaluationScore(
                    criterion=current_criterion,
                    score=current_score,
                    feedback=current_feedback
                ))
            
            # Calculate overall score
            if scores:
                overall_score = sum(s.score for s in scores) / len(scores)
            else:
                # Fallback parsing
                for line in lines:
                    if "SCORE:" in line and "OVERALL" in response[max(0, response.find(line)-50):response.find(line)]:
                        try:
                            score_text = line.split(":", 1)[1].strip()
                            if "/" in score_text:
                                overall_score = float(score_text.split("/")[0])
                            else:
                                overall_score = float(score_text)
                            break
                        except:
                            pass
            
            if not summary:
                summary = "Evaluation complete"
            
            if not suggestions:
                suggestions = ["Continue refining based on feedback"]
            
            return Evaluation(
                scores=scores if scores else [
                    EvaluationScore(c, overall_score, "See overall feedback")
                    for c in criteria
                ],
                overall_score=overall_score,
                summary=summary,
                suggestions=suggestions
            )
    
    
    class IterativeRefinementAgent:
        """
        Agent that iteratively refines solutions.
        """
        
        def __init__(
            self,
            model: str = "gpt-3.5-turbo",
            max_iterations: int = 3,
            quality_threshold: float = 8.0
        ):
            """
            Initialize iterative refinement agent.
            
            Args:
                model: LLM model to use
                max_iterations: Maximum refinement iterations
                quality_threshold: Minimum quality score to stop iterating
            """
            self.generator = SolutionGenerator(model)
            self.evaluator = SolutionEvaluator(model)
            self.max_iterations = max_iterations
            self.quality_threshold = quality_threshold
            self.history: List[Solution] = []
        
        def solve(
            self,
            task: str,
            criteria: List[EvaluationCriterion],
            requirements: Optional[str] = None
        ) -> Dict[str, Any]:
            """
            Solve task with iterative refinement.
            
            Args:
                task: Task description
                criteria: Evaluation criteria
                requirements: Optional requirements
                
            Returns:
                Solution with refinement history
            """
            print(f"\n[Agent] Starting iterative refinement")
            print(f"[Agent] Task: {task}")
            print(f"[Agent] Criteria: {[c.value for c in criteria]}")
            print(f"[Agent] Max iterations: {self.max_iterations}")
            print(f"[Agent] Quality threshold: {self.quality_threshold}/10\n")
            
            # Generate initial solution
            print("=" * 80)
            print("ITERATION 0: Initial Generation")
            print("=" * 80)
            
            current = self.generator.generate_initial(task, requirements)
            
            print("\nGenerated Solution:")
            print("-" * 80)
            print(current.content)
            print("-" * 80)
            
            # Evaluate initial solution
            print("\nEvaluating...")
            evaluation = self.evaluator.evaluate(task, current, criteria)
            current.evaluation = evaluation
            self.history.append(current)
            
            print(f"\nOverall Score: {evaluation.overall_score:.1f}/10")
            print("\nCriterion Scores:")
            for score in evaluation.scores:
                print(f"  - {score.criterion.value}: {score.score:.1f}/10")
            
            # Refinement loop
            iteration = 1
            while iteration = self.quality_threshold:
                    print(f"\n[Agent] ✓ Quality threshold met ({current.evaluation.overall_score:.1f} >= {self.quality_threshold})")
                    break
                
                print(f"\n{'=' * 80}")
                print(f"ITERATION {iteration}: Refinement")
                print("=" * 80)
                
                print("\nKey Suggestions:")
                for suggestion in current.evaluation.suggestions[:3]:
                    print(f"  - {suggestion}")
                
                # Refine solution
                print("\nRefining solution...")
                current = self.generator.refine(
                    task,
                    current.content,
                    current.evaluation,
                    iteration
                )
                
                print("\nRefined Solution:")
                print("-" * 80)
                print(current.content)
                print("-" * 80)
                
                # Evaluate refined solution
                print("\nEvaluating...")
                evaluation = self.evaluator.evaluate(task, current, criteria)
                current.evaluation = evaluation
                self.history.append(current)
                
                print(f"\nOverall Score: {evaluation.overall_score:.1f}/10")
                
                # Show improvement
                if len(self.history) > 1:
                    prev_score = self.history[-2].evaluation.overall_score
                    improvement = evaluation.overall_score - prev_score
                    print(f"Improvement: {improvement:+.1f}")
                
                print("\nCriterion Scores:")
                for score in evaluation.scores:
                    print(f"  - {score.criterion.value}: {score.score:.1f}/10")
                
                iteration += 1
            
            # Summary
            print(f"\n{'=' * 80}")
            print("REFINEMENT SUMMARY")
            print("=" * 80)
            
            final_solution = self.history[-1]
            initial_score = self.history[0].evaluation.overall_score
            final_score = final_solution.evaluation.overall_score
            
            print(f"\nIterations completed: {len(self.history) - 1}")
            print(f"Initial score: {initial_score:.1f}/10")
            print(f"Final score: {final_score:.1f}/10")
            print(f"Total improvement: {final_score - initial_score:+.1f}")
            print(f"Success: {final_score >= self.quality_threshold}")
            
            return {
                "task": task,
                "final_solution": final_solution,
                "history": self.history,
                "iterations": len(self.history) - 1,
                "initial_score": initial_score,
                "final_score": final_score,
                "improvement": final_score - initial_score,
                "threshold_met": final_score >= self.quality_threshold
            }
    
    
    def demonstrate_iterative_refinement():
        """Demonstrate the Iterative Refinement pattern."""
        
        print("=" * 80)
        print("ITERATIVE REFINEMENT PATTERN DEMONSTRATION")
        print("=" * 80)
        
        agent = IterativeRefinementAgent(max_iterations=3, quality_threshold=7.5)
        
        # Test 1: Short essay
        print("\n" + "=" * 80)
        print("TEST 1: Essay Writing with Refinement")
        print("=" * 80)
        
        task1 = "Write a concise essay (150-200 words) about the importance of artificial intelligence in healthcare"
        criteria1 = [
            EvaluationCriterion.ACCURACY,
            EvaluationCriterion.CLARITY,
            EvaluationCriterion.COHERENCE
        ]
        
        result1 = agent.solve(task1, criteria1)
        
        # Test 2: Problem solution
        print("\n\n" + "=" * 80)
        print("TEST 2: Problem Solving with Multiple Criteria")
        print("=" * 80)
        
        agent2 = IterativeRefinementAgent(max_iterations=2, quality_threshold=8.0)
        
        task2 = "Explain how to reduce carbon emissions in urban areas"
        criteria2 = [
            EvaluationCriterion.COMPLETENESS,
            EvaluationCriterion.RELEVANCE,
            EvaluationCriterion.CREATIVITY
        ]
        
        result2 = agent2.solve(task2, criteria2)
        
        # Overall summary
        print("\n\n" + "=" * 80)
        print("PATTERN SUMMARY")
        print("=" * 80)
        print("""
    The Iterative Refinement pattern demonstrates:
    
    1. **Progressive Improvement**: Solutions improve through feedback cycles
    2. **Multi-Criteria Evaluation**: Assesses multiple quality dimensions
    3. **Targeted Feedback**: Provides specific, actionable suggestions
    4. **Convergence Detection**: Stops when quality threshold met
    5. **Improvement Tracking**: Monitors progress across iterations
    
    Key Benefits:
    - **Higher Quality**: Iterative refinement produces better solutions
    - **Transparency**: Shows improvement process and reasoning
    - **Flexibility**: Works with any evaluation criteria
    - **Efficiency**: Stops early if threshold met
    - **Debuggability**: Complete history of refinements
    
    Use Cases:
    - Content creation and editing
    - Code optimization
    - Design iteration
    - Answer improvement
    - Strategy development
    
    Refinement Patterns Observed:
    - Initial solutions often score 5-7/10
    - Each iteration typically improves by 0.5-1.5 points
    - Convergence usually achieved in 2-3 iterations
    - Specific feedback drives targeted improvements
    
    This pattern is essential for producing high-quality outputs in
    production agentic systems where initial responses may be insufficient.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_iterative_refinement()
    
        

    Agentic Design Pattern: Iterative Refinement

    Pattern 024: Iterative Refinement – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Iterative Refinement pattern implementation.


    Pattern Description

    Pattern 024: Iterative Refinement

    Description: The Iterative Refinement pattern enables agents to progressively improve outputs through cycles of generation, evaluation, and refinement. The agent generates an initial solution, evaluates it against criteria, receives feedback, and refines the solution iteratively until quality thresholds are met or iteration limits reached.

    Components: – Generator: Creates initial and refined solutions – Evaluator: Assesses solution quality against criteria – Feedback Generator: Produces actionable improvement suggestions – Refinement Loop: Orchestrates iterative improvement cycles – Convergence Checker: Determines when to stop iterating

    Use Cases: – Content creation and editing – Code optimization and debugging – Design iteration and improvement – Answer quality enhancement – Complex problem solving

    LangChain Implementation: Uses separate LLM instances for generation and evaluation, implements feedback loops with structured criteria, and tracks improvement metrics across iterations.

    Key Features: – Multi-criteria evaluation – Targeted feedback generation – Iterative improvement tracking – Convergence detection – Quality metrics monitoring


    File Statistics

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

    Module Documentation

    """
    Pattern 024: Iterative Refinement
    
    Description:
        The Iterative Refinement pattern enables agents to progressively improve outputs
        through cycles of generation, evaluation, and refinement. The agent generates an
        initial solution, evaluates it against criteria, receives feedback, and refines
        the solution iteratively until quality thresholds are met or iteration limits reached.
    
    Components:
        - Generator: Creates initial and refined solutions
        - Evaluator: Assesses solution quality against criteria
        - Feedback Generator: Produces actionable improvement suggestions
        - Refinement Loop: Orchestrates iterative improvement cycles
        - Convergence Checker: Determines when to stop iterating
    
    Use Cases:
        - Content creation and editing
        - Code optimization and debugging
        - Design iteration and improvement
        - Answer quality enhancement
        - Complex problem solving
    
    LangChain Implementation:
        Uses separate LLM instances for generation and evaluation,
        implements feedback loops with structured criteria, and tracks
        improvement metrics across iterations.
    
    Key Features:
        - Multi-criteria evaluation
        - Targeted feedback generation
        - Iterative improvement tracking
        - Convergence detection
        - Quality metrics monitoring
    """
    

    Purpose: This module implements the Iterative Refinement 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 typing.Any
    import typing.Callable
    import typing.Dict
    import typing.List
    import typing.Optional
    

    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 – 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)

    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: EvaluationCriterion

    Lines 51-60

    Inherits from: Enum

    Purpose:

    Criteria for evaluating solutions.
    

    Class: EvaluationScore

    Lines 64-76

    Purpose:

    Score for a single criterion.
    

    Methods:

    to_dict(self)

    Lines 70-76

    Purpose:

    Convert to dictionary.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D677AD0>


    Class: Evaluation

    Lines 80-100

    Purpose:

    Complete evaluation of a solution.
    

    Methods:

    get_score(self, criterion)

    Lines 88-93

    Purpose:

    Get score for specific criterion.
    

    Parameters: – criterion: Criterion

    Returns: <ast.Subscript object at 0x000001A06D676DD0>

    get_feedback(self, criterion)

    Lines 95-100

    Purpose:

    Get feedback for specific criterion.
    

    Parameters: – criterion: Criterion

    Returns: <ast.Subscript object at 0x000001A06D72AF10>


    Class: Solution

    Lines 104-109

    Purpose:

    A solution with metadata.
    

    Class: SolutionGenerator

    Lines 112-224

    Purpose:

    Generates and refines solutions.
    

    Methods:

    __init__(self, model)

    Lines 117-124

    Purpose:

    Initialize solution generator.
    
    Args:
        model: LLM model to use
    

    Parameters: – model: Language model instance

    generate_initial(self, task, requirements)

    Lines 126-159

    Purpose:

    Generate initial solution.
    
    Args:
        task: Task description
        requirements: Optional requirements
    
    Returns:
        Initial solution
    

    Parameters: – task: Task – requirements: Requirements

    Returns: Solution

    refine(self, task, current_solution, evaluation, iteration)

    Lines 161-224

    Purpose:

    Refine solution based on feedback.
    
    Args:
        task: Original task
        current_solution: Current solution
        evaluation: Evaluation with feedback
        iteration: Current iteration number
    
    Returns:
        Refined solution
    

    Parameters: – task: Task – current_solution: Current solution – evaluation: Evaluation – iteration: Iteration

    Returns: Solution


    Class: SolutionEvaluator

    Lines 227-409

    Purpose:

    Evaluates solutions against criteria.
    

    Methods:

    __init__(self, model)

    Lines 232-239

    Purpose:

    Initialize solution evaluator.
    
    Args:
        model: LLM model to use
    

    Parameters: – model: Language model instance

    evaluate(self, task, solution, criteria)

    Lines 241-300

    Purpose:

    Evaluate solution against criteria.
    
    Args:
        task: Original task
        solution: Solution to evaluate
        criteria: Evaluation criteria
    
    Returns:
        Evaluation with scores and feedback
    

    Parameters: – task: Task – solution: Solution – criteria: Criteria

    Returns: Evaluation

    _parse_evaluation(self, response, criteria)

    Lines 302-409

    Purpose:

    Parse evaluation from LLM response.
    

    Parameters: – response: Response – criteria: Criteria

    Returns: Evaluation


    Class: IterativeRefinementAgent

    Lines 412-557

    Purpose:

    Agent that iteratively refines solutions.
    

    Methods:

    __init__(self, model, max_iterations, quality_threshold)

    Lines 417-435

    Purpose:

    Initialize iterative refinement agent.
    
    Args:
        model: LLM model to use
        max_iterations: Maximum refinement iterations
        quality_threshold: Minimum quality score to stop iterating
    

    Parameters: – model: Language model instance – max_iterations: Max iterations – quality_threshold: Quality threshold

    solve(self, task, criteria, requirements)

    Lines 437-557

    Purpose:

    Solve task with iterative refinement.
    
    Args:
        task: Task description
        criteria: Evaluation criteria
        requirements: Optional requirements
    
    Returns:
        Solution with refinement history
    

    Parameters: – task: Task – criteria: Criteria – requirements: Requirements

    Returns: <ast.Subscript object at 0x000001A06D633A90>


    Functions

    demonstrate_iterative_refinement()

    Lines 560-634

    Purpose:

    Demonstrate the Iterative Refinement pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Iterative Refinement 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 023
    • Pattern 025
    • Other Tool Use Patterns (020-025)

    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 Iterative Refinement 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: 024_iterative_refinement.py
    Total Lines: 639
    Pattern Category: AI Agent Design Pattern #024


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Rag

    
        """
    Pattern 023: Retrieval-Augmented Generation (RAG)
    
    Description:
        Agent retrieves relevant information from a knowledge base before generating responses.
        RAG combines the power of retrieval systems with generative models to provide
        grounded, factual responses based on external knowledge.
    
    Components:
        - Document Loader: Loads documents into the system
        - Text Splitter: Chunks documents into manageable pieces
        - Embedding Model: Converts text to vector representations
        - Vector Store: Stores and retrieves embeddings efficiently
        - Retriever: Finds relevant documents
        - LLM: Generates response based on retrieved context
    
    Use Cases:
        - Question answering over documents
        - Knowledge-intensive tasks
        - Chat with your data applications
        - Customer support with documentation
    
    LangChain Implementation:
        Uses LangChain's built-in RAG components with vector stores and retrievers.
    """
    
    import os
    from typing import List, Dict, Any
    from dotenv import load_dotenv
    
    from langchain_community.document_loaders import TextLoader
    from langchain_core.documents import Document
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_openai import OpenAIEmbeddings, ChatOpenAI
    from langchain_community.vectorstores import Chroma
    from langchain.chains import RetrievalQA
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.runnables import RunnablePassthrough
    
    # Load environment variables
    load_dotenv()
    
    
    # Sample documents for demonstration
    SAMPLE_DOCUMENTS = [
        {
            "content": """LangChain is a framework for developing applications powered by language models. 
    It enables applications that are context-aware and can reason. LangChain provides modular 
    components for working with language models, as well as pre-built chains and agents.""",
            "metadata": {"source": "langchain_intro", "topic": "framework"}
        },
        {
            "content": """Vector databases are specialized databases designed to store and query 
    high-dimensional vectors efficiently. They use techniques like approximate nearest neighbor 
    search to find similar vectors quickly. Popular vector databases include Pinecone, Weaviate, 
    and Chroma.""",
            "metadata": {"source": "vector_db_guide", "topic": "databases"}
        },
        {
            "content": """Embeddings are numerical representations of text that capture semantic meaning. 
    Words or sentences with similar meanings have similar embeddings. Common embedding models include 
    OpenAI's text-embedding-ada-002 and open-source alternatives like Sentence Transformers.""",
            "metadata": {"source": "embeddings_explained", "topic": "embeddings"}
        },
        {
            "content": """Retrieval-Augmented Generation (RAG) combines retrieval systems with language models. 
    First, relevant documents are retrieved from a knowledge base. Then, these documents are provided 
    as context to the language model, which generates a response grounded in the retrieved information.""",
            "metadata": {"source": "rag_overview", "topic": "rag"}
        },
        {
            "content": """Semantic search finds results based on meaning rather than exact keyword matches. 
    It uses embeddings to represent queries and documents in a vector space, then finds the most 
    semantically similar documents. This enables finding relevant information even when different 
    words are used.""",
            "metadata": {"source": "semantic_search_guide", "topic": "search"}
        }
    ]
    
    
    class RAGAgent:
        """Agent that uses Retrieval-Augmented Generation."""
        
        def __init__(
            self,
            model_name: str = "gpt-3.5-turbo",
            embedding_model: str = "text-embedding-ada-002",
            chunk_size: int = 500,
            chunk_overlap: int = 50,
            k_documents: int = 3
        ):
            """
            Initialize the RAG agent.
            
            Args:
                model_name: LLM model to use for generation
                embedding_model: Embedding model for vector representations
                chunk_size: Size of text chunks
                chunk_overlap: Overlap between chunks
                k_documents: Number of documents to retrieve
            """
            self.llm = ChatOpenAI(
                model=model_name,
                temperature=0,
                api_key=os.getenv("OPENAI_API_KEY")
            )
            
            self.embeddings = OpenAIEmbeddings(
                model=embedding_model,
                api_key=os.getenv("OPENAI_API_KEY")
            )
            
            self.text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=chunk_size,
                chunk_overlap=chunk_overlap,
                length_function=len
            )
            
            self.k_documents = k_documents
            self.vectorstore = None
            self.retriever = None
        
        def load_documents(self, documents: List[Dict[str, Any]]) -> None:
            """
            Load documents into the vector store.
            
            Args:
                documents: List of dictionaries with 'content' and 'metadata' keys
            """
            # Convert to Document objects
            docs = [
                Document(page_content=doc["content"], metadata=doc["metadata"])
                for doc in documents
            ]
            
            # Split documents into chunks
            splits = self.text_splitter.split_documents(docs)
            
            print(f"Loaded {len(docs)} documents, split into {len(splits)} chunks")
            
            # Create vector store
            self.vectorstore = Chroma.from_documents(
                documents=splits,
                embedding=self.embeddings,
                collection_name="rag_demo"
            )
            
            # Create retriever
            self.retriever = self.vectorstore.as_retriever(
                search_kwargs={"k": self.k_documents}
            )
        
        def retrieve_documents(self, query: str) -> List[Document]:
            """
            Retrieve relevant documents for a query.
            
            Args:
                query: Search query
                
            Returns:
                List of relevant documents
            """
            if not self.retriever:
                raise ValueError("No documents loaded. Call load_documents first.")
            
            return self.retriever.get_relevant_documents(query)
        
        def query_with_retrieval_qa(self, query: str) -> Dict[str, Any]:
            """
            Query using RetrievalQA chain (simple approach).
            
            Args:
                query: Question to answer
                
            Returns:
                Dictionary with answer and source documents
            """
            if not self.retriever:
                raise ValueError("No documents loaded. Call load_documents first.")
            
            # Create RetrievalQA chain
            qa_chain = RetrievalQA.from_chain_type(
                llm=self.llm,
                chain_type="stuff",  # "stuff" puts all docs into prompt
                retriever=self.retriever,
                return_source_documents=True
            )
            
            result = qa_chain.invoke({"query": query})
            
            return {
                "query": query,
                "answer": result["result"],
                "source_documents": result["source_documents"]
            }
        
        def query_with_custom_prompt(self, query: str) -> Dict[str, Any]:
            """
            Query using custom prompt template (more control).
            
            Args:
                query: Question to answer
                
            Returns:
                Dictionary with answer and retrieved documents
            """
            if not self.retriever:
                raise ValueError("No documents loaded. Call load_documents first.")
            
            # Custom prompt template
            template = """Answer the question based on the following context. 
    If you cannot answer the question based on the context, say so clearly.
    
    Context:
    {context}
    
    Question: {question}
    
    Answer:"""
            
            prompt = ChatPromptTemplate.from_template(template)
            
            # Create RAG chain using LCEL (LangChain Expression Language)
            def format_docs(docs):
                return "\n\n".join(doc.page_content for doc in docs)
            
            rag_chain = (
                {"context": self.retriever | format_docs, "question": RunnablePassthrough()}
                | prompt
                | self.llm
                | StrOutputParser()
            )
            
            # Get retrieved documents for transparency
            retrieved_docs = self.retrieve_documents(query)
            
            # Generate answer
            answer = rag_chain.invoke(query)
            
            return {
                "query": query,
                "answer": answer,
                "retrieved_documents": retrieved_docs,
                "num_retrieved": len(retrieved_docs)
            }
        
        def query_with_sources(self, query: str) -> Dict[str, Any]:
            """
            Query with explicit source citations.
            
            Args:
                query: Question to answer
                
            Returns:
                Dictionary with answer and cited sources
            """
            if not self.retriever:
                raise ValueError("No documents loaded. Call load_documents first.")
            
            # Retrieve documents
            docs = self.retrieve_documents(query)
            
            # Create context with source labels
            context_with_sources = ""
            sources = []
            
            for i, doc in enumerate(docs, 1):
                source_label = f"[Source {i}: {doc.metadata.get('source', 'unknown')}]"
                context_with_sources += f"{source_label}\n{doc.page_content}\n\n"
                sources.append({
                    "id": i,
                    "source": doc.metadata.get('source', 'unknown'),
                    "topic": doc.metadata.get('topic', 'unknown'),
                    "content": doc.page_content
                })
            
            # Generate answer with citations
            template = """Answer the question based on the following sources. 
    Cite your sources using [Source N] notation where applicable.
    
    Sources:
    {context}
    
    Question: {question}
    
    Answer (with citations):"""
            
            prompt = ChatPromptTemplate.from_template(template)
            chain = prompt | self.llm | StrOutputParser()
            
            answer = chain.invoke({
                "context": context_with_sources,
                "question": query
            })
            
            return {
                "query": query,
                "answer": answer,
                "sources": sources
            }
        
        def cleanup(self):
            """Clean up resources."""
            if self.vectorstore:
                try:
                    self.vectorstore.delete_collection()
                except:
                    pass
    
    
    def demonstrate_rag_pattern():
        """Demonstrates the RAG pattern with various approaches."""
        
        print("=" * 80)
        print("PATTERN 023: Retrieval-Augmented Generation (RAG)")
        print("=" * 80)
        print()
        
        # Create RAG agent
        agent = RAGAgent(k_documents=3)
        
        # Load sample documents
        print("Loading documents into vector store...")
        agent.load_documents(SAMPLE_DOCUMENTS)
        print()
        
        # Test queries
        queries = [
            "What is LangChain?",
            "How do vector databases work?",
            "What is RAG and how does it combine retrieval with generation?",
        ]
        
        # Approach 1: RetrievalQA chain
        print("\n" + "=" * 80)
        print("APPROACH 1: RetrievalQA Chain (Simple)")
        print("=" * 80)
        
        for i, query in enumerate(queries[:2], 1):
            print(f"\n{'- ' * 40}")
            print(f"Query {i}: {query}")
            print('- ' * 40)
            
            try:
                result = agent.query_with_retrieval_qa(query)
                print(f"\nAnswer:\n{result['answer']}")
                print(f"\nSources used: {len(result['source_documents'])} documents")
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
        
        # Approach 2: Custom prompt with LCEL
        print("\n\n" + "=" * 80)
        print("APPROACH 2: Custom Prompt with LCEL")
        print("=" * 80)
        
        query = queries[1]
        print(f"\n{'- ' * 40}")
        print(f"Query: {query}")
        print('- ' * 40)
        
        try:
            result = agent.query_with_custom_prompt(query)
            print(f"\nAnswer:\n{result['answer']}")
            print(f"\nRetrieved {result['num_retrieved']} documents")
            print("\nRetrieved document topics:")
            for doc in result['retrieved_documents']:
                print(f"  - {doc.metadata.get('topic', 'unknown')}")
        except Exception as e:
            print(f"\n✗ Error: {str(e)}")
        
        # Approach 3: With source citations
        print("\n\n" + "=" * 80)
        print("APPROACH 3: Answer with Source Citations")
        print("=" * 80)
        
        query = queries[2]
        print(f"\n{'- ' * 40}")
        print(f"Query: {query}")
        print('- ' * 40)
        
        try:
            result = agent.query_with_sources(query)
            print(f"\nAnswer:\n{result['answer']}")
            print(f"\nSources:")
            for source in result['sources']:
                print(f"  [{source['id']}] {source['source']} (Topic: {source['topic']})")
        except Exception as e:
            print(f"\n✗ Error: {str(e)}")
        
        # Cleanup
        agent.cleanup()
        
        # Summary
        print("\n\n" + "=" * 80)
        print("RAG PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("RAG Components Demonstrated:")
        print("1. Document loading and chunking")
        print("2. Vector embeddings and storage")
        print("3. Semantic retrieval")
        print("4. Context-aware generation")
        print("5. Source attribution")
        print()
        print("Key Benefits:")
        print("- Grounded responses based on actual data")
        print("- Reduced hallucinations")
        print("- Up-to-date information")
        print("- Source traceability")
        print("- Scalable knowledge base")
        print()
        print("LangChain Components Used:")
        print("- RecursiveCharacterTextSplitter: Chunks documents")
        print("- OpenAIEmbeddings: Creates vector embeddings")
        print("- Chroma: Vector store for efficient retrieval")
        print("- RetrievalQA: Pre-built RAG chain")
        print("- LCEL: Custom chain composition")
        print("- Document retriever: Finds relevant documents")
        print()
    
    
    if __name__ == "__main__":
        # Check for API key
        if not os.getenv("OPENAI_API_KEY"):
            print("Error: OPENAI_API_KEY not found in environment variables.")
            print("Please set it in your .env file or environment.")
            exit(1)
        
        demonstrate_rag_pattern()
    
        

    Agentic Design Pattern: Rag

    Pattern 023: Rag – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 023: Retrieval-Augmented Generation (RAG)

    Description: Agent retrieves relevant information from a knowledge base before generating responses. RAG combines the power of retrieval systems with generative models to provide grounded, factual responses based on external knowledge.

    Components: – Document Loader: Loads documents into the system – Text Splitter: Chunks documents into manageable pieces – Embedding Model: Converts text to vector representations – Vector Store: Stores and retrieves embeddings efficiently – Retriever: Finds relevant documents – LLM: Generates response based on retrieved context

    Use Cases: – Question answering over documents – Knowledge-intensive tasks – Chat with your data applications – Customer support with documentation

    LangChain Implementation: Uses LangChain’s built-in RAG components with vector stores and retrievers.


    File Statistics

    • Total Lines: 431
    • Classes: 1
    • Functions: 1
    • Constants: 1

    Module Documentation

    """
    Pattern 023: Retrieval-Augmented Generation (RAG)
    
    Description:
        Agent retrieves relevant information from a knowledge base before generating responses.
        RAG combines the power of retrieval systems with generative models to provide
        grounded, factual responses based on external knowledge.
    
    Components:
        - Document Loader: Loads documents into the system
        - Text Splitter: Chunks documents into manageable pieces
        - Embedding Model: Converts text to vector representations
        - Vector Store: Stores and retrieves embeddings efficiently
        - Retriever: Finds relevant documents
        - LLM: Generates response based on retrieved context
    
    Use Cases:
        - Question answering over documents
        - Knowledge-intensive tasks
        - Chat with your data applications
        - Customer support with documentation
    
    LangChain Implementation:
        Uses LangChain's built-in RAG components with vector stores and retrievers.
    """
    

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


    Import Statements

    Standard Library Imports

    import langchain_community.vectorstores.Chroma
    import langchain_core.documents.Document
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import langchain_core.runnables.RunnablePassthrough
    import os
    import typing.Any
    import typing.Dict
    import typing.List
    

    Purpose: Core Python functionality: – langchain_community.vectorstores.Chroma: Regular expression operations – langchain_core.documents.Document: Regular expression operations – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – langchain_core.runnables.RunnablePassthrough: Regular expression operations – os: Environment variable access and system operations – 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)

    LangChain Imports

    from langchain.chains.RetrievalQA
    from langchain.text_splitter.RecursiveCharacterTextSplitter
    from langchain_community.document_loaders.TextLoader
    from langchain_openai.ChatOpenAI
    from langchain_openai.OpenAIEmbeddings
    

    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.


    Module Constants

    SAMPLE_DOCUMENTS

    SAMPLE_DOCUMENTS = [
    

    Line 46: Module-level constant definition.


    Classes

    Class: RAGAgent

    Lines 82-309

    Purpose:

    Agent that uses Retrieval-Augmented Generation.
    

    Methods:

    __init__(self, model_name, embedding_model, chunk_size, chunk_overlap, k_documents)

    Lines 85-122

    Purpose:

    Initialize the RAG agent.
    
    Args:
        model_name: LLM model to use for generation
        embedding_model: Embedding model for vector representations
        chunk_size: Size of text chunks
        chunk_overlap: Overlap between chunks
        k_documents: Number of documents to retrieve
    

    Parameters: – model_name: Language model instance – embedding_model: Language model instance – chunk_size: Chunk size – chunk_overlap: Chunk overlap – k_documents: K documents

    load_documents(self, documents)

    Lines 124-152

    Purpose:

    Load documents into the vector store.
    
    Args:
        documents: List of dictionaries with 'content' and 'metadata' keys
    

    Parameters: – documents: Documents

    Returns: None

    retrieve_documents(self, query)

    Lines 154-167

    Purpose:

    Retrieve relevant documents for a query.
    
    Args:
        query: Search query
    
    Returns:
        List of relevant documents
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D6F7E90>

    query_with_retrieval_qa(self, query)

    Lines 169-196

    Purpose:

    Query using RetrievalQA chain (simple approach).
    
    Args:
        query: Question to answer
    
    Returns:
        Dictionary with answer and source documents
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D702390>

    query_with_custom_prompt(self, query)

    Lines 198-246

    Purpose:

    Query using custom prompt template (more control).
    
    Args:
        query: Question to answer
    
    Returns:
        Dictionary with answer and retrieved documents
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D702990>

    query_with_sources(self, query)

    Lines 248-301

    Purpose:

    Query with explicit source citations.
    
    Args:
        query: Question to answer
    
    Returns:
        Dictionary with answer and cited sources
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D64EB90>

    cleanup(self)

    Lines 303-309

    Purpose:

    Clean up resources.
    

    Parameters:


    Functions

    demonstrate_rag_pattern()

    Lines 312-420

    Purpose:

    Demonstrates the RAG pattern with various approaches.
    

    Key Concepts Summary

    Pattern Overview

    The 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 022
    • Pattern 024
    • Other Tool Use Patterns (020-025)

    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 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: 023_rag.py
    Total Lines: 431
    Pattern Category: AI Agent Design Pattern #023


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Code Generation

    
        """
    Pattern 022: Code Generation & Execution
    
    Description:
        The Code Generation & Execution pattern enables agents to generate executable code
        in response to user requests, safely execute the code in a sandboxed environment,
        and return results. This pattern combines LLM code generation capabilities with
        secure execution environments to solve computational problems programmatically.
    
    Components:
        - Code Generator: LLM generates code from natural language
        - Syntax Validator: Checks code for syntax errors
        - Sandbox Environment: Isolated execution context
        - Result Processor: Formats and returns execution results
        - Error Handler: Captures and explains execution errors
    
    Use Cases:
        - Data analysis and visualization
        - Mathematical problem solving
        - Text processing and transformation
        - Algorithm implementation
        - Quick prototyping and testing
    
    LangChain Implementation:
        Uses LLM for code generation with specific prompting strategies,
        implements safe execution with restricted environments, and handles
        multiple programming languages.
    
    Key Features:
        - Multi-language support (Python, JavaScript, etc.)
        - Sandboxed execution for security
        - Syntax and runtime error handling
        - Result capturing and formatting
        - Iterative refinement on errors
    """
    
    import os
    import sys
    from typing import List, Dict, Any, Optional
    from dataclasses import dataclass, field
    from enum import Enum
    from datetime import datetime
    import io
    import contextlib
    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 Language(Enum):
        """Supported programming languages."""
        PYTHON = "python"
        JAVASCRIPT = "javascript"
    
    
    class ExecutionStatus(Enum):
        """Status of code execution."""
        SUCCESS = "success"
        SYNTAX_ERROR = "syntax_error"
        RUNTIME_ERROR = "runtime_error"
        TIMEOUT = "timeout"
        FORBIDDEN = "forbidden"
    
    
    @dataclass
    class CodeSnippet:
        """Generated code snippet."""
        language: Language
        code: str
        description: str
        timestamp: datetime = field(default_factory=datetime.now)
    
    
    @dataclass
    class ExecutionResult:
        """Result of code execution."""
        code: str
        status: ExecutionStatus
        output: Optional[str] = None
        error: Optional[str] = None
        duration: Optional[float] = None
        timestamp: datetime = field(default_factory=datetime.now)
    
    
    class CodeGenerator:
        """
        Generates code from natural language descriptions.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """
            Initialize code generator.
            
            Args:
                model: LLM model to use
            """
            self.llm = ChatOpenAI(model=model, temperature=0.2)
        
        def generate_python(
            self,
            task: str,
            context: Optional[str] = None
        ) -> CodeSnippet:
            """
            Generate Python code for a task.
            
            Args:
                task: Task description
                context: Optional context or constraints
                
            Returns:
                Generated code snippet
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an expert Python programmer. Generate clean, efficient,
    and well-commented Python code to accomplish the given task.
    
    Guidelines:
    - Write complete, runnable code
    - Use standard library when possible
    - Include helpful comments
    - Handle edge cases
    - Use proper error handling
    - Print or return results clearly
    
    Respond with ONLY the Python code, no explanations before or after."""),
                ("user", "{task}{context_str}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            context_str = f"\n\nContext: {context}" if context else ""
            
            response = chain.invoke({
                "task": task,
                "context_str": context_str
            })
            
            # Extract code from response
            code = self._extract_code(response)
            
            return CodeSnippet(
                language=Language.PYTHON,
                code=code,
                description=task
            )
        
        def generate_javascript(
            self,
            task: str,
            context: Optional[str] = None
        ) -> CodeSnippet:
            """
            Generate JavaScript code for a task.
            
            Args:
                task: Task description
                context: Optional context
                
            Returns:
                Generated code snippet
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an expert JavaScript programmer. Generate clean, efficient
    JavaScript code to accomplish the given task.
    
    Guidelines:
    - Write complete, runnable Node.js code
    - Use modern ES6+ syntax
    - Include helpful comments
    - Handle edge cases
    - Use console.log for output
    
    Respond with ONLY the JavaScript code, no explanations."""),
                ("user", "{task}{context_str}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            context_str = f"\n\nContext: {context}" if context else ""
            
            response = chain.invoke({
                "task": task,
                "context_str": context_str
            })
            
            code = self._extract_code(response)
            
            return CodeSnippet(
                language=Language.JAVASCRIPT,
                code=code,
                description=task
            )
        
        def refine_code(
            self,
            original_code: str,
            error: str,
            language: Language
        ) -> str:
            """
            Refine code based on error feedback.
            
            Args:
                original_code: Original code that failed
                error: Error message
                language: Programming language
                
            Returns:
                Refined code
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are debugging {language.value} code. Fix the error in the
    provided code and return the corrected version.
    
    Respond with ONLY the corrected code, no explanations."""),
                ("user", """Original Code:
    ```{language}
    {code}
    ```
    
    Error:
    {error}
    
    Provide the corrected code:""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "language": language.value,
                "code": original_code,
                "error": error
            })
            
            return self._extract_code(response)
        
        def _extract_code(self, response: str) -> str:
            """Extract code from LLM response."""
            # Remove markdown code blocks
            lines = response.split("\n")
            code_lines = []
            in_code_block = False
            
            for line in lines:
                if line.strip().startswith("```"):
                    in_code_block = not in_code_block
                    continue
                if in_code_block or not line.strip().startswith("```"):
                    code_lines.append(line)
            
            code = "\n".join(code_lines).strip()
            
            # If no code blocks found, return entire response
            if not code:
                code = response.strip()
            
            return code
    
    
    class SandboxExecutor:
        """
        Safely executes code in sandboxed environment.
        """
        
        def __init__(self):
            """Initialize sandbox executor."""
            # Restricted builtins for Python execution
            self.safe_builtins = {
                'abs': abs, 'all': all, 'any': any, 'ascii': ascii,
                'bin': bin, 'bool': bool, 'chr': chr, 'dict': dict,
                'divmod': divmod, 'enumerate': enumerate, 'filter': filter,
                'float': float, 'format': format, 'hex': hex, 'int': int,
                'len': len, 'list': list, 'map': map, 'max': max,
                'min': min, 'oct': oct, 'ord': ord, 'pow': pow,
                'range': range, 'reversed': reversed, 'round': round,
                'set': set, 'slice': slice, 'sorted': sorted, 'str': str,
                'sum': sum, 'tuple': tuple, 'zip': zip,
                'print': print, 'isinstance': isinstance, 'type': type
            }
        
        def execute_python(self, code: str) -> ExecutionResult:
            """
            Execute Python code safely.
            
            Args:
                code: Python code to execute
                
            Returns:
                Execution result
            """
            import time
            
            # Check for forbidden operations
            forbidden_keywords = ['import os', 'import sys', 'eval', 'exec', '__import__', 
                                'open', 'file', 'compile']
            for keyword in forbidden_keywords:
                if keyword in code:
                    return ExecutionResult(
                        code=code,
                        status=ExecutionStatus.FORBIDDEN,
                        error=f"Forbidden operation: {keyword}"
                    )
            
            # Syntax check
            try:
                compile(code, '', 'exec')
            except SyntaxError as e:
                return ExecutionResult(
                    code=code,
                    status=ExecutionStatus.SYNTAX_ERROR,
                    error=str(e)
                )
            
            # Execute in sandbox
            output_buffer = io.StringIO()
            
            try:
                start_time = time.time()
                
                # Redirect stdout
                with contextlib.redirect_stdout(output_buffer):
                    # Create restricted namespace
                    namespace = {'__builtins__': self.safe_builtins}
                    
                    # Execute code
                    exec(code, namespace)
                
                duration = time.time() - start_time
                output = output_buffer.getvalue()
                
                return ExecutionResult(
                    code=code,
                    status=ExecutionStatus.SUCCESS,
                    output=output if output else "Code executed successfully (no output)",
                    duration=duration
                )
                
            except Exception as e:
                return ExecutionResult(
                    code=code,
                    status=ExecutionStatus.RUNTIME_ERROR,
                    error=f"{type(e).__name__}: {str(e)}"
                )
        
        def execute_javascript(self, code: str) -> ExecutionResult:
            """
            Execute JavaScript code (simulated - would need Node.js).
            
            Args:
                code: JavaScript code
                
            Returns:
                Execution result
            """
            # In real implementation, would execute with Node.js
            # For demonstration, we simulate execution
            
            return ExecutionResult(
                code=code,
                status=ExecutionStatus.SUCCESS,
                output="[Simulated] JavaScript execution would require Node.js runtime",
                duration=0.0
            )
    
    
    class CodeGenerationAgent:
        """
        Agent that generates and executes code to solve problems.
        """
        
        def __init__(
            self,
            model: str = "gpt-3.5-turbo",
            max_retries: int = 2
        ):
            """
            Initialize code generation agent.
            
            Args:
                model: LLM model to use
                max_retries: Maximum refinement attempts on errors
            """
            self.generator = CodeGenerator(model)
            self.executor = SandboxExecutor()
            self.max_retries = max_retries
            self.execution_history: List[ExecutionResult] = []
        
        def solve_with_code(
            self,
            task: str,
            language: Language = Language.PYTHON,
            context: Optional[str] = None
        ) -> Dict[str, Any]:
            """
            Solve a task by generating and executing code.
            
            Args:
                task: Task description
                language: Programming language to use
                context: Optional context
                
            Returns:
                Solution with code and results
            """
            print(f"\n[Agent] Solving task: {task}")
            print(f"[Agent] Language: {language.value}\n")
            
            # Generate initial code
            print("[Agent] Generating code...")
            if language == Language.PYTHON:
                snippet = self.generator.generate_python(task, context)
            else:
                snippet = self.generator.generate_javascript(task, context)
            
            print(f"\n[Agent] Generated code:\n")
            print("=" * 60)
            print(snippet.code)
            print("=" * 60)
            
            # Execute with retries
            attempts = []
            result = None
            
            for attempt in range(self.max_retries + 1):
                print(f"\n[Agent] Execution attempt {attempt + 1}...")
                
                # Execute code
                if language == Language.PYTHON:
                    result = self.executor.execute_python(snippet.code)
                else:
                    result = self.executor.execute_javascript(snippet.code)
                
                self.execution_history.append(result)
                attempts.append(result)
                
                # Check if successful
                if result.status == ExecutionStatus.SUCCESS:
                    print(f"[Agent] ✓ Success!")
                    print(f"\n[Agent] Output:")
                    print("-" * 60)
                    print(result.output)
                    print("-" * 60)
                    break
                
                # Handle error
                print(f"[Agent] ✗ {result.status.value}: {result.error}")
                
                if attempt < self.max_retries:
                    print(f"[Agent] Refining code...")
                    snippet.code = self.generator.refine_code(
                        snippet.code,
                        result.error,
                        language
                    )
                    print(f"\n[Agent] Refined code:\n")
                    print("=" * 60)
                    print(snippet.code)
                    print("=" * 60)
            
            return {
                "task": task,
                "language": language.value,
                "final_code": snippet.code,
                "result": result,
                "attempts": len(attempts),
                "success": result.status == ExecutionStatus.SUCCESS
            }
    
    
    def demonstrate_code_generation():
        """Demonstrate the Code Generation & Execution pattern."""
        
        print("=" * 80)
        print("CODE GENERATION & EXECUTION PATTERN DEMONSTRATION")
        print("=" * 80)
        
        agent = CodeGenerationAgent()
        
        # Test 1: Simple calculation
        print("\n" + "=" * 80)
        print("TEST 1: Mathematical Calculation")
        print("=" * 80)
        
        task1 = "Calculate the factorial of 10 and print the result"
        result1 = agent.solve_with_code(task1, Language.PYTHON)
        
        print("\n" + "=" * 80)
        print("SOLUTION SUMMARY")
        print("=" * 80)
        print(f"Success: {result1['success']}")
        print(f"Attempts: {result1['attempts']}")
        
        # Test 2: Data processing
        print("\n" + "=" * 80)
        print("TEST 2: Data Processing")
        print("=" * 80)
        
        task2 = """Create a list of numbers from 1 to 20, filter only even numbers,
    square each one, and print the sum of all squared even numbers"""
        result2 = agent.solve_with_code(task2, Language.PYTHON)
        
        print("\n" + "=" * 80)
        print("SOLUTION SUMMARY")
        print("=" * 80)
        print(f"Success: {result2['success']}")
        print(f"Attempts: {result2['attempts']}")
        
        # Test 3: String manipulation
        print("\n" + "=" * 80)
        print("TEST 3: String Manipulation")
        print("=" * 80)
        
        task3 = """Create a function that takes a string and returns a dictionary
    counting the frequency of each character. Test it with 'hello world' and print the result."""
        result3 = agent.solve_with_code(task3, Language.PYTHON)
        
        print("\n" + "=" * 80)
        print("SOLUTION SUMMARY")
        print("=" * 80)
        print(f"Success: {result3['success']}")
        print(f"Attempts: {result3['attempts']}")
        
        # Test 4: Algorithm implementation
        print("\n" + "=" * 80)
        print("TEST 4: Algorithm Implementation")
        print("=" * 80)
        
        task4 = """Implement the Fibonacci sequence up to the 15th number using iteration
    (not recursion) and print all numbers"""
        result4 = agent.solve_with_code(task4, Language.PYTHON)
        
        print("\n" + "=" * 80)
        print("SOLUTION SUMMARY")
        print("=" * 80)
        print(f"Success: {result4['success']}")
        print(f"Attempts: {result4['attempts']}")
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        
        successful = sum(1 for r in agent.execution_history if r.status == ExecutionStatus.SUCCESS)
        total = len(agent.execution_history)
        
        print(f"""
    Total tasks: 4
    Total execution attempts: {total}
    Successful executions: {successful}
    Success rate: {successful/total*100:.0f}%
    
    Execution Breakdown:
    """)
        
        status_counts = {}
        for result in agent.execution_history:
            status_counts[result.status.value] = status_counts.get(result.status.value, 0) + 1
        
        for status, count in status_counts.items():
            print(f"  {status}: {count}")
        
        print("""
    The Code Generation & Execution pattern demonstrates:
    
    1. **Natural Language to Code**: Converts descriptions to executable code
    2. **Safe Execution**: Sandboxed environment prevents harmful operations
    3. **Error Recovery**: Automatically refines code on failures
    4. **Multi-Language**: Supports multiple programming languages
    5. **Result Capture**: Captures and formats execution output
    
    Key Benefits:
    - **Programmatic Problem Solving**: Handles complex computations
    - **Safety**: Restricted execution environment
    - **Self-Correction**: Learns from execution errors
    - **Flexibility**: Works across languages and domains
    - **Verifiable**: Actual code execution provides concrete results
    
    Use Cases:
    - Data analysis and statistics
    - Mathematical problem solving
    - Algorithm implementation and testing
    - Text processing pipelines
    - Quick prototyping and experimentation
    
    Limitations:
    - Restricted operations for security
    - Limited library access in sandbox
    - Execution timeouts for safety
    - Language-specific constraints
    
    This pattern enables agents to leverage the full power of programming
    to solve computational problems that are difficult for pure LLMs.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_code_generation()
    
        

    Agentic Design Pattern: Code Generation

    Pattern 022: Code Generation – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Code Generation pattern implementation.


    Pattern Description

    Pattern 022: Code Generation & Execution

    Description: The Code Generation & Execution pattern enables agents to generate executable code in response to user requests, safely execute the code in a sandboxed environment, and return results. This pattern combines LLM code generation capabilities with secure execution environments to solve computational problems programmatically.

    Components: – Code Generator: LLM generates code from natural language – Syntax Validator: Checks code for syntax errors – Sandbox Environment: Isolated execution context – Result Processor: Formats and returns execution results – Error Handler: Captures and explains execution errors

    Use Cases: – Data analysis and visualization – Mathematical problem solving – Text processing and transformation – Algorithm implementation – Quick prototyping and testing

    LangChain Implementation: Uses LLM for code generation with specific prompting strategies, implements safe execution with restricted environments, and handles multiple programming languages.

    Key Features: – Multi-language support (Python, JavaScript, etc.) – Sandboxed execution for security – Syntax and runtime error handling – Result capturing and formatting – Iterative refinement on errors


    File Statistics

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

    Module Documentation

    """
    Pattern 022: Code Generation & Execution
    
    Description:
        The Code Generation & Execution pattern enables agents to generate executable code
        in response to user requests, safely execute the code in a sandboxed environment,
        and return results. This pattern combines LLM code generation capabilities with
        secure execution environments to solve computational problems programmatically.
    
    Components:
        - Code Generator: LLM generates code from natural language
        - Syntax Validator: Checks code for syntax errors
        - Sandbox Environment: Isolated execution context
        - Result Processor: Formats and returns execution results
        - Error Handler: Captures and explains execution errors
    
    Use Cases:
        - Data analysis and visualization
        - Mathematical problem solving
        - Text processing and transformation
        - Algorithm implementation
        - Quick prototyping and testing
    
    LangChain Implementation:
        Uses LLM for code generation with specific prompting strategies,
        implements safe execution with restricted environments, and handles
        multiple programming languages.
    
    Key Features:
        - Multi-language support (Python, JavaScript, etc.)
        - Sandboxed execution for security
        - Syntax and runtime error handling
        - Result capturing and formatting
        - Iterative refinement on errors
    """
    

    Purpose: This module implements the Code Generation 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 sys
    import time
    import typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    

    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)

    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 contextlib
    from dotenv.load_dotenv
    from io
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: Language

    Lines 54-57

    Inherits from: Enum

    Purpose:

    Supported programming languages.
    

    Class: ExecutionStatus

    Lines 60-66

    Inherits from: Enum

    Purpose:

    Status of code execution.
    

    Class: CodeSnippet

    Lines 70-75

    Purpose:

    Generated code snippet.
    

    Class: ExecutionResult

    Lines 79-86

    Purpose:

    Result of code execution.
    

    Class: CodeGenerator

    Lines 89-262

    Purpose:

    Generates code from natural language descriptions.
    

    Methods:

    __init__(self, model)

    Lines 94-101

    Purpose:

    Initialize code generator.
    
    Args:
        model: LLM model to use
    

    Parameters: – model: Language model instance

    generate_python(self, task, context)

    Lines 103-150

    Purpose:

    Generate Python code for a task.
    
    Args:
        task: Task description
        context: Optional context or constraints
    
    Returns:
        Generated code snippet
    

    Parameters: – task: Task – context: Context

    Returns: CodeSnippet

    generate_javascript(self, task, context)

    Lines 152-197

    Purpose:

    Generate JavaScript code for a task.
    
    Args:
        task: Task description
        context: Optional context
    
    Returns:
        Generated code snippet
    

    Parameters: – task: Task – context: Context

    Returns: CodeSnippet

    refine_code(self, original_code, error, language)

    Lines 199-240

    Purpose:

    Refine code based on error feedback.
    
    Args:
        original_code: Original code that failed
        error: Error message
        language: Programming language
    
    Returns:
        Refined code
    

    Parameters: – original_code: Original code – error: Error – language: Language

    Returns: str

    _extract_code(self, response)

    Lines 242-262

    Purpose:

    Extract code from LLM response.
    

    Parameters: – response: Response

    Returns: str


    Class: SandboxExecutor

    Lines 265-368

    Purpose:

    Safely executes code in sandboxed environment.
    

    Methods:

    __init__(self)

    Lines 270-284

    Purpose:

    Initialize sandbox executor.
    

    Parameters:

    execute_python(self, code)

    Lines 286-348

    Purpose:

    Execute Python code safely.
    
    Args:
        code: Python code to execute
    
    Returns:
        Execution result
    

    Parameters: – code: Code

    Returns: ExecutionResult

    execute_javascript(self, code)

    Lines 350-368

    Purpose:

    Execute JavaScript code (simulated - would need Node.js).
    
    Args:
        code: JavaScript code
    
    Returns:
        Execution result
    

    Parameters: – code: Code

    Returns: ExecutionResult


    Class: CodeGenerationAgent

    Lines 371-472

    Purpose:

    Agent that generates and executes code to solve problems.
    

    Methods:

    __init__(self, model, max_retries)

    Lines 376-391

    Purpose:

    Initialize code generation agent.
    
    Args:
        model: LLM model to use
        max_retries: Maximum refinement attempts on errors
    

    Parameters: – model: Language model instance – max_retries: Max retries

    solve_with_code(self, task, language, context)

    Lines 393-472

    Purpose:

    Solve a task by generating and executing code.
    
    Args:
        task: Task description
        language: Programming language to use
        context: Optional context
    
    Returns:
        Solution with code and results
    

    Parameters: – task: Task – language: Language – context: Context

    Returns: <ast.Subscript object at 0x000001A06D608890>


    Functions

    demonstrate_code_generation()

    Lines 475-598

    Purpose:

    Demonstrate the Code Generation & Execution pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Code Generation 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 021
    • Pattern 023
    • Other Tool Use Patterns (020-025)

    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 Code Generation 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: 022_code_generation.py
    Total Lines: 603
    Pattern Category: AI Agent Design Pattern #022


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Function Calling

    
        """
    Pattern 021: Function Calling
    
    Description:
        The Function Calling pattern enables LLMs to invoke structured functions with
        type-safe parameters. Unlike general tool use, function calling emphasizes strong
        typing, structured schemas, and direct integration with code functions. The LLM
        analyzes user intent, selects appropriate functions, extracts typed parameters,
        and invokes functions with validated inputs.
    
    Components:
        - Function Schema: Structured definitions with parameter types
        - Parameter Validation: Type checking and constraint enforcement
        - Function Binding: Direct mapping to callable functions
        - Response Formatting: Structured outputs from function calls
        - Multi-turn Handling: Multiple function calls in sequence
    
    Use Cases:
        - Database queries with typed parameters
        - API calls with strict schemas
        - System operations requiring validation
        - Structured data processing
        - Multi-step function orchestration
    
    LangChain Implementation:
        Uses LangChain's function calling with OpenAI's function calling API,
        implements type-safe parameter extraction, and handles multi-turn conversations.
    
    Key Features:
        - Type-safe parameter extraction
        - JSON schema validation
        - Multiple function selection
        - Error handling and retries
        - Conversation context preservation
    """
    
    import os
    from typing import List, Dict, Any, Optional, Union, Callable
    from dataclasses import dataclass, field
    from enum import Enum
    import json
    from datetime import datetime
    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 ParameterType(Enum):
        """Types of function parameters."""
        STRING = "string"
        INTEGER = "integer"
        NUMBER = "number"
        BOOLEAN = "boolean"
        ARRAY = "array"
        OBJECT = "object"
    
    
    @dataclass
    class FunctionParameter:
        """Definition of a function parameter."""
        name: str
        type: ParameterType
        description: str
        required: bool = True
        enum: Optional[List[Any]] = None
        default: Optional[Any] = None
        
        def to_schema(self) -> Dict[str, Any]:
            """Convert to JSON schema format."""
            schema = {
                "type": self.type.value,
                "description": self.description
            }
            if self.enum:
                schema["enum"] = self.enum
            return schema
    
    
    @dataclass
    class FunctionDefinition:
        """Complete function definition with schema."""
        name: str
        description: str
        parameters: List[FunctionParameter]
        function: Callable
        returns: str = "object"
        
        def to_openai_schema(self) -> Dict[str, Any]:
            """Convert to OpenAI function calling schema."""
            required_params = [p.name for p in self.parameters if p.required]
            
            properties = {
                param.name: param.to_schema()
                for param in self.parameters
            }
            
            return {
                "name": self.name,
                "description": self.description,
                "parameters": {
                    "type": "object",
                    "properties": properties,
                    "required": required_params
                }
            }
        
        def validate_parameters(self, params: Dict[str, Any]) -> tuple[bool, Optional[str]]:
            """Validate parameters against schema."""
            # Check required parameters
            for param in self.parameters:
                if param.required and param.name not in params:
                    return False, f"Missing required parameter: {param.name}"
            
            # Check types (basic validation)
            for param in self.parameters:
                if param.name in params:
                    value = params[param.name]
                    
                    # Type checking
                    if param.type == ParameterType.STRING and not isinstance(value, str):
                        return False, f"Parameter {param.name} must be string"
                    elif param.type == ParameterType.INTEGER and not isinstance(value, int):
                        return False, f"Parameter {param.name} must be integer"
                    elif param.type == ParameterType.NUMBER and not isinstance(value, (int, float)):
                        return False, f"Parameter {param.name} must be number"
                    elif param.type == ParameterType.BOOLEAN and not isinstance(value, bool):
                        return False, f"Parameter {param.name} must be boolean"
                    elif param.type == ParameterType.ARRAY and not isinstance(value, list):
                        return False, f"Parameter {param.name} must be array"
                    
                    # Enum validation
                    if param.enum and value not in param.enum:
                        return False, f"Parameter {param.name} must be one of {param.enum}"
            
            return True, None
    
    
    @dataclass
    class FunctionCall:
        """Record of a function call."""
        function_name: str
        arguments: Dict[str, Any]
        result: Any
        success: bool
        timestamp: datetime = field(default_factory=datetime.now)
        error: Optional[str] = None
    
    
    class FunctionRegistry:
        """
        Registry of callable functions with schemas.
        """
        
        def __init__(self):
            """Initialize function registry."""
            self.functions: Dict[str, FunctionDefinition] = {}
            self._register_builtin_functions()
        
        def _register_builtin_functions(self):
            """Register built-in functions."""
            
            # Get user info function
            self.register_function(FunctionDefinition(
                name="get_user_info",
                description="Retrieves information about a user by their ID",
                parameters=[
                    FunctionParameter("user_id", ParameterType.INTEGER, "The user's ID"),
                    FunctionParameter("fields", ParameterType.ARRAY, "Fields to retrieve", required=False)
                ],
                function=self._get_user_info
            ))
            
            # Create order function
            self.register_function(FunctionDefinition(
                name="create_order",
                description="Creates a new order in the system",
                parameters=[
                    FunctionParameter("product_id", ParameterType.STRING, "Product identifier"),
                    FunctionParameter("quantity", ParameterType.INTEGER, "Number of items"),
                    FunctionParameter("priority", ParameterType.STRING, "Order priority", 
                                    enum=["low", "normal", "high", "urgent"])
                ],
                function=self._create_order
            ))
            
            # Search database function
            self.register_function(FunctionDefinition(
                name="search_database",
                description="Searches the database with filters",
                parameters=[
                    FunctionParameter("table", ParameterType.STRING, "Table name"),
                    FunctionParameter("query", ParameterType.STRING, "Search query"),
                    FunctionParameter("limit", ParameterType.INTEGER, "Max results", required=False, default=10)
                ],
                function=self._search_database
            ))
            
            # Send notification function
            self.register_function(FunctionDefinition(
                name="send_notification",
                description="Sends a notification to a user",
                parameters=[
                    FunctionParameter("user_id", ParameterType.INTEGER, "Recipient user ID"),
                    FunctionParameter("message", ParameterType.STRING, "Notification message"),
                    FunctionParameter("urgent", ParameterType.BOOLEAN, "Is urgent", required=False, default=False)
                ],
                function=self._send_notification
            ))
            
            # Calculate price function
            self.register_function(FunctionDefinition(
                name="calculate_price",
                description="Calculates total price with tax and discount",
                parameters=[
                    FunctionParameter("base_price", ParameterType.NUMBER, "Base price"),
                    FunctionParameter("tax_rate", ParameterType.NUMBER, "Tax rate (0-1)", required=False, default=0.1),
                    FunctionParameter("discount", ParameterType.NUMBER, "Discount amount", required=False, default=0)
                ],
                function=self._calculate_price
            ))
        
        def register_function(self, function_def: FunctionDefinition):
            """Register a new function."""
            self.functions[function_def.name] = function_def
        
        def get_function(self, name: str) -> Optional[FunctionDefinition]:
            """Get function by name."""
            return self.functions.get(name)
        
        def get_all_schemas(self) -> List[Dict[str, Any]]:
            """Get all function schemas in OpenAI format."""
            return [func.to_openai_schema() for func in self.functions.values()]
        
        # Built-in function implementations (mock)
        
        def _get_user_info(self, user_id: int, fields: Optional[List[str]] = None) -> Dict[str, Any]:
            """Mock user info retrieval."""
            users = {
                1: {"name": "Alice", "email": "alice@example.com", "age": 30, "status": "active"},
                2: {"name": "Bob", "email": "bob@example.com", "age": 25, "status": "active"},
                3: {"name": "Charlie", "email": "charlie@example.com", "age": 35, "status": "inactive"}
            }
            
            user = users.get(user_id, {"error": "User not found"})
            
            if fields and "error" not in user:
                user = {k: v for k, v in user.items() if k in fields}
            
            return user
        
        def _create_order(self, product_id: str, quantity: int, priority: str) -> Dict[str, Any]:
            """Mock order creation."""
            import random
            return {
                "order_id": random.randint(1000, 9999),
                "product_id": product_id,
                "quantity": quantity,
                "priority": priority,
                "status": "pending",
                "created_at": datetime.now().isoformat()
            }
        
        def _search_database(self, table: str, query: str, limit: int = 10) -> Dict[str, Any]:
            """Mock database search."""
            mock_results = {
                "products": [
                    {"id": "P001", "name": "Laptop", "price": 999.99},
                    {"id": "P002", "name": "Mouse", "price": 29.99}
                ],
                "users": [
                    {"id": 1, "name": "Alice"},
                    {"id": 2, "name": "Bob"}
                ]
            }
            
            results = mock_results.get(table, [])
            return {
                "table": table,
                "query": query,
                "results": results[:limit],
                "total": len(results)
            }
        
        def _send_notification(self, user_id: int, message: str, urgent: bool = False) -> Dict[str, Any]:
            """Mock notification sending."""
            return {
                "notification_id": f"N{user_id}{datetime.now().microsecond}",
                "user_id": user_id,
                "message": message,
                "urgent": urgent,
                "sent": True,
                "timestamp": datetime.now().isoformat()
            }
        
        def _calculate_price(self, base_price: float, tax_rate: float = 0.1, discount: float = 0) -> Dict[str, Any]:
            """Calculate total price."""
            tax = base_price * tax_rate
            total = base_price + tax - discount
            
            return {
                "base_price": base_price,
                "tax": tax,
                "discount": discount,
                "total": total
            }
    
    
    class FunctionCallingAgent:
        """
        Agent that uses function calling to accomplish tasks.
        """
        
        def __init__(
            self,
            function_registry: FunctionRegistry,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize function calling agent.
            
            Args:
                function_registry: Registry of available functions
                model: LLM model to use
            """
            self.function_registry = function_registry
            self.llm = ChatOpenAI(model=model, temperature=0.1)
            self.call_history: List[FunctionCall] = []
        
        def select_and_extract(
            self,
            user_request: str,
            context: Optional[str] = None
        ) -> List[Dict[str, Any]]:
            """
            Select functions and extract parameters from user request.
            
            Args:
                user_request: User's request
                context: Optional context from previous turns
                
            Returns:
                List of function calls to make
            """
            # Get all function schemas
            schemas = self.function_registry.get_all_schemas()
            schemas_text = json.dumps(schemas, indent=2)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a function calling expert. Analyze user requests and
    determine which functions to call with what parameters.
    
    Available functions:
    {schemas}
    
    Respond with a JSON array of function calls. Each call should have:
    - "function": function name
    - "arguments": object with parameter values
    
    If multiple functions are needed, include them all in the array.
    If no function is needed, respond with an empty array []."""),
                ("user", "{request}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            full_request = user_request
            if context:
                full_request = f"Context: {context}\n\nRequest: {user_request}"
            
            response = chain.invoke({
                "schemas": schemas_text,
                "request": full_request
            })
            
            # Parse function calls
            try:
                json_start = response.find("[")
                json_end = response.rfind("]") + 1
                if json_start != -1 and json_end > json_start:
                    json_str = response[json_start:json_end]
                    function_calls = json.loads(json_str)
                    return function_calls if isinstance(function_calls, list) else []
                else:
                    return []
            except Exception as e:
                print(f"Error parsing function calls: {e}")
                return []
        
        def execute_function(
            self,
            function_name: str,
            arguments: Dict[str, Any]
        ) -> FunctionCall:
            """
            Execute a function with given arguments.
            
            Args:
                function_name: Name of function to call
                arguments: Arguments for the function
                
            Returns:
                FunctionCall record
            """
            func_def = self.function_registry.get_function(function_name)
            
            if func_def is None:
                call = FunctionCall(
                    function_name=function_name,
                    arguments=arguments,
                    result=None,
                    success=False,
                    error=f"Function '{function_name}' not found"
                )
                self.call_history.append(call)
                return call
            
            # Validate parameters
            valid, error = func_def.validate_parameters(arguments)
            if not valid:
                call = FunctionCall(
                    function_name=function_name,
                    arguments=arguments,
                    result=None,
                    success=False,
                    error=error
                )
                self.call_history.append(call)
                return call
            
            # Execute function
            try:
                result = func_def.function(**arguments)
                call = FunctionCall(
                    function_name=function_name,
                    arguments=arguments,
                    result=result,
                    success=True
                )
            except Exception as e:
                call = FunctionCall(
                    function_name=function_name,
                    arguments=arguments,
                    result=None,
                    success=False,
                    error=str(e)
                )
            
            self.call_history.append(call)
            return call
        
        def process_request(
            self,
            user_request: str,
            context: Optional[str] = None
        ) -> Dict[str, Any]:
            """
            Process user request with function calling.
            
            Args:
                user_request: User's request
                context: Optional context
                
            Returns:
                Results including function calls and response
            """
            print(f"\n[Agent] Processing request: {user_request}\n")
            
            # Select and extract function calls
            print("[Agent] Analyzing request and selecting functions...")
            function_calls = self.select_and_extract(user_request, context)
            
            if not function_calls:
                print("[Agent] No function calls needed\n")
                return {
                    "request": user_request,
                    "function_calls": [],
                    "response": "No functions needed for this request"
                }
            
            print(f"[Agent] Planning {len(function_calls)} function call(s)\n")
            
            # Execute function calls
            results = []
            for fc in function_calls:
                function_name = fc.get("function", "")
                arguments = fc.get("arguments", {})
                
                print(f"[Agent] Calling: {function_name}")
                print(f"[Agent] Arguments: {json.dumps(arguments, indent=2)}")
                
                call = self.execute_function(function_name, arguments)
                results.append(call)
                
                if call.success:
                    print(f"[Agent] Success: {json.dumps(call.result, indent=2)}\n")
                else:
                    print(f"[Agent] Error: {call.error}\n")
            
            # Generate final response
            response = self._generate_response(user_request, results)
            
            return {
                "request": user_request,
                "function_calls": results,
                "response": response
            }
        
        def _generate_response(
            self,
            user_request: str,
            function_calls: List[FunctionCall]
        ) -> str:
            """Generate natural language response from function results."""
            results_text = "\n".join([
                f"Function: {call.function_name}\n"
                f"Result: {json.dumps(call.result, indent=2) if call.success else f'Error: {call.error}'}"
                for call in function_calls
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", "You generate natural responses from function call results."),
                ("user", """User Request: {request}
    
    Function Results:
    {results}
    
    Provide a natural, helpful response incorporating these results.""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "request": user_request,
                "results": results_text
            })
            
            return response.strip()
    
    
    def demonstrate_function_calling():
        """Demonstrate the Function Calling pattern."""
        
        print("=" * 80)
        print("FUNCTION CALLING PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Initialize registry and agent
        registry = FunctionRegistry()
        agent = FunctionCallingAgent(registry)
        
        # Show available functions
        print("\n" + "=" * 80)
        print("AVAILABLE FUNCTIONS")
        print("=" * 80)
        schemas = registry.get_all_schemas()
        for schema in schemas:
            print(f"\n{schema['name']}:")
            print(f"  Description: {schema['description']}")
            print(f"  Parameters: {list(schema['parameters']['properties'].keys())}")
        
        # Test 1: Single function call
        print("\n" + "=" * 80)
        print("TEST 1: Single Function Call")
        print("=" * 80)
        
        request1 = "Get information about user ID 1"
        result1 = agent.process_request(request1)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result1["response"])
        
        # Test 2: Multiple function calls
        print("\n" + "=" * 80)
        print("TEST 2: Multiple Function Calls")
        print("=" * 80)
        
        request2 = "Create an order for product P001 with quantity 5 and high priority, then send an urgent notification to user 1 about the order"
        result2 = agent.process_request(request2)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result2["response"])
        
        # Test 3: Function with optional parameters
        print("\n" + "=" * 80)
        print("TEST 3: Optional Parameters")
        print("=" * 80)
        
        request3 = "Calculate the price for a $100 item with 15% tax and a $10 discount"
        result3 = agent.process_request(request3)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result3["response"])
        
        # Test 4: Database search
        print("\n" + "=" * 80)
        print("TEST 4: Database Query")
        print("=" * 80)
        
        request4 = "Search the products table for laptops, limit to 5 results"
        result4 = agent.process_request(request4)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result4["response"])
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print(f"""
    Total requests processed: 4
    Total function calls made: {len(agent.call_history)}
    Success rate: {sum(1 for call in agent.call_history if call.success) / len(agent.call_history) * 100:.0f}%
    
    Function Call Breakdown:
    """)
        
        for call in agent.call_history:
            status = "✓" if call.success else "✗"
            print(f"  {status} {call.function_name}({list(call.arguments.keys())})")
        
        print("""
    The Function Calling pattern demonstrates:
    
    1. **Type-Safe Invocation**: Strong parameter typing and validation
    2. **Schema-Driven**: JSON schemas define function contracts
    3. **Multi-Function**: Can orchestrate multiple function calls
    4. **Parameter Extraction**: LLM extracts typed parameters from text
    5. **Error Handling**: Validates inputs before execution
    
    Key Benefits:
    - **Type Safety**: Prevents invalid function calls
    - **Clear Contracts**: Well-defined function interfaces
    - **Composable**: Multiple functions work together
    - **Reliable**: Validation before execution
    - **Structured**: Predictable input/output formats
    
    Use Cases:
    - Database operations with typed queries
    - API integrations with strict schemas
    - System commands requiring validation
    - Multi-step workflows with dependencies
    - Enterprise integrations
    
    This pattern is essential for production agentic systems that need
    reliable, type-safe interactions with external systems and APIs.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_function_calling()
    
        

    Agentic Design Pattern: Function Calling

    Pattern 021: Function Calling – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Function Calling pattern implementation.


    Pattern Description

    Pattern 021: Function Calling

    Description: The Function Calling pattern enables LLMs to invoke structured functions with type-safe parameters. Unlike general tool use, function calling emphasizes strong typing, structured schemas, and direct integration with code functions. The LLM analyzes user intent, selects appropriate functions, extracts typed parameters, and invokes functions with validated inputs.

    Components: – Function Schema: Structured definitions with parameter types – Parameter Validation: Type checking and constraint enforcement – Function Binding: Direct mapping to callable functions – Response Formatting: Structured outputs from function calls – Multi-turn Handling: Multiple function calls in sequence

    Use Cases: – Database queries with typed parameters – API calls with strict schemas – System operations requiring validation – Structured data processing – Multi-step function orchestration

    LangChain Implementation: Uses LangChain’s function calling with OpenAI’s function calling API, implements type-safe parameter extraction, and handles multi-turn conversations.

    Key Features: – Type-safe parameter extraction – JSON schema validation – Multiple function selection – Error handling and retries – Conversation context preservation


    File Statistics

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

    Module Documentation

    """
    Pattern 021: Function Calling
    
    Description:
        The Function Calling pattern enables LLMs to invoke structured functions with
        type-safe parameters. Unlike general tool use, function calling emphasizes strong
        typing, structured schemas, and direct integration with code functions. The LLM
        analyzes user intent, selects appropriate functions, extracts typed parameters,
        and invokes functions with validated inputs.
    
    Components:
        - Function Schema: Structured definitions with parameter types
        - Parameter Validation: Type checking and constraint enforcement
        - Function Binding: Direct mapping to callable functions
        - Response Formatting: Structured outputs from function calls
        - Multi-turn Handling: Multiple function calls in sequence
    
    Use Cases:
        - Database queries with typed parameters
        - API calls with strict schemas
        - System operations requiring validation
        - Structured data processing
        - Multi-step function orchestration
    
    LangChain Implementation:
        Uses LangChain's function calling with OpenAI's function calling API,
        implements type-safe parameter extraction, and handles multi-turn conversations.
    
    Key Features:
        - Type-safe parameter extraction
        - JSON schema validation
        - Multiple function selection
        - Error handling and retries
        - Conversation context preservation
    """
    

    Purpose: This module implements the Function Calling 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 json
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import random
    import typing.Any
    import typing.Callable
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Union
    

    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 – json: JSON serialization/deserialization – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – 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.Union: 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: ParameterType

    Lines 52-59

    Inherits from: Enum

    Purpose:

    Types of function parameters.
    

    Class: FunctionParameter

    Lines 63-80

    Purpose:

    Definition of a function parameter.
    

    Methods:

    to_schema(self)

    Lines 72-80

    Purpose:

    Convert to JSON schema format.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D622310>


    Class: FunctionDefinition

    Lines 84-139

    Purpose:

    Complete function definition with schema.
    

    Methods:

    to_openai_schema(self)

    Lines 92-109

    Purpose:

    Convert to OpenAI function calling schema.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D70EF90>

    validate_parameters(self, params)

    Lines 111-139

    Purpose:

    Validate parameters against schema.
    

    Parameters: – params: Params

    Returns: <ast.Subscript object at 0x000001A06D6EA090>


    Class: FunctionCall

    Lines 143-150

    Purpose:

    Record of a function call.
    

    Class: FunctionRegistry

    Lines 153-309

    Purpose:

    Registry of callable functions with schemas.
    

    Methods:

    __init__(self)

    Lines 158-161

    Purpose:

    Initialize function registry.
    

    Parameters:

    _register_builtin_functions(self)

    Lines 163-224

    Purpose:

    Register built-in functions.
    

    Parameters:

    register_function(self, function_def)

    Lines 226-228

    Purpose:

    Register a new function.
    

    Parameters: – function_def: Function def

    get_function(self, name)

    Lines 230-232

    Purpose:

    Get function by name.
    

    Parameters: – name: Name

    Returns: <ast.Subscript object at 0x000001A06D15A090>

    get_all_schemas(self)

    Lines 234-236

    Purpose:

    Get all function schemas in OpenAI format.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D1588D0>

    _get_user_info(self, user_id, fields)

    Lines 240-253

    Purpose:

    Mock user info retrieval.
    

    Parameters: – user_id: User id – fields: Fields

    Returns: <ast.Subscript object at 0x000001A06D701B90>

    _create_order(self, product_id, quantity, priority)

    Lines 255-265

    Purpose:

    Mock order creation.
    

    Parameters: – product_id: Product id – quantity: Quantity – priority: Priority

    Returns: <ast.Subscript object at 0x000001A06D6F7810>

    _search_database(self, table, query, limit)

    Lines 267-286

    Purpose:

    Mock database search.
    

    Parameters: – table: Table – query: Input query or question – limit: Limit

    Returns: <ast.Subscript object at 0x000001A06D15FC50>

    _send_notification(self, user_id, message, urgent)

    Lines 288-297

    Purpose:

    Mock notification sending.
    

    Parameters: – user_id: User id – message: Message – urgent: Urgent

    Returns: <ast.Subscript object at 0x000001A06D15E950>

    _calculate_price(self, base_price, tax_rate, discount)

    Lines 299-309

    Purpose:

    Calculate total price.
    

    Parameters: – base_price: Base price – tax_rate: Tax rate – discount: Discount

    Returns: <ast.Subscript object at 0x000001A06D676D10>


    Class: FunctionCallingAgent

    Lines 312-541

    Purpose:

    Agent that uses function calling to accomplish tasks.
    

    Methods:

    __init__(self, function_registry, model)

    Lines 317-331

    Purpose:

    Initialize function calling agent.
    
    Args:
        function_registry: Registry of available functions
        model: LLM model to use
    

    Parameters: – function_registry: Function registry – model: Language model instance

    select_and_extract(self, user_request, context)

    Lines 333-391

    Purpose:

    Select functions and extract parameters from user request.
    
    Args:
        user_request: User's request
        context: Optional context from previous turns
    
    Returns:
        List of function calls to make
    

    Parameters: – user_request: User request – context: Context

    Returns: <ast.Subscript object at 0x000001A06D73B6D0>

    execute_function(self, function_name, arguments)

    Lines 393-453

    Purpose:

    Execute a function with given arguments.
    
    Args:
        function_name: Name of function to call
        arguments: Arguments for the function
    
    Returns:
        FunctionCall record
    

    Parameters: – function_name: Function name – arguments: Arguments

    Returns: FunctionCall

    process_request(self, user_request, context)

    Lines 455-510

    Purpose:

    Process user request with function calling.
    
    Args:
        user_request: User's request
        context: Optional context
    
    Returns:
        Results including function calls and response
    

    Parameters: – user_request: User request – context: Context

    Returns: <ast.Subscript object at 0x000001A06D178AD0>

    _generate_response(self, user_request, function_calls)

    Lines 512-541

    Purpose:

    Generate natural language response from function results.
    

    Parameters: – user_request: User request – function_calls: Function calls

    Returns: str


    Functions

    demonstrate_function_calling()

    Lines 544-658

    Purpose:

    Demonstrate the Function Calling pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Function Calling 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 020
    • Pattern 022
    • Other Tool Use Patterns (020-025)

    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 Function Calling 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: 021_function_calling.py
    Total Lines: 663
    Pattern Category: AI Agent Design Pattern #021


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Tool Selection

    
        """
    Pattern 020: Tool Selection & Use
    
    Description:
        The Tool Selection & Use pattern enables agents to discover, select, and use external
        tools and APIs to extend their capabilities beyond the LLM's inherent knowledge. The
        agent analyzes the task, identifies appropriate tools, invokes them correctly, and
        processes the results.
    
    Components:
        - Tool Registry: Catalog of available tools with descriptions
        - Selection Logic: Determines which tool(s) to use for a task
        - Parameter Extraction: Extracts required parameters from context
        - Tool Invocation: Executes tools with proper error handling
        - Result Integration: Incorporates tool outputs into responses
    
    Use Cases:
        - Web search and information retrieval
        - Mathematical calculations
        - API integrations (weather, news, databases)
        - File operations and system commands
        - Data transformations
    
    LangChain Implementation:
        Uses LangChain's Tool abstraction with structured tool definitions,
        implements tool selection logic, and integrates results into agent workflow.
    
    Key Features:
        - Dynamic tool discovery
        - Context-aware tool selection
        - Robust error handling
        - Result formatting and integration
    """
    
    import os
    from typing import List, Dict, Any, Optional, Callable
    from dataclasses import dataclass
    from enum import Enum
    import json
    from datetime import datetime
    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 ToolCategory(Enum):
        """Categories of tools available."""
        SEARCH = "search"
        COMPUTATION = "computation"
        DATA = "data"
        COMMUNICATION = "communication"
        UTILITY = "utility"
    
    
    @dataclass
    class ToolDefinition:
        """Definition of a tool available to the agent."""
        name: str
        category: ToolCategory
        description: str
        parameters: List[str]
        function: Callable
        
        def to_dict(self) -> Dict[str, Any]:
            """Convert to dictionary for LLM context."""
            return {
                "name": self.name,
                "category": self.category.value,
                "description": self.description,
                "parameters": self.parameters
            }
    
    
    @dataclass
    class ToolInvocation:
        """Record of a tool invocation."""
        tool_name: str
        parameters: Dict[str, Any]
        result: Any
        success: bool
        error: Optional[str] = None
    
    
    class ToolRegistry:
        """
        Registry of available tools with discovery and selection capabilities.
        """
        
        def __init__(self):
            """Initialize tool registry with built-in tools."""
            self.tools: Dict[str, ToolDefinition] = {}
            self._register_builtin_tools()
        
        def _register_builtin_tools(self):
            """Register built-in tools."""
            # Calculator tool
            self.register_tool(ToolDefinition(
                name="calculator",
                category=ToolCategory.COMPUTATION,
                description="Performs mathematical calculations. Supports +, -, *, /, **, sqrt, etc.",
                parameters=["expression"],
                function=self._calculator
            ))
            
            # Date/Time tool
            self.register_tool(ToolDefinition(
                name="datetime",
                category=ToolCategory.UTILITY,
                description="Gets current date and time, or formats dates",
                parameters=["format"],
                function=self._datetime
            ))
            
            # String manipulation tool
            self.register_tool(ToolDefinition(
                name="string_transform",
                category=ToolCategory.DATA,
                description="Transforms strings: uppercase, lowercase, reverse, length, etc.",
                parameters=["text", "operation"],
                function=self._string_transform
            ))
            
            # Search simulator (mock)
            self.register_tool(ToolDefinition(
                name="web_search",
                category=ToolCategory.SEARCH,
                description="Searches the web for information (simulated)",
                parameters=["query"],
                function=self._web_search_mock
            ))
            
            # Data formatter
            self.register_tool(ToolDefinition(
                name="format_data",
                category=ToolCategory.DATA,
                description="Formats data as JSON, CSV, or table",
                parameters=["data", "format"],
                function=self._format_data
            ))
        
        def register_tool(self, tool: ToolDefinition):
            """Register a new tool."""
            self.tools[tool.name] = tool
        
        def get_tool(self, name: str) -> Optional[ToolDefinition]:
            """Get a tool by name."""
            return self.tools.get(name)
        
        def get_tools_by_category(self, category: ToolCategory) -> List[ToolDefinition]:
            """Get all tools in a category."""
            return [tool for tool in self.tools.values() if tool.category == category]
        
        def search_tools(self, query: str) -> List[ToolDefinition]:
            """Search tools by query in name or description."""
            query_lower = query.lower()
            return [
                tool for tool in self.tools.values()
                if query_lower in tool.name.lower() or query_lower in tool.description.lower()
            ]
        
        def get_all_tools_description(self) -> str:
            """Get formatted description of all tools."""
            descriptions = []
            for tool in self.tools.values():
                params = ", ".join(tool.parameters)
                descriptions.append(
                    f"- {tool.name} ({tool.category.value}): {tool.description}\n"
                    f"  Parameters: {params}"
                )
            return "\n\n".join(descriptions)
        
        # Built-in tool implementations
        
        def _calculator(self, expression: str) -> str:
            """Calculate mathematical expression."""
            try:
                # Safe eval with limited scope
                allowed_names = {
                    "abs": abs, "round": round, "min": min, "max": max,
                    "sum": sum, "pow": pow, "sqrt": lambda x: x ** 0.5
                }
                result = eval(expression, {"__builtins__": {}}, allowed_names)
                return f"Result: {result}"
            except Exception as e:
                return f"Error: {str(e)}"
        
        def _datetime(self, format: str = "%Y-%m-%d %H:%M:%S") -> str:
            """Get current date/time."""
            try:
                return datetime.now().strftime(format)
            except Exception as e:
                return f"Error: {str(e)}"
        
        def _string_transform(self, text: str, operation: str) -> str:
            """Transform string."""
            operations = {
                "uppercase": lambda s: s.upper(),
                "lowercase": lambda s: s.lower(),
                "reverse": lambda s: s[::-1],
                "length": lambda s: str(len(s)),
                "capitalize": lambda s: s.capitalize(),
                "title": lambda s: s.title()
            }
            
            if operation in operations:
                return operations[operation](text)
            else:
                return f"Unknown operation: {operation}"
        
        def _web_search_mock(self, query: str) -> str:
            """Mock web search (simulated)."""
            # In real implementation, this would use actual search API
            mock_results = {
                "weather": "Current weather: Sunny, 72°F",
                "news": "Latest news: Tech stocks rise, new AI breakthrough announced",
                "python": "Python is a high-level programming language known for readability",
                "default": f"Search results for '{query}': Multiple relevant articles found"
            }
            
            for key, result in mock_results.items():
                if key in query.lower():
                    return result
            return mock_results["default"]
        
        def _format_data(self, data: str, format: str) -> str:
            """Format data in specified format."""
            try:
                if format == "json":
                    # Try to parse and pretty-print
                    parsed = json.loads(data)
                    return json.dumps(parsed, indent=2)
                elif format == "uppercase":
                    return data.upper()
                else:
                    return data
            except Exception as e:
                return f"Error formatting: {str(e)}"
    
    
    class ToolUsingAgent:
        """
        Agent that can discover, select, and use tools to accomplish tasks.
        """
        
        def __init__(
            self,
            tool_registry: ToolRegistry,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize tool-using agent.
            
            Args:
                tool_registry: Registry of available tools
                model: LLM model to use
            """
            self.tool_registry = tool_registry
            self.llm = ChatOpenAI(model=model, temperature=0.3)
            self.invocation_history: List[ToolInvocation] = []
        
        def select_tools(self, task: str) -> List[str]:
            """
            Select appropriate tools for a task.
            
            Args:
                task: Task description
                
            Returns:
                List of selected tool names
            """
            tools_description = self.tool_registry.get_all_tools_description()
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a tool selection expert. Given a task and available tools,
    select the most appropriate tool(s) to accomplish the task."""),
                ("user", """Task: {task}
    
    Available Tools:
    {tools_description}
    
    Select the best tool(s) for this task. Respond with ONLY the tool name(s), one per line.
    If no tool is needed, respond with "NONE".""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "task": task,
                "tools_description": tools_description
            })
            
            # Parse tool names from response
            lines = response.strip().split("\n")
            tool_names = [
                line.strip()
                for line in lines
                if line.strip() and line.strip() != "NONE"
            ]
            
            # Validate tool names
            valid_tools = [
                name for name in tool_names
                if self.tool_registry.get_tool(name) is not None
            ]
            
            return valid_tools
        
        def extract_parameters(
            self,
            task: str,
            tool: ToolDefinition
        ) -> Dict[str, Any]:
            """
            Extract tool parameters from task description.
            
            Args:
                task: Task description
                tool: Tool definition
                
            Returns:
                Dictionary of parameter values
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a parameter extraction expert. Extract the required
    parameters for a tool from the task description."""),
                ("user", """Task: {task}
    
    Tool: {tool_name}
    Description: {tool_description}
    Required Parameters: {parameters}
    
    Extract the parameter values from the task. Respond ONLY with a JSON object
    mapping parameter names to their values. If a parameter is not mentioned,
    use a reasonable default or omit it.
    
    Example: {{"expression": "2+2", "format": "json"}}""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "task": task,
                "tool_name": tool.name,
                "tool_description": tool.description,
                "parameters": ", ".join(tool.parameters)
            })
            
            try:
                # Extract JSON from response
                json_start = response.find("{")
                json_end = response.rfind("}") + 1
                if json_start != -1 and json_end > json_start:
                    json_str = response[json_start:json_end]
                    parameters = json.loads(json_str)
                    return parameters
                else:
                    return {}
            except Exception as e:
                print(f"Error parsing parameters: {e}")
                return {}
        
        def invoke_tool(
            self,
            tool_name: str,
            parameters: Dict[str, Any]
        ) -> ToolInvocation:
            """
            Invoke a tool with given parameters.
            
            Args:
                tool_name: Name of tool to invoke
                parameters: Parameters for the tool
                
            Returns:
                ToolInvocation record
            """
            tool = self.tool_registry.get_tool(tool_name)
            
            if tool is None:
                invocation = ToolInvocation(
                    tool_name=tool_name,
                    parameters=parameters,
                    result=None,
                    success=False,
                    error=f"Tool '{tool_name}' not found"
                )
                self.invocation_history.append(invocation)
                return invocation
            
            try:
                # Invoke tool function with parameters
                result = tool.function(**parameters)
                
                invocation = ToolInvocation(
                    tool_name=tool_name,
                    parameters=parameters,
                    result=result,
                    success=True
                )
            except Exception as e:
                invocation = ToolInvocation(
                    tool_name=tool_name,
                    parameters=parameters,
                    result=None,
                    success=False,
                    error=str(e)
                )
            
            self.invocation_history.append(invocation)
            return invocation
        
        def process_task(self, task: str) -> Dict[str, Any]:
            """
            Process a task using tool selection and invocation.
            
            Args:
                task: Task to process
                
            Returns:
                Results including tool invocations and final response
            """
            print(f"\n[Agent] Processing task: {task}\n")
            
            # Step 1: Select tools
            print("[Agent] Step 1: Selecting tools...")
            selected_tools = self.select_tools(task)
            
            if not selected_tools:
                print("[Agent] No tools needed for this task\n")
                return {
                    "task": task,
                    "tools_used": [],
                    "invocations": [],
                    "final_response": "Task does not require tool usage"
                }
            
            print(f"[Agent] Selected tools: {', '.join(selected_tools)}\n")
            
            # Step 2: Extract parameters and invoke tools
            print("[Agent] Step 2: Invoking tools...")
            invocations = []
            
            for tool_name in selected_tools:
                tool = self.tool_registry.get_tool(tool_name)
                
                # Extract parameters
                parameters = self.extract_parameters(task, tool)
                print(f"[Agent] {tool_name} parameters: {parameters}")
                
                # Invoke tool
                invocation = self.invoke_tool(tool_name, parameters)
                invocations.append(invocation)
                
                if invocation.success:
                    print(f"[Agent] {tool_name} result: {invocation.result}\n")
                else:
                    print(f"[Agent] {tool_name} error: {invocation.error}\n")
            
            # Step 3: Integrate results
            print("[Agent] Step 3: Integrating results...")
            final_response = self._integrate_results(task, invocations)
            
            return {
                "task": task,
                "tools_used": selected_tools,
                "invocations": invocations,
                "final_response": final_response
            }
        
        def _integrate_results(
            self,
            task: str,
            invocations: List[ToolInvocation]
        ) -> str:
            """Integrate tool results into final response."""
            results_text = "\n".join([
                f"- {inv.tool_name}: {inv.result if inv.success else f'Error: {inv.error}'}"
                for inv in invocations
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", "You integrate tool results into coherent responses."),
                ("user", """Task: {task}
    
    Tool Results:
    {results_text}
    
    Provide a complete answer to the task incorporating these results naturally.""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "task": task,
                "results_text": results_text
            })
            
            return response.strip()
    
    
    def demonstrate_tool_selection():
        """Demonstrate the Tool Selection & Use pattern."""
        
        print("=" * 80)
        print("TOOL SELECTION & USE PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Initialize registry and agent
        registry = ToolRegistry()
        agent = ToolUsingAgent(registry)
        
        # Show available tools
        print("\n" + "=" * 80)
        print("AVAILABLE TOOLS")
        print("=" * 80)
        print(registry.get_all_tools_description())
        
        # Test 1: Calculation task
        print("\n" + "=" * 80)
        print("TEST 1: Mathematical Calculation")
        print("=" * 80)
        
        task1 = "Calculate the result of 15 squared plus 25"
        result1 = agent.process_task(task1)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result1["final_response"])
        
        # Test 2: Multiple tools
        print("\n" + "=" * 80)
        print("TEST 2: Multiple Tools")
        print("=" * 80)
        
        task2 = "What is the current date and time, and also calculate 100 divided by 4?"
        result2 = agent.process_task(task2)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result2["final_response"])
        
        print("\n" + "-" * 80)
        print("TOOLS USED:")
        print("-" * 80)
        for inv in result2["invocations"]:
            print(f"  - {inv.tool_name}: {'Success' if inv.success else 'Failed'}")
        
        # Test 3: String manipulation
        print("\n" + "=" * 80)
        print("TEST 3: String Transformation")
        print("=" * 80)
        
        task3 = "Convert 'hello world' to uppercase and tell me its length"
        result3 = agent.process_task(task3)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result3["final_response"])
        
        # Test 4: Search task
        print("\n" + "=" * 80)
        print("TEST 4: Web Search (Simulated)")
        print("=" * 80)
        
        task4 = "Search for information about Python programming"
        result4 = agent.process_task(task4)
        
        print("=" * 80)
        print("FINAL RESPONSE:")
        print("=" * 80)
        print(result4["final_response"])
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print(f"""
    Total tasks processed: 4
    Total tool invocations: {len(agent.invocation_history)}
    Success rate: {sum(1 for inv in agent.invocation_history if inv.success) / len(agent.invocation_history) * 100:.0f}%
    
    The Tool Selection & Use pattern demonstrates:
    
    1. **Tool Discovery**: Agent can identify available tools
    2. **Intelligent Selection**: Chooses appropriate tools for tasks
    3. **Parameter Extraction**: Extracts parameters from natural language
    4. **Robust Invocation**: Handles tool execution with error handling
    5. **Result Integration**: Combines tool outputs into coherent responses
    
    Key Benefits:
    - **Extended Capabilities**: Goes beyond LLM knowledge
    - **Task-Appropriate**: Selects right tool for each job
    - **Error Resilient**: Graceful handling of failures
    - **Composable**: Can use multiple tools together
    
    Use Cases:
    - API integrations (weather, news, databases)
    - Mathematical computations
    - Web searches and data retrieval
    - File operations
    - Data transformations
    
    This pattern is the foundation for agentic AI systems that can interact
    with the real world through tools and APIs.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_tool_selection()
    
        

    Agentic Design Pattern: Tool Selection

    Pattern 020: Tool Selection – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Tool Selection pattern implementation.


    Pattern Description

    Pattern 020: Tool Selection & Use

    Description: The Tool Selection & Use pattern enables agents to discover, select, and use external tools and APIs to extend their capabilities beyond the LLM’s inherent knowledge. The agent analyzes the task, identifies appropriate tools, invokes them correctly, and processes the results.

    Components: – Tool Registry: Catalog of available tools with descriptions – Selection Logic: Determines which tool(s) to use for a task – Parameter Extraction: Extracts required parameters from context – Tool Invocation: Executes tools with proper error handling – Result Integration: Incorporates tool outputs into responses

    Use Cases: – Web search and information retrieval – Mathematical calculations – API integrations (weather, news, databases) – File operations and system commands – Data transformations

    LangChain Implementation: Uses LangChain’s Tool abstraction with structured tool definitions, implements tool selection logic, and integrates results into agent workflow.

    Key Features: – Dynamic tool discovery – Context-aware tool selection – Robust error handling – Result formatting and integration


    File Statistics

    • Total Lines: 617
    • Classes: 5
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 020: Tool Selection & Use
    
    Description:
        The Tool Selection & Use pattern enables agents to discover, select, and use external
        tools and APIs to extend their capabilities beyond the LLM's inherent knowledge. The
        agent analyzes the task, identifies appropriate tools, invokes them correctly, and
        processes the results.
    
    Components:
        - Tool Registry: Catalog of available tools with descriptions
        - Selection Logic: Determines which tool(s) to use for a task
        - Parameter Extraction: Extracts required parameters from context
        - Tool Invocation: Executes tools with proper error handling
        - Result Integration: Incorporates tool outputs into responses
    
    Use Cases:
        - Web search and information retrieval
        - Mathematical calculations
        - API integrations (weather, news, databases)
        - File operations and system commands
        - Data transformations
    
    LangChain Implementation:
        Uses LangChain's Tool abstraction with structured tool definitions,
        implements tool selection logic, and integrates results into agent workflow.
    
    Key Features:
        - Dynamic tool discovery
        - Context-aware tool selection
        - Robust error handling
        - Result formatting and integration
    """
    

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


    Import Statements

    Standard Library Imports

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

    Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – json: JSON serialization/deserialization – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – 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)

    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: ToolCategory

    Lines 50-56

    Inherits from: Enum

    Purpose:

    Categories of tools available.
    

    Class: ToolDefinition

    Lines 60-75

    Purpose:

    Definition of a tool available to the agent.
    

    Methods:

    to_dict(self)

    Lines 68-75

    Purpose:

    Convert to dictionary for LLM context.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D60E950>


    Class: ToolInvocation

    Lines 79-85

    Purpose:

    Record of a tool invocation.
    

    Class: ToolRegistry

    Lines 88-241

    Purpose:

    Registry of available tools with discovery and selection capabilities.
    

    Methods:

    __init__(self)

    Lines 93-96

    Purpose:

    Initialize tool registry with built-in tools.
    

    Parameters:

    _register_builtin_tools(self)

    Lines 98-143

    Purpose:

    Register built-in tools.
    

    Parameters:

    register_tool(self, tool)

    Lines 145-147

    Purpose:

    Register a new tool.
    

    Parameters: – tool: Tool

    get_tool(self, name)

    Lines 149-151

    Purpose:

    Get a tool by name.
    

    Parameters: – name: Name

    Returns: <ast.Subscript object at 0x000001A06D6A1510>

    get_tools_by_category(self, category)

    Lines 153-155

    Purpose:

    Get all tools in a category.
    

    Parameters: – category: Category

    Returns: <ast.Subscript object at 0x000001A06D6A1E50>

    search_tools(self, query)

    Lines 157-163

    Purpose:

    Search tools by query in name or description.
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D168490>

    get_all_tools_description(self)

    Lines 165-174

    Purpose:

    Get formatted description of all tools.
    

    Parameters:

    Returns: str

    _calculator(self, expression)

    Lines 178-189

    Purpose:

    Calculate mathematical expression.
    

    Parameters: – expression: Expression

    Returns: str

    _datetime(self, format)

    Lines 191-196

    Purpose:

    Get current date/time.
    

    Parameters: – format: Format

    Returns: str

    _string_transform(self, text, operation)

    Lines 198-212

    Purpose:

    Transform string.
    

    Parameters: – text: Text – operation: Operation

    Returns: str

    _web_search_mock(self, query)

    Lines 214-227

    Purpose:

    Mock web search (simulated).
    

    Parameters: – query: Input query or question

    Returns: str

    _format_data(self, data, format)

    Lines 229-241

    Purpose:

    Format data in specified format.
    

    Parameters: – data: Data – format: Format

    Returns: str


    Class: ToolUsingAgent

    Lines 244-502

    Purpose:

    Agent that can discover, select, and use tools to accomplish tasks.
    

    Methods:

    __init__(self, tool_registry, model)

    Lines 249-263

    Purpose:

    Initialize tool-using agent.
    
    Args:
        tool_registry: Registry of available tools
        model: LLM model to use
    

    Parameters: – tool_registry: Tool registry – model: Language model instance

    select_tools(self, task)

    Lines 265-310

    Purpose:

    Select appropriate tools for a task.
    
    Args:
        task: Task description
    
    Returns:
        List of selected tool names
    

    Parameters: – task: Task

    Returns: <ast.Subscript object at 0x000001A06D627510>

    extract_parameters(self, task, tool)

    Lines 312-364

    Purpose:

    Extract tool parameters from task description.
    
    Args:
        task: Task description
        tool: Tool definition
    
    Returns:
        Dictionary of parameter values
    

    Parameters: – task: Task – tool: Tool

    Returns: <ast.Subscript object at 0x000001A06D6076D0>

    invoke_tool(self, tool_name, parameters)

    Lines 366-414

    Purpose:

    Invoke a tool with given parameters.
    
    Args:
        tool_name: Name of tool to invoke
        parameters: Parameters for the tool
    
    Returns:
        ToolInvocation record
    

    Parameters: – tool_name: Tool name – parameters: Parameters

    Returns: ToolInvocation

    process_task(self, task)

    Lines 416-472

    Purpose:

    Process a task using tool selection and invocation.
    
    Args:
        task: Task to process
    
    Returns:
        Results including tool invocations and final response
    

    Parameters: – task: Task

    Returns: <ast.Subscript object at 0x000001A06D633290>

    _integrate_results(self, task, invocations)

    Lines 474-502

    Purpose:

    Integrate tool results into final response.
    

    Parameters: – task: Task – invocations: Invocations

    Returns: str


    Functions

    demonstrate_tool_selection()

    Lines 505-612

    Purpose:

    Demonstrate the Tool Selection & Use pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Tool Selection 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 019
    • Pattern 021
    • Other Tool Use Patterns (020-025)

    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 Tool Selection 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: 020_tool_selection.py
    Total Lines: 617
    Pattern Category: AI Agent Design Pattern #020


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Society Of Mind

    
        """
    Pattern 019: Society of Mind
    
    Description:
        The Society of Mind pattern implements a cognitive architecture where multiple
        specialized sub-agents (each handling a specific aspect of cognition) work together
        to form a coherent intelligent system. Inspired by Marvin Minsky's theory, this
        pattern views intelligence as emerging from the interaction of many simple processes.
    
    Components:
        - Perception Agents: Process and interpret inputs
        - Memory Agents: Store and retrieve information
        - Reasoning Agents: Logical thinking and analysis
        - Planning Agents: Goal setting and strategy
        - Execution Agents: Action taking and output generation
        - Meta-Cognitive Agent: Coordinates other agents
    
    Use Cases:
        - Complex cognitive modeling
        - General AI systems
        - Multi-faceted problem-solving
        - Comprehensive decision-making systems
    
    LangChain Implementation:
        Uses specialized LLM agents for different cognitive functions, implements
        inter-agent communication protocols, maintains shared mental state, and
        coordinates through a meta-cognitive controller.
    
    Key Features:
        - Specialized cognitive sub-agents
        - Integrated cognitive architecture
        - Emergent intelligent behavior
        - Meta-cognitive coordination
    """
    
    import os
    from typing import List, Dict, Any, Optional
    from dataclasses import dataclass, field
    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 CognitiveFunction(Enum):
        """Types of cognitive functions."""
        PERCEPTION = "perception"
        MEMORY = "memory"
        REASONING = "reasoning"
        PLANNING = "planning"
        EXECUTION = "execution"
        EMOTION = "emotion"
    
    
    @dataclass
    class MentalState:
        """Shared mental state of the society."""
        current_input: Optional[str] = None
        perceived_situation: Optional[str] = None
        relevant_memories: List[str] = field(default_factory=list)
        reasoning_output: Optional[str] = None
        plan: Optional[str] = None
        emotional_state: Optional[str] = None
        final_output: Optional[str] = None
    
    
    @dataclass
    class CognitiveOutput:
        """Output from a cognitive agent."""
        function: CognitiveFunction
        agent_id: str
        output: str
        confidence: float
    
    
    class CognitiveAgent:
        """
        A specialized cognitive agent handling one aspect of cognition.
        """
        
        def __init__(
            self,
            agent_id: str,
            function: CognitiveFunction,
            description: str,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize a cognitive agent.
            
            Args:
                agent_id: Unique identifier
                function: Cognitive function this agent handles
                description: Description of agent's role
                model: LLM model to use
            """
            self.agent_id = agent_id
            self.function = function
            self.description = description
            self.llm = ChatOpenAI(model=model, temperature=0.6)
        
        def get_system_prompt(self) -> str:
            """Get the system prompt for this cognitive agent."""
            prompts = {
                CognitiveFunction.PERCEPTION: f"""You are the Perception Agent.
    Role: {self.description}
    You interpret inputs, identify key elements, and understand the situation.
    Provide clear, objective perception of what you observe.""",
                
                CognitiveFunction.MEMORY: f"""You are the Memory Agent.
    Role: {self.description}
    You recall relevant past experiences, facts, and patterns.
    Provide relevant memories that can inform current processing.""",
                
                CognitiveFunction.REASONING: f"""You are the Reasoning Agent.
    Role: {self.description}
    You analyze information logically, identify patterns, and draw conclusions.
    Provide sound logical reasoning and analysis.""",
                
                CognitiveFunction.PLANNING: f"""You are the Planning Agent.
    Role: {self.description}
    You create strategies, set goals, and plan actions.
    Provide clear, actionable plans.""",
                
                CognitiveFunction.EXECUTION: f"""You are the Execution Agent.
    Role: {self.description}
    You take plans and create concrete outputs and actions.
    Provide clear, actionable outputs.""",
                
                CognitiveFunction.EMOTION: f"""You are the Emotion Agent.
    Role: {self.description}
    You assess emotional context and implications.
    Provide emotional intelligence and empathy."""
            }
            return prompts[self.function]
        
        def process(
            self,
            mental_state: MentalState,
            context: Optional[str] = None
        ) -> CognitiveOutput:
            """
            Process information according to cognitive function.
            
            Args:
                mental_state: Current mental state
                context: Optional additional context
                
            Returns:
                Cognitive output
            """
            # Build context based on what's available in mental state
            state_context = self._build_context(mental_state)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", self.get_system_prompt()),
                ("user", """Current Mental State:
    {state_context}
    
    {additional_context}
    
    Perform your cognitive function and provide output (2-3 sentences):""")
            ])
            
            additional = f"Additional Context: {context}" if context else ""
            
            chain = prompt | self.llm | StrOutputParser()
            
            output = chain.invoke({
                "state_context": state_context,
                "additional_context": additional
            })
            
            return CognitiveOutput(
                function=self.function,
                agent_id=self.agent_id,
                output=output.strip(),
                confidence=0.8  # Simplified confidence
            )
        
        def _build_context(self, mental_state: MentalState) -> str:
            """Build context string from mental state."""
            parts = []
            
            if mental_state.current_input:
                parts.append(f"Input: {mental_state.current_input}")
            if mental_state.perceived_situation:
                parts.append(f"Perception: {mental_state.perceived_situation}")
            if mental_state.relevant_memories:
                parts.append(f"Memories: {'; '.join(mental_state.relevant_memories[:3])}")
            if mental_state.reasoning_output:
                parts.append(f"Reasoning: {mental_state.reasoning_output}")
            if mental_state.plan:
                parts.append(f"Plan: {mental_state.plan}")
            if mental_state.emotional_state:
                parts.append(f"Emotional Context: {mental_state.emotional_state}")
            
            return "\n".join(parts) if parts else "No information yet"
    
    
    class MetaCognitiveController:
        """
        Meta-cognitive controller that coordinates cognitive agents.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """Initialize meta-cognitive controller."""
            self.llm = ChatOpenAI(model=model, temperature=0.5)
        
        def decide_next_step(
            self,
            mental_state: MentalState,
            completed_functions: List[CognitiveFunction]
        ) -> Optional[CognitiveFunction]:
            """
            Decide which cognitive function to invoke next.
            
            Args:
                mental_state: Current mental state
                completed_functions: Functions already completed
                
            Returns:
                Next cognitive function to invoke, or None if done
            """
            # Standard cognitive pipeline
            pipeline = [
                CognitiveFunction.PERCEPTION,
                CognitiveFunction.MEMORY,
                CognitiveFunction.EMOTION,
                CognitiveFunction.REASONING,
                CognitiveFunction.PLANNING,
                CognitiveFunction.EXECUTION
            ]
            
            for function in pipeline:
                if function not in completed_functions:
                    return function
            
            return None  # All done
        
        def integrate_outputs(
            self,
            mental_state: MentalState,
            outputs: List[CognitiveOutput]
        ) -> str:
            """
            Integrate outputs from all cognitive agents.
            
            Args:
                mental_state: Final mental state
                outputs: All cognitive outputs
                
            Returns:
                Integrated final response
            """
            outputs_text = "\n\n".join([
                f"{out.function.value.upper()}:\n{out.output}"
                for out in outputs
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are the Meta-Cognitive Integrator.
    You synthesize outputs from specialized cognitive agents into a coherent response."""),
                ("user", """Original Input: {original_input}
    
    Cognitive Processing Results:
    {outputs_text}
    
    Synthesize these cognitive outputs into a unified, coherent final response that:
    1. Addresses the original input completely
    2. Integrates insights from all cognitive functions
    3. Is clear and actionable
    4. Shows balanced consideration of all aspects""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            integration = chain.invoke({
                "original_input": mental_state.current_input,
                "outputs_text": outputs_text
            })
            
            return integration.strip()
    
    
    class SocietyOfMind:
        """
        Implements the Society of Mind cognitive architecture.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """Initialize the society of mind."""
            self.model = model
            self.cognitive_agents: Dict[CognitiveFunction, CognitiveAgent] = {}
            self.meta_controller = MetaCognitiveController(model)
            self.mental_state = MentalState()
            
            # Initialize cognitive agents
            self._initialize_agents()
        
        def _initialize_agents(self):
            """Initialize all cognitive agents."""
            agents_config = {
                CognitiveFunction.PERCEPTION: ("Perceptor", "Interprets and understands inputs"),
                CognitiveFunction.MEMORY: ("Memorist", "Recalls relevant past information"),
                CognitiveFunction.REASONING: ("Reasoner", "Analyzes and draws conclusions"),
                CognitiveFunction.PLANNING: ("Planner", "Creates strategies and plans"),
                CognitiveFunction.EXECUTION: ("Executor", "Generates concrete outputs"),
                CognitiveFunction.EMOTION: ("Empath", "Assesses emotional context")
            }
            
            for function, (agent_id, description) in agents_config.items():
                self.cognitive_agents[function] = CognitiveAgent(
                    agent_id,
                    function,
                    description,
                    self.model
                )
        
        def process(self, input_text: str) -> Dict[str, Any]:
            """
            Process input through the entire society of mind.
            
            Args:
                input_text: Input to process
                
            Returns:
                Processing results with all cognitive outputs
            """
            print(f"\n[Society of Mind] Processing: {input_text}\n")
            
            # Initialize mental state
            self.mental_state = MentalState(current_input=input_text)
            
            completed_functions: List[CognitiveFunction] = []
            cognitive_outputs: List[CognitiveOutput] = []
            
            # Cognitive processing pipeline
            while True:
                # Meta-controller decides next step
                next_function = self.meta_controller.decide_next_step(
                    self.mental_state,
                    completed_functions
                )
                
                if next_function is None:
                    break  # Processing complete
                
                # Invoke cognitive agent
                agent = self.cognitive_agents[next_function]
                
                print(f"[{agent.agent_id}] Processing ({next_function.value})...")
                
                output = agent.process(self.mental_state)
                cognitive_outputs.append(output)
                completed_functions.append(next_function)
                
                print(f"[{agent.agent_id}] Output: {output.output}\n")
                
                # Update mental state
                self._update_mental_state(output)
            
            # Integrate all outputs
            print("[Meta-Controller] Integrating cognitive outputs...\n")
            
            final_output = self.meta_controller.integrate_outputs(
                self.mental_state,
                cognitive_outputs
            )
            
            self.mental_state.final_output = final_output
            
            return {
                "input": input_text,
                "cognitive_outputs": cognitive_outputs,
                "mental_state": self.mental_state,
                "final_output": final_output
            }
        
        def _update_mental_state(self, output: CognitiveOutput):
            """Update mental state based on cognitive output."""
            if output.function == CognitiveFunction.PERCEPTION:
                self.mental_state.perceived_situation = output.output
            elif output.function == CognitiveFunction.MEMORY:
                self.mental_state.relevant_memories = [output.output]
            elif output.function == CognitiveFunction.REASONING:
                self.mental_state.reasoning_output = output.output
            elif output.function == CognitiveFunction.PLANNING:
                self.mental_state.plan = output.output
            elif output.function == CognitiveFunction.EMOTION:
                self.mental_state.emotional_state = output.output
        
        def get_architecture_summary(self) -> Dict[str, Any]:
            """Get summary of the cognitive architecture."""
            return {
                "cognitive_agents": [
                    {
                        "id": agent.agent_id,
                        "function": agent.function.value,
                        "description": agent.description
                    }
                    for agent in self.cognitive_agents.values()
                ],
                "processing_pipeline": [f.value for f in CognitiveFunction]
            }
    
    
    def demonstrate_society_of_mind():
        """Demonstrate the Society of Mind pattern."""
        
        print("=" * 80)
        print("SOCIETY OF MIND PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Initialize the society
        society = SocietyOfMind()
        
        # Show architecture
        print("\n" + "=" * 80)
        print("COGNITIVE ARCHITECTURE")
        print("=" * 80)
        
        arch = society.get_architecture_summary()
        print("\nCognitive Agents:")
        for agent in arch["cognitive_agents"]:
            print(f"  - {agent['id']} ({agent['function']}): {agent['description']}")
        
        # Test 1: Decision-making scenario
        print("\n" + "=" * 80)
        print("TEST 1: Complex Decision-Making")
        print("=" * 80)
        
        input1 = "I'm considering changing careers from engineering to product management. What should I consider?"
        
        result1 = society.process(input1)
        
        print("=" * 80)
        print("COGNITIVE PROCESSING BREAKDOWN:")
        print("=" * 80)
        for output in result1["cognitive_outputs"]:
            print(f"\n{output.function.value.upper()}:")
            print(f"  {output.output}")
        
        print("\n" + "=" * 80)
        print("FINAL INTEGRATED RESPONSE:")
        print("=" * 80)
        print(result1["final_output"])
        
        # Test 2: Creative problem-solving
        print("\n" + "=" * 80)
        print("TEST 2: Creative Problem-Solving")
        print("=" * 80)
        
        input2 = "How can I make studying more engaging and effective for students who find it boring?"
        
        result2 = society.process(input2)
        
        print("=" * 80)
        print("FINAL INTEGRATED RESPONSE:")
        print("=" * 80)
        print(result2["final_output"])
        
        print("\n" + "-" * 80)
        print("MENTAL STATE SNAPSHOT:")
        print("-" * 80)
        state = result2["mental_state"]
        print(f"Perception: {state.perceived_situation[:80]}...")
        print(f"Emotion: {state.emotional_state[:80]}...")
        print(f"Plan: {state.plan[:80]}...")
        
        # Test 3: Interpersonal situation
        print("\n" + "=" * 80)
        print("TEST 3: Interpersonal Situation")
        print("=" * 80)
        
        input3 = "My teammate is often late to meetings and it's affecting team morale. How should I address this?"
        
        result3 = society.process(input3)
        
        print("=" * 80)
        print("COGNITIVE PROCESSING BREAKDOWN:")
        print("=" * 80)
        for output in result3["cognitive_outputs"]:
            print(f"\n{output.function.value.upper()}:")
            print(f"  {output.output}")
        
        print("\n" + "=" * 80)
        print("FINAL INTEGRATED RESPONSE:")
        print("=" * 80)
        print(result3["final_output"])
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Society of Mind pattern demonstrates several key benefits:
    
    1. **Cognitive Specialization**: Each agent handles one aspect of cognition
    2. **Emergent Intelligence**: Complex behavior from simple component interactions
    3. **Comprehensive Processing**: All aspects of problem considered
    4. **Integrated Output**: Coherent response from diverse cognitive functions
    
    Cognitive Architecture:
    - **Perception**: Interprets and understands inputs
    - **Memory**: Recalls relevant past information
    - **Emotion**: Assesses emotional context and empathy
    - **Reasoning**: Analyzes logically and draws conclusions
    - **Planning**: Creates strategies and action plans
    - **Execution**: Generates concrete outputs
    
    Processing Flow:
    1. Input received and stored in mental state
    2. Each cognitive agent processes in sequence
    3. Mental state updated after each agent
    4. Meta-controller integrates all outputs
    5. Final coherent response generated
    
    Use Cases:
    - Complex decision-making systems
    - General AI architectures
    - Multi-faceted problem-solving
    - Cognitive modeling and simulation
    - Comprehensive analysis tasks
    
    The pattern is particularly effective when:
    - Problems require multiple perspectives
    - Cognitive diversity adds value
    - Comprehensive analysis is needed
    - Emotional intelligence matters
    - Integration of multiple factors is important
    
    This architecture models how human intelligence emerges from the interaction
    of many specialized mental processes, creating a more human-like AI system.
    """)
    
    
    if __name__ == "__main__":
        demonstrate_society_of_mind()
    
        

    Agentic Design Pattern: Society Of Mind

    Pattern 019: Society Of Mind – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Society Of Mind pattern implementation.


    Pattern Description

    Pattern 019: Society of Mind

    Description: The Society of Mind pattern implements a cognitive architecture where multiple specialized sub-agents (each handling a specific aspect of cognition) work together to form a coherent intelligent system. Inspired by Marvin Minsky’s theory, this pattern views intelligence as emerging from the interaction of many simple processes.

    Components: – Perception Agents: Process and interpret inputs – Memory Agents: Store and retrieve information – Reasoning Agents: Logical thinking and analysis – Planning Agents: Goal setting and strategy – Execution Agents: Action taking and output generation – Meta-Cognitive Agent: Coordinates other agents

    Use Cases: – Complex cognitive modeling – General AI systems – Multi-faceted problem-solving – Comprehensive decision-making systems

    LangChain Implementation: Uses specialized LLM agents for different cognitive functions, implements inter-agent communication protocols, maintains shared mental state, and coordinates through a meta-cognitive controller.

    Key Features: – Specialized cognitive sub-agents – Integrated cognitive architecture – Emergent intelligent behavior – Meta-cognitive coordination


    File Statistics

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

    Module Documentation

    """
    Pattern 019: Society of Mind
    
    Description:
        The Society of Mind pattern implements a cognitive architecture where multiple
        specialized sub-agents (each handling a specific aspect of cognition) work together
        to form a coherent intelligent system. Inspired by Marvin Minsky's theory, this
        pattern views intelligence as emerging from the interaction of many simple processes.
    
    Components:
        - Perception Agents: Process and interpret inputs
        - Memory Agents: Store and retrieve information
        - Reasoning Agents: Logical thinking and analysis
        - Planning Agents: Goal setting and strategy
        - Execution Agents: Action taking and output generation
        - Meta-Cognitive Agent: Coordinates other agents
    
    Use Cases:
        - Complex cognitive modeling
        - General AI systems
        - Multi-faceted problem-solving
        - Comprehensive decision-making systems
    
    LangChain Implementation:
        Uses specialized LLM agents for different cognitive functions, implements
        inter-agent communication protocols, maintains shared mental state, and
        coordinates through a meta-cognitive controller.
    
    Key Features:
        - Specialized cognitive sub-agents
        - Integrated cognitive architecture
        - Emergent intelligent behavior
        - Meta-cognitive coordination
    """
    

    Purpose: This module implements the Society Of Mind 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 typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    

    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 – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)

    LangChain Imports

    from langchain_openai.ChatOpenAI
    

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

    Other Imports

    from dotenv.load_dotenv
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: CognitiveFunction

    Lines 49-56

    Inherits from: Enum

    Purpose:

    Types of cognitive functions.
    

    Class: MentalState

    Lines 60-68

    Purpose:

    Shared mental state of the society.
    

    Class: CognitiveOutput

    Lines 72-77

    Purpose:

    Output from a cognitive agent.
    

    Class: CognitiveAgent

    Lines 80-202

    Purpose:

    A specialized cognitive agent handling one aspect of cognition.
    

    Methods:

    __init__(self, agent_id, function, description, model)

    Lines 85-104

    Purpose:

    Initialize a cognitive agent.
    
    Args:
        agent_id: Unique identifier
        function: Cognitive function this agent handles
        description: Description of agent's role
        model: LLM model to use
    

    Parameters: – agent_id: Agent id – function: Function – description: Description – model: Language model instance

    get_system_prompt(self)

    Lines 106-139

    Purpose:

    Get the system prompt for this cognitive agent.
    

    Parameters:

    Returns: str

    process(self, mental_state, context)

    Lines 141-183

    Purpose:

    Process information according to cognitive function.
    
    Args:
        mental_state: Current mental state
        context: Optional additional context
    
    Returns:
        Cognitive output
    

    Parameters: – mental_state: Mental state – context: Context

    Returns: CognitiveOutput

    _build_context(self, mental_state)

    Lines 185-202

    Purpose:

    Build context string from mental state.
    

    Parameters: – mental_state: Mental state

    Returns: str


    Class: MetaCognitiveController

    Lines 205-287

    Purpose:

    Meta-cognitive controller that coordinates cognitive agents.
    

    Methods:

    __init__(self, model)

    Lines 210-212

    Purpose:

    Initialize meta-cognitive controller.
    

    Parameters: – model: Language model instance

    decide_next_step(self, mental_state, completed_functions)

    Lines 214-243

    Purpose:

    Decide which cognitive function to invoke next.
    
    Args:
        mental_state: Current mental state
        completed_functions: Functions already completed
    
    Returns:
        Next cognitive function to invoke, or None if done
    

    Parameters: – mental_state: Mental state – completed_functions: Completed functions

    Returns: <ast.Subscript object at 0x000001A06D71B610>

    integrate_outputs(self, mental_state, outputs)

    Lines 245-287

    Purpose:

    Integrate outputs from all cognitive agents.
    
    Args:
        mental_state: Final mental state
        outputs: All cognitive outputs
    
    Returns:
        Integrated final response
    

    Parameters: – mental_state: Mental state – outputs: Outputs

    Returns: str


    Class: SocietyOfMind

    Lines 290-409

    Purpose:

    Implements the Society of Mind cognitive architecture.
    

    Methods:

    __init__(self, model)

    Lines 295-303

    Purpose:

    Initialize the society of mind.
    

    Parameters: – model: Language model instance

    _initialize_agents(self)

    Lines 305-322

    Purpose:

    Initialize all cognitive agents.
    

    Parameters:

    process(self, input_text)

    Lines 324-382

    Purpose:

    Process input through the entire society of mind.
    
    Args:
        input_text: Input to process
    
    Returns:
        Processing results with all cognitive outputs
    

    Parameters: – input_text: Input data

    Returns: <ast.Subscript object at 0x000001A06D6597D0>

    _update_mental_state(self, output)

    Lines 384-395

    Purpose:

    Update mental state based on cognitive output.
    

    Parameters: – output: Output

    get_architecture_summary(self)

    Lines 397-409

    Purpose:

    Get summary of the cognitive architecture.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D158E90>


    Functions

    demonstrate_society_of_mind()

    Lines 412-539

    Purpose:

    Demonstrate the Society of Mind pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Society Of Mind 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 018
    • Pattern 020
    • Other Multi-Agent Patterns (012-019)

    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 Society Of Mind 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: 019_society_of_mind.py
    Total Lines: 544
    Pattern Category: AI Agent Design Pattern #019


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Cooperative Multi Agent

    
        """
    Pattern 018: Cooperative Multi-Agent
    
    Description:
        The Cooperative Multi-Agent pattern features agents working together toward shared
        goals through collaboration, communication, and coordination. Agents share information,
        coordinate actions, and help each other to achieve collective objectives more
        effectively than they could individually.
    
    Components:
        - Cooperative Agents: Agents designed to collaborate
        - Shared Goal System: Common objectives all agents work toward
        - Communication Protocol: Message passing and information sharing
        - Coordination Mechanism: Synchronizing agent actions
        - Resource Sharing: Pooling knowledge and capabilities
    
    Use Cases:
        - Complex collaborative tasks
        - Distributed problem-solving
        - Team-based projects
        - Multi-perspective analysis
    
    LangChain Implementation:
        Uses multiple LLM agents with shared context, implements message passing
        between agents, maintains shared knowledge base, and coordinates actions
        through explicit communication protocols.
    
    Key Features:
        - Shared goal alignment
        - Inter-agent communication
        - Knowledge sharing
        - Coordinated action planning
    """
    
    import os
    from typing import List, Dict, Any, Optional
    from dataclasses import dataclass, field
    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 MessageType(Enum):
        """Types of messages agents can exchange."""
        INFORMATION = "information"
        REQUEST = "request"
        OFFER = "offer"
        COORDINATION = "coordination"
        RESULT = "result"
    
    
    @dataclass
    class Message:
        """Message exchanged between agents."""
        from_agent: str
        to_agent: Optional[str]  # None means broadcast to all
        message_type: MessageType
        content: str
        context: Optional[Dict[str, Any]] = None
    
    
    @dataclass
    class SharedKnowledge:
        """Knowledge base shared among cooperative agents."""
        facts: List[str] = field(default_factory=list)
        insights: List[str] = field(default_factory=list)
        decisions: List[str] = field(default_factory=list)
    
    
    @dataclass
    class CooperativeTask:
        """Task for cooperative agents to work on."""
        task_id: str
        description: str
        assigned_agents: List[str]
        result: Optional[str] = None
        completed: bool = False
    
    
    class CooperativeAgent:
        """
        An agent designed for cooperation and collaboration.
        """
        
        def __init__(
            self,
            agent_id: str,
            role: str,
            expertise: str,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize a cooperative agent.
            
            Args:
                agent_id: Unique identifier
                role: Agent's role in the team
                expertise: Agent's area of expertise
                model: LLM model to use
            """
            self.agent_id = agent_id
            self.role = role
            self.expertise = expertise
            self.llm = ChatOpenAI(model=model, temperature=0.6)
            
            # Collaboration state
            self.inbox: List[Message] = []
            self.outbox: List[Message] = []
            self.knowledge: List[str] = []
        
        def get_persona(self) -> str:
            """Get agent's cooperative persona."""
            return f"""You are {self.agent_id}, a cooperative team member.
    Role: {self.role}
    Expertise: {self.expertise}
    
    You are collaborative, supportive, and focused on team success.
    You actively share information, offer help, and coordinate with teammates."""
        
        def analyze_task(
            self,
            task: CooperativeTask,
            shared_knowledge: SharedKnowledge
        ) -> Dict[str, Any]:
            """
            Analyze a task from the agent's perspective.
            
            Args:
                task: Task to analyze
                shared_knowledge: Shared team knowledge
                
            Returns:
                Analysis with contributions and needs
            """
            knowledge_context = "\n".join([
                f"- {fact}" for fact in shared_knowledge.facts[:5]
            ]) if shared_knowledge.facts else "No shared knowledge yet"
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", self.get_persona()),
                ("user", """Task: {task_description}
    
    Team's shared knowledge:
    {knowledge_context}
    
    Analyze this task and provide:
    1. What you can contribute (based on your expertise)
    2. What help you need from teammates
    3. Key considerations
    
    Be specific and collaborative.""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            analysis = chain.invoke({
                "task_description": task.description,
                "knowledge_context": knowledge_context
            })
            
            return {"analysis": analysis.strip()}
        
        def contribute_to_task(
            self,
            task: CooperativeTask,
            team_messages: List[Message],
            shared_knowledge: SharedKnowledge
        ) -> str:
            """
            Contribute to solving the task.
            
            Args:
                task: Task to contribute to
                team_messages: Recent messages from team
                shared_knowledge: Shared knowledge base
                
            Returns:
                Agent's contribution
            """
            # Build context from team communication
            recent_messages = [
                f"{msg.from_agent} ({msg.message_type.value}): {msg.content}"
                for msg in team_messages[-5:]  # Last 5 messages
            ]
            message_context = "\n".join(recent_messages) if recent_messages else "No recent messages"
            
            knowledge_context = "\n".join([
                f"- {item}" for item in shared_knowledge.facts[:5]
            ]) if shared_knowledge.facts else "None yet"
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", self.get_persona()),
                ("user", """Task: {task_description}
    
    Recent team communication:
    {message_context}
    
    Shared knowledge:
    {knowledge_context}
    
    Provide your contribution to solving this task.
    Build upon teammates' input and share your expertise.
    Be specific and actionable (2-3 sentences).""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            contribution = chain.invoke({
                "task_description": task.description,
                "message_context": message_context,
                "knowledge_context": knowledge_context
            })
            
            return contribution.strip()
        
        def send_message(
            self,
            to_agent: Optional[str],
            message_type: MessageType,
            content: str
        ) -> Message:
            """
            Send a message to another agent or broadcast.
            
            Args:
                to_agent: Recipient agent ID (None for broadcast)
                message_type: Type of message
                content: Message content
                
            Returns:
                Created message
            """
            message = Message(
                from_agent=self.agent_id,
                to_agent=to_agent,
                message_type=message_type,
                content=content
            )
            self.outbox.append(message)
            return message
        
        def receive_message(self, message: Message):
            """Receive a message from another agent."""
            self.inbox.append(message)
    
    
    class CooperativeTeam:
        """
        Manages a team of cooperative agents.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """Initialize cooperative team."""
            self.model = model
            self.agents: Dict[str, CooperativeAgent] = {}
            self.shared_knowledge = SharedKnowledge()
            self.message_log: List[Message] = []
            self.completed_tasks: List[CooperativeTask] = []
        
        def add_agent(
            self,
            agent_id: str,
            role: str,
            expertise: str
        ):
            """Add an agent to the team."""
            agent = CooperativeAgent(agent_id, role, expertise, self.model)
            self.agents[agent_id] = agent
        
        def broadcast_message(self, message: Message):
            """Broadcast a message to all agents."""
            for agent in self.agents.values():
                if agent.agent_id != message.from_agent:
                    agent.receive_message(message)
            self.message_log.append(message)
        
        def direct_message(self, message: Message):
            """Send a message to a specific agent."""
            if message.to_agent and message.to_agent in self.agents:
                self.agents[message.to_agent].receive_message(message)
            self.message_log.append(message)
        
        def share_knowledge(self, knowledge_item: str, category: str = "facts"):
            """Add knowledge to shared knowledge base."""
            if category == "facts":
                self.shared_knowledge.facts.append(knowledge_item)
            elif category == "insights":
                self.shared_knowledge.insights.append(knowledge_item)
            elif category == "decisions":
                self.shared_knowledge.decisions.append(knowledge_item)
        
        def collaborate_on_task(
            self,
            task_description: str,
            assigned_agents: Optional[List[str]] = None
        ) -> Dict[str, Any]:
            """
            Agents collaborate to complete a task.
            
            Args:
                task_description: Description of the task
                assigned_agents: Agents to assign (None = all agents)
                
            Returns:
                Collaboration results
            """
            if assigned_agents is None:
                assigned_agents = list(self.agents.keys())
            
            task = CooperativeTask(
                task_id=f"task_{len(self.completed_tasks) + 1}",
                description=task_description,
                assigned_agents=assigned_agents
            )
            
            print(f"\n[Team] Starting collaborative task: {task_description}")
            print(f"[Team] Assigned: {', '.join(assigned_agents)}\n")
            
            # Phase 1: Individual analysis
            print("=" * 80)
            print("PHASE 1: Individual Analysis")
            print("=" * 80 + "\n")
            
            analyses = {}
            for agent_id in assigned_agents:
                agent = self.agents[agent_id]
                analysis = agent.analyze_task(task, self.shared_knowledge)
                analyses[agent_id] = analysis
                
                print(f"[{agent_id}] Analysis:")
                print(f"  {analysis['analysis'][:150]}...\n")
                
                # Share analysis with team
                msg = agent.send_message(
                    None,  # Broadcast
                    MessageType.INFORMATION,
                    f"My analysis: {analysis['analysis']}"
                )
                self.broadcast_message(msg)
            
            # Phase 2: Coordination and planning
            print("=" * 80)
            print("PHASE 2: Coordination & Planning")
            print("=" * 80 + "\n")
            
            # First agent initiates coordination
            coordinator_id = assigned_agents[0]
            coordinator = self.agents[coordinator_id]
            
            coord_msg = coordinator.send_message(
                None,
                MessageType.COORDINATION,
                f"Let's coordinate our efforts. I'll focus on {coordinator.expertise}. Who can handle other aspects?"
            )
            self.broadcast_message(coord_msg)
            print(f"[{coordinator_id}] Coordination: {coord_msg.content}\n")
            
            # Other agents respond with offers
            for agent_id in assigned_agents[1:]:
                agent = self.agents[agent_id]
                offer_msg = agent.send_message(
                    coordinator_id,
                    MessageType.OFFER,
                    f"I can contribute my {agent.expertise} expertise"
                )
                self.direct_message(offer_msg)
                print(f"[{agent_id}] Offer: {offer_msg.content}\n")
            
            # Phase 3: Collaborative execution
            print("=" * 80)
            print("PHASE 3: Collaborative Execution")
            print("=" * 80 + "\n")
            
            contributions = {}
            for agent_id in assigned_agents:
                agent = self.agents[agent_id]
                
                contribution = agent.contribute_to_task(
                    task,
                    self.message_log,
                    self.shared_knowledge
                )
                contributions[agent_id] = contribution
                
                print(f"[{agent_id}] Contribution:")
                print(f"  {contribution}\n")
                
                # Share contribution
                result_msg = agent.send_message(
                    None,
                    MessageType.RESULT,
                    contribution
                )
                self.broadcast_message(result_msg)
                
                # Add to shared knowledge
                self.share_knowledge(
                    f"{agent_id}: {contribution}",
                    "insights"
                )
            
            # Phase 4: Synthesis
            print("=" * 80)
            print("PHASE 4: Synthesis")
            print("=" * 80 + "\n")
            
            final_result = self._synthesize_contributions(
                task_description,
                contributions
            )
            
            task.result = final_result
            task.completed = True
            self.completed_tasks.append(task)
            
            print(f"[Team] Task completed successfully!\n")
            
            return {
                "task": task,
                "individual_analyses": analyses,
                "contributions": contributions,
                "final_result": final_result,
                "messages_exchanged": len(self.message_log),
                "shared_knowledge_items": len(self.shared_knowledge.facts) + len(self.shared_knowledge.insights)
            }
        
        def _synthesize_contributions(
            self,
            task_description: str,
            contributions: Dict[str, str]
        ) -> str:
            """Synthesize individual contributions into final result."""
            # Use a synthesis agent
            synthesizer = ChatOpenAI(model=self.model, temperature=0.5)
            
            contributions_text = "\n\n".join([
                f"{agent_id} ({self.agents[agent_id].expertise}):\n{contrib}"
                for agent_id, contrib in contributions.items()
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", "You are synthesizing collaborative team outputs into a cohesive result."),
                ("user", """Task: {task_description}
    
    Team Contributions:
    {contributions_text}
    
    Synthesize these contributions into a unified, coherent solution that:
    1. Integrates all team members' expertise
    2. Addresses the task completely
    3. Highlights collaborative synergies
    4. Provides actionable results""")
            ])
            
            chain = prompt | synthesizer | StrOutputParser()
            
            synthesis = chain.invoke({
                "task_description": task_description,
                "contributions_text": contributions_text
            })
            
            return synthesis.strip()
        
        def get_team_summary(self) -> Dict[str, Any]:
            """Get summary of team composition and performance."""
            return {
                "team_size": len(self.agents),
                "agents": [
                    {
                        "id": agent.agent_id,
                        "role": agent.role,
                        "expertise": agent.expertise
                    }
                    for agent in self.agents.values()
                ],
                "tasks_completed": len(self.completed_tasks),
                "messages_exchanged": len(self.message_log),
                "shared_knowledge_items": len(self.shared_knowledge.facts) + len(self.shared_knowledge.insights)
            }
    
    
    def demonstrate_cooperative_multi_agent():
        """Demonstrate the Cooperative Multi-Agent pattern."""
        
        print("=" * 80)
        print("COOPERATIVE MULTI-AGENT PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Test 1: Product development team
        print("\n" + "=" * 80)
        print("TEST 1: Product Development Team")
        print("=" * 80)
        
        team1 = CooperativeTeam()
        
        # Build diverse team
        team1.add_agent("Alice", "Product Manager", "Product Strategy")
        team1.add_agent("Bob", "Engineer", "Technical Implementation")
        team1.add_agent("Carol", "Designer", "User Experience")
        team1.add_agent("Dave", "Analyst", "Data Analysis")
        
        print("\nTeam Composition:")
        for agent_id, agent in team1.agents.items():
            print(f"  - {agent_id}: {agent.role} ({agent.expertise})")
        
        task1 = "Design and plan a new feature that helps users track their productivity goals"
        
        result1 = team1.collaborate_on_task(task1)
        
        print("=" * 80)
        print("FINAL RESULT:")
        print("=" * 80)
        print(result1["final_result"])
        
        print("\n" + "-" * 80)
        print("COLLABORATION METRICS:")
        print("-" * 80)
        print(f"  Messages exchanged: {result1['messages_exchanged']}")
        print(f"  Knowledge items shared: {result1['shared_knowledge_items']}")
        print(f"  Agents participated: {len(result1['contributions'])}")
        
        # Test 2: Research team
        print("\n" + "=" * 80)
        print("TEST 2: Research Team Collaboration")
        print("=" * 80)
        
        team2 = CooperativeTeam()
        
        team2.add_agent("ResearcherA", "Data Scientist", "Machine Learning")
        team2.add_agent("ResearcherB", "Domain Expert", "Healthcare")
        team2.add_agent("ResearcherC", "Statistician", "Statistical Analysis")
        
        print("\nTeam Composition:")
        for agent_id, agent in team2.agents.items():
            print(f"  - {agent_id}: {agent.role} ({agent.expertise})")
        
        task2 = "Develop a methodology to predict patient readmission risk using ML"
        
        result2 = team2.collaborate_on_task(task2)
        
        print("=" * 80)
        print("FINAL RESULT:")
        print("=" * 80)
        print(result2["final_result"])
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Cooperative Multi-Agent pattern demonstrates several key benefits:
    
    1. **Synergy**: Combined expertise exceeds individual capabilities
    2. **Knowledge Sharing**: Agents build on each other's contributions
    3. **Coordinated Action**: Agents work together toward shared goals
    4. **Communication**: Rich information exchange improves outcomes
    
    Collaboration Phases:
    1. **Individual Analysis**: Each agent analyzes from their perspective
    2. **Coordination**: Agents plan how to work together
    3. **Execution**: Agents contribute their expertise
    4. **Synthesis**: Contributions integrated into final result
    
    Communication Types:
    - **Information**: Sharing facts and knowledge
    - **Request**: Asking for help or information
    - **Offer**: Offering assistance or expertise
    - **Coordination**: Planning joint action
    - **Result**: Sharing completed work
    
    Use Cases:
    - Complex multi-disciplinary projects
    - Team-based problem-solving
    - Distributed collaborative work
    - Multi-perspective analysis
    - Cross-functional initiatives
    
    The pattern is particularly effective when:
    - Tasks require diverse expertise
    - Collaboration adds value over individual work
    - Communication and coordination are feasible
    - Shared goals align team members
    - Synergies between specialties exist
    """)
    
    
    if __name__ == "__main__":
        demonstrate_cooperative_multi_agent()
    
        

    Agentic Design Pattern: Cooperative Multi Agent

    Pattern 018: Cooperative Multi Agent – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 018: Cooperative Multi-Agent

    Description: The Cooperative Multi-Agent pattern features agents working together toward shared goals through collaboration, communication, and coordination. Agents share information, coordinate actions, and help each other to achieve collective objectives more effectively than they could individually.

    Components: – Cooperative Agents: Agents designed to collaborate – Shared Goal System: Common objectives all agents work toward – Communication Protocol: Message passing and information sharing – Coordination Mechanism: Synchronizing agent actions – Resource Sharing: Pooling knowledge and capabilities

    Use Cases: – Complex collaborative tasks – Distributed problem-solving – Team-based projects – Multi-perspective analysis

    LangChain Implementation: Uses multiple LLM agents with shared context, implements message passing between agents, maintains shared knowledge base, and coordinates actions through explicit communication protocols.

    Key Features: – Shared goal alignment – Inter-agent communication – Knowledge sharing – Coordinated action planning


    File Statistics

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

    Module Documentation

    """
    Pattern 018: Cooperative Multi-Agent
    
    Description:
        The Cooperative Multi-Agent pattern features agents working together toward shared
        goals through collaboration, communication, and coordination. Agents share information,
        coordinate actions, and help each other to achieve collective objectives more
        effectively than they could individually.
    
    Components:
        - Cooperative Agents: Agents designed to collaborate
        - Shared Goal System: Common objectives all agents work toward
        - Communication Protocol: Message passing and information sharing
        - Coordination Mechanism: Synchronizing agent actions
        - Resource Sharing: Pooling knowledge and capabilities
    
    Use Cases:
        - Complex collaborative tasks
        - Distributed problem-solving
        - Team-based projects
        - Multi-perspective analysis
    
    LangChain Implementation:
        Uses multiple LLM agents with shared context, implements message passing
        between agents, maintains shared knowledge base, and coordinates actions
        through explicit communication protocols.
    
    Key Features:
        - Shared goal alignment
        - Inter-agent communication
        - Knowledge sharing
        - Coordinated action planning
    """
    

    Purpose: This module implements the Cooperative Multi Agent 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 typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    

    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 – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)

    LangChain Imports

    from langchain_openai.ChatOpenAI
    

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

    Other Imports

    from dotenv.load_dotenv
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: MessageType

    Lines 48-54

    Inherits from: Enum

    Purpose:

    Types of messages agents can exchange.
    

    Class: Message

    Lines 58-64

    Purpose:

    Message exchanged between agents.
    

    Class: SharedKnowledge

    Lines 68-72

    Purpose:

    Knowledge base shared among cooperative agents.
    

    Class: CooperativeTask

    Lines 76-82

    Purpose:

    Task for cooperative agents to work on.
    

    Class: CooperativeAgent

    Lines 85-249

    Purpose:

    An agent designed for cooperation and collaboration.
    

    Methods:

    __init__(self, agent_id, role, expertise, model)

    Lines 90-114

    Purpose:

    Initialize a cooperative agent.
    
    Args:
        agent_id: Unique identifier
        role: Agent's role in the team
        expertise: Agent's area of expertise
        model: LLM model to use
    

    Parameters: – agent_id: Agent id – role: Role – expertise: Expertise – model: Language model instance

    get_persona(self)

    Lines 116-123

    Purpose:

    Get agent's cooperative persona.
    

    Parameters:

    Returns: str

    analyze_task(self, task, shared_knowledge)

    Lines 125-166

    Purpose:

    Analyze a task from the agent's perspective.
    
    Args:
        task: Task to analyze
        shared_knowledge: Shared team knowledge
    
    Returns:
        Analysis with contributions and needs
    

    Parameters: – task: Task – shared_knowledge: Shared knowledge

    Returns: <ast.Subscript object at 0x000001A06D692550>

    contribute_to_task(self, task, team_messages, shared_knowledge)

    Lines 168-219

    Purpose:

    Contribute to solving the task.
    
    Args:
        task: Task to contribute to
        team_messages: Recent messages from team
        shared_knowledge: Shared knowledge base
    
    Returns:
        Agent's contribution
    

    Parameters: – task: Task – team_messages: Team messages – shared_knowledge: Shared knowledge

    Returns: str

    send_message(self, to_agent, message_type, content)

    Lines 221-245

    Purpose:

    Send a message to another agent or broadcast.
    
    Args:
        to_agent: Recipient agent ID (None for broadcast)
        message_type: Type of message
        content: Message content
    
    Returns:
        Created message
    

    Parameters: – to_agent: To agent – message_type: Message type – content: Content

    Returns: Message

    receive_message(self, message)

    Lines 247-249

    Purpose:

    Receive a message from another agent.
    

    Parameters: – message: Message


    Class: CooperativeTeam

    Lines 252-484

    Purpose:

    Manages a team of cooperative agents.
    

    Methods:

    __init__(self, model)

    Lines 257-263

    Purpose:

    Initialize cooperative team.
    

    Parameters: – model: Language model instance

    add_agent(self, agent_id, role, expertise)

    Lines 265-273

    Purpose:

    Add an agent to the team.
    

    Parameters: – agent_id: Agent id – role: Role – expertise: Expertise

    broadcast_message(self, message)

    Lines 275-280

    Purpose:

    Broadcast a message to all agents.
    

    Parameters: – message: Message

    direct_message(self, message)

    Lines 282-286

    Purpose:

    Send a message to a specific agent.
    

    Parameters: – message: Message

    share_knowledge(self, knowledge_item, category)

    Lines 288-295

    Purpose:

    Add knowledge to shared knowledge base.
    

    Parameters: – knowledge_item: Knowledge item – category: Category

    collaborate_on_task(self, task_description, assigned_agents)

    Lines 297-430

    Purpose:

    Agents collaborate to complete a task.
    
    Args:
        task_description: Description of the task
        assigned_agents: Agents to assign (None = all agents)
    
    Returns:
        Collaboration results
    

    Parameters: – task_description: Task description – assigned_agents: Assigned agents

    Returns: <ast.Subscript object at 0x000001A06D658050>

    _synthesize_contributions(self, task_description, contributions)

    Lines 432-467

    Purpose:

    Synthesize individual contributions into final result.
    

    Parameters: – task_description: Task description – contributions: Contributions

    Returns: str

    get_team_summary(self)

    Lines 469-484

    Purpose:

    Get summary of team composition and performance.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D609690>


    Functions

    demonstrate_cooperative_multi_agent()

    Lines 487-589

    Purpose:

    Demonstrate the Cooperative Multi-Agent pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Cooperative Multi 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 017
    • Pattern 019
    • Other Multi-Agent Patterns (012-019)

    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 Cooperative Multi 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: 018_cooperative_multi_agent.py
    Total Lines: 594
    Pattern Category: AI Agent Design Pattern #018


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Competitive Multi Agent

    
        """
    Pattern 017: Competitive Multi-Agent
    
    Description:
        The Competitive Multi-Agent pattern uses multiple agents that compete to produce
        the best solution. Agents work independently, their solutions are evaluated against
        each other, and the winning solution is selected. This competition drives quality
        improvement and innovation.
    
    Components:
        - Competitor Agents: Multiple agents with different strategies
        - Evaluation System: Judges solutions across multiple criteria
        - Competition Framework: Manages rounds and determines winners
        - Selection Mechanism: Chooses best solution(s)
    
    Use Cases:
        - Creative tasks (writing, design, ideation)
        - Optimization problems with multiple local optima
        - Generating diverse solution approaches
        - Quality improvement through competition
    
    LangChain Implementation:
        Uses multiple LLM agents with different configurations competing on tasks,
        implements multi-criteria evaluation, tournament-style competition, and
        quality-driven selection mechanisms.
    
    Key Features:
        - Tournament-style competitions
        - Multi-criteria evaluation
        - Diverse agent strategies
        - Winner selection and ranking
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from dataclasses import dataclass
    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 CompetitionMode(Enum):
        """Modes of competition."""
        TOURNAMENT = "tournament"  # Bracket-style elimination
        ROUND_ROBIN = "round_robin"  # All agents compete in every round
        SURVIVAL = "survival"  # Lowest scorers eliminated each round
    
    
    @dataclass
    class Solution:
        """Represents a solution from a competitor."""
        agent_id: str
        content: str
        round_number: int
        scores: Dict[str, float] = None  # Criterion -> Score
        total_score: float = 0.0
        rank: Optional[int] = None
    
    
    @dataclass
    class CompetitionResult:
        """Results from a competition."""
        winner: Solution
        all_solutions: List[Solution]
        rounds: int
        mode: str
    
    
    class CompetitorAgent:
        """
        An agent that competes to produce the best solution.
        """
        
        def __init__(
            self,
            agent_id: str,
            strategy: str,
            model: str = "gpt-3.5-turbo",
            temperature: float = 0.7
        ):
            """
            Initialize a competitor agent.
            
            Args:
                agent_id: Unique identifier
                strategy: Competitive strategy description
                model: LLM model to use
                temperature: Creativity level
            """
            self.agent_id = agent_id
            self.strategy = strategy
            self.llm = ChatOpenAI(model=model, temperature=temperature)
            self.wins = 0
            self.total_score = 0.0
        
        def generate_solution(
            self,
            problem: str,
            round_number: int,
            previous_best: Optional[Solution] = None
        ) -> Solution:
            """
            Generate a solution for the problem.
            
            Args:
                problem: Problem to solve
                round_number: Current round number
                previous_best: Previous best solution (for improvement)
                
            Returns:
                Generated solution
            """
            strategy_context = f"\nYour strategy: {self.strategy}"
            
            if previous_best and round_number > 1:
                improvement_context = f"\n\nPrevious best solution to beat:\n{previous_best.content}\n\nImprove upon this!"
            else:
                improvement_context = ""
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are a competitor in a solution competition.{strategy_context}
    
    Generate your BEST solution. Be creative, thorough, and competitive!"""),
                ("user", """Problem: {problem}{improvement_context}
    
    Your solution (aim to win the competition!):""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            content = chain.invoke({
                "problem": problem,
                "improvement_context": improvement_context
            })
            
            return Solution(
                agent_id=self.agent_id,
                content=content.strip(),
                round_number=round_number
            )
    
    
    class CompetitionJudge:
        """
        Evaluates and ranks solutions in competitions.
        """
        
        def __init__(
            self,
            criteria: List[str],
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize the judge.
            
            Args:
                criteria: List of evaluation criteria
                model: LLM model for judging
            """
            self.criteria = criteria
            self.llm = ChatOpenAI(model=model, temperature=0.2)
        
        def evaluate_solution(
            self,
            problem: str,
            solution: Solution
        ) -> Solution:
            """
            Evaluate a solution across all criteria.
            
            Args:
                problem: Original problem
                solution: Solution to evaluate
                
            Returns:
                Solution with scores populated
            """
            scores = {}
            
            for criterion in self.criteria:
                score = self._score_criterion(problem, solution.content, criterion)
                scores[criterion] = score
            
            solution.scores = scores
            solution.total_score = sum(scores.values()) / len(scores)
            
            return solution
        
        def _score_criterion(
            self,
            problem: str,
            solution: str,
            criterion: str
        ) -> float:
            """Score a solution on a specific criterion."""
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"You are an expert judge evaluating solutions based on: {criterion}"),
                ("user", """Problem: {problem}
    
    Solution: {solution}
    
    Rate this solution on {criterion} from 0.0 (worst) to 10.0 (best).
    Consider only this specific criterion.
    
    Provide only a number between 0.0 and 10.0:""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            try:
                score_str = chain.invoke({
                    "problem": problem,
                    "solution": solution,
                    "criterion": criterion
                })
                score = float(score_str.strip())
                return max(0.0, min(10.0, score))
            except:
                return 5.0  # Default moderate score
        
        def rank_solutions(
            self,
            solutions: List[Solution]
        ) -> List[Solution]:
            """
            Rank solutions by total score.
            
            Args:
                solutions: List of solutions to rank
                
            Returns:
                Ranked solutions (highest to lowest)
            """
            sorted_solutions = sorted(
                solutions,
                key=lambda s: s.total_score,
                reverse=True
            )
            
            for rank, solution in enumerate(sorted_solutions, 1):
                solution.rank = rank
            
            return sorted_solutions
    
    
    class CompetitiveSystem:
        """
        Manages competitive multi-agent system.
        """
        
        def __init__(
            self,
            evaluation_criteria: Optional[List[str]] = None,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize competitive system.
            
            Args:
                evaluation_criteria: Criteria for evaluating solutions
                model: LLM model for agents
            """
            if evaluation_criteria is None:
                evaluation_criteria = [
                    "Creativity and Innovation",
                    "Practicality and Feasibility",
                    "Completeness and Detail",
                    "Clarity and Communication"
                ]
            
            self.criteria = evaluation_criteria
            self.judge = CompetitionJudge(evaluation_criteria, model)
            self.agents: List[CompetitorAgent] = []
            self.model = model
        
        def add_competitor(
            self,
            agent_id: str,
            strategy: str,
            temperature: float = 0.7
        ):
            """Add a competitor to the system."""
            agent = CompetitorAgent(agent_id, strategy, self.model, temperature)
            self.agents.append(agent)
        
        def run_competition(
            self,
            problem: str,
            mode: CompetitionMode = CompetitionMode.ROUND_ROBIN,
            rounds: int = 2
        ) -> CompetitionResult:
            """
            Run a competition among agents.
            
            Args:
                problem: Problem for agents to solve
                mode: Competition mode
                rounds: Number of competition rounds
                
            Returns:
                Competition results
            """
            print(f"\n[Competition] Starting {mode.value} with {len(self.agents)} competitors")
            print(f"[Competition] {rounds} rounds, criteria: {', '.join(self.criteria)}\n")
            
            all_solutions: List[Solution] = []
            current_best: Optional[Solution] = None
            
            for round_num in range(1, rounds + 1):
                print(f"{'=' * 80}")
                print(f"ROUND {round_num}/{rounds}")
                print(f"{'=' * 80}\n")
                
                round_solutions = []
                
                # Each agent generates a solution
                for agent in self.agents:
                    print(f"[{agent.agent_id}] Generating solution (strategy: {agent.strategy})...")
                    
                    solution = agent.generate_solution(
                        problem,
                        round_num,
                        current_best
                    )
                    
                    # Evaluate solution
                    solution = self.judge.evaluate_solution(problem, solution)
                    
                    print(f"[{agent.agent_id}] Score: {solution.total_score:.2f}/10.0")
                    print(f"[{agent.agent_id}] Solution: {solution.content[:80]}...\n")
                    
                    round_solutions.append(solution)
                    all_solutions.append(solution)
                
                # Rank this round's solutions
                ranked = self.judge.rank_solutions(round_solutions)
                
                print(f"\n[Judge] Round {round_num} Rankings:")
                for solution in ranked[:3]:  # Top 3
                    print(f"  #{solution.rank} {solution.agent_id}: {solution.total_score:.2f}/10.0")
                
                # Update current best
                if ranked[0].total_score > (current_best.total_score if current_best else 0):
                    current_best = ranked[0]
                    print(f"\n[Judge] New leader: {current_best.agent_id}!")
                
                # Handle elimination for survival mode
                if mode == CompetitionMode.SURVIVAL and round_num  List[Tuple[str, int, float]]:
            """
            Get agent leaderboard.
            
            Returns:
                List of (agent_id, wins, avg_score) tuples
            """
            return sorted(
                [(a.agent_id, a.wins, a.total_score) for a in self.agents],
                key=lambda x: (x[1], x[2]),
                reverse=True
            )
    
    
    def demonstrate_competitive_multi_agent():
        """Demonstrate the Competitive Multi-Agent pattern."""
        
        print("=" * 80)
        print("COMPETITIVE MULTI-AGENT PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Test 1: Creative competition
        print("\n" + "=" * 80)
        print("TEST 1: Creative Problem-Solving Competition")
        print("=" * 80)
        
        system1 = CompetitiveSystem()
        
        # Add competitors with different strategies
        system1.add_competitor(
            "Innovator",
            "Focus on novel, cutting-edge, unconventional ideas",
            temperature=0.9
        )
        system1.add_competitor(
            "Pragmatist",
            "Focus on practical, proven, implementable solutions",
            temperature=0.5
        )
        system1.add_competitor(
            "Optimizer",
            "Focus on efficiency, cost-effectiveness, and ROI",
            temperature=0.6
        )
        system1.add_competitor(
            "User-Centric",
            "Focus on user experience, accessibility, and satisfaction",
            temperature=0.7
        )
        
        problem1 = "Design a feature to increase user engagement in a productivity app"
        
        result1 = system1.run_competition(
            problem1,
            mode=CompetitionMode.ROUND_ROBIN,
            rounds=2
        )
        
        print(f"\n{'*' * 80}")
        print("WINNER:")
        print(f"{'*' * 80}")
        print(f"Agent: {result1.winner.agent_id}")
        print(f"Score: {result1.winner.total_score:.2f}/10.0")
        print(f"Round: {result1.winner.round_number}")
        print(f"\nWinning Solution:\n{result1.winner.content}")
        
        print(f"\n{'-' * 80}")
        print("SCORE BREAKDOWN:")
        print(f"{'-' * 80}")
        for criterion, score in result1.winner.scores.items():
            print(f"  {criterion}: {score:.2f}/10.0")
        
        # Test 2: Survival mode competition
        print("\n" + "=" * 80)
        print("TEST 2: Survival Mode Competition")
        print("=" * 80)
        
        system2 = CompetitiveSystem(
            evaluation_criteria=[
                "Innovation",
                "Feasibility",
                "Impact"
            ]
        )
        
        system2.add_competitor("Alpha", "Aggressive innovation", 0.9)
        system2.add_competitor("Beta", "Balanced approach", 0.7)
        system2.add_competitor("Gamma", "Conservative and safe", 0.5)
        
        problem2 = "Propose a strategy to reduce customer churn by 30%"
        
        result2 = system2.run_competition(
            problem2,
            mode=CompetitionMode.SURVIVAL,
            rounds=2
        )
        
        print(f"\n{'*' * 80}")
        print("SURVIVOR & WINNER:")
        print(f"{'*' * 80}")
        print(f"Agent: {result2.winner.agent_id}")
        print(f"Score: {result2.winner.total_score:.2f}/10.0")
        print(f"\nWinning Solution:\n{result2.winner.content}")
        
        # Test 3: Tournament with diverse strategies
        print("\n" + "=" * 80)
        print("TEST 3: Multi-Round Tournament")
        print("=" * 80)
        
        system3 = CompetitiveSystem()
        
        system3.add_competitor("Creative", "Maximum creativity and originality", 1.0)
        system3.add_competitor("Analytical", "Data-driven and analytical", 0.4)
        system3.add_competitor("Hybrid", "Balance creativity with analysis", 0.7)
        
        problem3 = "Develop a marketing campaign for an eco-friendly product"
        
        result3 = system3.run_competition(
            problem3,
            mode=CompetitionMode.ROUND_ROBIN,
            rounds=3
        )
        
        print(f"\n{'*' * 80}")
        print("TOURNAMENT WINNER:")
        print(f"{'*' * 80}")
        print(f"Agent: {result3.winner.agent_id}")
        print(f"Average Score: {result3.winner.total_score:.2f}/10.0")
        
        # Show score progression
        print(f"\n{'-' * 80}")
        print("SCORE PROGRESSION:")
        print(f"{'-' * 80}")
        
        for agent_id in ["Creative", "Analytical", "Hybrid"]:
            agent_solutions = [s for s in result3.all_solutions if s.agent_id == agent_id]
            scores = [s.total_score for s in agent_solutions]
            print(f"{agent_id}: {' -> '.join([f'{s:.2f}' for s in scores])}")
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Competitive Multi-Agent pattern demonstrates several key benefits:
    
    1. **Quality Through Competition**: Competition drives agents to produce better solutions
    2. **Diverse Approaches**: Different strategies explore solution space differently
    3. **Iterative Improvement**: Agents can improve based on previous best solutions
    4. **Objective Selection**: Multi-criteria evaluation ensures fair judging
    
    Competition Modes:
    - **Round Robin**: All agents compete in every round (fair, comprehensive)
    - **Tournament**: Bracket-style elimination (exciting, efficient)
    - **Survival**: Lowest scorers eliminated each round (competitive pressure)
    
    Evaluation Approach:
    - **Multi-Criteria**: Solutions judged on multiple dimensions
    - **Objective Scoring**: Numerical scores (0-10) for each criterion
    - **Weighted Total**: Final score from criterion average
    - **Ranking**: Clear winner determination
    
    Use Cases:
    - Creative tasks (writing, design, ideation)
    - Optimization with multiple local optima
    - Generating diverse solution approaches
    - Quality improvement through competition
    - A/B testing alternative approaches
    
    The pattern is particularly effective when:
    - Solution quality is critical
    - Multiple valid approaches exist
    - Diversity of perspectives adds value
    - Competition motivates better outputs
    - Objective evaluation is possible
    """)
    
    
    if __name__ == "__main__":
        demonstrate_competitive_multi_agent()
    
        

    Agentic Design Pattern: Competitive Multi Agent

    Pattern 017: Competitive Multi Agent – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 017: Competitive Multi-Agent

    Description: The Competitive Multi-Agent pattern uses multiple agents that compete to produce the best solution. Agents work independently, their solutions are evaluated against each other, and the winning solution is selected. This competition drives quality improvement and innovation.

    Components: – Competitor Agents: Multiple agents with different strategies – Evaluation System: Judges solutions across multiple criteria – Competition Framework: Manages rounds and determines winners – Selection Mechanism: Chooses best solution(s)

    Use Cases: – Creative tasks (writing, design, ideation) – Optimization problems with multiple local optima – Generating diverse solution approaches – Quality improvement through competition

    LangChain Implementation: Uses multiple LLM agents with different configurations competing on tasks, implements multi-criteria evaluation, tournament-style competition, and quality-driven selection mechanisms.

    Key Features: – Tournament-style competitions – Multi-criteria evaluation – Diverse agent strategies – Winner selection and ranking


    File Statistics

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

    Module Documentation

    """
    Pattern 017: Competitive Multi-Agent
    
    Description:
        The Competitive Multi-Agent pattern uses multiple agents that compete to produce
        the best solution. Agents work independently, their solutions are evaluated against
        each other, and the winning solution is selected. This competition drives quality
        improvement and innovation.
    
    Components:
        - Competitor Agents: Multiple agents with different strategies
        - Evaluation System: Judges solutions across multiple criteria
        - Competition Framework: Manages rounds and determines winners
        - Selection Mechanism: Chooses best solution(s)
    
    Use Cases:
        - Creative tasks (writing, design, ideation)
        - Optimization problems with multiple local optima
        - Generating diverse solution approaches
        - Quality improvement through competition
    
    LangChain Implementation:
        Uses multiple LLM agents with different configurations competing on tasks,
        implements multi-criteria evaluation, tournament-style competition, and
        quality-driven selection mechanisms.
    
    Key Features:
        - Tournament-style competitions
        - Multi-criteria evaluation
        - Diverse agent strategies
        - Winner selection and ranking
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import enum.Enum
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import random
    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 – 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 – 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: CompetitionMode

    Lines 48-52

    Inherits from: Enum

    Purpose:

    Modes of competition.
    

    Class: Solution

    Lines 56-63

    Purpose:

    Represents a solution from a competitor.
    

    Class: CompetitionResult

    Lines 67-72

    Purpose:

    Results from a competition.
    

    Class: CompetitorAgent

    Lines 75-146

    Purpose:

    An agent that competes to produce the best solution.
    

    Methods:

    __init__(self, agent_id, strategy, model, temperature)

    Lines 80-100

    Purpose:

    Initialize a competitor agent.
    
    Args:
        agent_id: Unique identifier
        strategy: Competitive strategy description
        model: LLM model to use
        temperature: Creativity level
    

    Parameters: – agent_id: Agent id – strategy: Strategy – model: Language model instance – temperature: Sampling temperature (0-1)

    generate_solution(self, problem, round_number, previous_best)

    Lines 102-146

    Purpose:

    Generate a solution for the problem.
    
    Args:
        problem: Problem to solve
        round_number: Current round number
        previous_best: Previous best solution (for improvement)
    
    Returns:
        Generated solution
    

    Parameters: – problem: Problem – round_number: Round number – previous_best: Previous best

    Returns: Solution


    Class: CompetitionJudge

    Lines 149-249

    Purpose:

    Evaluates and ranks solutions in competitions.
    

    Methods:

    __init__(self, criteria, model)

    Lines 154-167

    Purpose:

    Initialize the judge.
    
    Args:
        criteria: List of evaluation criteria
        model: LLM model for judging
    

    Parameters: – criteria: Criteria – model: Language model instance

    evaluate_solution(self, problem, solution)

    Lines 169-193

    Purpose:

    Evaluate a solution across all criteria.
    
    Args:
        problem: Original problem
        solution: Solution to evaluate
    
    Returns:
        Solution with scores populated
    

    Parameters: – problem: Problem – solution: Solution

    Returns: Solution

    _score_criterion(self, problem, solution, criterion)

    Lines 195-225

    Purpose:

    Score a solution on a specific criterion.
    

    Parameters: – problem: Problem – solution: Solution – criterion: Criterion

    Returns: float

    rank_solutions(self, solutions)

    Lines 227-249

    Purpose:

    Rank solutions by total score.
    
    Args:
        solutions: List of solutions to rank
    
    Returns:
        Ranked solutions (highest to lowest)
    

    Parameters: – solutions: Solutions

    Returns: <ast.Subscript object at 0x000001A06D64EC90>


    Class: CompetitiveSystem

    Lines 252-390

    Purpose:

    Manages competitive multi-agent system.
    

    Methods:

    __init__(self, evaluation_criteria, model)

    Lines 257-280

    Purpose:

    Initialize competitive system.
    
    Args:
        evaluation_criteria: Criteria for evaluating solutions
        model: LLM model for agents
    

    Parameters: – evaluation_criteria: Evaluation criteria – model: Language model instance

    add_competitor(self, agent_id, strategy, temperature)

    Lines 282-290

    Purpose:

    Add a competitor to the system.
    

    Parameters: – agent_id: Agent id – strategy: Strategy – temperature: Sampling temperature (0-1)

    run_competition(self, problem, mode, rounds)

    Lines 292-377

    Purpose:

    Run a competition among agents.
    
    Args:
        problem: Problem for agents to solve
        mode: Competition mode
        rounds: Number of competition rounds
    
    Returns:
        Competition results
    

    Parameters: – problem: Problem – mode: Mode – rounds: Rounds

    Returns: CompetitionResult

    get_leaderboard(self)

    Lines 379-390

    Purpose:

    Get agent leaderboard.
    
    Returns:
        List of (agent_id, wins, avg_score) tuples
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D63CC50>


    Functions

    demonstrate_competitive_multi_agent()

    Lines 393-554

    Purpose:

    Demonstrate the Competitive Multi-Agent pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Competitive Multi 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 016
    • Pattern 018
    • Other Multi-Agent Patterns (012-019)

    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 Competitive Multi 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: 017_competitive_multi_agent.py
    Total Lines: 559
    Pattern Category: AI Agent Design Pattern #017


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Hierarchical Multi Agent

    
        """
    Pattern 016: Hierarchical Multi-Agent
    
    Description:
        The Hierarchical Multi-Agent pattern organizes agents in a hierarchical structure with
        multiple levels of management and specialization. Managers coordinate workers, workers
        execute tasks, and specialists provide domain expertise. This creates a scalable
        organizational structure for complex problems.
    
    Components:
        - Manager Agents: High-level coordination and decision-making
        - Worker Agents: Execute assigned tasks
        - Specialist Agents: Provide domain expertise
        - Communication Protocol: Top-down delegation and bottom-up reporting
        - Task Decomposition: Breaking complex tasks into hierarchical subtasks
    
    Use Cases:
        - Large-scale project management
        - Complex multi-domain problems
        - Organizational modeling and simulation
        - Scalable distributed systems
    
    LangChain Implementation:
        Uses multiple LLM instances representing different hierarchy levels,
        implements delegation chains, task assignment strategies, and result
        aggregation across organizational levels.
    
    Key Features:
        - Multi-level hierarchy (Manager -> Worker -> Specialist)
        - Top-down task delegation
        - Bottom-up result reporting
        - Cross-functional coordination
    """
    
    import os
    from typing import List, Dict, Any, Optional, Set
    from dataclasses import dataclass, field
    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 AgentLevel(Enum):
        """Hierarchy levels in the organization."""
        EXECUTIVE = "executive"
        MANAGER = "manager"
        WORKER = "worker"
        SPECIALIST = "specialist"
    
    
    class TaskPriority(Enum):
        """Task priority levels."""
        CRITICAL = "critical"
        HIGH = "high"
        MEDIUM = "medium"
        LOW = "low"
    
    
    @dataclass
    class HierarchicalTask:
        """Represents a task in the hierarchy."""
        task_id: str
        description: str
        priority: TaskPriority
        assigned_to: Optional[str] = None
        assigned_level: Optional[AgentLevel] = None
        parent_task_id: Optional[str] = None
        subtasks: List[str] = field(default_factory=list)
        result: Optional[str] = None
        completed: bool = False
    
    
    @dataclass
    class AgentReport:
        """Report from an agent to their manager."""
        agent_id: str
        agent_level: AgentLevel
        task_id: str
        status: str
        result: str
        issues: List[str] = field(default_factory=list)
        recommendations: List[str] = field(default_factory=list)
    
    
    class HierarchicalAgent:
        """
        Base class for agents in the hierarchy.
        """
        
        def __init__(
            self,
            agent_id: str,
            level: AgentLevel,
            specialty: Optional[str] = None,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize a hierarchical agent.
            
            Args:
                agent_id: Unique identifier
                level: Hierarchy level
                specialty: Optional domain specialty
                model: LLM model to use
            """
            self.agent_id = agent_id
            self.level = level
            self.specialty = specialty
            self.llm = ChatOpenAI(model=model, temperature=0.5)
            
            # Reporting relationships
            self.manager: Optional[str] = None
            self.direct_reports: List[str] = []
            
            # Task management
            self.assigned_tasks: List[HierarchicalTask] = []
            self.completed_tasks: List[HierarchicalTask] = []
        
        def get_role_description(self) -> str:
            """Get the role description for this agent."""
            descriptions = {
                AgentLevel.EXECUTIVE: "You are an executive leader responsible for strategic direction and high-level coordination.",
                AgentLevel.MANAGER: f"You are a manager responsible for coordinating teams and ensuring task completion. Specialty: {self.specialty or 'General'}",
                AgentLevel.WORKER: f"You are a worker who executes assigned tasks efficiently. Specialty: {self.specialty or 'General'}",
                AgentLevel.SPECIALIST: f"You are a specialist with deep expertise in {self.specialty}. You provide expert guidance and solutions."
            }
            return descriptions[self.level]
        
        def delegate_task(
            self,
            task: HierarchicalTask,
            subordinate_id: str
        ) -> str:
            """
            Delegate a task to a subordinate with clear instructions.
            
            Args:
                task: Task to delegate
                subordinate_id: ID of subordinate to delegate to
                
            Returns:
                Delegation message
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"{self.get_role_description()}\n\nYou are delegating a task to {subordinate_id}."),
                ("user", """Task: {task_description}
    Priority: {priority}
    
    Provide clear delegation instructions including:
    1. What needs to be done
    2. Expected outcomes
    3. Any constraints or guidelines
    4. Reporting requirements
    
    Keep it concise (3-4 sentences).""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            delegation = chain.invoke({
                "task_description": task.description,
                "priority": task.priority.value
            })
            
            return delegation.strip()
        
        def execute_task(
            self,
            task: HierarchicalTask,
            context: Optional[str] = None
        ) -> str:
            """
            Execute an assigned task.
            
            Args:
                task: Task to execute
                context: Optional context from manager
                
            Returns:
                Task result
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", self.get_role_description()),
                ("user", """Execute this task:
    
    Task: {task_description}
    Priority: {priority}
    {context_section}
    
    Provide your result:""")
            ])
            
            context_section = f"\nManager's Instructions:\n{context}" if context else ""
            
            chain = prompt | self.llm | StrOutputParser()
            
            result = chain.invoke({
                "task_description": task.description,
                "priority": task.priority.value,
                "context_section": context_section
            })
            
            return result.strip()
        
        def create_report(
            self,
            task: HierarchicalTask,
            result: str
        ) -> AgentReport:
            """
            Create a report for manager.
            
            Args:
                task: Completed task
                result: Task result
                
            Returns:
                Agent report
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"{self.get_role_description()}\n\nCreate a concise status report for your manager."),
                ("user", """Task: {task_description}
    Result: {result}
    
    Provide:
    1. Status (one word: Completed/Blocked/In-Progress)
    2. Any issues encountered (or "None")
    3. Brief recommendations (or "None")
    
    Format:
    STATUS: [status]
    ISSUES: [issues]
    RECOMMENDATIONS: [recommendations]""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            report_text = chain.invoke({
                "task_description": task.description,
                "result": result
            })
            
            # Parse report
            lines = report_text.strip().split("\n")
            status = "Completed"
            issues = []
            recommendations = []
            
            for line in lines:
                if line.startswith("STATUS:"):
                    status = line.split(":", 1)[1].strip()
                elif line.startswith("ISSUES:"):
                    issue_text = line.split(":", 1)[1].strip()
                    if issue_text.lower() != "none":
                        issues = [issue_text]
                elif line.startswith("RECOMMENDATIONS:"):
                    rec_text = line.split(":", 1)[1].strip()
                    if rec_text.lower() != "none":
                        recommendations = [rec_text]
            
            return AgentReport(
                agent_id=self.agent_id,
                agent_level=self.level,
                task_id=task.task_id,
                status=status,
                result=result,
                issues=issues,
                recommendations=recommendations
            )
    
    
    class HierarchicalOrganization:
        """
        Manages a hierarchical multi-agent organization.
        """
        
        def __init__(self, model: str = "gpt-3.5-turbo"):
            """Initialize the hierarchical organization."""
            self.model = model
            self.agents: Dict[str, HierarchicalAgent] = {}
            self.tasks: Dict[str, HierarchicalTask] = {}
            self.reports: List[AgentReport] = []
            
            # Build default organization structure
            self._build_organization()
        
        def _build_organization(self):
            """Build a default organizational structure."""
            # Executive level
            self.add_agent("CEO", AgentLevel.EXECUTIVE, "Strategic Leadership")
            
            # Manager level
            self.add_agent("PM_Tech", AgentLevel.MANAGER, "Technology")
            self.add_agent("PM_Ops", AgentLevel.MANAGER, "Operations")
            self.add_agent("PM_Research", AgentLevel.MANAGER, "Research")
            
            # Worker level
            self.add_agent("Worker_Dev", AgentLevel.WORKER, "Development")
            self.add_agent("Worker_QA", AgentLevel.WORKER, "Quality Assurance")
            self.add_agent("Worker_Data", AgentLevel.WORKER, "Data Processing")
            
            # Specialist level
            self.add_agent("Spec_ML", AgentLevel.SPECIALIST, "Machine Learning")
            self.add_agent("Spec_Security", AgentLevel.SPECIALIST, "Security")
            
            # Set up reporting structure
            self.set_manager("PM_Tech", "CEO")
            self.set_manager("PM_Ops", "CEO")
            self.set_manager("PM_Research", "CEO")
            
            self.set_manager("Worker_Dev", "PM_Tech")
            self.set_manager("Worker_QA", "PM_Tech")
            self.set_manager("Worker_Data", "PM_Ops")
            
            self.set_manager("Spec_ML", "PM_Research")
            self.set_manager("Spec_Security", "PM_Tech")
        
        def add_agent(
            self,
            agent_id: str,
            level: AgentLevel,
            specialty: Optional[str] = None
        ):
            """Add an agent to the organization."""
            agent = HierarchicalAgent(agent_id, level, specialty, self.model)
            self.agents[agent_id] = agent
        
        def set_manager(self, agent_id: str, manager_id: str):
            """Set manager-subordinate relationship."""
            if agent_id in self.agents and manager_id in self.agents:
                self.agents[agent_id].manager = manager_id
                self.agents[manager_id].direct_reports.append(agent_id)
        
        def decompose_task(
            self,
            task_description: str,
            priority: TaskPriority = TaskPriority.MEDIUM
        ) -> List[HierarchicalTask]:
            """
            Decompose a high-level task into hierarchical subtasks.
            
            Args:
                task_description: Main task description
                priority: Task priority
                
            Returns:
                List of hierarchical tasks
            """
            # CEO decomposes into manager-level tasks
            ceo = self.agents["CEO"]
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", ceo.get_role_description()),
                ("user", """Decompose this high-level task into 3-4 manager-level subtasks:
    
    Task: {task_description}
    
    For each subtask, specify:
    1. Which manager should handle it (PM_Tech, PM_Ops, or PM_Research)
    2. Brief description
    
    Format each as:
    [MANAGER_ID]: [task description]""")
            ])
            
            chain = prompt | ceo.llm | StrOutputParser()
            
            decomposition = chain.invoke({"task_description": task_description})
            
            # Parse decomposition
            tasks = []
            parent_task = HierarchicalTask(
                task_id="task_0",
                description=task_description,
                priority=priority,
                assigned_to="CEO",
                assigned_level=AgentLevel.EXECUTIVE
            )
            self.tasks[parent_task.task_id] = parent_task
            tasks.append(parent_task)
            
            for i, line in enumerate(decomposition.strip().split("\n"), 1):
                if ":" in line:
                    try:
                        manager_id, desc = line.split(":", 1)
                        manager_id = manager_id.strip().replace("[", "").replace("]", "")
                        
                        if manager_id in self.agents:
                            subtask = HierarchicalTask(
                                task_id=f"task_{i}",
                                description=desc.strip(),
                                priority=priority,
                                assigned_to=manager_id,
                                assigned_level=AgentLevel.MANAGER,
                                parent_task_id=parent_task.task_id
                            )
                            self.tasks[subtask.task_id] = subtask
                            parent_task.subtasks.append(subtask.task_id)
                            tasks.append(subtask)
                    except:
                        continue
            
            return tasks
        
        def execute_hierarchical_workflow(
            self,
            main_task: str,
            priority: TaskPriority = TaskPriority.MEDIUM
        ) -> Dict[str, Any]:
            """
            Execute a complete hierarchical workflow.
            
            Args:
                main_task: Main task description
                priority: Task priority
                
            Returns:
                Workflow results and summary
            """
            print(f"\n[Organization] Executive assigning task: {main_task}")
            print(f"[Organization] Priority: {priority.value}\n")
            
            # Step 1: Decompose at executive level
            tasks = self.decompose_task(main_task, priority)
            
            print(f"[CEO] Decomposed into {len(tasks)-1} manager-level tasks")
            
            # Step 2: Managers delegate to workers
            for task in tasks[1:]:  # Skip parent task
                if task.assigned_level == AgentLevel.MANAGER:
                    manager = self.agents[task.assigned_to]
                    
                    print(f"\n[{manager.agent_id}] Received task: {task.description[:60]}...")
                    
                    # Manager selects appropriate subordinate
                    subordinates = [self.agents[sid] for sid in manager.direct_reports]
                    if subordinates:
                        # Select first available subordinate (simplified)
                        selected = subordinates[0]
                        
                        # Create worker task
                        worker_task = HierarchicalTask(
                            task_id=f"{task.task_id}_w",
                            description=task.description,
                            priority=priority,
                            assigned_to=selected.agent_id,
                            assigned_level=selected.level,
                            parent_task_id=task.task_id
                        )
                        self.tasks[worker_task.task_id] = worker_task
                        task.subtasks.append(worker_task.task_id)
                        
                        # Delegate with instructions
                        delegation = manager.delegate_task(worker_task, selected.agent_id)
                        print(f"[{manager.agent_id}] Delegating to {selected.agent_id}")
                        print(f"   Instructions: {delegation[:80]}...")
                        
                        # Step 3: Workers execute
                        print(f"\n[{selected.agent_id}] Executing task...")
                        result = selected.execute_task(worker_task, delegation)
                        worker_task.result = result
                        worker_task.completed = True
                        
                        print(f"[{selected.agent_id}] Completed: {result[:80]}...")
                        
                        # Step 4: Workers report to manager
                        report = selected.create_report(worker_task, result)
                        self.reports.append(report)
                        
                        print(f"[{selected.agent_id}] Report status: {report.status}")
                        
                        # Step 5: Manager synthesizes results
                        task.result = result
                        task.completed = True
            
            # Step 6: CEO synthesizes executive summary
            print(f"\n[CEO] Synthesizing final results...")
            
            all_results = "\n\n".join([
                f"Manager {t.assigned_to}: {t.result}"
                for t in tasks[1:]
                if t.result
            ])
            
            exec_summary = self._create_executive_summary(main_task, all_results)
            tasks[0].result = exec_summary
            tasks[0].completed = True
            
            return {
                "main_task": main_task,
                "executive_summary": exec_summary,
                "manager_results": {t.assigned_to: t.result for t in tasks[1:] if t.result},
                "reports": self.reports,
                "total_tasks": len(self.tasks),
                "completed_tasks": sum(1 for t in self.tasks.values() if t.completed)
            }
        
        def _create_executive_summary(
            self,
            main_task: str,
            all_results: str
        ) -> str:
            """Create executive summary from all results."""
            ceo = self.agents["CEO"]
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", ceo.get_role_description()),
                ("user", """Synthesize an executive summary from the results below:
    
    Main Task: {main_task}
    
    Team Results:
    {all_results}
    
    Provide a concise executive summary (3-4 sentences) highlighting:
    1. Overall completion status
    2. Key findings
    3. Strategic recommendations""")
            ])
            
            chain = prompt | ceo.llm | StrOutputParser()
            
            summary = chain.invoke({
                "main_task": main_task,
                "all_results": all_results
            })
            
            return summary.strip()
        
        def get_org_chart(self) -> str:
            """Generate a text representation of the org chart."""
            lines = []
            
            def print_agent(agent_id: str, indent: int = 0):
                agent = self.agents[agent_id]
                prefix = "  " * indent
                lines.append(f"{prefix}- {agent_id} ({agent.level.value}) [{agent.specialty or 'General'}]")
                
                for report_id in agent.direct_reports:
                    print_agent(report_id, indent + 1)
            
            print_agent("CEO")
            return "\n".join(lines)
    
    
    def demonstrate_hierarchical_multi_agent():
        """Demonstrate the Hierarchical Multi-Agent pattern."""
        
        print("=" * 80)
        print("HIERARCHICAL MULTI-AGENT PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Create organization
        org = HierarchicalOrganization()
        
        # Show org chart
        print("\n" + "=" * 80)
        print("ORGANIZATIONAL STRUCTURE")
        print("=" * 80)
        print(org.get_org_chart())
        
        # Test 1: Complex project
        print("\n" + "=" * 80)
        print("TEST 1: Complex Project Execution")
        print("=" * 80)
        
        task1 = "Launch a new mobile app with AI-powered features"
        
        result1 = org.execute_hierarchical_workflow(task1, TaskPriority.HIGH)
        
        print("\n" + "-" * 80)
        print("EXECUTIVE SUMMARY:")
        print("-" * 80)
        print(result1["executive_summary"])
        
        print("\n" + "-" * 80)
        print("DEPARTMENTAL RESULTS:")
        print("-" * 80)
        for manager, result in result1["manager_results"].items():
            print(f"\n{manager}:")
            print(f"  {result[:150]}...")
        
        print("\n" + "-" * 80)
        print("WORKFLOW METRICS:")
        print("-" * 80)
        print(f"  Total tasks created: {result1['total_tasks']}")
        print(f"  Completed tasks: {result1['completed_tasks']}")
        print(f"  Reports submitted: {len(result1['reports'])}")
        
        # Test 2: Strategic initiative
        print("\n" + "=" * 80)
        print("TEST 2: Strategic Initiative")
        print("=" * 80)
        
        task2 = "Improve system security and implement new compliance measures"
        
        result2 = org.execute_hierarchical_workflow(task2, TaskPriority.CRITICAL)
        
        print("\n" + "-" * 80)
        print("EXECUTIVE SUMMARY:")
        print("-" * 80)
        print(result2["executive_summary"])
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Hierarchical Multi-Agent pattern demonstrates several key benefits:
    
    1. **Scalability**: Can handle large, complex tasks through decomposition
    2. **Specialization**: Agents focus on their area of expertise
    3. **Clear Accountability**: Well-defined reporting relationships
    4. **Efficient Coordination**: Structured communication channels
    
    Organizational Structure:
    - **Executive Level**: Strategic direction and overall coordination
    - **Manager Level**: Team coordination and task delegation
    - **Worker Level**: Task execution and implementation
    - **Specialist Level**: Domain expertise and specialized guidance
    
    Communication Flow:
    - **Top-Down**: Task delegation with clear instructions
    - **Bottom-Up**: Status reports and results
    - **Cross-Functional**: Coordination between departments
    
    Use Cases:
    - Large-scale project management
    - Complex multi-domain problems
    - Organizational simulation
    - Scalable distributed systems
    
    The pattern is particularly effective when:
    - Tasks require multiple areas of expertise
    - Clear accountability is important
    - Scalability is needed for large projects
    - Organizational modeling is beneficial
    - Structured communication is preferred
    """)
    
    
    if __name__ == "__main__":
        demonstrate_hierarchical_multi_agent()
    
        

    Agentic Design Pattern: Hierarchical Multi Agent

    Pattern 016: Hierarchical Multi Agent – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 016: Hierarchical Multi-Agent

    Description: The Hierarchical Multi-Agent pattern organizes agents in a hierarchical structure with multiple levels of management and specialization. Managers coordinate workers, workers execute tasks, and specialists provide domain expertise. This creates a scalable organizational structure for complex problems.

    Components: – Manager Agents: High-level coordination and decision-making – Worker Agents: Execute assigned tasks – Specialist Agents: Provide domain expertise – Communication Protocol: Top-down delegation and bottom-up reporting – Task Decomposition: Breaking complex tasks into hierarchical subtasks

    Use Cases: – Large-scale project management – Complex multi-domain problems – Organizational modeling and simulation – Scalable distributed systems

    LangChain Implementation: Uses multiple LLM instances representing different hierarchy levels, implements delegation chains, task assignment strategies, and result aggregation across organizational levels.

    Key Features: – Multi-level hierarchy (Manager -> Worker -> Specialist) – Top-down task delegation – Bottom-up result reporting – Cross-functional coordination


    File Statistics

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

    Module Documentation

    """
    Pattern 016: Hierarchical Multi-Agent
    
    Description:
        The Hierarchical Multi-Agent pattern organizes agents in a hierarchical structure with
        multiple levels of management and specialization. Managers coordinate workers, workers
        execute tasks, and specialists provide domain expertise. This creates a scalable
        organizational structure for complex problems.
    
    Components:
        - Manager Agents: High-level coordination and decision-making
        - Worker Agents: Execute assigned tasks
        - Specialist Agents: Provide domain expertise
        - Communication Protocol: Top-down delegation and bottom-up reporting
        - Task Decomposition: Breaking complex tasks into hierarchical subtasks
    
    Use Cases:
        - Large-scale project management
        - Complex multi-domain problems
        - Organizational modeling and simulation
        - Scalable distributed systems
    
    LangChain Implementation:
        Uses multiple LLM instances representing different hierarchy levels,
        implements delegation chains, task assignment strategies, and result
        aggregation across organizational levels.
    
    Key Features:
        - Multi-level hierarchy (Manager -> Worker -> Specialist)
        - Top-down task delegation
        - Bottom-up result reporting
        - Cross-functional coordination
    """
    

    Purpose: This module implements the Hierarchical Multi Agent 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 typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Set
    

    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 – 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)

    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: AgentLevel

    Lines 48-53

    Inherits from: Enum

    Purpose:

    Hierarchy levels in the organization.
    

    Class: TaskPriority

    Lines 56-61

    Inherits from: Enum

    Purpose:

    Task priority levels.
    

    Class: HierarchicalTask

    Lines 65-75

    Purpose:

    Represents a task in the hierarchy.
    

    Class: AgentReport

    Lines 79-87

    Purpose:

    Report from an agent to their manager.
    

    Class: HierarchicalAgent

    Lines 90-274

    Purpose:

    Base class for agents in the hierarchy.
    

    Methods:

    __init__(self, agent_id, level, specialty, model)

    Lines 95-122

    Purpose:

    Initialize a hierarchical agent.
    
    Args:
        agent_id: Unique identifier
        level: Hierarchy level
        specialty: Optional domain specialty
        model: LLM model to use
    

    Parameters: – agent_id: Agent id – level: Level – specialty: Specialty – model: Language model instance

    get_role_description(self)

    Lines 124-132

    Purpose:

    Get the role description for this agent.
    

    Parameters:

    Returns: str

    delegate_task(self, task, subordinate_id)

    Lines 134-170

    Purpose:

    Delegate a task to a subordinate with clear instructions.
    
    Args:
        task: Task to delegate
        subordinate_id: ID of subordinate to delegate to
    
    Returns:
        Delegation message
    

    Parameters: – task: Task – subordinate_id: Subordinate id

    Returns: str

    execute_task(self, task, context)

    Lines 172-208

    Purpose:

    Execute an assigned task.
    
    Args:
        task: Task to execute
        context: Optional context from manager
    
    Returns:
        Task result
    

    Parameters: – task: Task – context: Context

    Returns: str

    create_report(self, task, result)

    Lines 210-274

    Purpose:

    Create a report for manager.
    
    Args:
        task: Completed task
        result: Task result
    
    Returns:
        Agent report
    

    Parameters: – task: Task – result: Result

    Returns: AgentReport


    Class: HierarchicalOrganization

    Lines 277-548

    Purpose:

    Manages a hierarchical multi-agent organization.
    

    Methods:

    __init__(self, model)

    Lines 282-290

    Purpose:

    Initialize the hierarchical organization.
    

    Parameters: – model: Language model instance

    _build_organization(self)

    Lines 292-321

    Purpose:

    Build a default organizational structure.
    

    Parameters:

    add_agent(self, agent_id, level, specialty)

    Lines 323-331

    Purpose:

    Add an agent to the organization.
    

    Parameters: – agent_id: Agent id – level: Level – specialty: Specialty

    set_manager(self, agent_id, manager_id)

    Lines 333-337

    Purpose:

    Set manager-subordinate relationship.
    

    Parameters: – agent_id: Agent id – manager_id: Manager id

    decompose_task(self, task_description, priority)

    Lines 339-408

    Purpose:

    Decompose a high-level task into hierarchical subtasks.
    
    Args:
        task_description: Main task description
        priority: Task priority
    
    Returns:
        List of hierarchical tasks
    

    Parameters: – task_description: Task description – priority: Priority

    Returns: <ast.Subscript object at 0x000001A06D631250>

    execute_hierarchical_workflow(self, main_task, priority)

    Lines 410-501

    Purpose:

    Execute a complete hierarchical workflow.
    
    Args:
        main_task: Main task description
        priority: Task priority
    
    Returns:
        Workflow results and summary
    

    Parameters: – main_task: Main task – priority: Priority

    Returns: <ast.Subscript object at 0x000001A06D63D850>

    _create_executive_summary(self, main_task, all_results)

    Lines 503-533

    Purpose:

    Create executive summary from all results.
    

    Parameters: – main_task: Main task – all_results: All results

    Returns: str

    get_org_chart(self)

    Lines 535-548

    Purpose:

    Generate a text representation of the org chart.
    

    Parameters:

    Returns: str


    Functions

    demonstrate_hierarchical_multi_agent()

    Lines 551-644

    Purpose:

    Demonstrate the Hierarchical Multi-Agent pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Hierarchical Multi 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 015
    • Pattern 017
    • Other Multi-Agent Patterns (012-019)

    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 Hierarchical Multi 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: 016_hierarchical_multi_agent.py
    Total Lines: 649
    Pattern Category: AI Agent Design Pattern #016


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Swarm Intelligence

    
        """
    Pattern 015: Swarm Intelligence
    
    Description:
        The Swarm Intelligence pattern uses many simple, decentralized agents that collaborate
        through local interactions to solve complex problems. Inspired by natural phenomena like
        ant colonies, bee swarms, and bird flocking, this pattern achieves emergent intelligent
        behavior without centralized control.
    
    Components:
        - Swarm Agents: Simple agents with local decision rules
        - Environment/Pheromone System: Shared information space
        - Local Interaction Rules: How agents influence each other
        - Emergent Behavior: Complex patterns from simple rules
    
    Use Cases:
        - Optimization problems (path finding, resource allocation)
        - Search and exploration tasks
        - Distributed problem-solving
        - Parallel hypothesis exploration
    
    LangChain Implementation:
        Uses multiple simple LLM agents with local decision-making, a shared information
        space (pheromone trails), iterative exploration rounds, and convergence detection
        to achieve swarm-based optimization.
    
    Key Features:
        - Decentralized decision-making
        - Pheromone-based communication
        - Iterative convergence
        - Parallel exploration
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from dataclasses import dataclass, field
    from collections import defaultdict
    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()
    
    
    @dataclass
    class Solution:
        """Represents a solution explored by an agent."""
        agent_id: str
        content: str
        quality_score: float
        iteration: int
    
    
    @dataclass
    class PheromoneTrail:
        """Represents accumulated pheromone on a solution path."""
        solution_key: str
        strength: float
        contributors: List[str] = field(default_factory=list)
    
    
    class SwarmBehavior(Enum):
        """Types of swarm behaviors."""
        EXPLORATION = "exploration"  # Explore diverse solutions
        EXPLOITATION = "exploitation"  # Refine best solutions
        BALANCED = "balanced"  # Mix of both
    
    
    class SwarmAgent:
        """
        A simple swarm agent that makes local decisions based on pheromones.
        """
        
        def __init__(
            self,
            agent_id: str,
            model: str = "gpt-3.5-turbo",
            temperature: float = 0.8
        ):
            """
            Initialize a swarm agent.
            
            Args:
                agent_id: Unique identifier for this agent
                model: LLM model to use
                temperature: Creativity/exploration level
            """
            self.agent_id = agent_id
            self.llm = ChatOpenAI(model=model, temperature=temperature)
            self.local_memory: List[Solution] = []
        
        def explore_solution(
            self,
            problem: str,
            pheromone_trails: List[PheromoneTrail],
            behavior: SwarmBehavior = SwarmBehavior.BALANCED
        ) -> str:
            """
            Explore a solution based on local information and pheromones.
            
            Args:
                problem: Problem to solve
                pheromone_trails: Current pheromone information
                behavior: Exploration vs exploitation preference
                
            Returns:
                Proposed solution
            """
            # Build context from pheromone trails
            if pheromone_trails and behavior != SwarmBehavior.EXPLORATION:
                # Sort by strength and take top trails
                top_trails = sorted(pheromone_trails, key=lambda x: x.strength, reverse=True)[:3]
                trail_context = "\n".join([
                    f"- Strong path: {trail.solution_key} (strength: {trail.strength:.2f})"
                    for trail in top_trails
                ])
            else:
                trail_context = "No strong paths found yet. Explore freely."
            
            behavior_instructions = {
                SwarmBehavior.EXPLORATION: "Focus on exploring new, diverse approaches. Be creative and unconventional.",
                SwarmBehavior.EXPLOITATION: "Focus on refining and improving the strongest existing approaches.",
                SwarmBehavior.BALANCED: "Balance between exploring new ideas and building on promising existing approaches."
            }
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", f"""You are a swarm agent working as part of a collective intelligence.
    Your role is to propose solutions based on local information and pheromone trails left by other agents.
    
    {behavior_instructions[behavior]}
    
    Keep your solution concise (2-3 sentences)."""),
                ("user", """Problem: {problem}
    
    Pheromone trails from other agents:
    {trail_context}
    
    Propose your solution:""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            solution = chain.invoke({
                "problem": problem,
                "trail_context": trail_context
            })
            
            return solution.strip()
        
        def evaluate_solution(
            self,
            problem: str,
            solution: str
        ) -> float:
            """
            Evaluate the quality of a solution.
            
            Args:
                problem: Original problem
                solution: Proposed solution
                
            Returns:
                Quality score (0.0-1.0)
            """
            eval_prompt = ChatPromptTemplate.from_messages([
                ("system", "You are an evaluator. Rate solution quality from 0.0 to 1.0."),
                ("user", """Problem: {problem}
    
    Solution: {solution}
    
    Rate this solution's quality (0.0 = poor, 1.0 = excellent).
    Consider: correctness, completeness, creativity, practicality.
    
    Provide only a number between 0.0 and 1.0:""")
            ])
            
            chain = eval_prompt | self.llm | StrOutputParser()
            
            try:
                score_str = chain.invoke({"problem": problem, "solution": solution})
                score = float(score_str.strip())
                return max(0.0, min(1.0, score))
            except:
                return 0.5  # Default moderate score
    
    
    class SwarmIntelligence:
        """
        Swarm intelligence system coordinating multiple simple agents.
        
        Uses pheromone-based communication for decentralized problem-solving.
        """
        
        def __init__(
            self,
            num_agents: int = 10,
            evaporation_rate: float = 0.1,
            model: str = "gpt-3.5-turbo"
        ):
            """
            Initialize swarm intelligence system.
            
            Args:
                num_agents: Number of agents in the swarm
                evaporation_rate: Rate at which pheromones decay (0.0-1.0)
                model: LLM model for agents
            """
            self.num_agents = num_agents
            self.evaporation_rate = evaporation_rate
            
            # Create swarm agents with varying temperatures for diversity
            self.agents = [
                SwarmAgent(
                    agent_id=f"agent_{i}",
                    model=model,
                    temperature=0.6 + (i / num_agents) * 0.6  # Range: 0.6-1.2
                )
                for i in range(num_agents)
            ]
            
            # Pheromone trail system
            self.pheromones: Dict[str, PheromoneTrail] = {}
            
            # Solution history
            self.all_solutions: List[Solution] = []
        
        def _compute_solution_key(self, solution: str) -> str:
            """
            Compute a key for similar solutions.
            
            Uses first 50 characters as a simple similarity metric.
            In production, would use embeddings or semantic similarity.
            """
            # Simple approach: use first significant words
            words = solution.lower().split()[:8]
            return " ".join(words)
        
        def _deposit_pheromone(
            self,
            solution: Solution
        ):
            """
            Deposit pheromone on a solution path based on quality.
            
            Args:
                solution: Solution to deposit pheromone for
            """
            key = self._compute_solution_key(solution.content)
            
            if key in self.pheromones:
                # Strengthen existing trail
                self.pheromones[key].strength += solution.quality_score
                self.pheromones[key].contributors.append(solution.agent_id)
            else:
                # Create new trail
                self.pheromones[key] = PheromoneTrail(
                    solution_key=key,
                    strength=solution.quality_score,
                    contributors=[solution.agent_id]
                )
        
        def _evaporate_pheromones(self):
            """Evaporate pheromones to forget old solutions."""
            keys_to_remove = []
            
            for key, trail in self.pheromones.items():
                trail.strength *= (1 - self.evaporation_rate)
                
                # Remove very weak trails
                if trail.strength  SwarmBehavior:
            """
            Determine swarm behavior based on iteration.
            
            Early iterations: More exploration
            Later iterations: More exploitation
            """
            progress = iteration / max_iterations
            
            if progress  0.7:
                return SwarmBehavior.EXPLOITATION
            else:
                return SwarmBehavior.BALANCED
        
        def solve(
            self,
            problem: str,
            iterations: int = 5,
            agents_per_iteration: Optional[int] = None
        ) -> Tuple[Solution, List[Solution]]:
            """
            Solve a problem using swarm intelligence.
            
            Args:
                problem: Problem to solve
                iterations: Number of swarm iterations
                agents_per_iteration: Agents to activate per iteration (default: all)
                
            Returns:
                (best_solution, all_solutions)
            """
            if agents_per_iteration is None:
                agents_per_iteration = self.num_agents
            
            print(f"\n[Swarm] Starting with {self.num_agents} agents, {iterations} iterations")
            
            for iteration in range(iterations):
                behavior = self._determine_behavior(iteration, iterations)
                
                print(f"\n[Swarm] Iteration {iteration + 1}/{iterations} - Behavior: {behavior.value}")
                
                # Randomly select agents to activate this iteration
                active_agents = random.sample(self.agents, min(agents_per_iteration, self.num_agents))
                
                iteration_solutions = []
                
                for agent in active_agents:
                    # Get current pheromone trails
                    trails = list(self.pheromones.values())
                    
                    # Agent explores solution
                    solution_text = agent.explore_solution(problem, trails, behavior)
                    
                    # Agent evaluates its own solution
                    quality = agent.evaluate_solution(problem, solution_text)
                    
                    solution = Solution(
                        agent_id=agent.agent_id,
                        content=solution_text,
                        quality_score=quality,
                        iteration=iteration
                    )
                    
                    iteration_solutions.append(solution)
                    self.all_solutions.append(solution)
                    
                    # Deposit pheromone
                    self._deposit_pheromone(solution)
                
                # Show best solution this iteration
                best_iteration = max(iteration_solutions, key=lambda s: s.quality_score)
                print(f"[Swarm] Best this iteration (quality: {best_iteration.quality_score:.2f}): {best_iteration.content[:80]}...")
                
                # Evaporate pheromones
                self._evaporate_pheromones()
                
                # Show pheromone trail status
                print(f"[Swarm] Active pheromone trails: {len(self.pheromones)}")
            
            # Find best overall solution
            best_solution = max(self.all_solutions, key=lambda s: s.quality_score)
            
            print(f"\n[Swarm] Convergence complete!")
            print(f"[Swarm] Best solution quality: {best_solution.quality_score:.2f}")
            
            return best_solution, self.all_solutions
        
        def get_convergence_analysis(self) -> Dict[str, Any]:
            """
            Analyze how the swarm converged on solutions.
            
            Returns:
                Analysis of convergence patterns
            """
            # Quality progression over iterations
            iterations = max(s.iteration for s in self.all_solutions) + 1
            quality_by_iteration = [
                [s.quality_score for s in self.all_solutions if s.iteration == i]
                for i in range(iterations)
            ]
            
            avg_quality = [
                sum(scores) / len(scores) if scores else 0
                for scores in quality_by_iteration
            ]
            
            max_quality = [
                max(scores) if scores else 0
                for scores in quality_by_iteration
            ]
            
            # Solution diversity (number of unique solution keys)
            unique_solutions = len(set(
                self._compute_solution_key(s.content)
                for s in self.all_solutions
            ))
            
            # Top pheromone trails
            top_trails = sorted(
                self.pheromones.values(),
                key=lambda t: t.strength,
                reverse=True
            )[:3]
            
            return {
                "iterations": iterations,
                "total_solutions": len(self.all_solutions),
                "unique_solutions": unique_solutions,
                "avg_quality_progression": avg_quality,
                "max_quality_progression": max_quality,
                "final_avg_quality": avg_quality[-1],
                "final_max_quality": max_quality[-1],
                "top_pheromone_trails": [
                    {
                        "solution": trail.solution_key,
                        "strength": trail.strength,
                        "contributors": len(trail.contributors)
                    }
                    for trail in top_trails
                ]
            }
    
    
    def demonstrate_swarm_intelligence():
        """Demonstrate the Swarm Intelligence pattern with various examples."""
        
        print("=" * 80)
        print("SWARM INTELLIGENCE PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Test 1: Problem-solving task
        print("\n" + "=" * 80)
        print("TEST 1: Creative Problem Solving")
        print("=" * 80)
        
        swarm = SwarmIntelligence(num_agents=8, evaporation_rate=0.15)
        
        problem1 = "How can a small restaurant reduce food waste while maintaining quality?"
        
        best_solution, all_solutions = swarm.solve(problem1, iterations=4)
        
        print("\n" + "-" * 80)
        print("BEST SOLUTION:")
        print("-" * 80)
        print(f"Quality: {best_solution.quality_score:.2f}")
        print(f"From: {best_solution.agent_id} (iteration {best_solution.iteration})")
        print(f"\n{best_solution.content}")
        
        # Show convergence analysis
        analysis = swarm.get_convergence_analysis()
        
        print("\n" + "-" * 80)
        print("CONVERGENCE ANALYSIS:")
        print("-" * 80)
        print(f"Total solutions explored: {analysis['total_solutions']}")
        print(f"Unique solution approaches: {analysis['unique_solutions']}")
        print(f"Quality improvement: {analysis['avg_quality_progression'][0]:.2f} -> {analysis['final_avg_quality']:.2f}")
        
        print("\nQuality progression by iteration:")
        for i, (avg, max_q) in enumerate(zip(analysis['avg_quality_progression'], analysis['max_quality_progression'])):
            print(f"  Iteration {i+1}: avg={avg:.2f}, max={max_q:.2f}")
        
        print("\nTop pheromone trails (strongest convergence):")
        for i, trail in enumerate(analysis['top_pheromone_trails'], 1):
            print(f"  {i}. {trail['solution'][:60]}...")
            print(f"     Strength: {trail['strength']:.2f}, Contributors: {trail['contributors']}")
        
        # Test 2: Optimization task
        print("\n" + "=" * 80)
        print("TEST 2: Optimization Problem")
        print("=" * 80)
        
        swarm2 = SwarmIntelligence(num_agents=6, evaporation_rate=0.2)
        
        problem2 = "Design an efficient morning routine for a busy professional with 90 minutes."
        
        best_solution2, _ = swarm2.solve(problem2, iterations=3, agents_per_iteration=4)
        
        print("\n" + "-" * 80)
        print("BEST SOLUTION:")
        print("-" * 80)
        print(f"Quality: {best_solution2.quality_score:.2f}")
        print(f"\n{best_solution2.content}")
        
        # Test 3: Compare with different swarm sizes
        print("\n" + "=" * 80)
        print("TEST 3: Swarm Size Comparison")
        print("=" * 80)
        
        problem3 = "What are three innovative ways to improve employee engagement?"
        
        swarm_sizes = [4, 8, 12]
        results = []
        
        for size in swarm_sizes:
            print(f"\nTesting swarm size: {size}")
            swarm = SwarmIntelligence(num_agents=size, evaporation_rate=0.15)
            best, all_sols = swarm.solve(problem3, iterations=3, agents_per_iteration=size)
            analysis = swarm.get_convergence_analysis()
            
            results.append({
                "size": size,
                "best_quality": best.quality_score,
                "unique_solutions": analysis['unique_solutions'],
                "solution": best.content
            })
        
        print("\n" + "-" * 80)
        print("COMPARISON RESULTS:")
        print("-" * 80)
        for result in results:
            print(f"\nSwarm size {result['size']}:")
            print(f"  Best quality: {result['best_quality']:.2f}")
            print(f"  Unique approaches: {result['unique_solutions']}")
            print(f"  Solution: {result['solution'][:100]}...")
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Swarm Intelligence pattern demonstrates several key benefits:
    
    1. **Decentralized Problem-Solving**: No single coordinator needed
    2. **Emergent Intelligence**: Complex solutions from simple agent rules
    3. **Robustness**: Failure of individual agents doesn't stop the swarm
    4. **Parallel Exploration**: Multiple solutions explored simultaneously
    
    Key Mechanisms:
    - **Pheromone Trails**: Shared information about solution quality
    - **Evaporation**: Forgetting of old/poor solutions over time
    - **Local Decisions**: Agents decide based on nearby information
    - **Convergence**: Swarm naturally converges on good solutions
    
    Behavior Phases:
    - **Exploration**: Early iterations explore diverse approaches
    - **Balanced**: Middle iterations balance exploration and exploitation
    - **Exploitation**: Late iterations refine best solutions
    
    Use Cases:
    - Optimization problems (routing, scheduling, design)
    - Search and exploration tasks
    - Distributed problem-solving
    - Situations requiring diverse perspectives
    
    The pattern is particularly effective when:
    - Problem space is large and complex
    - Multiple good solutions may exist
    - Parallel exploration is beneficial
    - Robustness to individual failures is important
    - Emergent intelligence is desired over centralized control
    """)
    
    
    if __name__ == "__main__":
        demonstrate_swarm_intelligence()
    
        

    Agentic Design Pattern: Swarm Intelligence

    Pattern 015: Swarm Intelligence – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Swarm Intelligence pattern implementation.


    Pattern Description

    Pattern 015: Swarm Intelligence

    Description: The Swarm Intelligence pattern uses many simple, decentralized agents that collaborate through local interactions to solve complex problems. Inspired by natural phenomena like ant colonies, bee swarms, and bird flocking, this pattern achieves emergent intelligent behavior without centralized control.

    Components: – Swarm Agents: Simple agents with local decision rules – Environment/Pheromone System: Shared information space – Local Interaction Rules: How agents influence each other – Emergent Behavior: Complex patterns from simple rules

    Use Cases: – Optimization problems (path finding, resource allocation) – Search and exploration tasks – Distributed problem-solving – Parallel hypothesis exploration

    LangChain Implementation: Uses multiple simple LLM agents with local decision-making, a shared information space (pheromone trails), iterative exploration rounds, and convergence detection to achieve swarm-based optimization.

    Key Features: – Decentralized decision-making – Pheromone-based communication – Iterative convergence – Parallel exploration


    File Statistics

    • Total Lines: 558
    • Classes: 5
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 015: Swarm Intelligence
    
    Description:
        The Swarm Intelligence pattern uses many simple, decentralized agents that collaborate
        through local interactions to solve complex problems. Inspired by natural phenomena like
        ant colonies, bee swarms, and bird flocking, this pattern achieves emergent intelligent
        behavior without centralized control.
    
    Components:
        - Swarm Agents: Simple agents with local decision rules
        - Environment/Pheromone System: Shared information space
        - Local Interaction Rules: How agents influence each other
        - Emergent Behavior: Complex patterns from simple rules
    
    Use Cases:
        - Optimization problems (path finding, resource allocation)
        - Search and exploration tasks
        - Distributed problem-solving
        - Parallel hypothesis exploration
    
    LangChain Implementation:
        Uses multiple simple LLM agents with local decision-making, a shared information
        space (pheromone trails), iterative exploration rounds, and convergence detection
        to achieve swarm-based optimization.
    
    Key Features:
        - Decentralized decision-making
        - Pheromone-based communication
        - Iterative convergence
        - Parallel exploration
    """
    

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


    Import Statements

    Standard Library Imports

    import collections.defaultdict
    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 typing.Any
    import typing.Dict
    import typing.List
    import typing.Optional
    import typing.Tuple
    

    Purpose: Core Python functionality: – collections.defaultdict: 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 – 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: Solution

    Lines 50-55

    Purpose:

    Represents a solution explored by an agent.
    

    Class: PheromoneTrail

    Lines 59-63

    Purpose:

    Represents accumulated pheromone on a solution path.
    

    Class: SwarmBehavior

    Lines 66-70

    Inherits from: Enum

    Purpose:

    Types of swarm behaviors.
    

    Class: SwarmAgent

    Lines 73-188

    Purpose:

    A simple swarm agent that makes local decisions based on pheromones.
    

    Methods:

    __init__(self, agent_id, model, temperature)

    Lines 78-94

    Purpose:

    Initialize a swarm agent.
    
    Args:
        agent_id: Unique identifier for this agent
        model: LLM model to use
        temperature: Creativity/exploration level
    

    Parameters: – agent_id: Agent id – model: Language model instance – temperature: Sampling temperature (0-1)

    explore_solution(self, problem, pheromone_trails, behavior)

    Lines 96-152

    Purpose:

    Explore a solution based on local information and pheromones.
    
    Args:
        problem: Problem to solve
        pheromone_trails: Current pheromone information
        behavior: Exploration vs exploitation preference
    
    Returns:
        Proposed solution
    

    Parameters: – problem: Problem – pheromone_trails: Pheromone trails – behavior: Behavior

    Returns: str

    evaluate_solution(self, problem, solution)

    Lines 154-188

    Purpose:

    Evaluate the quality of a solution.
    
    Args:
        problem: Original problem
        solution: Proposed solution
    
    Returns:
        Quality score (0.0-1.0)
    

    Parameters: – problem: Problem – solution: Solution

    Returns: float


    Class: SwarmIntelligence

    Lines 191-422

    Purpose:

    Swarm intelligence system coordinating multiple simple agents.
    
    Uses pheromone-based communication for decentralized problem-solving.
    

    Methods:

    __init__(self, num_agents, evaporation_rate, model)

    Lines 198-229

    Purpose:

    Initialize swarm intelligence system.
    
    Args:
        num_agents: Number of agents in the swarm
        evaporation_rate: Rate at which pheromones decay (0.0-1.0)
        model: LLM model for agents
    

    Parameters: – num_agents: Num agents – evaporation_rate: Evaporation rate – model: Language model instance

    _compute_solution_key(self, solution)

    Lines 231-240

    Purpose:

    Compute a key for similar solutions.
    
    Uses first 50 characters as a simple similarity metric.
    In production, would use embeddings or semantic similarity.
    

    Parameters: – solution: Solution

    Returns: str

    _deposit_pheromone(self, solution)

    Lines 242-264

    Purpose:

    Deposit pheromone on a solution path based on quality.
    
    Args:
        solution: Solution to deposit pheromone for
    

    Parameters: – solution: Solution

    _evaporate_pheromones(self)

    Lines 266-278

    Purpose:

    Evaporate pheromones to forget old solutions.
    

    Parameters:

    _determine_behavior(self, iteration, max_iterations)

    Lines 280-294

    Purpose:

    Determine swarm behavior based on iteration.
    
    Early iterations: More exploration
    Later iterations: More exploitation
    

    Parameters: – iteration: Iteration – max_iterations: Max iterations

    Returns: SwarmBehavior

    solve(self, problem, iterations, agents_per_iteration)

    Lines 296-367

    Purpose:

    Solve a problem using swarm intelligence.
    
    Args:
        problem: Problem to solve
        iterations: Number of swarm iterations
        agents_per_iteration: Agents to activate per iteration (default: all)
    
    Returns:
        (best_solution, all_solutions)
    

    Parameters: – problem: Problem – iterations: Iterations – agents_per_iteration: Agents per iteration

    Returns: <ast.Subscript object at 0x000001A06D6A8610>

    get_convergence_analysis(self)

    Lines 369-422

    Purpose:

    Analyze how the swarm converged on solutions.
    
    Returns:
        Analysis of convergence patterns
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D16AE50>


    Functions

    demonstrate_swarm_intelligence()

    Lines 425-553

    Purpose:

    Demonstrate the Swarm Intelligence pattern with various examples.
    

    Key Concepts Summary

    Pattern Overview

    The Swarm Intelligence 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 014
    • Pattern 016
    • Other Multi-Agent Patterns (012-019)

    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 Swarm Intelligence 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: 015_swarm_intelligence.py
    Total Lines: 558
    Pattern Category: AI Agent Design Pattern #015


    Generated from actual implementation code
    For educational and reference purposes