Agentic Architecture Pattern: Observer

Written by

in


    """
Pattern 103: Observer

Purpose: Enhanced implementation with full LangChain/LangGraph integration
"""

from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict


@dataclass
class ObserverConfig:
    """Configuration for Observer."""
    model_name: str = "gpt-4"
    temperature: float = 0.7
    max_iterations: int = 10
    enable_logging: bool = True


@dataclass
class ObserverState:
    """State information for Observer."""
    current_step: int = 0
    data: Dict[str, Any] = field(default_factory=dict)
    results: List[Any] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.now)


class ObserverAgent:
    """Agent implementing Observer."""
    
    def __init__(self, name: str, config: ObserverConfig = None):
        self.name = name
        self.config = config or ObserverConfig()
        self.llm = ChatOpenAI(
            model=self.config.model_name,
            temperature=self.config.temperature
        )
        self.state = ObserverState()
        self.execution_history: List[Dict[str, Any]] = []
    
    def process(self, input_data: Any) -> Dict[str, Any]:
        """Process input according to Observer."""
        self.state.current_step += 1
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", f"You are {self.name}, implementing Observer. "
                      f"Provide clear, structured responses."),
            ("human", "Process this input: {input_data}")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({"input_data": str(input_data)})
        
        result = {
            "agent": self.name,
            "step": self.state.current_step,
            "input": input_data,
            "output": str(response.content),
            "timestamp": datetime.now()
        }
        
        self.execution_history.append(result)
        self.state.results.append(result)
        
        return result
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get agent statistics."""
        return {
            "name": self.name,
            "total_executions": len(self.execution_history),
            "current_step": self.state.current_step,
            "results_count": len(self.state.results)
        }


class ObserverPattern:
    """
    Observer - Enhanced implementation
    
    This pattern provides comprehensive functionality for observer
    with full LangChain/LangGraph integration.
    
    Features:
    - Multi-agent coordination
    - State management
    - Execution tracking
    - LangGraph workflow integration
    """
    
    def __init__(self, config: ObserverConfig = None):
        self.config = config or ObserverConfig()
        self.agents: Dict[str, ObserverAgent] = {}
        self.workflow = self._build_workflow()
        self.execution_log: List[Dict[str, Any]] = []
    
    def _build_workflow(self) -> StateGraph:
        """Build LangGraph workflow for pattern execution."""
        
        class WorkflowState(TypedDict):
            input: str
            processed: str
            output: str
            metadata: Dict[str, Any]
        
        def process_input(state: WorkflowState) -> WorkflowState:
            """Process input node."""
            return {
                **state,
                "processed": f"Processed: {state['input']}",
                "metadata": {"step": "process_input"}
            }
        
        def generate_output(state: WorkflowState) -> WorkflowState:
            """Generate output node."""
            llm = ChatOpenAI(model=self.config.model_name, temperature=self.config.temperature)
            
            prompt = ChatPromptTemplate.from_messages([
                ("system", "You are implementing Observer. Generate comprehensive output."),
                ("human", "{input}")
            ])
            
            chain = prompt | llm
            response = chain.invoke({"input": state["processed"]})
            
            return {
                **state,
                "output": str(response.content),
                "metadata": {**state.get("metadata", {}), "step": "generate_output"}
            }
        
        # Build workflow graph
        workflow = StateGraph(WorkflowState)
        workflow.add_node("process", process_input)
        workflow.add_node("generate", generate_output)
        workflow.set_entry_point("process")
        workflow.add_edge("process", "generate")
        workflow.add_edge("generate", END)
        
        return workflow.compile()
    
    def register_agent(self, agent: ObserverAgent):
        """Register an agent with the pattern."""
        self.agents[agent.name] = agent
        if self.config.enable_logging:
            print(f"Registered agent: {agent.name}")
    
    def execute(self, input_data: Any, agent_name: str = None) -> Dict[str, Any]:
        """Execute the pattern with given input."""
        
        # Use workflow if no specific agent requested
        if agent_name is None:
            result = self.workflow.invoke({
                "input": str(input_data),
                "processed": "",
                "output": "",
                "metadata": {}
            })
            
            execution_record = {
                "pattern": "Observer",
                "input": input_data,
                "output": result.get("output", ""),
                "metadata": result.get("metadata", {}),
                "timestamp": datetime.now()
            }
            
            self.execution_log.append(execution_record)
            return execution_record
        
        # Execute with specific agent
        agent = self.agents.get(agent_name)
        if not agent:
            raise ValueError(f"Agent {agent_name} not found")
        
        result = agent.process(input_data)
        self.execution_log.append(result)
        return result
    
    def run(self, input_data: Any) -> Dict[str, Any]:
        """Main execution method."""
        return self.execute(input_data)
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get pattern statistics."""
        return {
            "pattern": "Observer",
            "total_executions": len(self.execution_log),
            "registered_agents": len(self.agents),
            "agents": [agent.get_statistics() for agent in self.agents.values()]
        }


# Example usage
if __name__ == "__main__":
    print("=== Observer Demo ===\n")
    
    # Create configuration
    config = ObserverConfig(
        model_name="gpt-4",
        temperature=0.7,
        enable_logging=True
    )
    
    # Create pattern
    pattern = ObserverPattern(config)
    
    # Create and register agents
    agent1 = ObserverAgent("Agent1", config)
    agent2 = ObserverAgent("Agent2", config)
    pattern.register_agent(agent1)
    pattern.register_agent(agent2)
    
    print("\nExecuting pattern workflow...")
    
    # Execute with workflow
    result1 = pattern.run("Demonstrate observer functionality")
    print(f"\nWorkflow Result:")
    print(f"  Output: {result1['output'][:150]}...")
    
    # Execute with specific agents
    result2 = pattern.execute("Specific task for Agent1", agent_name="Agent1")
    print(f"\nAgent1 Result:")
    print(f"  Output: {result2['output'][:150]}...")
    
    result3 = pattern.execute("Specific task for Agent2", agent_name="Agent2")
    print(f"\nAgent2 Result:")
    print(f"  Output: {result3['output'][:150]}...")
    
    # Show statistics
    print("\n=== Statistics ===")
    stats = pattern.get_statistics()
    print(f"Pattern: {stats['pattern']}")
    print(f"Total Executions: {stats['total_executions']}")
    print(f"Registered Agents: {stats['registered_agents']}")
    print(f"\nAgent Statistics:")
    for agent_stats in stats['agents']:
        print(f"  {agent_stats['name']}: {agent_stats['total_executions']} executions")

    

Agentic Architecture Pattern: Observer

Pattern 103: 103 Observer Pattern – Line-by-Line Explanation

Overview

Pattern 103: Observer Pattern Subscribe to and react to state changes in observed subjects.


Code Structure Analysis

File Information

  • Pattern Number: 103
  • Pattern Name: 103 Observer Pattern
  • File Path: patterns\perception_sensing\103_observer_pattern.py
  • Total Lines: 181

Detailed Code Explanation

Module Docstring

"""
Pattern 103: Observer Pattern
Subscribe to and react to state changes in observed subjects.
"""

Explanation: This module docstring describes the purpose and key features of the 103 Observer Pattern pattern.


Imports Section

from typing import Dict, Any, List, Optional, Protocol
from dataclasses import dataclass, field
from datetime import datetime
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate  # First 20 imports shown

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional, Protocol: Type hints for better code documentation – from dataclasses import dataclass, field: Data class decorators for clean data structures – from datetime import datetime: Date and time utilities – from langchain_openai import ChatOpenAI: LangChain framework integration – from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration


Classes

StateChange (Line 14)

Description: Represents a state change event.

Observer (Line 23)

Description: Observer interface.

Methods:

update (Line 25)
def update(self, change):
    """Called when observed subject changes."""

Purpose: Called when observed subject changes.

Parameters: – self: Instance reference – change: Parameter for change

Subject (Line 30)

Description: Subject that can be observed.

Methods:

__init__ (Line 33)
def __init__(self, subject_id):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – subject_id: Parameter for subject_id

attach (Line 38)
def attach(self, observer):
    """Attach an observer."""

Purpose: Attach an observer.

Parameters: – self: Instance reference – observer: Parameter for observer

detach (Line 43)
def detach(self, observer):
    """Detach an observer."""

Purpose: Detach an observer.

Parameters: – self: Instance reference – observer: Parameter for observer

set_state (Line 48)
def set_state(self, property_name, value):
    """Set state and notify observers."""

Purpose: Set state and notify observers.

Parameters: – self: Instance reference – property_name: Parameter for property_name – value: Parameter for value

get_state (Line 61)
def get_state(self, property_name):
    """Get state property."""

Purpose: Get state property.

Parameters: – self: Instance reference – property_name: Parameter for property_name

_notify (Line 65)
def _notify(self, change):
    """Notify all observers of state change."""

Purpose: Notify all observers of state change.

Parameters: – self: Instance reference – change: Parameter for change

ObserverAgent (Line 71)

Description: Agent that observes subjects and reacts to changes.

Methods:

__init__ (Line 74)
def __init__(self, name, model_name):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – name: Identifier or label – model_name: LLM model to use (e.g., “gpt-4”)

update (Line 79)
def update(self, change):
    """React to state change."""

Purpose: React to state change.

Parameters: – self: Instance reference – change: Parameter for change

_generate_reaction (Line 87)
def _generate_reaction(self, change):
    """Generate intelligent reaction to state change."""

Purpose: Generate intelligent reaction to state change.

Parameters: – self: Instance reference – change: Parameter for change

get_observation_count (Line 104)
def get_observation_count(self):
    """Get number of observations made."""

Purpose: Get number of observations made.

Parameters: – self: Instance reference

ObserverPattern (Line 109)

Description: Observer Pattern – Subscribe and react to state changes.

Methods:

__init__ (Line 112)
def __init__(self):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference

create_subject (Line 116)
def create_subject(self, subject_id):
    """Create an observable subject."""

Purpose: Create an observable subject.

Parameters: – self: Instance reference – subject_id: Parameter for subject_id

create_observer (Line 122)
def create_observer(self, name):
    """Create an observer agent."""

Purpose: Create an observer agent.

Parameters: – self: Instance reference – name: Identifier or label

run (Line 128)
def run(self, scenario):
    """Execute observer pattern scenario."""

Purpose: Execute observer pattern scenario.

Parameters: – self: Instance reference – scenario: Parameter for scenario


Design Patterns Used

  • Strategy Pattern: Different execution strategies for various scenarios
  • State Pattern: Managing different states in the workflow
  • Chain of Responsibility: Sequential processing through workflow nodes
  • Observer Pattern: Tracking and monitoring pattern execution
  • Factory Pattern: Creating instances with proper configuration

Usage Example

# Initialize the pattern
pattern = StateChange(name="example", model_name="gpt-4")

# Configure and execute
result = pattern.execute(data={"key": "value"})

# Check results
print(result)

Key Concepts

  • LLM Integration: Uses LangChain/OpenAI for intelligent decision-making
  • Workflow Orchestration: LangGraph manages execution flow through nodes
  • State Management: TypedDict defines workflow state structure
  • Error Handling: Comprehensive error handling and fallback mechanisms
  • Extensibility: Designed for easy customization and extension

Extension Points

This pattern can be extended by: 1. Adding custom workflow nodes 2. Implementing additional validation logic 3. Integrating with external services 4. Customizing LLM prompts and responses 5. Adding monitoring and logging capabilities

Comments

Leave a Reply

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