Agentic Architecture Pattern: Planning Agent

Written by

in


    """
Pattern 42: Planning Agent Pattern

Agent that creates plans to achieve goals, considering preconditions, actions, and outcomes.
Uses search algorithms and domain knowledge for goal-directed behavior.
"""

from typing import Dict, Any, List, Optional, Set
from dataclasses import dataclass, field
from enum import Enum
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI


class ActionStatus(Enum):
    PLANNED = "planned"
    EXECUTING = "executing"
    COMPLETED = "completed"
    FAILED = "failed"


@dataclass
class Action:
    """An action in a plan."""
    name: str
    preconditions: List[str]
    effects: List[str]
    cost: float = 1.0
    status: ActionStatus = ActionStatus.PLANNED


@dataclass
class Plan:
    """A sequence of actions to achieve a goal."""
    goal: str
    actions: List[Action] = field(default_factory=list)
    current_state: Set[str] = field(default_factory=set)
    target_state: Set[str] = field(default_factory=set)


class PlanningAgentPattern:
    """
    Planning Agent Pattern - Goal-directed planning and execution.
    
    Features:
    - Goal decomposition
    - Precondition checking
    - Action sequencing
    - Plan execution
    - Replanning on failure
    """
    
    def __init__(self, model_name: str = "gpt-4"):
        self.llm = ChatOpenAI(model=model_name, temperature=0.7)
        self.available_actions: Dict[str, Action] = {}
        self.plans: List[Plan] = []
    
    def register_action(self, action: Action):
        """Register an available action."""
        self.available_actions[action.name] = action
        print(f"✓ Registered action: {action.name}")
        print(f"  Preconditions: {', '.join(action.preconditions)}")
        print(f"  Effects: {', '.join(action.effects)}")
    
    def create_plan(self, goal: str, current_state: Set[str], 
                   target_conditions: List[str]) -> Plan:
        """Create a plan to achieve goal."""
        plan = Plan(
            goal=goal,
            current_state=current_state.copy(),
            target_state=set(target_conditions)
        )
        
        # Use LLM to generate plan
        state_str = ", ".join(current_state)
        target_str = ", ".join(target_conditions)
        actions_str = "\n".join([
            f"- {name}: requires [{', '.join(a.preconditions)}] → produces [{', '.join(a.effects)}]"
            for name, a in self.available_actions.items()
        ])
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are a planning agent. Create a sequence of actions to achieve the goal. "
                      "List actions in order, one per line."),
            ("human", f"Goal: {goal}\n"
                     f"Current State: {state_str}\n"
                     f"Target Conditions: {target_str}\n\n"
                     f"Available Actions:\n{actions_str}\n\n"
                     f"Plan (list action names in order):")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({})
        
        # Parse action sequence
        lines = [line.strip() for line in response.content.split('\n') if line.strip()]
        for line in lines:
            # Extract action name
            clean_line = line.lstrip('0123456789.-•* ').strip()
            # Find matching action
            for action_name, action_template in self.available_actions.items():
                if action_name.lower() in clean_line.lower():
                    # Create action instance
                    action_instance = Action(
                        name=action_template.name,
                        preconditions=action_template.preconditions.copy(),
                        effects=action_template.effects.copy(),
                        cost=action_template.cost
                    )
                    plan.actions.append(action_instance)
                    break
        
        self.plans.append(plan)
        return plan
    
    def check_preconditions(self, action: Action, state: Set[str]) -> bool:
        """Check if action preconditions are met."""
        return all(precond in state for precond in action.preconditions)
    
    def execute_plan(self, plan: Plan) -> Dict[str, Any]:
        """Execute a plan step by step."""
        current_state = plan.current_state.copy()
        executed_actions = []
        failed_action = None
        
        for action in plan.actions:
            # Check preconditions
            if not self.check_preconditions(action, current_state):
                action.status = ActionStatus.FAILED
                failed_action = action.name
                print(f"✗ Action failed: {action.name} - preconditions not met")
                break
            
            # Execute action
            action.status = ActionStatus.EXECUTING
            print(f"→ Executing: {action.name}")
            
            # Apply effects
            for effect in action.effects:
                current_state.add(effect)
            
            action.status = ActionStatus.COMPLETED
            executed_actions.append(action.name)
            print(f"✓ Completed: {action.name}")
        
        # Check if goal achieved
        goal_achieved = plan.target_state.issubset(current_state)
        
        return {
            "goal": plan.goal,
            "executed_actions": executed_actions,
            "goal_achieved": goal_achieved,
            "failed_action": failed_action,
            "final_state": current_state
        }
    
    def replan(self, original_plan: Plan, current_state: Set[str]) -> Plan:
        """Create a new plan when original plan fails."""
        print(f"\n🔄 Replanning for goal: {original_plan.goal}")
        return self.create_plan(
            original_plan.goal,
            current_state,
            list(original_plan.target_state)
        )
    
    def run(self, scenario: str) -> Dict[str, Any]:
        """Execute planning scenario."""
        return {
            "pattern": "Planning Agent",
            "scenario": scenario,
            "available_actions": len(self.available_actions),
            "plans_created": len(self.plans)
        }


# Example usage
if __name__ == "__main__":
    print("=== Planning Agent Pattern Demo ===\n")
    
    agent = PlanningAgentPattern()
    
    # Register available actions
    print("--- Registering Actions ---")
    agent.register_action(Action(
        "GetIngredients",
        preconditions=["has_money"],
        effects=["has_ingredients"],
        cost=1.0
    ))
    agent.register_action(Action(
        "PrepareFood",
        preconditions=["has_ingredients", "has_kitchen"],
        effects=["has_prepared_food"],
        cost=2.0
    ))
    agent.register_action(Action(
        "CookFood",
        preconditions=["has_prepared_food"],
        effects=["has_cooked_food"],
        cost=1.5
    ))
    agent.register_action(Action(
        "ServeMeal",
        preconditions=["has_cooked_food", "has_table"],
        effects=["meal_ready"],
        cost=0.5
    ))
    
    # Create plan
    print("\n--- Creating Plan ---")
    initial_state = {"has_money", "has_kitchen", "has_table"}
    goal_conditions = ["meal_ready"]
    
    plan = agent.create_plan(
        "Prepare and serve a meal",
        initial_state,
        goal_conditions
    )
    
    print(f"\nPlan for: {plan.goal}")
    print(f"Actions: {', '.join([a.name for a in plan.actions])}")
    
    # Execute plan
    print("\n--- Executing Plan ---")
    result = agent.execute_plan(plan)
    
    print(f"\nExecution Result:")
    print(f"Goal: {result['goal']}")
    print(f"Executed: {', '.join(result['executed_actions'])}")
    print(f"Goal Achieved: {result['goal_achieved']}")
    if result['failed_action']:
        print(f"Failed At: {result['failed_action']}")
    
    # Summary
    print("\n--- Summary ---")
    summary = agent.run("Meal preparation planning")
    print(f"Pattern: {summary['pattern']}")
    print(f"Available Actions: {summary['available_actions']}")
    print(f"Plans Created: {summary['plans_created']}")

    

Agentic Architecture Pattern: Planning Agent

Pattern 42: 42 Planning Agent – Line-by-Line Explanation

Overview

Pattern 42: Planning Agent Pattern

Agent that creates plans to achieve goals, considering preconditions, actions, and outcomes. Uses search algorithms and domain knowledge for goal-directed behavior.


Code Structure Analysis

File Information

  • Pattern Number: 42
  • Pattern Name: 42 Planning Agent
  • File Path: patterns\decision_making\42_planning_agent.py
  • Total Lines: 240

Detailed Code Explanation

Module Docstring

"""
Pattern 42: Planning Agent Pattern

Agent that creates plans to achieve goals, considering preconditions, actions, and outcomes.
Uses search algorithms and domain knowledge for goal-directed behavior.
"""

Explanation: This module docstring describes the purpose and key features of the 42 Planning Agent pattern.


Imports Section

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

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional, Set: Type hints for better code documentation – from dataclasses import dataclass, field: Data class decorators for clean data structures – from enum import Enum: Enumeration support for fixed sets of values – from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration – from langchain_openai import ChatOpenAI: LangChain framework integration


Enumerations

ActionStatus (Line 15)

class ActionStatus(Enum):
    PLANNED
    EXECUTING
    COMPLETED
    FAILED

Purpose: No enum docstring.

Values: – PLANNED: Represents planned – EXECUTING: Represents executing – COMPLETED: Successfully finished – FAILED: Execution failed


Classes

ActionStatus (Line 15)

Description: No class docstring.

Action (Line 23)

Description: An action in a plan.

Plan (Line 33)

Description: A sequence of actions to achieve a goal.

PlanningAgentPattern (Line 41)

Description: Planning Agent Pattern – Goal-directed planning and execution.

Features: – Goal decomposition – Precondition checking – Action sequencing – Plan execution – Replanning on failure

Methods:

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

Purpose: No method docstring.

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

register_action (Line 58)
def register_action(self, action):
    """Register an available action."""

Purpose: Register an available action.

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

create_plan (Line 65)
def create_plan(self, goal, current_state, target_conditions):
    """Create a plan to achieve goal."""

Purpose: Create a plan to achieve goal.

Parameters: – self: Instance reference – goal: Parameter for goal – current_state: Parameter for current_state – target_conditions: Parameter for target_conditions

check_preconditions (Line 116)
def check_preconditions(self, action, state):
    """Check if action preconditions are met."""

Purpose: Check if action preconditions are met.

Parameters: – self: Instance reference – action: Parameter for action – state: Current workflow state

execute_plan (Line 120)
def execute_plan(self, plan):
    """Execute a plan step by step."""

Purpose: Execute a plan step by step.

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

replan (Line 157)
def replan(self, original_plan, current_state):
    """Create a new plan when original plan fails."""

Purpose: Create a new plan when original plan fails.

Parameters: – self: Instance reference – original_plan: Parameter for original_plan – current_state: Parameter for current_state

run (Line 166)
def run(self, scenario):
    """Execute planning scenario."""

Purpose: Execute planning 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 = ActionStatus(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 *