Tag: artificial-intelligence

  • Agentic Design Pattern: Self Consistency

    
        """
    Pattern 005: Self-Consistency
    
    Description:
        Samples multiple reasoning paths for the same question and selects
        the most consistent answer through majority voting or consistency scoring.
        This approach improves reliability by generating diverse solutions and
        aggregating them.
    
    Key Concepts:
        - Multiple Sampling: Generate several independent reasoning chains
        - Diverse Reasoning: Use temperature or prompt variations
        - Aggregation: Majority voting or consistency analysis
        - Answer Selection: Choose most frequent or highest confidence answer
    
    Use Cases:
        - Math problems with multiple solution methods
        - Questions with ambiguous reasoning paths
        - Improving reliability of complex reasoning
        - Reducing model hallucinations
    
    LangChain Implementation:
        Multiple chain invocations with temperature variation and result aggregation.
    """
    
    import os
    from typing import List, Dict, Any, Tuple
    from collections import Counter
    import re
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    # Load environment variables
    load_dotenv()
    
    
    class SelfConsistencyAgent:
        """Agent that uses self-consistency for reliable reasoning."""
        
        def __init__(self, model_name: str = "gpt-3.5-turbo", num_samples: int = 5):
            """
            Initialize the Self-Consistency agent.
            
            Args:
                model_name: Name of the OpenAI model to use
                num_samples: Number of reasoning paths to sample
            """
            self.model_name = model_name
            self.num_samples = num_samples
            self.base_temperature = 0.7
            
        def _create_llm(self, temperature: float) -> ChatOpenAI:
            """Create an LLM instance with specified temperature."""
            return ChatOpenAI(
                model=self.model_name,
                temperature=temperature,
                api_key=os.getenv("OPENAI_API_KEY")
            )
        
        def generate_reasoning_paths(self, question: str, context: str = "") -> List[Dict[str, str]]:
            """
            Generate multiple diverse reasoning paths for a question.
            
            Args:
                question: The question to answer
                context: Optional context information
                
            Returns:
                List of dictionaries with 'reasoning' and 'answer' keys
            """
            # Base prompt for chain-of-thought reasoning
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Answer the question with clear step-by-step reasoning.
    Show your work and explain each step.
    At the end, clearly state your final answer."""),
                ("human", "{context}\n\nQuestion: {question}\n\nLet's think step by step:")
            ])
            
            reasoning_paths = []
            
            # Generate multiple samples with varying temperatures
            for i in range(self.num_samples):
                # Vary temperature for diversity
                temperature = self.base_temperature + (i - self.num_samples // 2) * 0.1
                temperature = max(0.1, min(1.0, temperature))
                
                llm = self._create_llm(temperature)
                chain = prompt | llm | StrOutputParser()
                
                try:
                    response = chain.invoke({
                        "question": question,
                        "context": context if context else "No additional context."
                    })
                    
                    # Extract answer from response
                    answer = self._extract_answer(response)
                    
                    reasoning_paths.append({
                        "reasoning": response,
                        "answer": answer,
                        "temperature": temperature
                    })
                    
                except Exception as e:
                    print(f"Warning: Sample {i+1} failed: {str(e)}")
                    continue
            
            return reasoning_paths
        
        def _extract_answer(self, reasoning: str) -> str:
            """Extract the final answer from reasoning text."""
            # Look for common answer patterns
            patterns = [
                r"final answer is:?\s*(.+?)(?:\n|$)",
                r"answer is:?\s*(.+?)(?:\n|$)",
                r"therefore,?\s+(?:the answer is )?\s*(.+?)(?:\n|$)",
                r"thus,?\s+(.+?)(?:\n|$)",
                r"so,?\s+(?:the answer is )?\s*(.+?)(?:\n|$)",
            ]
            
            reasoning_lower = reasoning.lower()
            for pattern in patterns:
                match = re.search(pattern, reasoning_lower, re.IGNORECASE)
                if match:
                    answer = match.group(1).strip()
                    # Clean up the answer
                    answer = re.sub(r'[*_]', '', answer)  # Remove markdown
                    return answer
            
            # If no pattern matches, take last non-empty line
            lines = [line.strip() for line in reasoning.split('\n') if line.strip()]
            return lines[-1] if lines else reasoning[:100]
        
        def aggregate_by_majority_vote(self, reasoning_paths: List[Dict[str, str]]) -> Dict[str, Any]:
            """
            Aggregate answers using majority voting.
            
            Args:
                reasoning_paths: List of reasoning paths with answers
                
            Returns:
                Dictionary with final answer and aggregation statistics
            """
            answers = [path['answer'] for path in reasoning_paths]
            
            # Normalize answers for comparison (lowercase, strip punctuation)
            normalized_answers = []
            for ans in answers:
                normalized = ans.lower().strip().rstrip('.')
                normalized_answers.append(normalized)
            
            # Count occurrences
            answer_counts = Counter(normalized_answers)
            most_common_answer, count = answer_counts.most_common(1)[0]
            
            # Find original form of the answer
            original_answer = next(
                (answers[i] for i, norm in enumerate(normalized_answers) 
                 if norm == most_common_answer),
                most_common_answer
            )
            
            return {
                "final_answer": original_answer,
                "confidence": count / len(answers),
                "vote_count": count,
                "total_samples": len(answers),
                "all_answers": answer_counts,
                "agreement_rate": count / len(answers)
            }
        
        def aggregate_by_consistency_score(self, reasoning_paths: List[Dict[str, str]], 
                                           question: str) -> Dict[str, Any]:
            """
            Aggregate answers using consistency scoring via LLM.
            
            Args:
                reasoning_paths: List of reasoning paths with answers
                question: Original question
                
            Returns:
                Dictionary with final answer and consistency scores
            """
            # Use LLM to evaluate consistency of each answer with others
            answers_text = "\n".join([
                f"{i+1}. {path['answer']}" 
                for i, path in enumerate(reasoning_paths)
            ])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are evaluating multiple answers to the same question.
    Identify which answer is most consistent with the majority of other answers.
    Consider semantic similarity, not just exact matches.
    
    Respond with the number (1, 2, 3, etc.) of the most consistent answer."""),
                ("human", """Question: {question}
    
    Answers:
    {answers}
    
    Most consistent answer number:""")
            ])
            
            llm = self._create_llm(0.3)  # Low temperature for consistent evaluation
            chain = prompt | llm | StrOutputParser()
            
            try:
                response = chain.invoke({
                    "question": question,
                    "answers": answers_text
                })
                
                # Extract number
                match = re.search(r'\d+', response)
                if match:
                    index = int(match.group()) - 1
                    if 0 <= index  Dict[str, Any]:
            """
            Solve a question using self-consistency.
            
            Args:
                question: The question to answer
                context: Optional context
                method: Aggregation method ('majority_vote' or 'consistency_score')
                
            Returns:
                Dictionary with solution and metadata
            """
            print(f"\nQuestion: {question}\n")
            print(f"Generating {self.num_samples} reasoning paths...\n")
            
            # Generate multiple reasoning paths
            reasoning_paths = self.generate_reasoning_paths(question, context)
            
            if not reasoning_paths:
                return {
                    "question": question,
                    "final_answer": "Unable to generate reasoning paths",
                    "error": "All samples failed"
                }
            
            print(f"Generated {len(reasoning_paths)} paths\n")
            
            # Show individual reasoning paths
            for i, path in enumerate(reasoning_paths, 1):
                print(f"Path {i} (temp={path['temperature']:.2f}):")
                print(f"  Answer: {path['answer']}")
                print()
            
            # Aggregate results
            if method == "majority_vote":
                result = self.aggregate_by_majority_vote(reasoning_paths)
            else:
                result = self.aggregate_by_consistency_score(reasoning_paths, question)
            
            result["question"] = question
            result["reasoning_paths"] = len(reasoning_paths)
            
            return result
    
    
    def demonstrate_self_consistency():
        """Demonstrates the Self-Consistency pattern."""
        
        print("=" * 80)
        print("PATTERN 005: Self-Consistency")
        print("=" * 80)
        print()
        print("Self-Consistency improves answer reliability by:")
        print("1. Generating multiple independent reasoning paths")
        print("2. Sampling with different temperatures for diversity")
        print("3. Aggregating answers via majority voting or consistency scoring")
        print()
        
        # Create agent
        agent = SelfConsistencyAgent(num_samples=5)
        
        # Test cases
        test_cases = [
            {
                "question": "If a train travels 120 miles in 2 hours, then travels 180 miles in 3 hours, what is its average speed for the entire journey?",
                "context": "",
                "method": "majority_vote"
            },
            {
                "question": "A bat and ball cost $1.10 together. The bat costs $1.00 more than the ball. How much does the ball cost?",
                "context": "",
                "method": "majority_vote"
            },
            {
                "question": "Is it ethical for AI to make medical diagnoses?",
                "context": "Consider patient safety, accuracy, and doctor-patient relationships.",
                "method": "consistency_score"
            }
        ]
        
        for i, test_case in enumerate(test_cases, 1):
            print(f"\n{'=' * 80}")
            print(f"Example {i}: {test_case['method'].replace('_', ' ').title()}")
            print('=' * 80)
            
            try:
                result = agent.solve_with_self_consistency(
                    question=test_case['question'],
                    context=test_case['context'],
                    method=test_case['method']
                )
                
                print(f"\n{'=' * 80}")
                print("FINAL RESULT")
                print('=' * 80)
                print(f"\nFinal Answer: {result['final_answer']}")
                
                if 'confidence' in result:
                    print(f"Confidence: {result['confidence']:.1%}")
                    print(f"Agreement Rate: {result['agreement_rate']:.1%}")
                    print(f"Vote Distribution: {dict(result['all_answers'])}")
                
                print(f"Reasoning Paths: {result['reasoning_paths']}/{agent.num_samples}")
                
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
                import traceback
                traceback.print_exc()
        
        # Summary
        print("\n\n" + "=" * 80)
        print("SELF-CONSISTENCY PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("Key Techniques Demonstrated:")
        print("1. Multiple Sampling: Generated 5 diverse reasoning paths")
        print("2. Temperature Variation: Used different temperatures for diversity")
        print("3. Majority Voting: Aggregated answers by frequency")
        print("4. Consistency Scoring: LLM-based semantic consistency evaluation")
        print("5. Confidence Metrics: Agreement rate and vote distribution")
        print()
        print("Benefits:")
        print("- Improved reliability through multiple reasoning paths")
        print("- Reduced impact of outlier/hallucinated answers")
        print("- Confidence estimation via agreement metrics")
        print("- Works with any reasoning task")
        print()
        print("LangChain Components Used:")
        print("- ChatPromptTemplate: Structures reasoning prompts")
        print("- StrOutputParser: Parses LLM outputs")
        print("- Temperature variation: Creates diverse samples")
        print("- Multiple chain invocations: Parallel reasoning paths")
        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_self_consistency()
    
        

    Agentic Design Pattern: Self Consistency

    Pattern 005: Self Consistency – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 005: Self-Consistency

    Description: Samples multiple reasoning paths for the same question and selects the most consistent answer through majority voting or consistency scoring. This approach improves reliability by generating diverse solutions and aggregating them.

    Key Concepts: – Multiple Sampling: Generate several independent reasoning chains – Diverse Reasoning: Use temperature or prompt variations – Aggregation: Majority voting or consistency analysis – Answer Selection: Choose most frequent or highest confidence answer

    Use Cases: – Math problems with multiple solution methods – Questions with ambiguous reasoning paths – Improving reliability of complex reasoning – Reducing model hallucinations

    LangChain Implementation: Multiple chain invocations with temperature variation and result aggregation.


    File Statistics

    • Total Lines: 384
    • Classes: 1
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 005: Self-Consistency
    
    Description:
        Samples multiple reasoning paths for the same question and selects
        the most consistent answer through majority voting or consistency scoring.
        This approach improves reliability by generating diverse solutions and
        aggregating them.
    
    Key Concepts:
        - Multiple Sampling: Generate several independent reasoning chains
        - Diverse Reasoning: Use temperature or prompt variations
        - Aggregation: Majority voting or consistency analysis
        - Answer Selection: Choose most frequent or highest confidence answer
    
    Use Cases:
        - Math problems with multiple solution methods
        - Questions with ambiguous reasoning paths
        - Improving reliability of complex reasoning
        - Reducing model hallucinations
    
    LangChain Implementation:
        Multiple chain invocations with temperature variation and result aggregation.
    """
    

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


    Import Statements

    Standard Library Imports

    import collections.Counter
    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import os
    import re
    import typing.Any
    import typing.Dict
    import typing.List
    import typing.Tuple
    

    Purpose: Core Python functionality: – collections.Counter: Specialized container datatypes – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – re: Regular expression operations – 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.Tuple: Type hints for better code documentation (List, Dict, Any, Optional)

    LangChain Imports

    from langchain_openai.ChatOpenAI
    

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

    Other Imports

    from dotenv.load_dotenv
    from traceback
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: SelfConsistencyAgent

    Lines 40-283

    Purpose:

    Agent that uses self-consistency for reliable reasoning.
    

    Methods:

    __init__(self, model_name, num_samples)

    Lines 43-53

    Purpose:

    Initialize the Self-Consistency agent.
    
    Args:
        model_name: Name of the OpenAI model to use
        num_samples: Number of reasoning paths to sample
    

    Parameters: – model_name: Language model instance – num_samples: Num samples

    _create_llm(self, temperature)

    Lines 55-61

    Purpose:

    Create an LLM instance with specified temperature.
    

    Parameters: – temperature: Sampling temperature (0-1)

    Returns: ChatOpenAI

    generate_reasoning_paths(self, question, context)

    Lines 63-112

    Purpose:

    Generate multiple diverse reasoning paths for a question.
    
    Args:
        question: The question to answer
        context: Optional context information
    
    Returns:
        List of dictionaries with 'reasoning' and 'answer' keys
    

    Parameters: – question: Question to answer – context: Context

    Returns: <ast.Subscript object at 0x000001A06D696050>

    _extract_answer(self, reasoning)

    Lines 114-136

    Purpose:

    Extract the final answer from reasoning text.
    

    Parameters: – reasoning: Reasoning

    Returns: str

    aggregate_by_majority_vote(self, reasoning_paths)

    Lines 138-174

    Purpose:

    Aggregate answers using majority voting.
    
    Args:
        reasoning_paths: List of reasoning paths with answers
    
    Returns:
        Dictionary with final answer and aggregation statistics
    

    Parameters: – reasoning_paths: Reasoning paths

    Returns: <ast.Subscript object at 0x000001A06D168F10>

    aggregate_by_consistency_score(self, reasoning_paths, question)

    Lines 176-238

    Purpose:

    Aggregate answers using consistency scoring via LLM.
    
    Args:
        reasoning_paths: List of reasoning paths with answers
        question: Original question
    
    Returns:
        Dictionary with final answer and consistency scores
    

    Parameters: – reasoning_paths: Reasoning paths – question: Question to answer

    Returns: <ast.Subscript object at 0x000001A06D158310>

    solve_with_self_consistency(self, question, context, method)

    Lines 240-283

    Purpose:

    Solve a question using self-consistency.
    
    Args:
        question: The question to answer
        context: Optional context
        method: Aggregation method ('majority_vote' or 'consistency_score')
    
    Returns:
        Dictionary with solution and metadata
    

    Parameters: – question: Question to answer – context: Context – method: Method

    Returns: <ast.Subscript object at 0x000001A06D156DD0>


    Functions

    demonstrate_self_consistency()

    Lines 286-373

    Purpose:

    Demonstrates the Self-Consistency pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Self Consistency 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 004
    • Pattern 006
    • Other Reasoning Patterns (001-011)

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


    Summary

    The Self Consistency 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: 005_self_consistency.py
    Total Lines: 384
    Pattern Category: AI Agent Design Pattern #005


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Graph Of Thoughts

    
        """
    Pattern 004: Graph-of-Thoughts (GoT)
    
    Description:
        Represents reasoning as a directed graph with nodes and edges.
        Unlike Tree-of-Thoughts which explores a tree structure, GoT allows for
        more flexible graph structures with cycles, merging paths, and complex
        relationships between thoughts.
    
    Components:
        - Thought Nodes: Individual reasoning steps or ideas
        - Transformation Edges: Operations that transform one thought to another
        - Aggregation: Combining multiple thoughts into one
        - Graph Operations: Add, merge, transform, aggregate
    
    Use Cases:
        - Complex multi-step reasoning with dependencies
        - Document analysis with cross-references
        - Problem-solving requiring non-linear thinking
        - Knowledge synthesis from multiple sources
    
    LangChain Implementation:
        Uses LangGraph for state graph management and custom graph operations.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Set
    from enum import Enum
    from dataclasses import dataclass, field
    from collections import defaultdict
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    # Load environment variables
    load_dotenv()
    
    
    class EdgeType(Enum):
        """Types of edges in the graph."""
        TRANSFORM = "transform"  # Transform one thought to another
        AGGREGATE = "aggregate"  # Combine multiple thoughts
        REFINE = "refine"  # Refine/improve a thought
        COMPARE = "compare"  # Compare two thoughts
        VALIDATE = "validate"  # Validate a thought
    
    
    @dataclass
    class ThoughtNode:
        """Represents a node in the graph of thoughts."""
        id: str
        content: str
        score: float = 0.0
        metadata: Dict[str, Any] = field(default_factory=dict)
        
        def __hash__(self):
            return hash(self.id)
        
        def __eq__(self, other):
            return isinstance(other, ThoughtNode) and self.id == other.id
    
    
    @dataclass
    class ThoughtEdge:
        """Represents an edge in the graph of thoughts."""
        source: str  # Source node ID
        target: str  # Target node ID
        edge_type: EdgeType
        operation: str  # Description of the operation
        weight: float = 1.0
    
    
    class ThoughtGraph:
        """Graph structure for managing thoughts."""
        
        def __init__(self):
            self.nodes: Dict[str, ThoughtNode] = {}
            self.edges: List[ThoughtEdge] = []
            self.adjacency: Dict[str, List[str]] = defaultdict(list)
            self.reverse_adjacency: Dict[str, List[str]] = defaultdict(list)
        
        def add_node(self, node: ThoughtNode):
            """Add a node to the graph."""
            self.nodes[node.id] = node
        
        def add_edge(self, edge: ThoughtEdge):
            """Add an edge to the graph."""
            self.edges.append(edge)
            self.adjacency[edge.source].append(edge.target)
            self.reverse_adjacency[edge.target].append(edge.source)
        
        def get_node(self, node_id: str) -> Optional[ThoughtNode]:
            """Get a node by ID."""
            return self.nodes.get(node_id)
        
        def get_children(self, node_id: str) -> List[ThoughtNode]:
            """Get all child nodes."""
            return [self.nodes[child_id] for child_id in self.adjacency.get(node_id, [])]
        
        def get_parents(self, node_id: str) -> List[ThoughtNode]:
            """Get all parent nodes."""
            return [self.nodes[parent_id] for parent_id in self.reverse_adjacency.get(node_id, [])]
        
        def get_all_paths(self, start_id: str, end_id: str) -> List[List[str]]:
            """Find all paths from start to end node."""
            paths = []
            visited = set()
            
            def dfs(current: str, path: List[str]):
                if current == end_id:
                    paths.append(path.copy())
                    return
                
                visited.add(current)
                for neighbor in self.adjacency.get(current, []):
                    if neighbor not in visited:
                        path.append(neighbor)
                        dfs(neighbor, path)
                        path.pop()
                visited.remove(current)
            
            dfs(start_id, [start_id])
            return paths
    
    
    class GraphOfThoughtsAgent:
        """Agent that uses Graph-of-Thoughts reasoning."""
        
        def __init__(self, model_name: str = "gpt-3.5-turbo", temperature: float = 0.7):
            """Initialize the Graph-of-Thoughts agent."""
            self.llm = ChatOpenAI(
                model=model_name,
                temperature=temperature,
                api_key=os.getenv("OPENAI_API_KEY")
            )
            self.graph = ThoughtGraph()
            self.node_counter = 0
        
        def generate_initial_thoughts(self, problem: str, num_thoughts: int = 3) -> List[ThoughtNode]:
            """Generate initial thoughts for the problem."""
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Generate {num_thoughts} diverse initial approaches or perspectives 
    for solving the problem. Each should be a distinct starting point.
    
    Format your response as:
    1. [First approach]
    2. [Second approach]
    3. [Third approach]"""),
                ("human", "Problem: {problem}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            response = chain.invoke({"problem": problem, "num_thoughts": num_thoughts})
            
            # Parse thoughts
            thoughts = []
            for line in response.strip().split('\n'):
                line = line.strip()
                if line and (line[0].isdigit() or line.startswith('-')):
                    content = line.split('.', 1)[-1].strip().lstrip('- ').strip()
                    if content:
                        node = ThoughtNode(
                            id=f"node_{self.node_counter}",
                            content=content,
                            metadata={"type": "initial", "problem": problem}
                        )
                        self.node_counter += 1
                        thoughts.append(node)
                        self.graph.add_node(node)
            
            return thoughts[:num_thoughts]
        
        def transform_thought(self, node: ThoughtNode, transformation: str) -> ThoughtNode:
            """Transform a thought using a specific operation."""
            prompt = ChatPromptTemplate.from_messages([
                ("system", "Transform the given thought by: {transformation}"),
                ("human", "Original thought: {thought}\n\nTransformed thought:")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            result = chain.invoke({
                "transformation": transformation,
                "thought": node.content
            })
            
            new_node = ThoughtNode(
                id=f"node_{self.node_counter}",
                content=result.strip(),
                metadata={"type": "transformed", "parent": node.id}
            )
            self.node_counter += 1
            
            self.graph.add_node(new_node)
            self.graph.add_edge(ThoughtEdge(
                source=node.id,
                target=new_node.id,
                edge_type=EdgeType.TRANSFORM,
                operation=transformation
            ))
            
            return new_node
        
        def aggregate_thoughts(self, nodes: List[ThoughtNode], aggregation_type: str = "synthesis") -> ThoughtNode:
            """Aggregate multiple thoughts into one."""
            thoughts_text = "\n".join([f"- {node.content}" for node in nodes])
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Synthesize the following thoughts into a single, coherent insight.
    Combine the key ideas while maintaining logical consistency."""),
                ("human", "Thoughts to synthesize:\n{thoughts}\n\nSynthesized insight:")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            result = chain.invoke({"thoughts": thoughts_text})
            
            new_node = ThoughtNode(
                id=f"node_{self.node_counter}",
                content=result.strip(),
                metadata={"type": "aggregated", "parents": [n.id for n in nodes]}
            )
            self.node_counter += 1
            
            self.graph.add_node(new_node)
            for node in nodes:
                self.graph.add_edge(ThoughtEdge(
                    source=node.id,
                    target=new_node.id,
                    edge_type=EdgeType.AGGREGATE,
                    operation=aggregation_type
                ))
            
            return new_node
        
        def refine_thought(self, node: ThoughtNode, feedback: str) -> ThoughtNode:
            """Refine a thought based on feedback."""
            prompt = ChatPromptTemplate.from_messages([
                ("system", "Refine the thought based on this feedback: {feedback}"),
                ("human", "Original thought: {thought}\n\nRefined thought:")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            result = chain.invoke({
                "feedback": feedback,
                "thought": node.content
            })
            
            new_node = ThoughtNode(
                id=f"node_{self.node_counter}",
                content=result.strip(),
                metadata={"type": "refined", "parent": node.id}
            )
            self.node_counter += 1
            
            self.graph.add_node(new_node)
            self.graph.add_edge(ThoughtEdge(
                source=node.id,
                target=new_node.id,
                edge_type=EdgeType.REFINE,
                operation="refinement"
            ))
            
            return new_node
        
        def evaluate_thought(self, node: ThoughtNode, problem: str) -> float:
            """Evaluate how well a thought addresses the problem."""
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Rate how well this thought addresses the problem on a scale of 0.0 to 1.0.
    Consider relevance, feasibility, and completeness.
    Respond with ONLY a number between 0.0 and 1.0."""),
                ("human", """Problem: {problem}
    
    Thought: {thought}
    
    Score (0.0-1.0):""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            try:
                response = chain.invoke({"problem": problem, "thought": node.content})
                score = float(response.strip())
                node.score = max(0.0, min(1.0, score))
                return node.score
            except:
                return 0.5
        
        def solve_with_graph(self, problem: str, max_iterations: int = 3) -> Dict[str, Any]:
            """
            Solve a problem using Graph-of-Thoughts approach.
            
            Args:
                problem: The problem to solve
                max_iterations: Maximum number of transformation iterations
                
            Returns:
                Dictionary with solution and graph information
            """
            print(f"\nSolving: {problem}\n")
            
            # Generate initial thoughts
            print("Step 1: Generate initial thoughts")
            initial_thoughts = self.generate_initial_thoughts(problem, num_thoughts=3)
            for i, node in enumerate(initial_thoughts, 1):
                print(f"  Thought {i}: {node.content[:70]}...")
            
            # Transform and refine thoughts
            print(f"\nStep 2: Transform and refine thoughts")
            transformed_nodes = []
            
            for node in initial_thoughts[:2]:  # Transform first two
                # Apply transformations
                transformation = "Make it more specific and actionable"
                transformed = self.transform_thought(node, transformation)
                print(f"  Transformed: {transformed.content[:70]}...")
                transformed_nodes.append(transformed)
            
            # Aggregate thoughts
            print(f"\nStep 3: Aggregate insights")
            if len(transformed_nodes) >= 2:
                aggregated = self.aggregate_thoughts(transformed_nodes[:2])
                print(f"  Aggregated: {aggregated.content[:70]}...")
                
                # Refine the aggregated thought
                print(f"\nStep 4: Refine final insight")
                refined = self.refine_thought(aggregated, "Make it more comprehensive and practical")
                print(f"  Refined: {refined.content[:70]}...")
                
                # Evaluate final thought
                final_score = self.evaluate_thought(refined, problem)
                print(f"\n  Final score: {final_score:.2f}")
                
                final_node = refined
            else:
                final_node = transformed_nodes[0] if transformed_nodes else initial_thoughts[0]
                final_score = self.evaluate_thought(final_node, problem)
            
            return {
                "problem": problem,
                "solution": final_node.content,
                "score": final_score,
                "graph_stats": {
                    "total_nodes": len(self.graph.nodes),
                    "total_edges": len(self.graph.edges),
                    "node_types": self._count_node_types()
                }
            }
        
        def _count_node_types(self) -> Dict[str, int]:
            """Count nodes by type."""
            counts = defaultdict(int)
            for node in self.graph.nodes.values():
                node_type = node.metadata.get("type", "unknown")
                counts[node_type] += 1
            return dict(counts)
        
        def visualize_graph(self) -> str:
            """Generate a text visualization of the graph."""
            lines = ["Graph Structure:", "=" * 60]
            
            for node in self.graph.nodes.values():
                node_type = node.metadata.get("type", "unknown")
                lines.append(f"\n[{node.id}] ({node_type})")
                lines.append(f"  Content: {node.content[:60]}...")
                lines.append(f"  Score: {node.score:.2f}")
                
                children = self.graph.get_children(node.id)
                if children:
                    lines.append(f"  Children: {', '.join([c.id for c in children])}")
            
            return "\n".join(lines)
    
    
    def demonstrate_got_pattern():
        """Demonstrates the Graph-of-Thoughts pattern."""
        
        print("=" * 80)
        print("PATTERN 004: Graph-of-Thoughts (GoT)")
        print("=" * 80)
        print()
        
        # Create GoT agent
        agent = GraphOfThoughtsAgent()
        
        # Test problems
        problems = [
            "Design a sustainable urban transportation system for a growing city",
            "Create a strategy to improve team collaboration in a remote work environment"
        ]
        
        for i, problem in enumerate(problems, 1):
            print(f"\n{'=' * 80}")
            print(f"Problem {i}: {problem}")
            print('=' * 80)
            
            try:
                # Reset agent for new problem
                agent = GraphOfThoughtsAgent()
                
                result = agent.solve_with_graph(problem, max_iterations=3)
                
                print(f"\n\n{'=' * 80}")
                print("SOLUTION")
                print('=' * 80)
                print(result['solution'])
                print(f"\nFinal Score: {result['score']:.2f}")
                print(f"\nGraph Statistics:")
                print(f"  Total Nodes: {result['graph_stats']['total_nodes']}")
                print(f"  Total Edges: {result['graph_stats']['total_edges']}")
                print(f"  Node Types: {result['graph_stats']['node_types']}")
                
                # Show graph structure
                print(f"\n{agent.visualize_graph()}")
                
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
                import traceback
                traceback.print_exc()
        
        # Summary
        print("\n\n" + "=" * 80)
        print("GRAPH-OF-THOUGHTS PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("Key Features Demonstrated:")
        print("1. Graph structure for non-linear reasoning")
        print("2. Thought transformation operations")
        print("3. Aggregation of multiple thoughts")
        print("4. Refinement based on feedback")
        print("5. Flexible graph operations (transform, aggregate, refine)")
        print()
        print("Advantages over Tree-of-Thoughts:")
        print("- More flexible thought relationships")
        print("- Can merge multiple reasoning paths")
        print("- Supports cycles and cross-references")
        print("- Better for complex, interconnected problems")
        print()
        print("LangChain Components Used:")
        print("- ChatPromptTemplate: Structures transformation prompts")
        print("- StrOutputParser: Parses LLM responses")
        print("- Custom graph structure: Manages thought relationships")
        print("- Multiple edge types: Different operations between thoughts")
        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_got_pattern()
    
        

    Agentic Design Pattern: Graph Of Thoughts

    Pattern 004: Graph Of Thoughts – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Graph Of Thoughts pattern implementation.


    Pattern Description

    Pattern 004: Graph-of-Thoughts (GoT)

    Description: Represents reasoning as a directed graph with nodes and edges. Unlike Tree-of-Thoughts which explores a tree structure, GoT allows for more flexible graph structures with cycles, merging paths, and complex relationships between thoughts.

    Components: – Thought Nodes: Individual reasoning steps or ideas – Transformation Edges: Operations that transform one thought to another – Aggregation: Combining multiple thoughts into one – Graph Operations: Add, merge, transform, aggregate

    Use Cases: – Complex multi-step reasoning with dependencies – Document analysis with cross-references – Problem-solving requiring non-linear thinking – Knowledge synthesis from multiple sources

    LangChain Implementation: Uses LangGraph for state graph management and custom graph operations.


    File Statistics

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

    Module Documentation

    """
    Pattern 004: Graph-of-Thoughts (GoT)
    
    Description:
        Represents reasoning as a directed graph with nodes and edges.
        Unlike Tree-of-Thoughts which explores a tree structure, GoT allows for
        more flexible graph structures with cycles, merging paths, and complex
        relationships between thoughts.
    
    Components:
        - Thought Nodes: Individual reasoning steps or ideas
        - Transformation Edges: Operations that transform one thought to another
        - Aggregation: Combining multiple thoughts into one
        - Graph Operations: Add, merge, transform, aggregate
    
    Use Cases:
        - Complex multi-step reasoning with dependencies
        - Document analysis with cross-references
        - Problem-solving requiring non-linear thinking
        - Knowledge synthesis from multiple sources
    
    LangChain Implementation:
        Uses LangGraph for state graph management and custom graph operations.
    """
    

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

    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 – 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
    from traceback
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: EdgeType

    Lines 41-47

    Inherits from: Enum

    Purpose:

    Types of edges in the graph.
    

    Class: ThoughtNode

    Lines 51-62

    Purpose:

    Represents a node in the graph of thoughts.
    

    Methods:

    __hash__(self)

    Lines 58-59

    Purpose: Hash operation.

    Parameters:

    __eq__(self, other)

    Lines 61-62

    Purpose: Eq operation.

    Parameters: – other: Other


    Class: ThoughtEdge

    Lines 66-72

    Purpose:

    Represents an edge in the graph of thoughts.
    

    Class: ThoughtGraph

    Lines 75-125

    Purpose:

    Graph structure for managing thoughts.
    

    Methods:

    __init__(self)

    Lines 78-82

    Purpose: Initialize the class instance with required parameters.

    Parameters:

    add_node(self, node)

    Lines 84-86

    Purpose:

    Add a node to the graph.
    

    Parameters: – node: Node

    add_edge(self, edge)

    Lines 88-92

    Purpose:

    Add an edge to the graph.
    

    Parameters: – edge: Edge

    get_node(self, node_id)

    Lines 94-96

    Purpose:

    Get a node by ID.
    

    Parameters: – node_id: Node id

    Returns: <ast.Subscript object at 0x000001A06D624950>

    get_children(self, node_id)

    Lines 98-100

    Purpose:

    Get all child nodes.
    

    Parameters: – node_id: Node id

    Returns: <ast.Subscript object at 0x000001A06D6AB9D0>

    get_parents(self, node_id)

    Lines 102-104

    Purpose:

    Get all parent nodes.
    

    Parameters: – node_id: Node id

    Returns: <ast.Subscript object at 0x000001A06D6AAA90>

    get_all_paths(self, start_id, end_id)

    Lines 106-125

    Purpose:

    Find all paths from start to end node.
    

    Parameters: – start_id: Start id – end_id: End id

    Returns: <ast.Subscript object at 0x000001A06D5F7CD0>


    Class: GraphOfThoughtsAgent

    Lines 128-372

    Purpose:

    Agent that uses Graph-of-Thoughts reasoning.
    

    Methods:

    __init__(self, model_name, temperature)

    Lines 131-139

    Purpose:

    Initialize the Graph-of-Thoughts agent.
    

    Parameters: – model_name: Language model instance – temperature: Sampling temperature (0-1)

    generate_initial_thoughts(self, problem, num_thoughts)

    Lines 141-173

    Purpose:

    Generate initial thoughts for the problem.
    

    Parameters: – problem: Problem – num_thoughts: Num thoughts

    Returns: <ast.Subscript object at 0x000001A06D617310>

    transform_thought(self, node, transformation)

    Lines 175-203

    Purpose:

    Transform a thought using a specific operation.
    

    Parameters: – node: Node – transformation: Transformation

    Returns: ThoughtNode

    aggregate_thoughts(self, nodes, aggregation_type)

    Lines 205-234

    Purpose:

    Aggregate multiple thoughts into one.
    

    Parameters: – nodes: Nodes – aggregation_type: Aggregation type

    Returns: ThoughtNode

    refine_thought(self, node, feedback)

    Lines 236-264

    Purpose:

    Refine a thought based on feedback.
    

    Parameters: – node: Node – feedback: Feedback

    Returns: ThoughtNode

    evaluate_thought(self, node, problem)

    Lines 266-287

    Purpose:

    Evaluate how well a thought addresses the problem.
    

    Parameters: – node: Node – problem: Problem

    Returns: float

    solve_with_graph(self, problem, max_iterations)

    Lines 289-348

    Purpose:

    Solve a problem using Graph-of-Thoughts approach.
    
    Args:
        problem: The problem to solve
        max_iterations: Maximum number of transformation iterations
    
    Returns:
        Dictionary with solution and graph information
    

    Parameters: – problem: Problem – max_iterations: Max iterations

    Returns: <ast.Subscript object at 0x000001A06D6C9C90>

    _count_node_types(self)

    Lines 350-356

    Purpose:

    Count nodes by type.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D6CB550>

    visualize_graph(self)

    Lines 358-372

    Purpose:

    Generate a text visualization of the graph.
    

    Parameters:

    Returns: str


    Functions

    demonstrate_got_pattern()

    Lines 375-444

    Purpose:

    Demonstrates the Graph-of-Thoughts pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Graph Of Thoughts 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 003
    • Pattern 005
    • Other Reasoning Patterns (001-011)

    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 Graph Of Thoughts 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: 004_graph_of_thoughts.py
    Total Lines: 455
    Pattern Category: AI Agent Design Pattern #004


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Tree Of Thoughts

    
        """
    Pattern 003: Tree-of-Thoughts (ToT)
    
    Description:
        Explores multiple reasoning paths simultaneously in a tree structure.
        ToT allows the model to generate multiple candidate thoughts at each step,
        evaluate them, and search through the tree (BFS or DFS) to find the best solution.
    
    Components:
        - Thought Generation: Creates multiple candidate next steps
        - State Evaluation: Scores each thought/state
        - Search Strategy: BFS, DFS, or beam search
        - Backtracking: Can revisit and try alternative paths
    
    Use Cases:
        - Strategic planning and game playing
        - Creative writing with multiple storylines
        - Complex problem-solving with multiple approaches
        - Optimization problems
    
    LangChain Implementation:
        Uses LangGraph for state management and custom search logic for tree exploration.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from enum import Enum
    from dataclasses import dataclass, field
    from collections import deque
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    # Load environment variables
    load_dotenv()
    
    
    class SearchStrategy(Enum):
        """Search strategies for tree exploration."""
        BFS = "breadth_first"
        DFS = "depth_first"
        BEAM = "beam_search"
    
    
    @dataclass
    class ThoughtNode:
        """Represents a node in the tree of thoughts."""
        thought: str
        state: str
        depth: int
        score: float = 0.0
        parent: Optional['ThoughtNode'] = None
        children: List['ThoughtNode'] = field(default_factory=list)
        is_solution: bool = False
        
        def get_path(self) -> List[str]:
            """Returns the path from root to this node."""
            path = []
            current = self
            while current:
                path.append(current.thought)
                current = current.parent
            return list(reversed(path))
    
    
    class TreeOfThoughtsAgent:
        """Agent that uses Tree-of-Thoughts reasoning."""
        
        def __init__(
            self,
            model_name: str = "gpt-3.5-turbo",
            temperature: float = 0.7,
            branching_factor: int = 3,
            max_depth: int = 4
        ):
            """
            Initialize the Tree-of-Thoughts agent.
            
            Args:
                model_name: LLM model to use
                temperature: Temperature for thought generation
                branching_factor: Number of thoughts to generate at each node
                max_depth: Maximum depth of the tree
            """
            self.llm = ChatOpenAI(
                model=model_name,
                temperature=temperature,
                api_key=os.getenv("OPENAI_API_KEY")
            )
            self.branching_factor = branching_factor
            self.max_depth = max_depth
        
        def generate_thoughts(
            self,
            problem: str,
            current_state: str,
            num_thoughts: int = 3
        ) -> List[str]:
            """
            Generate multiple candidate thoughts/next steps.
            
            Args:
                problem: The original problem
                current_state: Current problem-solving state
                num_thoughts: Number of thoughts to generate
                
            Returns:
                List of candidate thoughts
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a creative problem solver. Generate {num_thoughts} different 
    potential next steps or thoughts for solving the problem. Each thought should explore 
    a different approach or direction. Be concise and specific.
    
    Format your response as:
    1. [First thought]
    2. [Second thought]
    3. [Third thought]"""),
                ("human", """Problem: {problem}
    
    Current state: {current_state}
    
    Generate {num_thoughts} diverse next steps:""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "problem": problem,
                "current_state": current_state,
                "num_thoughts": num_thoughts
            })
            
            # Parse thoughts from numbered list
            thoughts = []
            for line in response.strip().split('\n'):
                line = line.strip()
                if line and (line[0].isdigit() or line.startswith('-')):
                    # Remove numbering/bullets
                    thought = line.split('.', 1)[-1].strip()
                    thought = thought.lstrip('- ').strip()
                    if thought:
                        thoughts.append(thought)
            
            return thoughts[:num_thoughts]
        
        def evaluate_thought(self, problem: str, thought: str, current_state: str) -> float:
            """
            Evaluate the quality/promise of a thought.
            
            Args:
                problem: The original problem
                thought: The thought to evaluate
                current_state: Current state description
                
            Returns:
                Score between 0 and 1
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an expert evaluator. Rate how promising this thought is 
    for solving the problem on a scale of 0.0 to 1.0, where:
    - 0.0-0.3: Unlikely to help or may lead to dead end
    - 0.4-0.6: Moderately promising
    - 0.7-0.9: Very promising
    - 1.0: This is the solution or very close to it
    
    Respond with ONLY a number between 0.0 and 1.0."""),
                ("human", """Problem: {problem}
    
    Current state: {current_state}
    
    Thought to evaluate: {thought}
    
    Score (0.0-1.0):""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            try:
                response = chain.invoke({
                    "problem": problem,
                    "current_state": current_state,
                    "thought": thought
                })
                score = float(response.strip())
                return max(0.0, min(1.0, score))  # Clamp to [0, 1]
            except:
                return 0.5  # Default score if parsing fails
        
        def is_solution(self, problem: str, current_state: str) -> bool:
            """
            Check if current state represents a solution.
            
            Args:
                problem: The original problem
                current_state: Current state description
                
            Returns:
                True if this is a valid solution
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """Determine if the current state represents a complete solution 
    to the problem. Respond with ONLY 'yes' or 'no'."""),
                ("human", """Problem: {problem}
    
    Current state: {current_state}
    
    Is this a complete solution? (yes/no):""")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({
                "problem": problem,
                "current_state": current_state
            })
            
            return response.strip().lower().startswith('yes')
        
        def search_bfs(self, problem: str, max_nodes: int = 20) -> Optional[ThoughtNode]:
            """
            Breadth-first search through the tree of thoughts.
            
            Args:
                problem: The problem to solve
                max_nodes: Maximum number of nodes to explore
                
            Returns:
                Solution node if found, None otherwise
            """
            # Initialize root
            root = ThoughtNode(
                thought="Start",
                state=f"Initial problem: {problem}",
                depth=0
            )
            
            queue = deque([root])
            nodes_explored = 0
            
            while queue and nodes_explored  0 and self.is_solution(problem, current.state):
                    current.is_solution = True
                    return current
                
                # Don't expand beyond max depth
                if current.depth >= self.max_depth:
                    continue
                
                # Generate and evaluate child thoughts
                thoughts = self.generate_thoughts(
                    problem,
                    current.state,
                    self.branching_factor
                )
                
                for thought in thoughts:
                    new_state = f"{current.state} → {thought}"
                    score = self.evaluate_thought(problem, thought, current.state)
                    
                    child = ThoughtNode(
                        thought=thought,
                        state=new_state,
                        depth=current.depth + 1,
                        score=score,
                        parent=current
                    )
                    
                    current.children.append(child)
                    
                    # Only add promising nodes to queue (score > 0.4)
                    if score > 0.4:
                        queue.append(child)
            
            # Return best leaf node if no solution found
            return self._find_best_leaf(root)
        
        def search_dfs(self, problem: str, max_nodes: int = 20) -> Optional[ThoughtNode]:
            """
            Depth-first search through the tree of thoughts.
            
            Args:
                problem: The problem to solve
                max_nodes: Maximum number of nodes to explore
                
            Returns:
                Solution node if found, None otherwise
            """
            # Initialize root
            root = ThoughtNode(
                thought="Start",
                state=f"Initial problem: {problem}",
                depth=0
            )
            
            stack = [root]
            nodes_explored = 0
            
            while stack and nodes_explored  0 and self.is_solution(problem, current.state):
                    current.is_solution = True
                    return current
                
                # Don't expand beyond max depth
                if current.depth >= self.max_depth:
                    continue
                
                # Generate and evaluate child thoughts
                thoughts = self.generate_thoughts(
                    problem,
                    current.state,
                    self.branching_factor
                )
                
                for thought in thoughts:
                    new_state = f"{current.state} → {thought}"
                    score = self.evaluate_thought(problem, thought, current.state)
                    
                    child = ThoughtNode(
                        thought=thought,
                        state=new_state,
                        depth=current.depth + 1,
                        score=score,
                        parent=current
                    )
                    
                    current.children.append(child)
                    
                    # Only explore promising nodes (score > 0.4)
                    if score > 0.4:
                        stack.append(child)
            
            return self._find_best_leaf(root)
        
        def _find_best_leaf(self, root: ThoughtNode) -> ThoughtNode:
            """Find the leaf node with the highest score."""
            best_node = root
            best_score = root.score
            
            def traverse(node):
                nonlocal best_node, best_score
                
                if not node.children and node.score > best_score:
                    best_node = node
                    best_score = node.score
                
                for child in node.children:
                    traverse(child)
            
            traverse(root)
            return best_node
        
        def solve(
            self,
            problem: str,
            strategy: SearchStrategy = SearchStrategy.BFS,
            max_nodes: int = 20
        ) -> Dict[str, Any]:
            """
            Solve a problem using Tree-of-Thoughts.
            
            Args:
                problem: The problem to solve
                strategy: Search strategy to use
                max_nodes: Maximum nodes to explore
                
            Returns:
                Dictionary with solution path and statistics
            """
            print(f"\nSolving with {strategy.value} strategy...")
            
            if strategy == SearchStrategy.BFS:
                solution_node = self.search_bfs(problem, max_nodes)
            else:  # DFS
                solution_node = self.search_dfs(problem, max_nodes)
            
            if solution_node:
                path = solution_node.get_path()
                return {
                    "problem": problem,
                    "solution_found": solution_node.is_solution,
                    "path": path,
                    "final_score": solution_node.score,
                    "depth": solution_node.depth,
                    "strategy": strategy.value
                }
            
            return {
                "problem": problem,
                "solution_found": False,
                "path": [],
                "final_score": 0.0,
                "depth": 0,
                "strategy": strategy.value
            }
    
    
    def demonstrate_tot_pattern():
        """Demonstrates the Tree-of-Thoughts pattern."""
        
        print("=" * 80)
        print("PATTERN 003: Tree-of-Thoughts (ToT)")
        print("=" * 80)
        print()
        
        # Create ToT agent
        agent = TreeOfThoughtsAgent(
            branching_factor=3,
            max_depth=3
        )
        
        # Test problems
        problems = [
            "Plan a surprise birthday party for a friend who loves nature and astronomy",
            "Design a simple mobile app to help people track their daily water intake"
        ]
        
        for i, problem in enumerate(problems, 1):
            print(f"\n{'=' * 80}")
            print(f"Problem {i}: {problem}")
            print('=' * 80)
            
            # Try BFS strategy
            try:
                result = agent.solve(
                    problem,
                    strategy=SearchStrategy.BFS,
                    max_nodes=15
                )
                
                print(f"\n✓ Solution found: {result['solution_found']}")
                print(f"  Final score: {result['final_score']:.2f}")
                print(f"  Depth reached: {result['depth']}")
                print(f"\nThought path:")
                for j, thought in enumerate(result['path'], 1):
                    print(f"  {j}. {thought}")
            
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
        
        # Summary
        print("\n\n" + "=" * 80)
        print("TREE-OF-THOUGHTS PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("Key Features Demonstrated:")
        print("1. Multiple thought generation at each step")
        print("2. Thought evaluation and scoring")
        print("3. Tree search strategies (BFS/DFS)")
        print("4. Path exploration and backtracking")
        print("5. Solution detection")
        print()
        print("Advantages:")
        print("- Explores multiple solution paths")
        print("- Can recover from poor initial choices")
        print("- Better solution quality through exploration")
        print("- Systematic search through problem space")
        print()
        print("LangChain Components Used:")
        print("- ChatPromptTemplate: Structures generation and evaluation prompts")
        print("- StrOutputParser: Parses LLM responses")
        print("- Custom search logic: Implements BFS/DFS")
        print("- State management: Tracks tree structure")
        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_tot_pattern()
    
        

    Agentic Design Pattern: Tree Of Thoughts

    Pattern 003: Tree Of Thoughts – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Tree Of Thoughts pattern implementation.


    Pattern Description

    Pattern 003: Tree-of-Thoughts (ToT)

    Description: Explores multiple reasoning paths simultaneously in a tree structure. ToT allows the model to generate multiple candidate thoughts at each step, evaluate them, and search through the tree (BFS or DFS) to find the best solution.

    Components: – Thought Generation: Creates multiple candidate next steps – State Evaluation: Scores each thought/state – Search Strategy: BFS, DFS, or beam search – Backtracking: Can revisit and try alternative paths

    Use Cases: – Strategic planning and game playing – Creative writing with multiple storylines – Complex problem-solving with multiple approaches – Optimization problems

    LangChain Implementation: Uses LangGraph for state management and custom search logic for tree exploration.


    File Statistics

    • Total Lines: 489
    • Classes: 3
    • Functions: 1
    • Constants: 0

    Module Documentation

    """
    Pattern 003: Tree-of-Thoughts (ToT)
    
    Description:
        Explores multiple reasoning paths simultaneously in a tree structure.
        ToT allows the model to generate multiple candidate thoughts at each step,
        evaluate them, and search through the tree (BFS or DFS) to find the best solution.
    
    Components:
        - Thought Generation: Creates multiple candidate next steps
        - State Evaluation: Scores each thought/state
        - Search Strategy: BFS, DFS, or beam search
        - Backtracking: Can revisit and try alternative paths
    
    Use Cases:
        - Strategic planning and game playing
        - Creative writing with multiple storylines
        - Complex problem-solving with multiple approaches
        - Optimization problems
    
    LangChain Implementation:
        Uses LangGraph for state management and custom search logic for tree exploration.
    """
    

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


    Import Statements

    Standard Library Imports

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

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

    Lines 40-44

    Inherits from: Enum

    Purpose:

    Search strategies for tree exploration.
    

    Class: ThoughtNode

    Lines 48-65

    Purpose:

    Represents a node in the tree of thoughts.
    

    Methods:

    get_path(self)

    Lines 58-65

    Purpose:

    Returns the path from root to this node.
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D179650>


    Class: TreeOfThoughtsAgent

    Lines 68-409

    Purpose:

    Agent that uses Tree-of-Thoughts reasoning.
    

    Methods:

    __init__(self, model_name, temperature, branching_factor, max_depth)

    Lines 71-93

    Purpose:

    Initialize the Tree-of-Thoughts agent.
    
    Args:
        model_name: LLM model to use
        temperature: Temperature for thought generation
        branching_factor: Number of thoughts to generate at each node
        max_depth: Maximum depth of the tree
    

    Parameters: – model_name: Language model instance – temperature: Sampling temperature (0-1) – branching_factor: Branching factor – max_depth: Max depth

    generate_thoughts(self, problem, current_state, num_thoughts)

    Lines 95-147

    Purpose:

    Generate multiple candidate thoughts/next steps.
    
    Args:
        problem: The original problem
        current_state: Current problem-solving state
        num_thoughts: Number of thoughts to generate
    
    Returns:
        List of candidate thoughts
    

    Parameters: – problem: Problem – current_state: Current state – num_thoughts: Num thoughts

    Returns: <ast.Subscript object at 0x000001A06D16AFD0>

    evaluate_thought(self, problem, thought, current_state)

    Lines 149-190

    Purpose:

    Evaluate the quality/promise of a thought.
    
    Args:
        problem: The original problem
        thought: The thought to evaluate
        current_state: Current state description
    
    Returns:
        Score between 0 and 1
    

    Parameters: – problem: Problem – thought: Thought – current_state: Current state

    Returns: float

    is_solution(self, problem, current_state)

    Lines 192-220

    Purpose:

    Check if current state represents a solution.
    
    Args:
        problem: The original problem
        current_state: Current state description
    
    Returns:
        True if this is a valid solution
    

    Parameters: – problem: Problem – current_state: Current state

    Returns: bool

    search_bfs(self, problem, max_nodes)

    Lines 222-284

    Purpose:

    Breadth-first search through the tree of thoughts.
    
    Args:
        problem: The problem to solve
        max_nodes: Maximum number of nodes to explore
    
    Returns:
        Solution node if found, None otherwise
    

    Parameters: – problem: Problem – max_nodes: Max nodes

    Returns: <ast.Subscript object at 0x000001A06D15A6D0>

    search_dfs(self, problem, max_nodes)

    Lines 286-347

    Purpose:

    Depth-first search through the tree of thoughts.
    
    Args:
        problem: The problem to solve
        max_nodes: Maximum number of nodes to explore
    
    Returns:
        Solution node if found, None otherwise
    

    Parameters: – problem: Problem – max_nodes: Max nodes

    Returns: <ast.Subscript object at 0x000001A06D663ED0>

    _find_best_leaf(self, root)

    Lines 349-365

    Purpose:

    Find the leaf node with the highest score.
    

    Parameters: – root: Root

    Returns: ThoughtNode

    solve(self, problem, strategy, max_nodes)

    Lines 367-409

    Purpose:

    Solve a problem using Tree-of-Thoughts.
    
    Args:
        problem: The problem to solve
        strategy: Search strategy to use
        max_nodes: Maximum nodes to explore
    
    Returns:
        Dictionary with solution path and statistics
    

    Parameters: – problem: Problem – strategy: Strategy – max_nodes: Max nodes

    Returns: <ast.Subscript object at 0x000001A06D685210>


    Functions

    demonstrate_tot_pattern()

    Lines 412-478

    Purpose:

    Demonstrates the Tree-of-Thoughts pattern.
    

    Key Concepts Summary

    Pattern Overview

    The Tree Of Thoughts 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 002
    • Pattern 004
    • Other Reasoning Patterns (001-011)

    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 Tree Of Thoughts 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: 003_tree_of_thoughts.py
    Total Lines: 489
    Pattern Category: AI Agent Design Pattern #003


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Chain Of Thought

    
        """
    Pattern 002: Chain-of-Thought (CoT)
    
    Description:
        Agent breaks down complex problems into intermediate reasoning steps.
        CoT prompting encourages the model to explain its reasoning process
        step-by-step before arriving at a final answer.
    
    Variants:
        - Zero-shot CoT: "Let's think step by step..."
        - Few-shot CoT: Provides examples with reasoning steps
        - Auto-CoT: Automatically generates reasoning examples
    
    Use Cases:
        - Mathematical reasoning
        - Logical puzzles
        - Complex analysis tasks
        - Multi-step problem solving
    
    LangChain Implementation:
        Uses structured prompts and output parsers to ensure step-by-step reasoning.
    """
    
    import os
    from typing import List, Dict, Any
    from dotenv import load_dotenv
    
    from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    from langchain.chains import LLMChain
    
    # Load environment variables
    load_dotenv()
    
    
    # Few-shot examples demonstrating Chain-of-Thought reasoning
    COT_EXAMPLES = [
        {
            "question": "Roger has 5 tennis balls. He buys 2 more cans of tennis balls. Each can has 3 tennis balls. How many tennis balls does he have now?",
            "reasoning": """Let me think step by step:
    1. Roger starts with 5 tennis balls
    2. He buys 2 cans of tennis balls
    3. Each can contains 3 tennis balls
    4. So he gets 2 × 3 = 6 new tennis balls
    5. Total tennis balls = 5 (original) + 6 (new) = 11
    
    Therefore, Roger has 11 tennis balls now.""",
            "answer": "11"
        },
        {
            "question": "If it takes 5 machines 5 minutes to make 5 widgets, how long would it take 100 machines to make 100 widgets?",
            "reasoning": """Let me think step by step:
    1. 5 machines make 5 widgets in 5 minutes
    2. This means each machine makes 1 widget in 5 minutes
    3. The machines work in parallel (simultaneously)
    4. So 100 machines working in parallel would still take 5 minutes
    5. In 5 minutes, 100 machines would make 100 widgets (1 widget per machine)
    
    Therefore, it would take 5 minutes.""",
            "answer": "5 minutes"
        }
    ]
    
    
    class ChainOfThoughtAgent:
        """Agent that uses Chain-of-Thought reasoning."""
        
        def __init__(self, model_name: str = "gpt-3.5-turbo", temperature: float = 0):
            """Initialize the Chain-of-Thought agent."""
            self.llm = ChatOpenAI(
                model=model_name,
                temperature=temperature,
                api_key=os.getenv("OPENAI_API_KEY")
            )
        
        def zero_shot_cot(self, question: str) -> Dict[str, str]:
            """
            Zero-shot Chain-of-Thought: Uses "Let's think step by step" prompt.
            
            Args:
                question: The question to answer
                
            Returns:
                Dictionary with reasoning and answer
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", "You are a helpful assistant that solves problems step-by-step."),
                ("human", "{question}\n\nLet's think step by step:")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            reasoning = chain.invoke({"question": question})
            
            # Extract final answer
            answer_prompt = ChatPromptTemplate.from_messages([
                ("system", "Based on the reasoning provided, give only the final answer concisely."),
                ("human", "Reasoning:\n{reasoning}\n\nFinal answer:")
            ])
            
            answer_chain = answer_prompt | self.llm | StrOutputParser()
            answer = answer_chain.invoke({"reasoning": reasoning})
            
            return {
                "question": question,
                "reasoning": reasoning,
                "answer": answer.strip()
            }
        
        def few_shot_cot(self, question: str, examples: List[Dict] = None) -> Dict[str, str]:
            """
            Few-shot Chain-of-Thought: Provides examples with reasoning steps.
            
            Args:
                question: The question to answer
                examples: List of example question-reasoning-answer triplets
                
            Returns:
                Dictionary with reasoning and answer
            """
            if examples is None:
                examples = COT_EXAMPLES
            
            # Create few-shot prompt template
            example_prompt = ChatPromptTemplate.from_messages([
                ("human", "{question}"),
                ("ai", "{reasoning}\n\nAnswer: {answer}")
            ])
            
            few_shot_prompt = FewShotChatMessagePromptTemplate(
                example_prompt=example_prompt,
                examples=examples
            )
            
            final_prompt = ChatPromptTemplate.from_messages([
                ("system", "You are a helpful assistant that solves problems step-by-step. Show your reasoning process clearly."),
                few_shot_prompt,
                ("human", "{question}")
            ])
            
            chain = final_prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({"question": question})
            
            # Parse response to separate reasoning and answer
            lines = response.strip().split('\n')
            answer_line = [line for line in lines if line.strip().startswith("Answer:")]
            
            if answer_line:
                answer = answer_line[0].replace("Answer:", "").strip()
                reasoning = response.replace(answer_line[0], "").strip()
            else:
                # If no explicit answer line, last line is the answer
                reasoning = '\n'.join(lines[:-1])
                answer = lines[-1].strip()
            
            return {
                "question": question,
                "reasoning": reasoning,
                "answer": answer
            }
        
        def auto_cot(self, question: str) -> Dict[str, str]:
            """
            Auto-CoT: Automatically generates reasoning with structured steps.
            
            Args:
                question: The question to answer
                
            Returns:
                Dictionary with reasoning and answer
            """
            prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a helpful assistant that solves problems systematically.
    For each problem:
    1. Break it down into clear steps
    2. Number each step
    3. Show your work clearly
    4. Provide a final answer
    
    Format your response as:
    Step 1: [description]
    Step 2: [description]
    ...
    Final Answer: [answer]"""),
                ("human", "{question}")
            ])
            
            chain = prompt | self.llm | StrOutputParser()
            
            response = chain.invoke({"question": question})
            
            # Parse response
            lines = response.strip().split('\n')
            final_answer_line = [line for line in lines if "final answer" in line.lower()]
            
            if final_answer_line:
                answer = final_answer_line[0].split(':', 1)[-1].strip()
                reasoning = '\n'.join([line for line in lines if line not in final_answer_line])
            else:
                reasoning = response
                answer = "Unable to extract final answer"
            
            return {
                "question": question,
                "reasoning": reasoning,
                "answer": answer
            }
    
    
    def demonstrate_cot_pattern():
        """Demonstrates the Chain-of-Thought pattern with various approaches."""
        
        print("=" * 80)
        print("PATTERN 002: Chain-of-Thought (CoT)")
        print("=" * 80)
        print()
        
        # Create CoT agent
        agent = ChainOfThoughtAgent()
        
        # Test questions
        test_questions = [
            "A farmer has 17 sheep and all but 9 die. How many sheep are left?",
            "If a train travels 120 km in 2 hours, how far will it travel in 5 hours at the same speed?",
            "Lisa has 10 apples. She gives 3 to her friend and buys 5 more. Her friend gives back 1 apple. How many apples does Lisa have now?"
        ]
        
        # Demonstrate Zero-shot CoT
        print("\n" + "=" * 80)
        print("ZERO-SHOT CHAIN-OF-THOUGHT")
        print("=" * 80)
        
        for i, question in enumerate(test_questions[:2], 1):
            print(f"\n{'- ' * 40}")
            print(f"Question {i}: {question}")
            print('- ' * 40)
            
            try:
                result = agent.zero_shot_cot(question)
                print(f"\nReasoning:\n{result['reasoning']}")
                print(f"\n✓ Answer: {result['answer']}")
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
        
        # Demonstrate Few-shot CoT
        print("\n\n" + "=" * 80)
        print("FEW-SHOT CHAIN-OF-THOUGHT")
        print("=" * 80)
        
        for i, question in enumerate(test_questions[1:], 1):
            print(f"\n{'- ' * 40}")
            print(f"Question {i}: {question}")
            print('- ' * 40)
            
            try:
                result = agent.few_shot_cot(question)
                print(f"\nReasoning:\n{result['reasoning']}")
                print(f"\n✓ Answer: {result['answer']}")
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
        
        # Demonstrate Auto-CoT
        print("\n\n" + "=" * 80)
        print("AUTO-COT (STRUCTURED STEPS)")
        print("=" * 80)
        
        question = test_questions[2]
        print(f"\n{'- ' * 40}")
        print(f"Question: {question}")
        print('- ' * 40)
        
        try:
            result = agent.auto_cot(question)
            print(f"\nReasoning:\n{result['reasoning']}")
            print(f"\n✓ Answer: {result['answer']}")
        except Exception as e:
            print(f"\n✗ Error: {str(e)}")
        
        # Summary
        print("\n\n" + "=" * 80)
        print("CHAIN-OF-THOUGHT PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("CoT Variants Demonstrated:")
        print("1. Zero-shot CoT: 'Let's think step by step' prompting")
        print("2. Few-shot CoT: Examples with reasoning provided")
        print("3. Auto-CoT: Structured step-by-step format")
        print()
        print("Key Benefits:")
        print("- Improved accuracy on complex tasks")
        print("- Transparent reasoning process")
        print("- Better error detection")
        print("- Enhanced interpretability")
        print()
        print("LangChain Components Used:")
        print("- ChatPromptTemplate: Structures prompts")
        print("- FewShotChatMessagePromptTemplate: Manages few-shot examples")
        print("- StrOutputParser: Parses LLM outputs")
        print("- Chain composition: Connects components")
        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_cot_pattern()
    
        

    Agentic Design Pattern: Chain Of Thought

    Pattern 002: Chain Of Thought – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Chain Of Thought pattern implementation.


    Pattern Description

    Pattern 002: Chain-of-Thought (CoT)

    Description: Agent breaks down complex problems into intermediate reasoning steps. CoT prompting encourages the model to explain its reasoning process step-by-step before arriving at a final answer.

    Variants: – Zero-shot CoT: “Let’s think step by step…” – Few-shot CoT: Provides examples with reasoning steps – Auto-CoT: Automatically generates reasoning examples

    Use Cases: – Mathematical reasoning – Logical puzzles – Complex analysis tasks – Multi-step problem solving

    LangChain Implementation: Uses structured prompts and output parsers to ensure step-by-step reasoning.


    File Statistics

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

    Module Documentation

    """
    Pattern 002: Chain-of-Thought (CoT)
    
    Description:
        Agent breaks down complex problems into intermediate reasoning steps.
        CoT prompting encourages the model to explain its reasoning process
        step-by-step before arriving at a final answer.
    
    Variants:
        - Zero-shot CoT: "Let's think step by step..."
        - Few-shot CoT: Provides examples with reasoning steps
        - Auto-CoT: Automatically generates reasoning examples
    
    Use Cases:
        - Mathematical reasoning
        - Logical puzzles
        - Complex analysis tasks
        - Multi-step problem solving
    
    LangChain Implementation:
        Uses structured prompts and output parsers to ensure step-by-step reasoning.
    """
    

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


    Import Statements

    Standard Library Imports

    import langchain_core.output_parsers.StrOutputParser
    import langchain_core.prompts.ChatPromptTemplate
    import langchain_core.prompts.FewShotChatMessagePromptTemplate
    import os
    import typing.Any
    import typing.Dict
    import typing.List
    

    Purpose: Core Python functionality: – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – langchain_core.prompts.FewShotChatMessagePromptTemplate: 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.LLMChain
    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.


    Module Constants

    COT_EXAMPLES

    COT_EXAMPLES = [
    

    Line 38: Module-level constant definition.


    Classes

    Class: ChainOfThoughtAgent

    Lines 66-209

    Purpose:

    Agent that uses Chain-of-Thought reasoning.
    

    Methods:

    __init__(self, model_name, temperature)

    Lines 69-75

    Purpose:

    Initialize the Chain-of-Thought agent.
    

    Parameters: – model_name: Language model instance – temperature: Sampling temperature (0-1)

    zero_shot_cot(self, question)

    Lines 77-109

    Purpose:

    Zero-shot Chain-of-Thought: Uses "Let's think step by step" prompt.
    
    Args:
        question: The question to answer
    
    Returns:
        Dictionary with reasoning and answer
    

    Parameters: – question: Question to answer

    Returns: <ast.Subscript object at 0x000001A06D60E590>

    few_shot_cot(self, question, examples)

    Lines 111-162

    Purpose:

    Few-shot Chain-of-Thought: Provides examples with reasoning steps.
    
    Args:
        question: The question to answer
        examples: List of example question-reasoning-answer triplets
    
    Returns:
        Dictionary with reasoning and answer
    

    Parameters: – question: Question to answer – examples: Examples

    Returns: <ast.Subscript object at 0x000001A06D608810>

    auto_cot(self, question)

    Lines 164-209

    Purpose:

    Auto-CoT: Automatically generates reasoning with structured steps.
    
    Args:
        question: The question to answer
    
    Returns:
        Dictionary with reasoning and answer
    

    Parameters: – question: Question to answer

    Returns: <ast.Subscript object at 0x000001A06D5F7F50>


    Functions

    demonstrate_cot_pattern()

    Lines 212-302

    Purpose:

    Demonstrates the Chain-of-Thought pattern with various approaches.
    

    Key Concepts Summary

    Pattern Overview

    The Chain Of Thought 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 001
    • Pattern 003
    • Other Reasoning Patterns (001-011)

    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 Chain Of Thought 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: 002_chain_of_thought.py
    Total Lines: 313
    Pattern Category: AI Agent Design Pattern #002


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: React

    
        """
    Pattern 001: ReAct (Reasoning + Acting)
    
    Description:
        Agent alternates between reasoning about the task and taking actions.
        The ReAct pattern combines reasoning traces with task-specific actions,
        allowing the agent to reason about what to do and then take actions.
    
    Components:
        - Thought: Agent reasons about current state
        - Action: Agent takes an action (e.g., tool use)
        - Observation: Agent observes the result
        - Loop continues until task is complete
    
    Use Cases:
        - Question answering with external knowledge
        - Task completion requiring multiple steps
        - Interactive problem solving
    
    LangChain Implementation:
        Uses LangChain's AgentExecutor with structured tools and ReAct prompt template.
    """
    
    import os
    from typing import List, Optional
    from dotenv import load_dotenv
    
    from langchain.agents import AgentExecutor, create_react_agent
    from langchain.tools import Tool
    from langchain_core.prompts import PromptTemplate
    from langchain_openai import ChatOpenAI
    
    # Load environment variables
    load_dotenv()
    
    
    # Define example tools
    def calculator(expression: str) -> str:
        """Evaluates a mathematical expression."""
        try:
            # Safe evaluation for demo purposes
            result = eval(expression, {"__builtins__": {}}, {})
            return f"Result: {result}"
        except Exception as e:
            return f"Error: {str(e)}"
    
    
    def search_tool(query: str) -> str:
        """Simulates a search tool that retrieves information."""
        # Simulated search results
        knowledge_base = {
            "capital of france": "Paris",
            "population of tokyo": "approximately 14 million",
            "height of mount everest": "8,848.86 meters (29,031.7 feet)",
            "speed of light": "299,792,458 meters per second",
            "boiling point of water": "100°C (212°F) at sea level",
        }
        
        query_lower = query.lower()
        for key, value in knowledge_base.items():
            if key in query_lower:
                return f"Search result: {value}"
        
        return f"Search result: Information about '{query}' not found in knowledge base."
    
    
    def get_current_date() -> str:
        """Returns the current date."""
        from datetime import datetime
        return f"Current date: {datetime.now().strftime('%Y-%m-%d')}"
    
    
    # Create tools list
    tools = [
        Tool(
            name="Calculator",
            func=calculator,
            description="Useful for performing mathematical calculations. Input should be a valid mathematical expression."
        ),
        Tool(
            name="Search",
            func=search_tool,
            description="Useful for searching factual information. Input should be a search query."
        ),
        Tool(
            name="GetDate",
            func=get_current_date,
            description="Returns the current date. No input required."
        )
    ]
    
    
    # ReAct prompt template
    REACT_PROMPT = """Answer the following questions as best you can. You have access to the following tools:
    
    {tools}
    
    Use the following format:
    
    Question: the input question you must answer
    Thought: you should always think about what to do
    Action: the action to take, should be one of [{tool_names}]
    Action Input: the input to the action
    Observation: the result of the action
    ... (this Thought/Action/Action Input/Observation can repeat N times)
    Thought: I now know the final answer
    Final Answer: the final answer to the original input question
    
    Begin!
    
    Question: {input}
    Thought: {agent_scratchpad}
    """
    
    
    def create_react_agent_system():
        """Creates a ReAct agent using LangChain."""
        
        # Initialize LLM
        llm = ChatOpenAI(
            model="gpt-3.5-turbo",
            temperature=0,
            api_key=os.getenv("OPENAI_API_KEY")
        )
        
        # Create prompt template
        prompt = PromptTemplate.from_template(REACT_PROMPT)
        
        # Create agent
        agent = create_react_agent(
            llm=llm,
            tools=tools,
            prompt=prompt
        )
        
        # Create agent executor
        agent_executor = AgentExecutor(
            agent=agent,
            tools=tools,
            verbose=True,
            max_iterations=10,
            handle_parsing_errors=True
        )
        
        return agent_executor
    
    
    def demonstrate_react_pattern():
        """Demonstrates the ReAct pattern with various examples."""
        
        print("=" * 80)
        print("PATTERN 001: ReAct (Reasoning + Acting)")
        print("=" * 80)
        print()
        
        # Create the ReAct agent
        agent_executor = create_react_agent_system()
        
        # Test cases demonstrating ReAct pattern
        test_questions = [
            "What is the capital of France?",
            "What is 25 * 4 + 10?",
            "What is the population of Tokyo and what is 100 divided by that number in millions?",
            "What's today's date?",
        ]
        
        for i, question in enumerate(test_questions, 1):
            print(f"\n{'=' * 80}")
            print(f"Example {i}: {question}")
            print('=' * 80)
            
            try:
                result = agent_executor.invoke({"input": question})
                print(f"\n✓ Final Answer: {result['output']}")
            except Exception as e:
                print(f"\n✗ Error: {str(e)}")
            
            print()
        
        # Summary
        print("\n" + "=" * 80)
        print("REACT PATTERN DEMONSTRATION COMPLETE")
        print("=" * 80)
        print()
        print("Key Features Demonstrated:")
        print("1. Thought-Action-Observation loop")
        print("2. Tool selection and usage")
        print("3. Multi-step reasoning")
        print("4. Dynamic decision making")
        print("5. Interpretable reasoning traces")
        print()
        print("LangChain Components Used:")
        print("- AgentExecutor: Manages agent execution loop")
        print("- create_react_agent: Creates ReAct-style agent")
        print("- Tool: Wraps functions as agent tools")
        print("- PromptTemplate: Structures the ReAct prompt format")
        print("- ChatOpenAI: LLM for reasoning and action selection")
        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_react_pattern()
    
        

    Agentic Design Pattern: React

    Pattern 001: React – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 001: ReAct (Reasoning + Acting)

    Description: Agent alternates between reasoning about the task and taking actions. The ReAct pattern combines reasoning traces with task-specific actions, allowing the agent to reason about what to do and then take actions.

    Components: – Thought: Agent reasons about current state – Action: Agent takes an action (e.g., tool use) – Observation: Agent observes the result – Loop continues until task is complete

    Use Cases: – Question answering with external knowledge – Task completion requiring multiple steps – Interactive problem solving

    LangChain Implementation: Uses LangChain’s AgentExecutor with structured tools and ReAct prompt template.


    File Statistics

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

    Module Documentation

    """
    Pattern 001: ReAct (Reasoning + Acting)
    
    Description:
        Agent alternates between reasoning about the task and taking actions.
        The ReAct pattern combines reasoning traces with task-specific actions,
        allowing the agent to reason about what to do and then take actions.
    
    Components:
        - Thought: Agent reasons about current state
        - Action: Agent takes an action (e.g., tool use)
        - Observation: Agent observes the result
        - Loop continues until task is complete
    
    Use Cases:
        - Question answering with external knowledge
        - Task completion requiring multiple steps
        - Interactive problem solving
    
    LangChain Implementation:
        Uses LangChain's AgentExecutor with structured tools and ReAct prompt template.
    """
    

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


    Import Statements

    Standard Library Imports

    import datetime.datetime
    import langchain.agents.create_react_agent
    import langchain_core.prompts.PromptTemplate
    import os
    import typing.List
    import typing.Optional
    

    Purpose: Core Python functionality: – datetime.datetime: Date and time handling – langchain.agents.create_react_agent: Regular expression operations – langchain_core.prompts.PromptTemplate: Regular expression operations – os: Environment variable access and system operations – 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.agents.AgentExecutor
    from langchain.tools.Tool
    from langchain_openai.ChatOpenAI
    

    Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows – Agents: Autonomous decision-making entities – Tools: Functions that agents can invoke

    Other Imports

    from dotenv.load_dotenv
    

    Purpose: Additional dependencies for specialized functionality.


    Module Constants

    REACT_PROMPT

    REACT_PROMPT = """Answer the following questions as best you can. You have access to the following tools:
    

    Line 94: Module-level constant definition.


    Functions

    calculator(expression)

    Lines 38-45

    Purpose:

    Evaluates a mathematical expression.
    

    Parameters: – expression: Expression

    Returns: str

    search_tool(query)

    Lines 48-64

    Purpose:

    Simulates a search tool that retrieves information.
    

    Parameters: – query: Input query or question

    Returns: str

    get_current_date()

    Lines 67-70

    Purpose:

    Returns the current date.
    

    Returns: str

    create_react_agent_system()

    Lines 116-145

    Purpose:

    Creates a ReAct agent using LangChain.
    

    demonstrate_react_pattern()

    Lines 148-198

    Purpose:

    Demonstrates the ReAct pattern with various examples.
    

    Key Concepts Summary

    Pattern Overview

    The React 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
    • Agents: Autonomous decision-making
    • Tools: External function calling

    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 002
    • Other Reasoning Patterns (001-011)

    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 React 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: 001_react.py
    Total Lines: 209
    Pattern Category: AI Agent Design Pattern #001


    Generated from actual implementation code
    For educational and reference purposes