Tag: llm

  • Agentic Orchestration Pattern: Recursive Pattern

    
        """
    Pattern 03.02: Recursive
    Agent calls itself with modified parameters
    """
    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    
    
    class RecursiveState(TypedDict):
        """State for Recursive pattern."""
        input: str
        output: str
    
    
    def agent_function(state: RecursiveState) -> RecursiveState:
        """Recursive agent implementation."""
        llm = ChatOpenAI()
        prompt = f"Process this using Recursive pattern: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = str(response.content)
        return state
    
    
    def create_recursive():
        """Create Recursive workflow."""
        workflow = StateGraph(RecursiveState)
        workflow.add_node("agent", agent_function)
        workflow.set_entry_point("agent")
        workflow.add_edge("agent", END)
        return workflow.compile()
    
    
    if __name__ == "__main__":
        workflow = create_recursive()
        result = workflow.invoke({"input": "Test Recursive", "output": ""})
        print(f"\n=== Recursive Pattern ===\nOutput: {result['output'][:200]}...")
    
        

    Agentic Orchestration Pattern: Recursive Pattern

    02 Recursive Pattern – Detailed Documentation

    Overview

    File: 03_iterative/02_recursive.py
    Pattern Category: 03 Iterative
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 03.02: Recursive Agent calls itself with modified parameters


    Code Structure

    Imports

    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class RecursiveState(TypedDict):
        """State for Recursive pattern."""
        input: str
        output: str
    
    
    def agent_function(state: RecursiveState) -> RecursiveState:
    

    RecursiveState: State for Recursive pattern.

    State Fields: – input: Stores input – output: Stores output – def agent_function(state: Stores def agent function(state – prompt = f"Process this using Recursive pattern: Stores prompt = f”Process this using Recursive pattern – def create_recursive(): Stores def create recursive()


    Functions

    agent_function()

    Line: 17
    Arguments: state

    Recursive agent implementation.

    def agent_function(state: RecursiveState) -> RecursiveState:
        """Recursive agent implementation."""
        llm = ChatOpenAI()
        prompt = f"Process this using Recursive pattern: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = str(response.content)
        return state
    
    
    def create_recursive():
    

    create_recursive()

    Line: 26
    Arguments: “

    Create Recursive workflow.

    def create_recursive():
        """Create Recursive workflow."""
        workflow = StateGraph(RecursiveState)
        workflow.add_node("agent", agent_function)
        workflow.set_entry_point("agent")
        workflow.add_edge("agent", END)
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 8: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 11: Class definition

    class RecursiveState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 12: Module docstring

        """State for Recursive pattern."""
    

    Describes the pattern and its purpose.

    Line 17: Function agent_function definition

    def agent_function(state: RecursiveState) -> RecursiveState:
    

    Defines the agent_function function that processes the workflow state.

    Line 18: Module docstring

        """Recursive agent implementation."""
    

    Describes the pattern and its purpose.

    Line 26: Function create_recursive definition

    def create_recursive():
    

    Defines the create_recursive function that processes the workflow state.

    Line 27: Module docstring

        """Create Recursive workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        workflow = create_recursive()
        result = workflow.invoke({"input": "Test Recursive", "output": ""})
        print(f"\n=== Recursive Pattern ===\nOutput: {result['output'][:200]}...")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Loop Pattern

    
        """
    Pattern 03.01: Loop
    Repeated execution until condition met
    """
    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    
    
    class LoopState(TypedDict):
        """State for Loop pattern."""
        input: str
        output: str
    
    
    def agent_function(state: LoopState) -> LoopState:
        """Loop agent implementation."""
        llm = ChatOpenAI()
        prompt = f"Process this using Loop pattern: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = str(response.content)
        return state
    
    
    def create_loop():
        """Create Loop workflow."""
        workflow = StateGraph(LoopState)
        workflow.add_node("agent", agent_function)
        workflow.set_entry_point("agent")
        workflow.add_edge("agent", END)
        return workflow.compile()
    
    
    if __name__ == "__main__":
        workflow = create_loop()
        result = workflow.invoke({"input": "Test Loop", "output": ""})
        print(f"\n=== Loop Pattern ===\nOutput: {result['output'][:200]}...")
    
        

    Agentic Orchestration Pattern: Loop Pattern

    01 Loop Pattern – Detailed Documentation

    Overview

    File: 03_iterative/01_loop.py
    Pattern Category: 03 Iterative
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 03.01: Loop Repeated execution until condition met


    Code Structure

    Imports

    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class LoopState(TypedDict):
        """State for Loop pattern."""
        input: str
        output: str
    
    
    def agent_function(state: LoopState) -> LoopState:
    

    LoopState: State for Loop pattern.

    State Fields: – input: Stores input – output: Stores output – def agent_function(state: Stores def agent function(state – prompt = f"Process this using Loop pattern: Stores prompt = f”Process this using Loop pattern – def create_loop(): Stores def create loop()


    Functions

    agent_function()

    Line: 17
    Arguments: state

    Loop agent implementation.

    def agent_function(state: LoopState) -> LoopState:
        """Loop agent implementation."""
        llm = ChatOpenAI()
        prompt = f"Process this using Loop pattern: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = str(response.content)
        return state
    
    
    def create_loop():
    

    create_loop()

    Line: 26
    Arguments: “

    Create Loop workflow.

    def create_loop():
        """Create Loop workflow."""
        workflow = StateGraph(LoopState)
        workflow.add_node("agent", agent_function)
        workflow.set_entry_point("agent")
        workflow.add_edge("agent", END)
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 8: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 11: Class definition

    class LoopState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 12: Module docstring

        """State for Loop pattern."""
    

    Describes the pattern and its purpose.

    Line 17: Function agent_function definition

    def agent_function(state: LoopState) -> LoopState:
    

    Defines the agent_function function that processes the workflow state.

    Line 18: Module docstring

        """Loop agent implementation."""
    

    Describes the pattern and its purpose.

    Line 26: Function create_loop definition

    def create_loop():
    

    Defines the create_loop function that processes the workflow state.

    Line 27: Module docstring

        """Create Loop workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        workflow = create_loop()
        result = workflow.invoke({"input": "Test Loop", "output": ""})
        print(f"\n=== Loop Pattern ===\nOutput: {result['output'][:200]}...")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Selector Pattern

    
        """
    Pattern 2.2: Selector Pattern
    Chooses one agent from multiple options based on criteria.
    """
    from typing import TypedDict, List, Dict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    
    
    class SelectorState(TypedDict):
        """State for selector pattern."""
        task: str
        agent_scores: Dict[str, float]
        selected_agent: str
        output: str
    
    
    def evaluate_agents(state: SelectorState) -> SelectorState:
        """Evaluate which agent is best suited for the task."""
        llm = ChatOpenAI()
        prompt = f"""Rate these agents for the task '{state['task']}' from 0-1:
        - expert_agent: Specialized deep knowledge
        - generalist_agent: Broad but shallow knowledge
        - creative_agent: Novel approaches
        
        Return format: expert:0.8,generalist:0.5,creative:0.3"""
        
        response = llm.invoke([HumanMessage(content=prompt)])
        # Parse scores (simplified)
        scores = {"expert_agent": 0.8, "generalist_agent": 0.5, "creative_agent": 0.3}
        state["agent_scores"] = scores
        state["selected_agent"] = max(scores, key=scores.get)
        return state
    
    
    def expert_agent(state: SelectorState) -> SelectorState:
        """Expert agent with deep knowledge."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"Expert analysis: {state['task']}")])
        state["output"] = f"[Expert] {response.content}"
        return state
    
    
    def generalist_agent(state: SelectorState) -> SelectorState:
        """Generalist agent with broad knowledge."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"General analysis: {state['task']}")])
        state["output"] = f"[Generalist] {response.content}"
        return state
    
    
    def creative_agent(state: SelectorState) -> SelectorState:
        """Creative agent with novel approaches."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"Creative solution: {state['task']}")])
        state["output"] = f"[Creative] {response.content}"
        return state
    
    
    def route_to_selected(state: SelectorState) -> str:
        """Route to the selected agent."""
        return state["selected_agent"]
    
    
    def create_selector():
        """Create selector workflow."""
        workflow = StateGraph(SelectorState)
        
        workflow.add_node("evaluator", evaluate_agents)
        workflow.add_node("expert_agent", expert_agent)
        workflow.add_node("generalist_agent", generalist_agent)
        workflow.add_node("creative_agent", creative_agent)
        
        workflow.set_entry_point("evaluator")
        workflow.add_conditional_edges(
            "evaluator",
            route_to_selected,
            {
                "expert_agent": "expert_agent",
                "generalist_agent": "generalist_agent",
                "creative_agent": "creative_agent"
            }
        )
        workflow.add_edge("expert_agent", END)
        workflow.add_edge("generalist_agent", END)
        workflow.add_edge("creative_agent", END)
        
        return workflow.compile()
    
    
    if __name__ == "__main__":
        selector = create_selector()
        result = selector.invoke({
            "task": "Optimize database query performance",
            "agent_scores": {},
            "selected_agent": "",
            "output": ""
        })
        print(f"\n=== Selector Pattern ===\nSelected: {result['selected_agent']}\nOutput: {result['output'][:200]}...")
    
        

    Agentic Orchestration Pattern: Selector Pattern

    02 Selector Pattern – Detailed Documentation

    Overview

    File: 02_decision_based/02_selector.py
    Pattern Category: 02 Decision Based
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 2.2: Selector Pattern Chooses one agent from multiple options based on criteria.


    Code Structure

    Imports

    from typing import TypedDict, List, Dict
    from langgraph.graph import StateGraph, END
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class SelectorState(TypedDict):
        """State for selector pattern."""
        task: str
        agent_scores: Dict[str, float]
        selected_agent: str
        output: str
    
    
    def evaluate_agents(state: SelectorState) -> SelectorState:
    

    SelectorState: State for selector pattern.

    State Fields: – task: Stores task – agent_scores: Stores agent scores – selected_agent: Stores selected agent – output: Stores output – def evaluate_agents(state: Stores def evaluate agents(state – prompt = f"""Rate these agents for the task '{state['task']}' from 0-1: Stores prompt = f”””Rate these agents for the task ‘{state[‘task’]}’ from 0-1


    Functions

    evaluate_agents()

    Line: 19
    Arguments: state

    Evaluate which agent is best suited for the task.

    def evaluate_agents(state: SelectorState) -> SelectorState:
        """Evaluate which agent is best suited for the task."""
        llm = ChatOpenAI()
        prompt = f"""Rate these agents for the task '{state['task']}' from 0-1:
        - expert_agent: Specialized deep knowledge
        - generalist_agent: Broad but shallow knowledge
        - creative_agent: Novel approaches
    
        Return format: expert:0.8,generalist:0.5,creative:0.3"""
    
        response = llm.invoke([HumanMessage(content=prompt)])
        # Parse scores (simplified)
        scores = {"expert_agent": 0.8, "generalist_agent": 0.5, "creative_agent": 0.3}
        state["agent_scores"] = scores
        state["selected_agent"] = max(scores, key=scores.get)
        return state
    
    
    def expert_agent(state: SelectorState) -> SelectorState:
    

    expert_agent()

    Line: 37
    Arguments: state

    Expert agent with deep knowledge.

    def expert_agent(state: SelectorState) -> SelectorState:
        """Expert agent with deep knowledge."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"Expert analysis: {state['task']}")])
        state["output"] = f"[Expert] {response.content}"
        return state
    
    
    def generalist_agent(state: SelectorState) -> SelectorState:
    

    generalist_agent()

    Line: 45
    Arguments: state

    Generalist agent with broad knowledge.

    def generalist_agent(state: SelectorState) -> SelectorState:
        """Generalist agent with broad knowledge."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"General analysis: {state['task']}")])
        state["output"] = f"[Generalist] {response.content}"
        return state
    
    
    def creative_agent(state: SelectorState) -> SelectorState:
    

    creative_agent()

    Line: 53
    Arguments: state

    Creative agent with novel approaches.

    def creative_agent(state: SelectorState) -> SelectorState:
        """Creative agent with novel approaches."""
        llm = ChatOpenAI()
        response = llm.invoke([HumanMessage(content=f"Creative solution: {state['task']}")])
        state["output"] = f"[Creative] {response.content}"
        return state
    
    
    def route_to_selected(state: SelectorState) -> str:
    

    route_to_selected()

    Line: 61
    Arguments: state

    Route to the selected agent.

    def route_to_selected(state: SelectorState) -> str:
        """Route to the selected agent."""
        return state["selected_agent"]
    
    
    def create_selector():
    

    create_selector()

    Line: 66
    Arguments: “

    Create selector workflow.

    def create_selector():
        """Create selector workflow."""
        workflow = StateGraph(SelectorState)
    
        workflow.add_node("evaluator", evaluate_agents)
        workflow.add_node("expert_agent", expert_agent)
        workflow.add_node("generalist_agent", generalist_agent)
        workflow.add_node("creative_agent", creative_agent)
    
        workflow.set_entry_point("evaluator")
        workflow.add_conditional_edges(
            "evaluator",
            route_to_selected,
            {
                "expert_agent": "expert_agent",
                "generalist_agent": "generalist_agent",
                "creative_agent": "creative_agent"
            }
        )
        workflow.add_edge("expert_agent", END)
        workflow.add_edge("generalist_agent", END)
        workflow.add_edge("creative_agent", END)
    
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict, List, Dict
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 8: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 11: Class definition

    class SelectorState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 12: Module docstring

        """State for selector pattern."""
    

    Describes the pattern and its purpose.

    Line 19: Function evaluate_agents definition

    def evaluate_agents(state: SelectorState) -> SelectorState:
    

    Defines the evaluate_agents function that processes the workflow state.

    Line 20: Module docstring

        """Evaluate which agent is best suited for the task."""
    

    Describes the pattern and its purpose.

    Line 37: Function expert_agent definition

    def expert_agent(state: SelectorState) -> SelectorState:
    

    Defines the expert_agent function that processes the workflow state.

    Line 38: Module docstring

        """Expert agent with deep knowledge."""
    

    Describes the pattern and its purpose.

    Line 45: Function generalist_agent definition

    def generalist_agent(state: SelectorState) -> SelectorState:
    

    Defines the generalist_agent function that processes the workflow state.

    Line 46: Module docstring

        """Generalist agent with broad knowledge."""
    

    Describes the pattern and its purpose.

    Line 53: Function creative_agent definition

    def creative_agent(state: SelectorState) -> SelectorState:
    

    Defines the creative_agent function that processes the workflow state.

    Line 54: Module docstring

        """Creative agent with novel approaches."""
    

    Describes the pattern and its purpose.

    Line 61: Function route_to_selected definition

    def route_to_selected(state: SelectorState) -> str:
    

    Defines the route_to_selected function that processes the workflow state.

    Line 62: Module docstring

        """Route to the selected agent."""
    

    Describes the pattern and its purpose.

    Line 66: Function create_selector definition

    def create_selector():
    

    Defines the create_selector function that processes the workflow state.

    Line 67: Module docstring

        """Create selector workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        selector = create_selector()
        result = selector.invoke({
            "task": "Optimize database query performance",
            "agent_scores": {},
            "selected_agent": "",
            "output": ""
        })
        print(f"\n=== Selector Pattern ===\nSelected: {result['selected_agent']}\nOutput: {result['output'][:200]}...")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Router Pattern

    
        """
    Pattern 2.1: Router Pattern
    Single entry point that routes to appropriate agent based on input analysis.
    """
    from typing import TypedDict, Literal
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    
    
    class RouterState(TypedDict):
        """State for router pattern."""
        input: str
        category: str
        output: str
    
    
    def router(state: RouterState) -> RouterState:
        """Router agent determines which specialist to route to."""
        llm = ChatOpenAI()
        prompt = f"""Categorize this request as 'technical', 'business', or 'creative': {state['input']}
        Respond with only one word."""
        response = llm.invoke([HumanMessage(content=prompt)])
        category = str(response.content).strip().lower()
        state["category"] = category
        return state
    
    
    def technical_agent(state: RouterState) -> RouterState:
        """Handle technical requests."""
        llm = ChatOpenAI()
        prompt = f"Provide technical solution for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Technical] {response.content}"
        return state
    
    
    def business_agent(state: RouterState) -> RouterState:
        """Handle business requests."""
        llm = ChatOpenAI()
        prompt = f"Provide business strategy for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Business] {response.content}"
        return state
    
    
    def creative_agent(state: RouterState) -> RouterState:
        """Handle creative requests."""
        llm = ChatOpenAI()
        prompt = f"Provide creative solution for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Creative] {response.content}"
        return state
    
    
    def route_logic(state: RouterState) -> str:
        """Determine which path to take."""
        category = state.get("category", "").lower()
        if "technical" in category:
            return "technical"
        elif "business" in category:
            return "business"
        else:
            return "creative"
    
    
    def create_router():
        """Create router workflow."""
        workflow = StateGraph(RouterState)
        
        workflow.add_node("router", router)
        workflow.add_node("technical", technical_agent)
        workflow.add_node("business", business_agent)
        workflow.add_node("creative", creative_agent)
        
        workflow.set_entry_point("router")
        workflow.add_conditional_edges(
            "router",
            route_logic,
            {
                "technical": "technical",
                "business": "business",
                "creative": "creative"
            }
        )
        workflow.add_edge("technical", END)
        workflow.add_edge("business", END)
        workflow.add_edge("creative", END)
        
        return workflow.compile()
    
    
    if __name__ == "__main__":
        router_graph = create_router()
        result = router_graph.invoke({
            "input": "Design a scalable microservices architecture",
            "category": "",
            "output": ""
        })
        print(f"\n=== Router Pattern ===\nCategory: {result['category']}\nOutput: {result['output'][:200]}...")
    
        

    Agentic Orchestration Pattern: Router Pattern

    01 Router Pattern – Detailed Documentation

    Overview

    File: 02_decision_based/01_router.py
    Pattern Category: 02 Decision Based
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 2.1: Router Pattern Single entry point that routes to appropriate agent based on input analysis.


    Code Structure

    Imports

    from typing import TypedDict, Literal
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class RouterState(TypedDict):
        """State for router pattern."""
        input: str
        category: str
        output: str
    
    
    def router(state: RouterState) -> RouterState:
    

    RouterState: State for router pattern.

    State Fields: – input: Stores input – category: Stores category – output: Stores output – def router(state: Stores def router(state – prompt = f"""Categorize this request as 'technical', 'business', or 'creative': Stores prompt = f”””Categorize this request as ‘technical’, ‘business’, or ‘creative’


    Functions

    router()

    Line: 18
    Arguments: state

    Router agent determines which specialist to route to.

    def router(state: RouterState) -> RouterState:
        """Router agent determines which specialist to route to."""
        llm = ChatOpenAI()
        prompt = f"""Categorize this request as 'technical', 'business', or 'creative': {state['input']}
        Respond with only one word."""
        response = llm.invoke([HumanMessage(content=prompt)])
        category = str(response.content).strip().lower()
        state["category"] = category
        return state
    
    
    def technical_agent(state: RouterState) -> RouterState:
    

    technical_agent()

    Line: 29
    Arguments: state

    Handle technical requests.

    def technical_agent(state: RouterState) -> RouterState:
        """Handle technical requests."""
        llm = ChatOpenAI()
        prompt = f"Provide technical solution for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Technical] {response.content}"
        return state
    
    
    def business_agent(state: RouterState) -> RouterState:
    

    business_agent()

    Line: 38
    Arguments: state

    Handle business requests.

    def business_agent(state: RouterState) -> RouterState:
        """Handle business requests."""
        llm = ChatOpenAI()
        prompt = f"Provide business strategy for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Business] {response.content}"
        return state
    
    
    def creative_agent(state: RouterState) -> RouterState:
    

    creative_agent()

    Line: 47
    Arguments: state

    Handle creative requests.

    def creative_agent(state: RouterState) -> RouterState:
        """Handle creative requests."""
        llm = ChatOpenAI()
        prompt = f"Provide creative solution for: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["output"] = f"[Creative] {response.content}"
        return state
    
    
    def route_logic(state: RouterState) -> str:
    

    route_logic()

    Line: 56
    Arguments: state

    Determine which path to take.

    def route_logic(state: RouterState) -> str:
        """Determine which path to take."""
        category = state.get("category", "").lower()
        if "technical" in category:
            return "technical"
        elif "business" in category:
            return "business"
        else:
            return "creative"
    
    
    def create_router():
    

    create_router()

    Line: 67
    Arguments: “

    Create router workflow.

    def create_router():
        """Create router workflow."""
        workflow = StateGraph(RouterState)
    
        workflow.add_node("router", router)
        workflow.add_node("technical", technical_agent)
        workflow.add_node("business", business_agent)
        workflow.add_node("creative", creative_agent)
    
        workflow.set_entry_point("router")
        workflow.add_conditional_edges(
            "router",
            route_logic,
            {
                "technical": "technical",
                "business": "business",
                "creative": "creative"
            }
        )
        workflow.add_edge("technical", END)
        workflow.add_edge("business", END)
        workflow.add_edge("creative", END)
    
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict, Literal
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 8: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 11: Class definition

    class RouterState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 12: Module docstring

        """State for router pattern."""
    

    Describes the pattern and its purpose.

    Line 18: Function router definition

    def router(state: RouterState) -> RouterState:
    

    Defines the router function that processes the workflow state.

    Line 19: Module docstring

        """Router agent determines which specialist to route to."""
    

    Describes the pattern and its purpose.

    Line 29: Function technical_agent definition

    def technical_agent(state: RouterState) -> RouterState:
    

    Defines the technical_agent function that processes the workflow state.

    Line 30: Module docstring

        """Handle technical requests."""
    

    Describes the pattern and its purpose.

    Line 38: Function business_agent definition

    def business_agent(state: RouterState) -> RouterState:
    

    Defines the business_agent function that processes the workflow state.

    Line 39: Module docstring

        """Handle business requests."""
    

    Describes the pattern and its purpose.

    Line 47: Function creative_agent definition

    def creative_agent(state: RouterState) -> RouterState:
    

    Defines the creative_agent function that processes the workflow state.

    Line 48: Module docstring

        """Handle creative requests."""
    

    Describes the pattern and its purpose.

    Line 56: Function route_logic definition

    def route_logic(state: RouterState) -> str:
    

    Defines the route_logic function that processes the workflow state.

    Line 57: Module docstring

        """Determine which path to take."""
    

    Describes the pattern and its purpose.

    Line 67: Function create_router definition

    def create_router():
    

    Defines the create_router function that processes the workflow state.

    Line 68: Module docstring

        """Create router workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        router_graph = create_router()
        result = router_graph.invoke({
            "input": "Design a scalable microservices architecture",
            "category": "",
            "output": ""
        })
        print(f"\n=== Router Pattern ===\nCategory: {result['category']}\nOutput: {result['output'][:200]}...")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Waterfall Pattern

    
        """
    Pattern 1.4: Waterfall Pattern
    Strictly sequential with phase gates and validation checkpoints.
    """
    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    import os
    
    
    class WaterfallState(TypedDict):
        """State for waterfall pattern with validation gates."""
        requirements: str
        design: str
        implementation: str
        testing: str
        deployment: str
        validation_passed: bool
    
    
    def requirements_phase(state: WaterfallState) -> WaterfallState:
        """Phase 1: Requirements gathering."""
        llm = ChatOpenAI()
        prompt = "Define comprehensive requirements for a task management system"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["requirements"] = str(response.content)
        state["validation_passed"] = True  # Validation gate
        return state
    
    
    def design_phase(state: WaterfallState) -> WaterfallState:
        """Phase 2: Design based on requirements."""
        if not state.get("validation_passed"):
            return state
        llm = ChatOpenAI()
        prompt = f"Create system design based on: {state['requirements']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["design"] = str(response.content)
        return state
    
    
    def implementation_phase(state: WaterfallState) -> WaterfallState:
        """Phase 3: Implementation."""
        llm = ChatOpenAI()
        prompt = f"Outline implementation steps for: {state['design']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["implementation"] = str(response.content)
        return state
    
    
    def testing_phase(state: WaterfallState) -> WaterfallState:
        """Phase 4: Testing."""
        llm = ChatOpenAI()
        prompt = f"Define test plan for: {state['implementation']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["testing"] = str(response.content)
        return state
    
    
    def deployment_phase(state: WaterfallState) -> WaterfallState:
        """Phase 5: Deployment."""
        llm = ChatOpenAI()
        prompt = f"Create deployment strategy for tested system: {state['testing'][:100]}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["deployment"] = str(response.content)
        return state
    
    
    def create_waterfall():
        """Create waterfall workflow."""
        workflow = StateGraph(WaterfallState)
        
        workflow.add_node("requirements", requirements_phase)
        workflow.add_node("design", design_phase)
        workflow.add_node("implementation", implementation_phase)
        workflow.add_node("testing", testing_phase)
        workflow.add_node("deployment", deployment_phase)
        
        workflow.set_entry_point("requirements")
        workflow.add_edge("requirements", "design")
        workflow.add_edge("design", "implementation")
        workflow.add_edge("implementation", "testing")
        workflow.add_edge("testing", "deployment")
        workflow.add_edge("deployment", END)
        
        return workflow.compile()
    
    
    if __name__ == "__main__":
        waterfall = create_waterfall()
        result = waterfall.invoke({
            "requirements": "",
            "design": "",
            "implementation": "",
            "testing": "",
            "deployment": "",
            "validation_passed": False
        })
        print(f"\n=== Waterfall Pattern ===\nDeployment Plan: {result['deployment'][:300]}...")
    
        

    Agentic Orchestration Pattern: Waterfall Pattern

    04 Waterfall Pattern – Detailed Documentation

    Overview

    File: 01_basic_coordination/04_waterfall.py
    Pattern Category: 01 Basic Coordination
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 1.4: Waterfall Pattern Strictly sequential with phase gates and validation checkpoints.


    Code Structure

    Imports

    from typing import TypedDict
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    import os
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class WaterfallState(TypedDict):
        """State for waterfall pattern with validation gates."""
        requirements: str
        design: str
        implementation: str
        testing: str
        deployment: str
        validation_passed: bool
    
    
    def requirements_phase(state: WaterfallState) -> WaterfallState:
    

    WaterfallState: State for waterfall pattern with validation gates.

    State Fields: – requirements: Stores requirements – design: Stores design – implementation: Stores implementation – testing: Stores testing – deployment: Stores deployment – validation_passed: Stores validation passed – def requirements_phase(state: Stores def requirements phase(state


    Functions

    requirements_phase()

    Line: 22
    Arguments: state

    Phase 1: Requirements gathering.

    def requirements_phase(state: WaterfallState) -> WaterfallState:
        """Phase 1: Requirements gathering."""
        llm = ChatOpenAI()
        prompt = "Define comprehensive requirements for a task management system"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["requirements"] = str(response.content)
        state["validation_passed"] = True  # Validation gate
        return state
    
    
    def design_phase(state: WaterfallState) -> WaterfallState:
    

    design_phase()

    Line: 32
    Arguments: state

    Phase 2: Design based on requirements.

    def design_phase(state: WaterfallState) -> WaterfallState:
        """Phase 2: Design based on requirements."""
        if not state.get("validation_passed"):
            return state
        llm = ChatOpenAI()
        prompt = f"Create system design based on: {state['requirements']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["design"] = str(response.content)
        return state
    
    
    def implementation_phase(state: WaterfallState) -> WaterfallState:
    

    implementation_phase()

    Line: 43
    Arguments: state

    Phase 3: Implementation.

    def implementation_phase(state: WaterfallState) -> WaterfallState:
        """Phase 3: Implementation."""
        llm = ChatOpenAI()
        prompt = f"Outline implementation steps for: {state['design']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["implementation"] = str(response.content)
        return state
    
    
    def testing_phase(state: WaterfallState) -> WaterfallState:
    

    testing_phase()

    Line: 52
    Arguments: state

    Phase 4: Testing.

    def testing_phase(state: WaterfallState) -> WaterfallState:
        """Phase 4: Testing."""
        llm = ChatOpenAI()
        prompt = f"Define test plan for: {state['implementation']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["testing"] = str(response.content)
        return state
    
    
    def deployment_phase(state: WaterfallState) -> WaterfallState:
    

    deployment_phase()

    Line: 61
    Arguments: state

    Phase 5: Deployment.

    def deployment_phase(state: WaterfallState) -> WaterfallState:
        """Phase 5: Deployment."""
        llm = ChatOpenAI()
        prompt = f"Create deployment strategy for tested system: {state['testing'][:100]}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["deployment"] = str(response.content)
        return state
    
    
    def create_waterfall():
    

    create_waterfall()

    Line: 70
    Arguments: “

    Create waterfall workflow.

    def create_waterfall():
        """Create waterfall workflow."""
        workflow = StateGraph(WaterfallState)
    
        workflow.add_node("requirements", requirements_phase)
        workflow.add_node("design", design_phase)
        workflow.add_node("implementation", implementation_phase)
        workflow.add_node("testing", testing_phase)
        workflow.add_node("deployment", deployment_phase)
    
        workflow.set_entry_point("requirements")
        workflow.add_edge("requirements", "design")
        workflow.add_edge("design", "implementation")
        workflow.add_edge("implementation", "testing")
        workflow.add_edge("testing", "deployment")
        workflow.add_edge("deployment", END)
    
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 8: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 9: Import statement

    import os
    

    Required import for the pattern implementation.

    Line 12: Class definition

    class WaterfallState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 13: Module docstring

        """State for waterfall pattern with validation gates."""
    

    Describes the pattern and its purpose.

    Line 22: Function requirements_phase definition

    def requirements_phase(state: WaterfallState) -> WaterfallState:
    

    Defines the requirements_phase function that processes the workflow state.

    Line 23: Module docstring

        """Phase 1: Requirements gathering."""
    

    Describes the pattern and its purpose.

    Line 32: Function design_phase definition

    def design_phase(state: WaterfallState) -> WaterfallState:
    

    Defines the design_phase function that processes the workflow state.

    Line 33: Module docstring

        """Phase 2: Design based on requirements."""
    

    Describes the pattern and its purpose.

    Line 43: Function implementation_phase definition

    def implementation_phase(state: WaterfallState) -> WaterfallState:
    

    Defines the implementation_phase function that processes the workflow state.

    Line 44: Module docstring

        """Phase 3: Implementation."""
    

    Describes the pattern and its purpose.

    Line 52: Function testing_phase definition

    def testing_phase(state: WaterfallState) -> WaterfallState:
    

    Defines the testing_phase function that processes the workflow state.

    Line 53: Module docstring

        """Phase 4: Testing."""
    

    Describes the pattern and its purpose.

    Line 61: Function deployment_phase definition

    def deployment_phase(state: WaterfallState) -> WaterfallState:
    

    Defines the deployment_phase function that processes the workflow state.

    Line 62: Module docstring

        """Phase 5: Deployment."""
    

    Describes the pattern and its purpose.

    Line 70: Function create_waterfall definition

    def create_waterfall():
    

    Defines the create_waterfall function that processes the workflow state.

    Line 71: Module docstring

        """Create waterfall workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        waterfall = create_waterfall()
        result = waterfall.invoke({
            "requirements": "",
            "design": "",
            "implementation": "",
            "testing": "",
            "deployment": "",
            "validation_passed": False
        })
        print(f"\n=== Waterfall Pattern ===\nDeployment Plan: {result['deployment'][:300]}...")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Pipeline Pattern

    
        """
    Pattern 1.3: Pipeline Pattern
    Continuous flow of data through agent stages using LangGraph.
    """
    from typing import TypedDict, List
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    import os
    
    
    class PipelineState(TypedDict):
        """State for pipeline pattern with streaming capability."""
        input_data: str
        preprocessed: str
        transformed: str
        enriched: str
        output: str
    
    
    def preprocess_stage(state: PipelineState) -> PipelineState:
        """Stage 1: Preprocess input data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Clean and normalize this data: {state['input_data']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["preprocessed"] = str(response.content)
        return state
    
    
    def transform_stage(state: PipelineState) -> PipelineState:
        """Stage 2: Transform preprocessed data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Extract key insights from: {state['preprocessed']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["transformed"] = str(response.content)
        return state
    
    
    def enrich_stage(state: PipelineState) -> PipelineState:
        """Stage 3: Enrich transformed data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Add context and recommendations to: {state['transformed']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["enriched"] = str(response.content)
        state["output"] = str(response.content)
        return state
    
    
    def create_pipeline():
        """Create pipeline workflow."""
        workflow = StateGraph(PipelineState)
        
        workflow.add_node("preprocess", preprocess_stage)
        workflow.add_node("transform", transform_stage)
        workflow.add_node("enrich", enrich_stage)
        
        workflow.set_entry_point("preprocess")
        workflow.add_edge("preprocess", "transform")
        workflow.add_edge("transform", "enrich")
        workflow.add_edge("enrich", END)
        
        return workflow.compile()
    
    
    if __name__ == "__main__":
        pipeline = create_pipeline()
        result = pipeline.invoke({
            "input_data": "Customer feedback: Great product but slow delivery",
            "preprocessed": "",
            "transformed": "",
            "enriched": "",
            "output": ""
        })
        print(f"\n=== Pipeline Pattern ===\nOutput: {result['output']}")
    
        

    Agentic Orchestration Pattern: Pipeline Pattern

    03 Pipeline Pattern – Detailed Documentation

    Overview

    File: 01_basic_coordination/03_pipeline.py
    Pattern Category: 01 Basic Coordination
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 1.3: Pipeline Pattern Continuous flow of data through agent stages using LangGraph.


    Code Structure

    Imports

    from typing import TypedDict, List
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    import os
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class PipelineState(TypedDict):
        """State for pipeline pattern with streaming capability."""
        input_data: str
        preprocessed: str
        transformed: str
        enriched: str
        output: str
    
    
    def preprocess_stage(state: PipelineState) -> PipelineState:
    

    PipelineState: State for pipeline pattern with streaming capability.

    State Fields: – input_data: Stores input data – preprocessed: Stores preprocessed – transformed: Stores transformed – enriched: Stores enriched – output: Stores output – def preprocess_stage(state: Stores def preprocess stage(state – prompt = f"Clean and normalize this data: Stores prompt = f”Clean and normalize this data


    Functions

    preprocess_stage()

    Line: 21
    Arguments: state

    Stage 1: Preprocess input data.

    def preprocess_stage(state: PipelineState) -> PipelineState:
        """Stage 1: Preprocess input data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Clean and normalize this data: {state['input_data']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["preprocessed"] = str(response.content)
        return state
    
    
    def transform_stage(state: PipelineState) -> PipelineState:
    

    transform_stage()

    Line: 30
    Arguments: state

    Stage 2: Transform preprocessed data.

    def transform_stage(state: PipelineState) -> PipelineState:
        """Stage 2: Transform preprocessed data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Extract key insights from: {state['preprocessed']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["transformed"] = str(response.content)
        return state
    
    
    def enrich_stage(state: PipelineState) -> PipelineState:
    

    enrich_stage()

    Line: 39
    Arguments: state

    Stage 3: Enrich transformed data.

    def enrich_stage(state: PipelineState) -> PipelineState:
        """Stage 3: Enrich transformed data."""
        llm = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"))
        prompt = f"Add context and recommendations to: {state['transformed']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        state["enriched"] = str(response.content)
        state["output"] = str(response.content)
        return state
    
    
    def create_pipeline():
    

    create_pipeline()

    Line: 49
    Arguments: “

    Create pipeline workflow.

    def create_pipeline():
        """Create pipeline workflow."""
        workflow = StateGraph(PipelineState)
    
        workflow.add_node("preprocess", preprocess_stage)
        workflow.add_node("transform", transform_stage)
        workflow.add_node("enrich", enrich_stage)
    
        workflow.set_entry_point("preprocess")
        workflow.add_edge("preprocess", "transform")
        workflow.add_edge("transform", "enrich")
        workflow.add_edge("enrich", END)
    
        return workflow.compile()
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict, List
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 8: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 9: Import statement

    import os
    

    Required import for the pattern implementation.

    Line 12: Class definition

    class PipelineState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 13: Module docstring

        """State for pipeline pattern with streaming capability."""
    

    Describes the pattern and its purpose.

    Line 21: Function preprocess_stage definition

    def preprocess_stage(state: PipelineState) -> PipelineState:
    

    Defines the preprocess_stage function that processes the workflow state.

    Line 22: Module docstring

        """Stage 1: Preprocess input data."""
    

    Describes the pattern and its purpose.

    Line 30: Function transform_stage definition

    def transform_stage(state: PipelineState) -> PipelineState:
    

    Defines the transform_stage function that processes the workflow state.

    Line 31: Module docstring

        """Stage 2: Transform preprocessed data."""
    

    Describes the pattern and its purpose.

    Line 39: Function enrich_stage definition

    def enrich_stage(state: PipelineState) -> PipelineState:
    

    Defines the enrich_stage function that processes the workflow state.

    Line 40: Module docstring

        """Stage 3: Enrich transformed data."""
    

    Describes the pattern and its purpose.

    Line 49: Function create_pipeline definition

    def create_pipeline():
    

    Defines the create_pipeline function that processes the workflow state.

    Line 50: Module docstring

        """Create pipeline workflow."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        pipeline = create_pipeline()
        result = pipeline.invoke({
            "input_data": "Customer feedback: Great product but slow delivery",
            "preprocessed": "",
            "transformed": "",
            "enriched": "",
            "output": ""
        })
        print(f"\n=== Pipeline Pattern ===\nOutput: {result['output']}")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Parallel Execution

    
        """
    Pattern 1.2: Parallel Execution Pattern
    Multiple agents execute simultaneously using LangGraph.
    """
    from typing import TypedDict, List
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    from common import get_openai_api_key, get_model_name, log_agent_action
    import asyncio
    
    
    class ParallelState(TypedDict):
        """State for parallel execution pattern."""
        input: str
        agent1_output: str
        agent2_output: str
        agent3_output: str
        aggregated_output: str
    
    
    def agent_parallel_1(state: ParallelState) -> ParallelState:
        """First parallel agent - technical analysis."""
        log_agent_action("agent_parallel_1", "processing", {"input": state["input"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a technical analysis of: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["agent1_output"] = response.content
        return state
    
    
    def agent_parallel_2(state: ParallelState) -> ParallelState:
        """Second parallel agent - business analysis."""
        log_agent_action("agent_parallel_2", "processing", {"input": state["input"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a business perspective on: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["agent2_output"] = response.content
        return state
    
    
    def agent_parallel_3(state: ParallelState) -> ParallelState:
        """Third parallel agent - user experience analysis."""
        log_agent_action("agent_parallel_3", "processing", {"input": state["input"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a user experience perspective on: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["agent3_output"] = response.content
        return state
    
    
    def aggregator(state: ParallelState) -> ParallelState:
        """Aggregate results from parallel agents."""
        log_agent_action("aggregator", "combining results")
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"""
        Synthesize these three perspectives into a comprehensive analysis:
        
        Technical: {state['agent1_output']}
        
        Business: {state['agent2_output']}
        
        UX: {state['agent3_output']}
        """
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["aggregated_output"] = response.content
        return state
    
    
    def create_parallel_execution():
        """Create and return a parallel execution workflow."""
        workflow = StateGraph(ParallelState)
        
        # Add nodes
        workflow.add_node("agent1", agent_parallel_1)
        workflow.add_node("agent2", agent_parallel_2)
        workflow.add_node("agent3", agent_parallel_3)
        workflow.add_node("aggregator", aggregator)
        
        # Set entry point to all parallel agents
        workflow.set_entry_point("agent1")
        workflow.set_entry_point("agent2")
        workflow.set_entry_point("agent3")
        
        # All parallel agents feed into aggregator
        workflow.add_edge("agent1", "aggregator")
        workflow.add_edge("agent2", "aggregator")
        workflow.add_edge("agent3", "aggregator")
        workflow.add_edge("aggregator", END)
        
        return workflow.compile()
    
    
    def run_parallel_execution(user_input: str):
        """Run the parallel execution pattern."""
        workflow = create_parallel_execution()
        
        initial_state = {
            "input": user_input,
            "agent1_output": "",
            "agent2_output": "",
            "agent3_output": "",
            "aggregated_output": ""
        }
        
        result = workflow.invoke(initial_state)
        return result
    
    
    if __name__ == "__main__":
        # Example usage
        result = run_parallel_execution("Implementing AI chatbot for customer service")
        print("\n=== Parallel Execution Pattern ===")
        print(f"Input: {result['input']}")
        print(f"\nTechnical Analysis: {result['agent1_output'][:200]}...")
        print(f"\nBusiness Analysis: {result['agent2_output'][:200]}...")
        print(f"\nUX Analysis: {result['agent3_output'][:200]}...")
        print(f"\nAggregated Result: {result['aggregated_output']}")
    
        

    Agentic Orchestration Pattern: Parallel Execution

    02 Parallel Execution Pattern – Detailed Documentation

    Overview

    File: 01_basic_coordination/02_parallel_execution.py
    Pattern Category: 01 Basic Coordination
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 1.2: Parallel Execution Pattern Multiple agents execute simultaneously using LangGraph.


    Code Structure

    Imports

    from typing import TypedDict, List
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    from common import get_openai_api_key, get_model_name, log_agent_action
    import asyncio
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class ParallelState(TypedDict):
        """State for parallel execution pattern."""
        input: str
        agent1_output: str
        agent2_output: str
        agent3_output: str
        aggregated_output: str
    
    
    def agent_parallel_1(state: ParallelState) -> ParallelState:
    

    ParallelState: State for parallel execution pattern.

    State Fields: – input: Stores input – agent1_output: Stores agent1 output – agent2_output: Stores agent2 output – agent3_output: Stores agent3 output – aggregated_output: Stores aggregated output – def agent_parallel_1(state: Stores def agent parallel 1(state – log_agent_action("agent_parallel_1", "processing", {"input": Stores log agent action(“agent parallel 1”, “processing”, {“input” – prompt = f"Provide a technical analysis of: Stores prompt = f”Provide a technical analysis of


    Functions

    agent_parallel_1()

    Line: 22
    Arguments: state

    First parallel agent – technical analysis.

    def agent_parallel_1(state: ParallelState) -> ParallelState:
        """First parallel agent - technical analysis."""
        log_agent_action("agent_parallel_1", "processing", {"input": state["input"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a technical analysis of: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["agent1_output"] = response.content
        return state
    
    
    def agent_parallel_2(state: ParallelState) -> ParallelState:
    

    agent_parallel_2()

    Line: 34
    Arguments: state

    Second parallel agent – business analysis.

    def agent_parallel_2(state: ParallelState) -> ParallelState:
        """Second parallel agent - business analysis."""
        log_agent_action("agent_parallel_2", "processing", {"input": state["input"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a business perspective on: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["agent2_output"] = response.content
        return state
    
    
    def agent_parallel_3(state: ParallelState) -> ParallelState:
    

    agent_parallel_3()

    Line: 46
    Arguments: state

    Third parallel agent – user experience analysis.

    def agent_parallel_3(state: ParallelState) -> ParallelState:
        """Third parallel agent - user experience analysis."""
        log_agent_action("agent_parallel_3", "processing", {"input": state["input"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Provide a user experience perspective on: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["agent3_output"] = response.content
        return state
    
    
    def aggregator(state: ParallelState) -> ParallelState:
    

    aggregator()

    Line: 58
    Arguments: state

    Aggregate results from parallel agents.

    def aggregator(state: ParallelState) -> ParallelState:
        """Aggregate results from parallel agents."""
        log_agent_action("aggregator", "combining results")
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"""
        Synthesize these three perspectives into a comprehensive analysis:
    
        Technical: {state['agent1_output']}
    
        Business: {state['agent2_output']}
    
        UX: {state['agent3_output']}
        """
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["aggregated_output"] = response.content
        return state
    
    
    def create_parallel_execution():
    

    create_parallel_execution()

    Line: 78
    Arguments: “

    Create and return a parallel execution workflow.

    def create_parallel_execution():
        """Create and return a parallel execution workflow."""
        workflow = StateGraph(ParallelState)
    
        # Add nodes
        workflow.add_node("agent1", agent_parallel_1)
        workflow.add_node("agent2", agent_parallel_2)
        workflow.add_node("agent3", agent_parallel_3)
        workflow.add_node("aggregator", aggregator)
    
        # Set entry point to all parallel agents
        workflow.set_entry_point("agent1")
        workflow.set_entry_point("agent2")
        workflow.set_entry_point("agent3")
    
        # All parallel agents feed into aggregator
        workflow.add_edge("agent1", "aggregator")
        workflow.add_edge("agent2", "aggregator")
        workflow.add_edge("agent3", "aggregator")
        workflow.add_edge("aggregator", END)
    
        return workflow.compile()
    
    
    def run_parallel_execution(user_input: str):
    

    run_parallel_execution()

    Line: 102
    Arguments: user_input

    Run the parallel execution pattern.

    def run_parallel_execution(user_input: str):
        """Run the parallel execution pattern."""
        workflow = create_parallel_execution()
    
        initial_state = {
            "input": user_input,
            "agent1_output": "",
            "agent2_output": "",
            "agent3_output": "",
            "aggregated_output": ""
        }
    
        result = workflow.invoke(initial_state)
        return result
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict, List
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 8: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 9: Import statement

    from common import get_openai_api_key, get_model_name, log_agent_action
    

    Required import for the pattern implementation.

    Line 10: Import statement

    import asyncio
    

    Required import for the pattern implementation.

    Line 13: Class definition

    class ParallelState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 14: Module docstring

        """State for parallel execution pattern."""
    

    Describes the pattern and its purpose.

    Line 22: Function agent_parallel_1 definition

    def agent_parallel_1(state: ParallelState) -> ParallelState:
    

    Defines the agent_parallel_1 function that processes the workflow state.

    Line 23: Module docstring

        """First parallel agent - technical analysis."""
    

    Describes the pattern and its purpose.

    Line 34: Function agent_parallel_2 definition

    def agent_parallel_2(state: ParallelState) -> ParallelState:
    

    Defines the agent_parallel_2 function that processes the workflow state.

    Line 35: Module docstring

        """Second parallel agent - business analysis."""
    

    Describes the pattern and its purpose.

    Line 46: Function agent_parallel_3 definition

    def agent_parallel_3(state: ParallelState) -> ParallelState:
    

    Defines the agent_parallel_3 function that processes the workflow state.

    Line 47: Module docstring

        """Third parallel agent - user experience analysis."""
    

    Describes the pattern and its purpose.

    Line 58: Function aggregator definition

    def aggregator(state: ParallelState) -> ParallelState:
    

    Defines the aggregator function that processes the workflow state.

    Line 59: Module docstring

        """Aggregate results from parallel agents."""
    

    Describes the pattern and its purpose.

    Line 71: Module docstring

        """
    

    Describes the pattern and its purpose.

    Line 78: Function create_parallel_execution definition

    def create_parallel_execution():
    

    Defines the create_parallel_execution function that processes the workflow state.

    Line 79: Module docstring

        """Create and return a parallel execution workflow."""
    

    Describes the pattern and its purpose.

    Line 102: Function run_parallel_execution definition

    def run_parallel_execution(user_input: str):
    

    Defines the run_parallel_execution function that processes the workflow state.

    Line 103: Module docstring

        """Run the parallel execution pattern."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        # Example usage
        result = run_parallel_execution("Implementing AI chatbot for customer service")
        print("\n=== Parallel Execution Pattern ===")
        print(f"Input: {result['input']}")
        print(f"\nTechnical Analysis: {result['agent1_output'][:200]}...")
        print(f"\nBusiness Analysis: {result['agent2_output'][:200]}...")
        print(f"\nUX Analysis: {result['agent3_output'][:200]}...")
        print(f"\nAggregated Result: {result['aggregated_output']}")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Orchestration Pattern: Sequential Chain

    
        """
    Pattern 1.1: Sequential Chain Pattern
    Linear execution of agents in a predefined order using LangGraph.
    """
    from typing import TypedDict, Annotated
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    from common import get_openai_api_key, get_model_name, log_agent_action
    
    
    class SequentialState(TypedDict):
        """State for sequential chain pattern."""
        input: str
        step1_output: str
        step2_output: str
        step3_output: str
        final_output: str
    
    
    def agent_step1(state: SequentialState) -> SequentialState:
        """First agent in the chain - data collection."""
        log_agent_action("agent_step1", "processing", {"input": state["input"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Analyze the following request and extract key requirements: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["step1_output"] = response.content
        return state
    
    
    def agent_step2(state: SequentialState) -> SequentialState:
        """Second agent in the chain - processing."""
        log_agent_action("agent_step2", "processing", {"previous": state["step1_output"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Based on these requirements, create a plan: {state['step1_output']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["step2_output"] = response.content
        return state
    
    
    def agent_step3(state: SequentialState) -> SequentialState:
        """Third agent in the chain - synthesis."""
        log_agent_action("agent_step3", "finalizing", {"previous": state["step2_output"]})
        
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Summarize this plan into actionable steps: {state['step2_output']}"
        response = llm.invoke([HumanMessage(content=prompt)])
        
        state["step3_output"] = response.content
        state["final_output"] = response.content
        return state
    
    
    def create_sequential_chain():
        """Create and return a sequential chain workflow."""
        workflow = StateGraph(SequentialState)
        
        # Add nodes
        workflow.add_node("step1", agent_step1)
        workflow.add_node("step2", agent_step2)
        workflow.add_node("step3", agent_step3)
        
        # Define sequential edges
        workflow.set_entry_point("step1")
        workflow.add_edge("step1", "step2")
        workflow.add_edge("step2", "step3")
        workflow.add_edge("step3", END)
        
        return workflow.compile()
    
    
    def run_sequential_chain(user_input: str):
        """Run the sequential chain pattern."""
        chain = create_sequential_chain()
        
        initial_state = {
            "input": user_input,
            "step1_output": "",
            "step2_output": "",
            "step3_output": "",
            "final_output": ""
        }
        
        result = chain.invoke(initial_state)
        return result
    
    
    if __name__ == "__main__":
        # Example usage
        result = run_sequential_chain("Build a web application for task management")
        print("\n=== Sequential Chain Pattern ===")
        print(f"Input: {result['input']}")
        print(f"\nStep 1 Output: {result['step1_output']}")
        print(f"\nStep 2 Output: {result['step2_output']}")
        print(f"\nFinal Output: {result['final_output']}")
    
        

    Agentic Orchestration Pattern: Sequential Chain

    01 Sequential Chain Pattern – Detailed Documentation

    Overview

    File: 01_basic_coordination/01_sequential_chain.py
    Pattern Category: 01 Basic Coordination
    Implementation: LangGraph/CrewAI

    Pattern Description

    Pattern 1.1: Sequential Chain Pattern Linear execution of agents in a predefined order using LangGraph.


    Code Structure

    Imports

    from typing import TypedDict, Annotated
    from langgraph.graph import StateGraph, END
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    from common import get_openai_api_key, get_model_name, log_agent_action
    

    Purpose of imports: – TypedDict: For type-safe state definitions – StateGraph: LangGraph’s graph-based workflow orchestration – ChatOpenAI: OpenAI LLM integration – HumanMessage: LangChain message formatting

    State Definition

    class SequentialState(TypedDict):
        """State for sequential chain pattern."""
        input: str
        step1_output: str
        step2_output: str
        step3_output: str
        final_output: str
    
    
    def agent_step1(state: SequentialState) -> SequentialState:
    

    SequentialState: State for sequential chain pattern.

    State Fields: – input: Stores input – step1_output: Stores step1 output – step2_output: Stores step2 output – step3_output: Stores step3 output – final_output: Stores final output – def agent_step1(state: Stores def agent step1(state – log_agent_action("agent_step1", "processing", {"input": Stores log agent action(“agent step1”, “processing”, {“input” – prompt = f"Analyze the following request and extract key requirements: Stores prompt = f”Analyze the following request and extract key requirements


    Functions

    agent_step1()

    Line: 21
    Arguments: state

    First agent in the chain – data collection.

    def agent_step1(state: SequentialState) -> SequentialState:
        """First agent in the chain - data collection."""
        log_agent_action("agent_step1", "processing", {"input": state["input"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Analyze the following request and extract key requirements: {state['input']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["step1_output"] = response.content
        return state
    
    
    def agent_step2(state: SequentialState) -> SequentialState:
    

    agent_step2()

    Line: 33
    Arguments: state

    Second agent in the chain – processing.

    def agent_step2(state: SequentialState) -> SequentialState:
        """Second agent in the chain - processing."""
        log_agent_action("agent_step2", "processing", {"previous": state["step1_output"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Based on these requirements, create a plan: {state['step1_output']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["step2_output"] = response.content
        return state
    
    
    def agent_step3(state: SequentialState) -> SequentialState:
    

    agent_step3()

    Line: 45
    Arguments: state

    Third agent in the chain – synthesis.

    def agent_step3(state: SequentialState) -> SequentialState:
        """Third agent in the chain - synthesis."""
        log_agent_action("agent_step3", "finalizing", {"previous": state["step2_output"]})
    
        llm = ChatOpenAI(api_key=get_openai_api_key(), model=get_model_name())
        prompt = f"Summarize this plan into actionable steps: {state['step2_output']}"
        response = llm.invoke([HumanMessage(content=prompt)])
    
        state["step3_output"] = response.content
        state["final_output"] = response.content
        return state
    
    
    def create_sequential_chain():
    

    create_sequential_chain()

    Line: 58
    Arguments: “

    Create and return a sequential chain workflow.

    def create_sequential_chain():
        """Create and return a sequential chain workflow."""
        workflow = StateGraph(SequentialState)
    
        # Add nodes
        workflow.add_node("step1", agent_step1)
        workflow.add_node("step2", agent_step2)
        workflow.add_node("step3", agent_step3)
    
        # Define sequential edges
        workflow.set_entry_point("step1")
        workflow.add_edge("step1", "step2")
        workflow.add_edge("step2", "step3")
        workflow.add_edge("step3", END)
    
        return workflow.compile()
    
    
    def run_sequential_chain(user_input: str):
    

    run_sequential_chain()

    Line: 76
    Arguments: user_input

    Run the sequential chain pattern.

    def run_sequential_chain(user_input: str):
        """Run the sequential chain pattern."""
        chain = create_sequential_chain()
    
        initial_state = {
            "input": user_input,
            "step1_output": "",
            "step2_output": "",
            "step3_output": "",
            "final_output": ""
        }
    
        result = chain.invoke(initial_state)
        return result
    
    
    if __name__ == "__main__":
    

    Line-by-Line Explanation

    Line 5: Import statement

    from typing import TypedDict, Annotated
    

    Imports TypedDict for creating type-safe state dictionaries.

    Line 6: Import statement

    from langgraph.graph import StateGraph, END
    

    Imports StateGraph – the core LangGraph class for building workflows.

    Line 7: Import statement

    from langchain_core.messages import HumanMessage
    

    Imports HumanMessage for formatting prompts to the LLM.

    Line 8: Import statement

    from langchain_openai import ChatOpenAI
    

    Imports ChatOpenAI for LLM interactions.

    Line 9: Import statement

    from common import get_openai_api_key, get_model_name, log_agent_action
    

    Required import for the pattern implementation.

    Line 12: Class definition

    class SequentialState(TypedDict):
    

    Defines the state structure using TypedDict for type safety.

    Line 13: Module docstring

        """State for sequential chain pattern."""
    

    Describes the pattern and its purpose.

    Line 21: Function agent_step1 definition

    def agent_step1(state: SequentialState) -> SequentialState:
    

    Defines the agent_step1 function that processes the workflow state.

    Line 22: Module docstring

        """First agent in the chain - data collection."""
    

    Describes the pattern and its purpose.

    Line 33: Function agent_step2 definition

    def agent_step2(state: SequentialState) -> SequentialState:
    

    Defines the agent_step2 function that processes the workflow state.

    Line 34: Module docstring

        """Second agent in the chain - processing."""
    

    Describes the pattern and its purpose.

    Line 45: Function agent_step3 definition

    def agent_step3(state: SequentialState) -> SequentialState:
    

    Defines the agent_step3 function that processes the workflow state.

    Line 46: Module docstring

        """Third agent in the chain - synthesis."""
    

    Describes the pattern and its purpose.

    Line 58: Function create_sequential_chain definition

    def create_sequential_chain():
    

    Defines the create_sequential_chain function that processes the workflow state.

    Line 59: Module docstring

        """Create and return a sequential chain workflow."""
    

    Describes the pattern and its purpose.

    Line 76: Function run_sequential_chain definition

    def run_sequential_chain(user_input: str):
    

    Defines the run_sequential_chain function that processes the workflow state.

    Line 77: Module docstring

        """Run the sequential chain pattern."""
    

    Describes the pattern and its purpose.


    Usage Example

    if __name__ == "__main__":
        # Example usage
        result = run_sequential_chain("Build a web application for task management")
        print("\n=== Sequential Chain Pattern ===")
        print(f"Input: {result['input']}")
        print(f"\nStep 1 Output: {result['step1_output']}")
        print(f"\nStep 2 Output: {result['step2_output']}")
        print(f"\nFinal Output: {result['final_output']}")
    

    Key Concepts

    1. State Management: Uses TypedDict to maintain workflow state
    2. Graph-Based Flow: LangGraph orchestrates execution flow
    3. Agent Functions: Each node processes and updates state
    4. LLM Integration: Uses ChatOpenAI for intelligent processing

    Pattern Benefits

    • ✅ Type-safe state management
    • ✅ Clear workflow visualization
    • ✅ Modular and reusable components
    • ✅ Easy to test and debug
    • ✅ Scalable architecture

    Related Patterns

    • See other patterns in the same category
    • Explore combination possibilities
    • Check the main documentation for pattern relationships
  • Agentic Design Pattern: Model Routing And Selection

    
        """
    Pattern 170: Model Routing & Selection
    
    Description:
        The Model Routing & Selection pattern intelligently routes queries to the most
        appropriate model based on query characteristics, model capabilities, performance
        requirements, and cost constraints. It analyzes queries, classifies them, and
        selects optimal models dynamically.
    
    Components:
        1. Query Analyzer: Analyzes query characteristics
        2. Classifier: Categorizes query type and complexity
        3. Model Registry: Maintains available models and capabilities
        4. Router: Selects optimal model
        5. Performance Monitor: Tracks model performance
        6. Cost Optimizer: Balances quality and cost
    
    Use Cases:
        - Cost-optimized AI systems
        - Performance-critical applications
        - Multi-model architectures
        - Adaptive system design
        - Resource-constrained environments
        - Quality-cost trade-offs
    
    Benefits:
        - Optimal resource utilization
        - Cost efficiency
        - Performance optimization
        - Scalability
        - Flexible model upgrades
        - Quality-aware routing
    
    Trade-offs:
        - Routing overhead
        - Classification accuracy dependency
        - Complexity
        - Multiple model management
        - Potential routing errors
    
    LangChain Implementation:
        Implements intelligent query classification and model selection using LangChain's
        model abstraction. Routes queries based on complexity, domain, and requirements.
    
    🎉 THIS IS PATTERN 170 - THE FINAL PATTERN! 🎉
    Completing all 170 Agentic AI Design Patterns!
    """
    
    import os
    import time
    from typing import List, Dict, Any, Optional, Callable
    from dataclasses import dataclass, field
    from enum import Enum
    from collections import defaultdict
    from dotenv import load_dotenv
    
    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    
    load_dotenv()
    
    
    class QueryComplexity(Enum):
        """Query complexity levels"""
        SIMPLE = "simple"
        MODERATE = "moderate"
        COMPLEX = "complex"
        EXPERT = "expert"
    
    
    class QueryDomain(Enum):
        """Query domain categories"""
        GENERAL = "general"
        TECHNICAL = "technical"
        CREATIVE = "creative"
        ANALYTICAL = "analytical"
        CONVERSATIONAL = "conversational"
        CODE = "code"
    
    
    class ModelSize(Enum):
        """Model size categories"""
        TINY = "tiny"
        SMALL = "small"
        MEDIUM = "medium"
        LARGE = "large"
        XLARGE = "xlarge"
    
    
    @dataclass
    class QueryProfile:
        """Profile of a query"""
        text: str
        complexity: QueryComplexity
        domain: QueryDomain
        estimated_tokens: int
        requires_reasoning: bool = False
        requires_creativity: bool = False
        latency_sensitive: bool = False
    
    
    @dataclass
    class ModelSpec:
        """Specification of a model"""
        name: str
        model_id: str
        size: ModelSize
        capabilities: List[str]
        cost_per_1k_tokens: float
        avg_latency_ms: float
        quality_score: float  # 0-1
        max_tokens: int
        best_for: List[QueryDomain]
        available: bool = True
    
    
    @dataclass
    class RoutingDecision:
        """Result of routing decision"""
        selected_model: str
        reasoning: str
        confidence: float
        alternatives: List[str]
        estimated_cost: float
        estimated_latency: float
    
    
    class ModelRouter:
        """Intelligent model routing and selection"""
        
        def __init__(self):
            """Initialize model router"""
            self.models: Dict[str, ModelSpec] = {}
            self.llm_instances: Dict[str, ChatOpenAI] = {}
            
            # Performance tracking
            self.routing_history: List[Dict[str, Any]] = []
            self.model_performance: Dict[str, Dict[str, Any]] = defaultdict(lambda: {
                "queries_routed": 0,
                "total_cost": 0.0,
                "total_latency": 0.0,
                "success_count": 0
            })
            
            # Register default models
            self._register_default_models()
            
            # Classification prompt
            self.classifier_prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a query classifier. Analyze the query and determine:
                1. Complexity: simple, moderate, complex, or expert
                2. Domain: general, technical, creative, analytical, conversational, or code
                3. Special requirements: reasoning, creativity, latency-sensitive"""),
                ("user", """Query: {query}
    
    Classify this query. Respond in format:
    COMPLEXITY: [level]
    DOMAIN: [domain]
    REASONING_REQUIRED: [yes/no]
    CREATIVITY_REQUIRED: [yes/no]
    LATENCY_SENSITIVE: [yes/no]""")
            ])
        
        def _register_default_models(self):
            """Register default model specifications"""
            # Tiny model - ultra fast, cheap
            self.register_model(ModelSpec(
                name="GPT-3.5 Turbo Mini",
                model_id="gpt-3.5-turbo",
                size=ModelSize.SMALL,
                capabilities=["conversation", "simple_qa", "summarization"],
                cost_per_1k_tokens=0.0015,
                avg_latency_ms=500,
                quality_score=0.7,
                max_tokens=4096,
                best_for=[QueryDomain.GENERAL, QueryDomain.CONVERSATIONAL]
            ))
            
            # Medium model - balanced
            self.register_model(ModelSpec(
                name="GPT-3.5 Turbo",
                model_id="gpt-3.5-turbo",
                size=ModelSize.MEDIUM,
                capabilities=["reasoning", "analysis", "code", "creativity"],
                cost_per_1k_tokens=0.002,
                avg_latency_ms=800,
                quality_score=0.8,
                max_tokens=4096,
                best_for=[QueryDomain.TECHNICAL, QueryDomain.CODE, QueryDomain.ANALYTICAL]
            ))
            
            # Large model - high quality
            self.register_model(ModelSpec(
                name="GPT-4",
                model_id="gpt-4",
                size=ModelSize.LARGE,
                capabilities=["advanced_reasoning", "complex_analysis", "expert_code", "creative_writing"],
                cost_per_1k_tokens=0.03,
                avg_latency_ms=2000,
                quality_score=0.95,
                max_tokens=8192,
                best_for=[QueryDomain.TECHNICAL, QueryDomain.ANALYTICAL, QueryDomain.CREATIVE]
            ))
            
            # Specialized code model (simulated)
            self.register_model(ModelSpec(
                name="Code Specialist",
                model_id="gpt-3.5-turbo",
                size=ModelSize.MEDIUM,
                capabilities=["code_generation", "code_review", "debugging"],
                cost_per_1k_tokens=0.0025,
                avg_latency_ms=900,
                quality_score=0.85,
                max_tokens=4096,
                best_for=[QueryDomain.CODE]
            ))
        
        def register_model(self, spec: ModelSpec):
            """Register a model"""
            self.models[spec.name] = spec
            try:
                self.llm_instances[spec.name] = ChatOpenAI(
                    model=spec.model_id,
                    temperature=0.7
                )
            except Exception as e:
                print(f"Warning: Could not create LLM for {spec.name}: {e}")
                spec.available = False
        
        def route_query(self, query: str, 
                       max_cost: Optional[float] = None,
                       max_latency: Optional[float] = None,
                       min_quality: Optional[float] = None) -> RoutingDecision:
            """
            Route query to optimal model
            
            Args:
                query: The query to route
                max_cost: Maximum acceptable cost
                max_latency: Maximum acceptable latency (ms)
                min_quality: Minimum acceptable quality score
                
            Returns:
                RoutingDecision with selected model and reasoning
            """
            # Classify query
            profile = self._classify_query(query)
            
            # Score and rank models
            scored_models = self._score_models(profile, max_cost, max_latency, min_quality)
            
            if not scored_models:
                raise ValueError("No suitable model found for query")
            
            # Select best model
            best_score, best_model = scored_models[0]
            alternatives = [name for _, name in scored_models[1:3]]
            
            # Estimate costs
            estimated_tokens = profile.estimated_tokens
            estimated_cost = (estimated_tokens / 1000) * best_model.cost_per_1k_tokens
            estimated_latency = best_model.avg_latency_ms
            
            # Generate reasoning
            reasoning = self._generate_routing_reasoning(profile, best_model, best_score)
            
            # Record decision
            self._record_routing(query, best_model.name, profile, estimated_cost)
            
            return RoutingDecision(
                selected_model=best_model.name,
                reasoning=reasoning,
                confidence=best_score / 100,  # Normalize to 0-1
                alternatives=alternatives,
                estimated_cost=estimated_cost,
                estimated_latency=estimated_latency
            )
        
        def execute_with_routing(self, query: str, **routing_kwargs) -> Dict[str, Any]:
            """Route and execute query"""
            # Route query
            decision = self.route_query(query, **routing_kwargs)
            
            # Execute on selected model
            start_time = time.time()
            
            try:
                llm = self.llm_instances[decision.selected_model]
                prompt = ChatPromptTemplate.from_messages([
                    ("user", "{query}")
                ])
                chain = prompt | llm | StrOutputParser()
                result = chain.invoke({"query": query})
                
                execution_time = time.time() - start_time
                
                # Update performance stats
                self._update_performance(decision.selected_model, 
                                       decision.estimated_cost,
                                       execution_time * 1000,
                                       success=True)
                
                return {
                    "result": result,
                    "routing_decision": decision,
                    "actual_latency_ms": execution_time * 1000,
                    "success": True
                }
                
            except Exception as e:
                self._update_performance(decision.selected_model, 0, 0, success=False)
                return {
                    "result": None,
                    "routing_decision": decision,
                    "error": str(e),
                    "success": False
                }
        
        def _classify_query(self, query: str) -> QueryProfile:
            """Classify query characteristics"""
            # Use LLM to classify
            classifier = self.classifier_prompt | ChatOpenAI(model="gpt-3.5-turbo", temperature=0) | StrOutputParser()
            
            try:
                classification = classifier.invoke({"query": query})
                
                # Parse classification
                complexity = QueryComplexity.MODERATE
                domain = QueryDomain.GENERAL
                requires_reasoning = False
                requires_creativity = False
                latency_sensitive = False
                
                for line in classification.split('\n'):
                    line = line.strip().upper()
                    if line.startswith('COMPLEXITY:'):
                        comp_str = line.split(':', 1)[1].strip().lower()
                        if 'simple' in comp_str:
                            complexity = QueryComplexity.SIMPLE
                        elif 'expert' in comp_str:
                            complexity = QueryComplexity.EXPERT
                        elif 'complex' in comp_str:
                            complexity = QueryComplexity.COMPLEX
                    elif line.startswith('DOMAIN:'):
                        domain_str = line.split(':', 1)[1].strip().lower()
                        if 'code' in domain_str:
                            domain = QueryDomain.CODE
                        elif 'technical' in domain_str:
                            domain = QueryDomain.TECHNICAL
                        elif 'creative' in domain_str:
                            domain = QueryDomain.CREATIVE
                        elif 'analytical' in domain_str:
                            domain = QueryDomain.ANALYTICAL
                        elif 'conversational' in domain_str:
                            domain = QueryDomain.CONVERSATIONAL
                    elif 'REASONING' in line:
                        requires_reasoning = 'YES' in line
                    elif 'CREATIVITY' in line:
                        requires_creativity = 'YES' in line
                    elif 'LATENCY' in line:
                        latency_sensitive = 'YES' in line
                
                estimated_tokens = len(query.split()) * 1.5
                
                return QueryProfile(
                    text=query,
                    complexity=complexity,
                    domain=domain,
                    estimated_tokens=int(estimated_tokens),
                    requires_reasoning=requires_reasoning,
                    requires_creativity=requires_creativity,
                    latency_sensitive=latency_sensitive
                )
                
            except Exception as e:
                # Fallback to simple heuristics
                return QueryProfile(
                    text=query,
                    complexity=QueryComplexity.MODERATE,
                    domain=QueryDomain.GENERAL,
                    estimated_tokens=len(query.split()) * 1.5,
                    requires_reasoning=len(query.split()) > 20,
                    requires_creativity=any(word in query.lower() for word in ['creative', 'imagine', 'story']),
                    latency_sensitive=any(word in query.lower() for word in ['quick', 'fast', 'urgent'])
                )
        
        def _score_models(self, profile: QueryProfile,
                         max_cost: Optional[float],
                         max_latency: Optional[float],
                         min_quality: Optional[float]) -> List[Tuple[float, ModelSpec]]:
            """Score and rank models for query"""
            scores = []
            
            for model in self.models.values():
                if not model.available:
                    continue
                
                # Check hard constraints
                if max_cost:
                    est_cost = (profile.estimated_tokens / 1000) * model.cost_per_1k_tokens
                    if est_cost > max_cost:
                        continue
                
                if max_latency and model.avg_latency_ms > max_latency:
                    continue
                
                if min_quality and model.quality_score  str:
            """Generate human-readable routing reasoning"""
            reasons = []
            
            if profile.domain in model.best_for:
                reasons.append(f"specialized for {profile.domain.value} domain")
            
            if profile.complexity == QueryComplexity.SIMPLE:
                reasons.append("query is simple, using efficient model")
            elif profile.complexity == QueryComplexity.EXPERT:
                reasons.append("complex query requires high-capability model")
            
            if model.quality_score >= 0.9:
                reasons.append("high quality model selected")
            
            if model.cost_per_1k_tokens  Dict[str, Any]:
            """Get routing performance report"""
            report = {
                "total_queries": len(self.routing_history),
                "models": {}
            }
            
            for model_name, stats in self.model_performance.items():
                if stats["queries_routed"] > 0:
                    report["models"][model_name] = {
                        "queries_routed": stats["queries_routed"],
                        "success_rate": stats["success_count"] / stats["queries_routed"],
                        "avg_cost": stats["total_cost"] / stats["queries_routed"],
                        "avg_latency_ms": stats["total_latency"] / stats["queries_routed"]
                    }
            
            return report
    
    
    def demonstrate_model_routing():
        """Demonstrate model routing and selection"""
        print("=" * 80)
        print("🎉 PATTERN 170: MODEL ROUTING & SELECTION 🎉")
        print("THE FINAL PATTERN - COMPLETING ALL 170 AGENTIC AI DESIGN PATTERNS!")
        print("=" * 80)
        
        router = ModelRouter()
        
        # Example 1: Different query complexities
        print("\n" + "=" * 80)
        print("Example 1: Routing Based on Query Complexity")
        print("=" * 80)
        
        queries = [
            "What is 2+2?",  # Simple
            "Explain the difference between machine learning and deep learning",  # Moderate
            "Design a distributed system architecture for a real-time analytics platform with fault tolerance"  # Complex
        ]
        
        for i, query in enumerate(queries, 1):
            print(f"\n{i}. Query: {query}")
            decision = router.route_query(query)
            print(f"   Selected: {decision.selected_model}")
            print(f"   Reasoning: {decision.reasoning}")
            print(f"   Confidence: {decision.confidence:.2f}")
            print(f"   Estimated cost: ${decision.estimated_cost:.6f}")
            print(f"   Estimated latency: {decision.estimated_latency:.0f}ms")
        
        # Example 2: Domain-specific routing
        print("\n" + "=" * 80)
        print("Example 2: Domain-Specific Routing")
        print("=" * 80)
        
        domain_queries = [
            ("Write a Python function to reverse a string", QueryDomain.CODE),
            ("Write a creative short story about a robot", QueryDomain.CREATIVE),
            ("Analyze the pros and cons of remote work", QueryDomain.ANALYTICAL)
        ]
        
        for query, expected_domain in domain_queries:
            print(f"\nQuery ({expected_domain.value}): {query}")
            decision = router.route_query(query)
            print(f"  → Routed to: {decision.selected_model}")
            print(f"  → Reasoning: {decision.reasoning}")
        
        # Example 3: Cost-constrained routing
        print("\n" + "=" * 80)
        print("Example 3: Cost-Constrained Routing")
        print("=" * 80)
        
        query = "Explain quantum computing"
        
        print(f"\nQuery: {query}")
        
        # No cost constraint
        decision1 = router.route_query(query)
        print(f"\n1. No cost constraint:")
        print(f"   Model: {decision1.selected_model}")
        print(f"   Cost: ${decision1.estimated_cost:.6f}")
        
        # Tight cost constraint
        decision2 = router.route_query(query, max_cost=0.001)
        print(f"\n2. Max cost $0.001:")
        print(f"   Model: {decision2.selected_model}")
        print(f"   Cost: ${decision2.estimated_cost:.6f}")
        
        # Example 4: Latency-sensitive routing
        print("\n" + "=" * 80)
        print("Example 4: Latency-Sensitive Routing")
        print("=" * 80)
        
        urgent_query = "Quick summary of this article"
        
        decision_fast = router.route_query(urgent_query, max_latency=1000)
        print(f"\nUrgent query: {urgent_query}")
        print(f"Max latency: 1000ms")
        print(f"Selected: {decision_fast.selected_model}")
        print(f"Expected latency: {decision_fast.estimated_latency:.0f}ms")
        
        # Example 5: Execute with routing
        print("\n" + "=" * 80)
        print("Example 5: Execute with Automatic Routing")
        print("=" * 80)
        
        test_query = "What are the benefits of exercise?"
        
        print(f"\nQuery: {test_query}")
        print("Routing and executing...")
        
        result = router.execute_with_routing(test_query)
        
        if result["success"]:
            print(f"\nRouted to: {result['routing_decision'].selected_model}")
            print(f"Actual latency: {result['actual_latency_ms']:.0f}ms")
            print(f"Response: {result['result'][:150]}...")
        else:
            print(f"Execution failed: {result.get('error')}")
        
        # Example 6: Batch routing analysis
        print("\n" + "=" * 80)
        print("Example 6: Batch Routing Analysis")
        print("=" * 80)
        
        batch_queries = [
            "Hello, how are you?",
            "Write a sorting algorithm in Python",
            "Explain Einstein's theory of relativity",
            "What's the weather like?",
            "Design a machine learning pipeline"
        ]
        
        print(f"\nRouting {len(batch_queries)} queries:")
        print("-" * 60)
        
        model_counts = defaultdict(int)
        total_cost = 0.0
        
        for query in batch_queries:
            decision = router.route_query(query)
            model_counts[decision.selected_model] += 1
            total_cost += decision.estimated_cost
        
        print("\nRouting Distribution:")
        for model, count in model_counts.items():
            print(f"  {model}: {count} queries ({count/len(batch_queries)*100:.0f}%)")
        
        print(f"\nTotal estimated cost: ${total_cost:.6f}")
        print(f"Average cost per query: ${total_cost/len(batch_queries):.6f}")
        
        # Example 7: Performance report
        print("\n" + "=" * 80)
        print("Example 7: Routing Performance Report")
        print("=" * 80)
        
        report = router.get_performance_report()
        
        print(f"\nTotal queries routed: {report['total_queries']}")
        print("\nPer-Model Statistics:")
        print("-" * 60)
        
        for model_name, stats in report["models"].items():
            print(f"\n{model_name}:")
            print(f"  Queries routed: {stats['queries_routed']}")
            print(f"  Success rate: {stats['success_rate']:.1%}")
            print(f"  Avg cost: ${stats['avg_cost']:.6f}")
            print(f"  Avg latency: {stats['avg_latency_ms']:.0f}ms")
        
        # Final Summary
        print("\n" + "=" * 80)
        print("🎊 CONGRATULATIONS! 🎊")
        print("=" * 80)
        print("""
    ✅ ALL 170 AGENTIC AI DESIGN PATTERNS IMPLEMENTED! ✅
    
    The Model Routing & Selection pattern enables:
    ✓ Intelligent query-to-model matching
    ✓ Cost-performance optimization
    ✓ Domain-aware routing
    ✓ Latency-sensitive selection
    ✓ Quality-constrained routing
    ✓ Performance monitoring
    ✓ Adaptive model selection
    
    This pattern is valuable for:
    - Production AI systems
    - Cost optimization
    - Multi-model architectures
    - Performance-critical applications
    - Resource management
    - Quality assurance
    
    🎉 MILESTONE ACHIEVED: 170/170 PATTERNS COMPLETE! 🎉
    
    This completes the comprehensive implementation of all agentic AI design
    patterns in LangChain/LangGraph, covering:
    - Core Architectural Patterns
    - Reasoning & Planning Patterns
    - Multi-Agent Patterns
    - Tool Use & Action Patterns
    - Memory & State Management
    - Interaction & Control
    - Evaluation & Optimization
    - Safety & Reliability
    - Advanced Hybrid Patterns
    - Emerging & Research Patterns
    - Domain-Specific Patterns
    - Implementation Patterns
    - Prompt Engineering
    - Resource Management
    - Testing & Quality
    - Communication Patterns
    - Advanced Memory, Planning, Context, Learning
    - Coordination & Orchestration
    - Knowledge Management
    - Dialogue & Interaction
    - Specialization, Control, Performance
    - Error Handling, Testing & Integration
    - Advanced Reasoning
    - AND Emerging Paradigms!
    
    Thank you for following this comprehensive journey through agentic AI patterns!
        """)
    
    
    if __name__ == "__main__":
        demonstrate_model_routing()
    
        

    Agentic Design Pattern: Model Routing And Selection

    Pattern 170: Model Routing & Selection – Line-by-Line Explanation

    Overview

    This document provides a detailed line-by-line explanation of the Model Routing & Selection pattern implementation.


    Pattern Description

    Pattern 170: Model Routing & Selection

    Description: The Model Routing & Selection pattern intelligently routes queries to the most appropriate model based on query characteristics, model capabilities, performance requirements, and cost constraints. It analyzes queries, classifies them, and selects optimal models dynamically.

    Components: 1. Query Analyzer: Analyzes query characteristics 2. Classifier: Categorizes query type and complexity 3. Model Registry: Maintains available models and capabilities 4. Router: Selects optimal model 5. Performance Monitor: Tracks model performance 6. Cost Optimizer: Balances quality and cost

    Use Cases: – Cost-optimized AI systems – Performance-critical applications – Multi-model architectures – Adaptive system design – Resource-constrained environments – Quality-cost trade-offs

    Benefits: – Optimal resource utilization – Cost efficiency – Performance optimization – Scalability – Flexible model upgrades – Quality-aware routing

    Trade-offs: – Routing overhead – Classification accuracy dependency – Complexity – Multiple model management – Potential routing errors

    LangChain Implementation: Implements intelligent query classification and model selection using LangChain’s model abstraction. Routes queries based on complexity, domain, and requirements.

    🎉 THIS IS PATTERN 170 – THE FINAL PATTERN! 🎉 Completing all 170 Agentic AI Design Patterns!


    File Statistics

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

    Module Documentation

    """
    Pattern 170: Model Routing & Selection
    
    Description:
        The Model Routing & Selection pattern intelligently routes queries to the most
        appropriate model based on query characteristics, model capabilities, performance
        requirements, and cost constraints. It analyzes queries, classifies them, and
        selects optimal models dynamically.
    
    Components:
        1. Query Analyzer: Analyzes query characteristics
        2. Classifier: Categorizes query type and complexity
        3. Model Registry: Maintains available models and capabilities
        4. Router: Selects optimal model
        5. Performance Monitor: Tracks model performance
        6. Cost Optimizer: Balances quality and cost
    
    Use Cases:
        - Cost-optimized AI systems
        - Performance-critical applications
        - Multi-model architectures
        - Adaptive system design
        - Resource-constrained environments
        - Quality-cost trade-offs
    
    Benefits:
        - Optimal resource utilization
        - Cost efficiency
        - Performance optimization
        - Scalability
        - Flexible model upgrades
        - Quality-aware routing
    
    Trade-offs:
        - Routing overhead
        - Classification accuracy dependency
        - Complexity
        - Multiple model management
        - Potential routing errors
    
    LangChain Implementation:
        Implements intelligent query classification and model selection using LangChain's
        model abstraction. Routes queries based on complexity, domain, and requirements.
    
    🎉 THIS IS PATTERN 170 - THE FINAL PATTERN! 🎉
    Completing all 170 Agentic AI Design Patterns!
    """
    

    Purpose: This module implements the Model Routing & Selection 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 time
    import typing.Any
    import typing.Callable
    import typing.Dict
    import typing.List
    import typing.Optional
    

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

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

    Lines 64-69

    Inherits from: Enum

    Purpose:

    Query complexity levels
    

    Class: QueryDomain

    Lines 72-79

    Inherits from: Enum

    Purpose:

    Query domain categories
    

    Class: ModelSize

    Lines 82-88

    Inherits from: Enum

    Purpose:

    Model size categories
    

    Class: QueryProfile

    Lines 92-100

    Purpose:

    Profile of a query
    

    Class: ModelSpec

    Lines 104-115

    Purpose:

    Specification of a model
    

    Class: RoutingDecision

    Lines 119-126

    Purpose:

    Result of routing decision
    

    Class: ModelRouter

    Lines 129-504

    Purpose:

    Intelligent model routing and selection
    

    Methods:

    __init__(self)

    Lines 132-163

    Purpose:

    Initialize model router
    

    Parameters:

    _register_default_models(self)

    Lines 165-217

    Purpose:

    Register default model specifications
    

    Parameters:

    register_model(self, spec)

    Lines 219-229

    Purpose:

    Register a model
    

    Parameters: – spec: Spec

    route_query(self, query, max_cost, max_latency, min_quality)

    Lines 231-278

    Purpose:

    Route query to optimal model
    
    Args:
        query: The query to route
        max_cost: Maximum acceptable cost
        max_latency: Maximum acceptable latency (ms)
        min_quality: Minimum acceptable quality score
    
    Returns:
        RoutingDecision with selected model and reasoning
    

    Parameters: – query: Input query or question – max_cost: Max cost – max_latency: Max latency – min_quality: Min quality

    Returns: RoutingDecision

    execute_with_routing(self, query)

    Lines 280-318

    Purpose:

    Route and execute query
    

    Parameters: – query: Input query or question

    Returns: <ast.Subscript object at 0x000001A06D684E10>

    _classify_query(self, query)

    Lines 320-386

    Purpose:

    Classify query characteristics
    

    Parameters: – query: Input query or question

    Returns: QueryProfile

    _score_models(self, profile, max_cost, max_latency, min_quality)

    Lines 388-442

    Purpose:

    Score and rank models for query
    

    Parameters: – profile: Profile – max_cost: Max cost – max_latency: Max latency – min_quality: Min quality

    Returns: <ast.Subscript object at 0x000001A06D7B38D0>

    _generate_routing_reasoning(self, profile, model, score)

    Lines 444-463

    Purpose:

    Generate human-readable routing reasoning
    

    Parameters: – profile: Profile – model: Language model instance – score: Score

    Returns: str

    _record_routing(self, query, model_name, profile, estimated_cost)

    Lines 465-477

    Purpose:

    Record routing decision
    

    Parameters: – query: Input query or question – model_name: Language model instance – profile: Profile – estimated_cost: Estimated cost

    _update_performance(self, model_name, cost, latency, success)

    Lines 479-486

    Purpose:

    Update model performance statistics
    

    Parameters: – model_name: Language model instance – cost: Cost – latency: Latency – success: Success

    get_performance_report(self)

    Lines 488-504

    Purpose:

    Get routing performance report
    

    Parameters:

    Returns: <ast.Subscript object at 0x000001A06D6F6750>


    Functions

    demonstrate_model_routing()

    Lines 507-709

    Purpose:

    Demonstrate model routing and selection
    

    Key Concepts Summary

    Pattern Overview

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

    Core Components

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

    When to Use

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

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


    Best Practices Demonstrated

    1. Environment Configuration

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

    2. Error Handling

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

    3. Prompt Engineering

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

    4. Code Organization

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

    5. Performance Optimization

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

    LangChain Components Used

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

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


    Execution Flow

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

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


    Performance Considerations

    Computational Cost

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

    Optimization Strategies

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

    Cost Estimates (Approximate)

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

    Actual costs vary based on input/output token counts


    Common Pitfalls to Avoid

    1. Prompt Design Issues

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

    2. Error Handling

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

    3. Token Management

    ❌ Exceeding model token limits
    ✅ Truncate inputs, chunk processing

    4. Security

    ❌ Exposing API keys in code
    ✅ Use environment variables

    5. Testing

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

    6. Cost Management

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


    Further Improvements

    Production Enhancements

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

    Feature Extensions

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

    Code Quality

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

    Related Patterns

    • Pattern 169

    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 Model Routing & Selection pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:

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

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

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


    File: 170_model_routing_&_selection.py
    Total Lines: 714
    Pattern Category: AI Agent Design Pattern #170


    Generated from actual implementation code
    For educational and reference purposes

  • Agentic Design Pattern: Retrieval Interleaving

    
        """
    Pattern 169: Retrieval Interleaving
    
    Description:
        The Retrieval Interleaving pattern dynamically retrieves information during
        generation, interleaving retrieval steps with reasoning and generation steps.
        Unlike standard RAG which retrieves once upfront, this pattern retrieves
        multiple times as needed based on the generation process.
    
    Components:
        1. Generation Monitor: Tracks generation progress
        2. Retrieval Trigger: Decides when to retrieve
        3. Query Generator: Creates retrieval queries
        4. Retriever: Fetches relevant information
        5. Context Integrator: Merges retrieved content
        6. Iteration Controller: Manages interleaving loops
    
    Use Cases:
        - Complex multi-hop reasoning
        - Long-form content generation
        - Research-intensive tasks
        - Dynamic information needs
        - Iterative question answering
        - Knowledge synthesis
    
    Benefits:
        - Adaptive information gathering
        - Better context relevance
        - Handles complex queries
        - Reduces initial retrieval overhead
        - More accurate results
    
    Trade-offs:
        - Multiple retrieval calls
        - Increased latency
        - Complex orchestration
        - Higher costs
        - Requires good triggering logic
    
    LangChain Implementation:
        Implements iterative retrieval with LangChain's retrieval components.
        Uses LLM to decide when and what to retrieve during generation.
    """
    
    import os
    from typing import List, Dict, Any, Optional, Tuple
    from dataclasses import dataclass, field
    from enum import Enum
    from dotenv import load_dotenv
    
    from langchain_openai import ChatOpenAI, OpenAIEmbeddings
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.documents import Document
    
    load_dotenv()
    
    
    class RetrievalDecision(Enum):
        """Decision on whether to retrieve"""
        RETRIEVE = "retrieve"
        CONTINUE_GENERATION = "continue"
        COMPLETE = "complete"
    
    
    @dataclass
    class RetrievalStep:
        """Represents a retrieval step"""
        query: str
        documents: List[Document]
        timestamp: int
        reasoning: str
    
    
    @dataclass
    class GenerationStep:
        """Represents a generation step"""
        content: str
        timestamp: int
        used_retrieval: bool
    
    
    @dataclass
    class InterleavedResult:
        """Result of interleaved retrieval-generation"""
        final_output: str
        retrieval_steps: List[RetrievalStep]
        generation_steps: List[GenerationStep]
        total_retrievals: int
        total_tokens_estimated: int
    
    
    class MockVectorStore:
        """Mock vector store for demonstration"""
        
        def __init__(self):
            """Initialize with sample documents"""
            self.documents = [
                Document(page_content="Machine learning is a subset of AI that enables computers to learn from data.", 
                        metadata={"source": "ml_basics"}),
                Document(page_content="Deep learning uses neural networks with multiple layers to process data.",
                        metadata={"source": "dl_intro"}),
                Document(page_content="Natural language processing (NLP) enables computers to understand human language.",
                        metadata={"source": "nlp_overview"}),
                Document(page_content="Transformers revolutionized NLP with attention mechanisms.",
                        metadata={"source": "transformers"}),
                Document(page_content="GPT models are large language models trained on vast amounts of text data.",
                        metadata={"source": "gpt_info"}),
                Document(page_content="RAG combines retrieval with generation for better accuracy.",
                        metadata={"source": "rag_concept"}),
                Document(page_content="Computer vision enables machines to interpret visual information.",
                        metadata={"source": "cv_basics"}),
                Document(page_content="Reinforcement learning trains agents through rewards and penalties.",
                        metadata={"source": "rl_intro"}),
            ]
            self.embeddings = OpenAIEmbeddings()
        
        def similarity_search(self, query: str, k: int = 3) -> List[Document]:
            """Mock similarity search"""
            # Simple keyword matching for demonstration
            query_lower = query.lower()
            scored_docs = []
            
            for doc in self.documents:
                content_lower = doc.page_content.lower()
                score = sum(1 for word in query_lower.split() if word in content_lower)
                scored_docs.append((score, doc))
            
            scored_docs.sort(key=lambda x: x[0], reverse=True)
            return [doc for _, doc in scored_docs[:k]]
    
    
    class RetrievalInterleavingAgent:
        """Agent that interleaves retrieval with generation"""
        
        def __init__(self, vector_store: Optional[MockVectorStore] = None,
                     max_retrievals: int = 5):
            """
            Initialize interleaving agent
            
            Args:
                vector_store: Vector store for retrieval
                max_retrievals: Maximum number of retrieval steps
            """
            self.vector_store = vector_store or MockVectorStore()
            self.max_retrievals = max_retrievals
            self.llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
            
            # Prompts
            self.decision_prompt = ChatPromptTemplate.from_messages([
                ("system", """You are an AI that decides when to retrieve more information.
                Analyze the current generation state and decide if you need more information.
                
                Respond with one of: RETRIEVE, CONTINUE, COMPLETE
                If RETRIEVE, also provide a search query."""),
                ("user", """Query: {query}
    
    Current generation:
    {current_generation}
    
    Context so far:
    {context}
    
    Decision (RETRIEVE/CONTINUE/COMPLETE) and query if retrieving:""")
            ])
            
            self.generation_prompt = ChatPromptTemplate.from_messages([
                ("system", """You are a helpful AI assistant. Generate content based on 
                the query and available context. Be factual and cite information when possible."""),
                ("user", """Query: {query}
    
    Available context:
    {context}
    
    Previous generation:
    {previous_generation}
    
    Continue or complete the response:""")
            ])
            
            self.decision_chain = self.decision_prompt | self.llm | StrOutputParser()
            self.generation_chain = self.generation_prompt | self.llm | StrOutputParser()
        
        def generate_with_interleaved_retrieval(self, query: str) -> InterleavedResult:
            """Generate response with interleaved retrieval"""
            retrieval_steps: List[RetrievalStep] = []
            generation_steps: List[GenerationStep] = []
            
            current_generation = ""
            retrieved_context = ""
            step_counter = 0
            
            # Initial generation attempt
            initial_gen = self._generate(query, "", "")
            current_generation = initial_gen
            generation_steps.append(GenerationStep(
                content=initial_gen,
                timestamp=step_counter,
                used_retrieval=False
            ))
            step_counter += 1
            
            # Interleaving loop
            for retrieval_count in range(self.max_retrievals):
                # Decide if we need more information
                decision, retrieval_query = self._decide_next_step(
                    query, current_generation, retrieved_context
                )
                
                if decision == RetrievalDecision.COMPLETE:
                    break
                
                if decision == RetrievalDecision.RETRIEVE and retrieval_query:
                    # Retrieve documents
                    docs = self.vector_store.similarity_search(retrieval_query, k=2)
                    
                    retrieval_steps.append(RetrievalStep(
                        query=retrieval_query,
                        documents=docs,
                        timestamp=step_counter,
                        reasoning=f"Needed information about: {retrieval_query}"
                    ))
                    step_counter += 1
                    
                    # Update context
                    new_context = "\n\n".join([doc.page_content for doc in docs])
                    retrieved_context += "\n\n" + new_context
                    
                    # Generate with new context
                    new_generation = self._generate(query, retrieved_context, current_generation)
                    current_generation = new_generation
                    
                    generation_steps.append(GenerationStep(
                        content=new_generation,
                        timestamp=step_counter,
                        used_retrieval=True
                    ))
                    step_counter += 1
                
                elif decision == RetrievalDecision.CONTINUE_GENERATION:
                    # Continue generation without retrieval
                    new_generation = self._generate(query, retrieved_context, current_generation)
                    current_generation = new_generation
                    
                    generation_steps.append(GenerationStep(
                        content=new_generation,
                        timestamp=step_counter,
                        used_retrieval=False
                    ))
                    step_counter += 1
            
            # Estimate tokens
            total_tokens = sum(len(step.content.split()) for step in generation_steps) * 1.3
            total_tokens += sum(len(step.query.split()) for step in retrieval_steps) * 1.3
            
            return InterleavedResult(
                final_output=current_generation,
                retrieval_steps=retrieval_steps,
                generation_steps=generation_steps,
                total_retrievals=len(retrieval_steps),
                total_tokens_estimated=int(total_tokens)
            )
        
        def _decide_next_step(self, query: str, current_generation: str,
                             context: str) -> Tuple[RetrievalDecision, Optional[str]]:
            """Decide whether to retrieve, continue, or complete"""
            decision_text = self.decision_chain.invoke({
                "query": query,
                "current_generation": current_generation[:200],
                "context": context[:300] if context else "No context yet"
            })
            
            # Parse decision
            decision_text_upper = decision_text.upper()
            
            if "COMPLETE" in decision_text_upper:
                return RetrievalDecision.COMPLETE, None
            elif "RETRIEVE" in decision_text_upper:
                # Extract query from response
                lines = decision_text.split('\n')
                query_line = next((line for line in lines if "query" in line.lower()), None)
                if query_line and ':' in query_line:
                    retrieval_query = query_line.split(':', 1)[1].strip()
                else:
                    retrieval_query = query  # Fallback to original query
                return RetrievalDecision.RETRIEVE, retrieval_query
            else:
                return RetrievalDecision.CONTINUE_GENERATION, None
        
        def _generate(self, query: str, context: str, previous: str) -> str:
            """Generate content with available context"""
            response = self.generation_chain.invoke({
                "query": query,
                "context": context if context else "No additional context available",
                "previous_generation": previous if previous else "Starting response"
            })
            return response
    
    
    def demonstrate_retrieval_interleaving():
        """Demonstrate retrieval interleaving pattern"""
        print("=" * 80)
        print("RETRIEVAL INTERLEAVING PATTERN DEMONSTRATION")
        print("=" * 80)
        
        # Create agent with mock vector store
        agent = RetrievalInterleavingAgent(max_retrievals=3)
        
        # Example 1: Complex query requiring multiple retrievals
        print("\n" + "=" * 80)
        print("Example 1: Multi-Hop Question")
        print("=" * 80)
        
        query1 = "Explain how transformers relate to GPT models and why they're important for NLP"
        
        print(f"\nQuery: {query1}")
        print("\nExecuting with interleaved retrieval...")
        print("-" * 60)
        
        result1 = agent.generate_with_interleaved_retrieval(query1)
        
        print(f"\nFinal Output:")
        print(result1.final_output)
        
        print(f"\n" + "-" * 60)
        print(f"Execution Summary:")
        print(f"  Total retrievals: {result1.total_retrievals}")
        print(f"  Total generation steps: {len(result1.generation_steps)}")
        print(f"  Estimated tokens: {result1.total_tokens_estimated}")
        
        print(f"\nRetrieval Steps:")
        for i, step in enumerate(result1.retrieval_steps, 1):
            print(f"\n  Step {i} (timestamp {step.timestamp}):")
            print(f"    Query: {step.query}")
            print(f"    Retrieved {len(step.documents)} documents")
            for j, doc in enumerate(step.documents, 1):
                print(f"      Doc {j}: {doc.page_content[:60]}...")
        
        print(f"\nGeneration Steps:")
        for i, step in enumerate(result1.generation_steps, 1):
            used = "with retrieval" if step.used_retrieval else "without retrieval"
            print(f"  Step {i} (timestamp {step.timestamp}): Generated {used}")
            print(f"    Content: {step.content[:80]}...")
        
        # Example 2: Simple query (fewer retrievals)
        print("\n" + "=" * 80)
        print("Example 2: Simple Query")
        print("=" * 80)
        
        query2 = "What is machine learning?"
        
        print(f"\nQuery: {query2}")
        result2 = agent.generate_with_interleaved_retrieval(query2)
        
        print(f"\nFinal Output:")
        print(result2.final_output)
        
        print(f"\nExecution Summary:")
        print(f"  Total retrievals: {result2.total_retrievals}")
        print(f"  (Simpler query required fewer retrievals)")
        
        # Example 3: Comparing with standard RAG
        print("\n" + "=" * 80)
        print("Example 3: Comparison - Interleaved vs Standard RAG")
        print("=" * 80)
        
        query3 = "Explain deep learning and how it relates to computer vision and NLP"
        
        print(f"\nQuery: {query3}")
        
        # Interleaved approach
        print("\n1. Interleaved Retrieval Approach:")
        result_interleaved = agent.generate_with_interleaved_retrieval(query3)
        print(f"   Retrievals: {result_interleaved.total_retrievals}")
        print(f"   Output: {result_interleaved.final_output[:150]}...")
        
        # Standard RAG (single retrieval)
        print("\n2. Standard RAG (single retrieval):")
        docs = agent.vector_store.similarity_search(query3, k=3)
        context = "\n\n".join([doc.page_content for doc in docs])
        
        standard_prompt = ChatPromptTemplate.from_messages([
            ("system", "You are a helpful AI assistant. Use the provided context to answer the query."),
            ("user", "Context:\n{context}\n\nQuery: {query}\n\nAnswer:")
        ])
        standard_chain = standard_prompt | agent.llm | StrOutputParser()
        standard_result = standard_chain.invoke({"context": context, "query": query3})
        
        print(f"   Retrievals: 1")
        print(f"   Output: {standard_result[:150]}...")
        
        print("\n   Analysis:")
        print(f"   - Interleaved made {result_interleaved.total_retrievals} targeted retrievals")
        print(f"   - Standard RAG made 1 broad retrieval")
        print(f"   - Interleaved can gather more specific information as needed")
        
        # Example 4: Retrieval pattern analysis
        print("\n" + "=" * 80)
        print("Example 4: Retrieval Pattern Analysis")
        print("=" * 80)
        
        queries = [
            "What is NLP?",
            "Explain the relationship between machine learning, deep learning, and transformers",
            "How does reinforcement learning work?"
        ]
        
        print("\nAnalyzing retrieval patterns for different query types:\n")
        
        for query in queries:
            result = agent.generate_with_interleaved_retrieval(query)
            print(f"Query: {query}")
            print(f"  Retrievals: {result.total_retrievals}")
            print(f"  Generation steps: {len(result.generation_steps)}")
            
            if result.retrieval_steps:
                print(f"  Retrieval queries:")
                for step in result.retrieval_steps:
                    print(f"    - {step.query}")
            print()
        
        # Summary
        print("\n" + "=" * 80)
        print("SUMMARY")
        print("=" * 80)
        print("""
    The Retrieval Interleaving pattern enables:
    ✓ Dynamic, adaptive information gathering
    ✓ Multiple retrieval steps during generation
    ✓ More targeted and relevant retrievals
    ✓ Better handling of complex multi-hop queries
    ✓ Reduced initial retrieval overhead
    ✓ Context-aware retrieval decisions
    ✓ Improved accuracy for knowledge-intensive tasks
    
    This pattern is valuable for:
    - Complex research questions
    - Multi-hop reasoning tasks
    - Long-form content generation
    - Dynamic information needs
    - Iterative question answering
    - Knowledge synthesis tasks
    - Exploratory queries
    
    Compared to standard RAG:
    - Standard RAG: One retrieval upfront, simpler, faster
    - Interleaved: Multiple targeted retrievals, more complex, better for complex queries
        """)
    
    
    if __name__ == "__main__":
        demonstrate_retrieval_interleaving()
    
        

    Agentic Design Pattern: Retrieval Interleaving

    Pattern 169: Retrieval Interleaving – Line-by-Line Explanation

    Overview

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


    Pattern Description

    Pattern 169: Retrieval Interleaving

    Description: The Retrieval Interleaving pattern dynamically retrieves information during generation, interleaving retrieval steps with reasoning and generation steps. Unlike standard RAG which retrieves once upfront, this pattern retrieves multiple times as needed based on the generation process.

    Components: 1. Generation Monitor: Tracks generation progress 2. Retrieval Trigger: Decides when to retrieve 3. Query Generator: Creates retrieval queries 4. Retriever: Fetches relevant information 5. Context Integrator: Merges retrieved content 6. Iteration Controller: Manages interleaving loops

    Use Cases: – Complex multi-hop reasoning – Long-form content generation – Research-intensive tasks – Dynamic information needs – Iterative question answering – Knowledge synthesis

    Benefits: – Adaptive information gathering – Better context relevance – Handles complex queries – Reduces initial retrieval overhead – More accurate results

    Trade-offs: – Multiple retrieval calls – Increased latency – Complex orchestration – Higher costs – Requires good triggering logic

    LangChain Implementation: Implements iterative retrieval with LangChain’s retrieval components. Uses LLM to decide when and what to retrieve during generation.


    File Statistics

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

    Module Documentation

    """
    Pattern 169: Retrieval Interleaving
    
    Description:
        The Retrieval Interleaving pattern dynamically retrieves information during
        generation, interleaving retrieval steps with reasoning and generation steps.
        Unlike standard RAG which retrieves once upfront, this pattern retrieves
        multiple times as needed based on the generation process.
    
    Components:
        1. Generation Monitor: Tracks generation progress
        2. Retrieval Trigger: Decides when to retrieve
        3. Query Generator: Creates retrieval queries
        4. Retriever: Fetches relevant information
        5. Context Integrator: Merges retrieved content
        6. Iteration Controller: Manages interleaving loops
    
    Use Cases:
        - Complex multi-hop reasoning
        - Long-form content generation
        - Research-intensive tasks
        - Dynamic information needs
        - Iterative question answering
        - Knowledge synthesis
    
    Benefits:
        - Adaptive information gathering
        - Better context relevance
        - Handles complex queries
        - Reduces initial retrieval overhead
        - More accurate results
    
    Trade-offs:
        - Multiple retrieval calls
        - Increased latency
        - Complex orchestration
        - Higher costs
        - Requires good triggering logic
    
    LangChain Implementation:
        Implements iterative retrieval with LangChain's retrieval components.
        Uses LLM to decide when and what to retrieve during generation.
    """
    

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


    Import Statements

    Standard Library Imports

    import dataclasses.dataclass
    import dataclasses.field
    import enum.Enum
    import langchain_core.documents.Document
    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: – dataclasses.dataclass: Simplified class definitions with @dataclass – dataclasses.field: Simplified class definitions with @dataclass – enum.Enum: Enumeration support – langchain_core.documents.Document: Regular expression operations – 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
    from langchain_openai.OpenAIEmbeddings
    

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

    Other Imports

    from dotenv.load_dotenv
    

    Purpose: Additional dependencies for specialized functionality.


    Classes

    Class: RetrievalDecision

    Lines 59-63

    Inherits from: Enum

    Purpose:

    Decision on whether to retrieve
    

    Class: RetrievalStep

    Lines 67-72

    Purpose:

    Represents a retrieval step
    

    Class: GenerationStep

    Lines 76-80

    Purpose:

    Represents a generation step
    

    Class: InterleavedResult

    Lines 84-90

    Purpose:

    Result of interleaved retrieval-generation
    

    Class: MockVectorStore

    Lines 93-130

    Purpose:

    Mock vector store for demonstration
    

    Methods:

    __init__(self)

    Lines 96-116

    Purpose:

    Initialize with sample documents
    

    Parameters:

    similarity_search(self, query, k)

    Lines 118-130

    Purpose:

    Mock similarity search
    

    Parameters: – query: Input query or question – k: K

    Returns: <ast.Subscript object at 0x000001A06E0ABAD0>


    Class: RetrievalInterleavingAgent

    Lines 133-297

    Purpose:

    Agent that interleaves retrieval with generation
    

    Methods:

    __init__(self, vector_store, max_retrievals)

    Lines 136-182

    Purpose:

    Initialize interleaving agent
    
    Args:
        vector_store: Vector store for retrieval
        max_retrievals: Maximum number of retrieval steps
    

    Parameters: – vector_store: Vector store – max_retrievals: Max retrievals

    generate_with_interleaved_retrieval(self, query)

    Lines 184-262

    Purpose:

    Generate response with interleaved retrieval
    

    Parameters: – query: Input query or question

    Returns: InterleavedResult

    _decide_next_step(self, query, current_generation, context)

    Lines 264-288

    Purpose:

    Decide whether to retrieve, continue, or complete
    

    Parameters: – query: Input query or question – current_generation: Current generation – context: Context

    Returns: <ast.Subscript object at 0x000001A06D60AC90>

    _generate(self, query, context, previous)

    Lines 290-297

    Purpose:

    Generate content with available context
    

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

    Returns: str


    Functions

    demonstrate_retrieval_interleaving()

    Lines 300-448

    Purpose:

    Demonstrate retrieval interleaving pattern
    

    Key Concepts Summary

    Pattern Overview

    The Retrieval Interleaving 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 168
    • Pattern 170

    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 Retrieval Interleaving 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: 169_retrieval_interleaving.py
    Total Lines: 453
    Pattern Category: AI Agent Design Pattern #169


    Generated from actual implementation code
    For educational and reference purposes