Agentic Architecture Pattern: Decision Support Agent

Written by

in


    """
Pattern 240: Decision Support Agent

Purpose: Multi-criteria decision analysis with risk assessment and recommendations.
Helps evaluate alternatives, assess risks, and generate actionable decisions.
"""

from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from collections import defaultdict
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import re


class DecisionType(Enum):
    """Types of decisions."""
    STRATEGIC = "strategic"  # Long-term strategic decisions
    OPERATIONAL = "operational"  # Day-to-day operations
    INVESTMENT = "investment"  # Financial investments
    RISK_ASSESSMENT = "risk_assessment"  # Risk evaluation
    PRIORITIZATION = "prioritization"  # Priority ranking
    SCENARIO_ANALYSIS = "scenario_analysis"  # What-if scenarios


class RiskLevel(Enum):
    """Risk level assessment."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


@dataclass
class DecisionCriteria:
    """Decision criteria with weight."""
    name: str
    weight: float  # 0.0 to 1.0
    description: str = ""
    is_benefit: bool = True  # True if higher is better


@dataclass
class Alternative:
    """Decision alternative."""
    name: str
    description: str
    scores: Dict[str, float] = field(default_factory=dict)  # Criteria -> score
    cost: float = 0.0
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class DecisionResult:
    """Decision analysis result."""
    decision_type: DecisionType
    alternatives: List[Alternative]
    criteria: List[DecisionCriteria]
    ranked_alternatives: List[tuple[str, float]] = field(default_factory=list)
    recommendation: str = ""
    risks: List[str] = field(default_factory=list)
    risk_level: RiskLevel = RiskLevel.MEDIUM
    confidence: float = 0.0
    reasoning: str = ""


class DecisionSupportAgent:
    """
    Decision Support Agent - Multi-criteria decision analysis.
    
    Features:
    - Multi-criteria evaluation
    - Alternative ranking
    - Risk assessment
    - Scenario analysis
    - AI-powered recommendations
    - Trade-off analysis
    - Sensitivity analysis
    - Decision justification
    """
    
    def __init__(self, name: str):
        self.name = name
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.3)
        
        # Statistics
        self.statistics: Dict[str, Any] = {
            "total_decisions": 0,
            "decision_types": {t.value: 0 for t in DecisionType},
            "recommendations_made": 0,
            "avg_alternatives": 0.0
        }
    
    def evaluate_decision(
        self,
        alternatives: List[Alternative],
        criteria: List[DecisionCriteria],
        decision_type: DecisionType = DecisionType.STRATEGIC
    ) -> DecisionResult:
        """Evaluate alternatives based on criteria."""
        
        # Rank alternatives
        ranked = self._rank_alternatives(alternatives, criteria)
        
        # Assess risks
        risks, risk_level = self._assess_risks(alternatives, ranked, decision_type)
        
        # Generate recommendation
        recommendation = self._generate_recommendation(ranked, risks, decision_type)
        
        # Generate reasoning
        reasoning = self._generate_reasoning(alternatives, criteria, ranked)
        
        # Calculate confidence
        confidence = self._calculate_confidence(alternatives, criteria, ranked)
        
        # Update statistics
        self.statistics["total_decisions"] += 1
        self.statistics["decision_types"][decision_type.value] += 1
        self.statistics["recommendations_made"] += 1
        
        # Update average alternatives
        prev_avg = self.statistics["avg_alternatives"]
        n = self.statistics["total_decisions"]
        self.statistics["avg_alternatives"] = (prev_avg * (n - 1) + len(alternatives)) / n
        
        return DecisionResult(
            decision_type=decision_type,
            alternatives=alternatives,
            criteria=criteria,
            ranked_alternatives=ranked,
            recommendation=recommendation,
            risks=risks,
            risk_level=risk_level,
            confidence=confidence,
            reasoning=reasoning
        )
    
    def _rank_alternatives(
        self,
        alternatives: List[Alternative],
        criteria: List[DecisionCriteria]
    ) -> List[tuple[str, float]]:
        """Rank alternatives using weighted scoring."""
        
        # Normalize weights
        total_weight = sum(c.weight for c in criteria)
        if total_weight == 0:
            total_weight = 1.0
        
        scores = {}
        
        for alt in alternatives:
            score = 0.0
            
            for criterion in criteria:
                if criterion.name in alt.scores:
                    value = alt.scores[criterion.name]
                    weight = criterion.weight / total_weight
                    
                    # Adjust for benefit/cost criteria
                    if criterion.is_benefit:
                        score += value * weight
                    else:
                        # For cost criteria, lower is better
                        score += (1.0 - value) * weight
            
            scores[alt.name] = score
        
        # Sort by score (descending)
        ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        
        return ranked
    
    def _assess_risks(
        self,
        alternatives: List[Alternative],
        ranked: List[tuple[str, float]],
        decision_type: DecisionType
    ) -> tuple[List[str], RiskLevel]:
        """Assess risks for decision."""
        
        risks = []
        
        # Check for close alternatives (uncertainty)
        if len(ranked) >= 2:
            diff = ranked[0][1] - ranked[1][1]
            if diff  50000:
            risks.append(f"Recommended option has high cost: ${top_alt.cost:,.2f}")
        
        # Decision type specific risks
        if decision_type == DecisionType.INVESTMENT:
            risks.append("Investment decisions carry inherent market risks")
        elif decision_type == DecisionType.STRATEGIC:
            risks.append("Strategic decisions have long-term implications")
        
        # Determine risk level
        if len(risks) >= 3 or (top_alt and top_alt.cost > 100000):
            risk_level = RiskLevel.HIGH
        elif len(risks) >= 2:
            risk_level = RiskLevel.MEDIUM
        else:
            risk_level = RiskLevel.LOW
        
        return risks, risk_level
    
    def _generate_recommendation(
        self,
        ranked: List[tuple[str, float]],
        risks: List[str],
        decision_type: DecisionType
    ) -> str:
        """Generate recommendation using LLM."""
        
        if not ranked:
            return "Unable to make recommendation - no alternatives provided"
        
        top_alt = ranked[0]
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", f"You are a decision support expert. Provide a clear, actionable "
                      f"recommendation for a {decision_type.value} decision."),
            ("human", f"Top Alternative: {top_alt[0]} (Score: {top_alt[1]:.3f})\n"
                     f"Identified Risks: {', '.join(risks) if risks else 'None'}\n\n"
                     f"Provide a concise recommendation (2-3 sentences):")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({})
        
        return str(response.content).strip()
    
    def _generate_reasoning(
        self,
        alternatives: List[Alternative],
        criteria: List[DecisionCriteria],
        ranked: List[tuple[str, float]]
    ) -> str:
        """Generate detailed reasoning."""
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Explain the decision reasoning clearly and objectively."),
            ("human", f"Decision Analysis:\n"
                     f"- Alternatives: {', '.join(a.name for a in alternatives)}\n"
                     f"- Criteria: {', '.join(c.name for c in criteria)}\n"
                     f"- Top Choice: {ranked[0][0]} ({ranked[0][1]:.3f})\n\n"
                     f"Explain why this is the best choice (3-4 sentences):")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({})
        
        return str(response.content).strip()
    
    def _calculate_confidence(
        self,
        alternatives: List[Alternative],
        criteria: List[DecisionCriteria],
        ranked: List[tuple[str, float]]
    ) -> float:
        """Calculate confidence in recommendation."""
        
        if not ranked:
            return 0.0
        
        # Base confidence
        confidence = 0.7
        
        # Boost if clear leader
        if len(ranked) >= 2:
            diff = ranked[0][1] - ranked[1][1]
            if diff > 0.2:
                confidence += 0.2
            elif diff = 4:
            confidence += 0.1
        
        # Clamp to [0, 1]
        return max(0.0, min(1.0, confidence))
    
    def compare_scenarios(
        self,
        scenarios: Dict[str, List[Alternative]],
        criteria: List[DecisionCriteria]
    ) -> Dict[str, Any]:
        """Compare different scenarios."""
        
        scenario_results = {}
        
        for scenario_name, alts in scenarios.items():
            result = self.evaluate_decision(
                alts,
                criteria,
                DecisionType.SCENARIO_ANALYSIS
            )
            scenario_results[scenario_name] = {
                "top_choice": result.ranked_alternatives[0] if result.ranked_alternatives else ("None", 0.0),
                "risk_level": result.risk_level.value,
                "confidence": result.confidence
            }
        
        return scenario_results
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get decision support statistics."""
        return {
            "name": self.name,
            **self.statistics
        }


class DecisionSupportPattern:
    """
    Decision Support Pattern - Manage decision support agents.
    
    Coordinates multiple decision support agents for different contexts.
    """
    
    def __init__(self):
        self.agents: Dict[str, DecisionSupportAgent] = {}
    
    def create_agent(self, name: str) -> DecisionSupportAgent:
        """Create a new decision support agent."""
        agent = DecisionSupportAgent(name)
        self.agents[name] = agent
        print(f"   Created decision support agent: {name}")
        return agent
    
    def run(
        self,
        agent_name: str,
        alternatives: List[Alternative],
        criteria: List[DecisionCriteria],
        decision_type: DecisionType = DecisionType.STRATEGIC
    ) -> Dict[str, Any]:
        """Perform decision analysis using specified agent."""
        if agent_name not in self.agents:
            return {"error": f"Agent {agent_name} not found"}
        
        agent = self.agents[agent_name]
        result = agent.evaluate_decision(alternatives, criteria, decision_type)
        
        print(f"\n=== Decision Analysis by {agent_name} ===")
        print(f"Decision Type: {result.decision_type.value}")
        print(f"Alternatives Evaluated: {len(result.alternatives)}")
        print(f"Criteria Used: {len(result.criteria)}\n")
        
        print("Rankings:")
        for i, (name, score) in enumerate(result.ranked_alternatives, 1):
            print(f"  {i}. {name}: {score:.3f}")
        
        print(f"\nRecommendation: {result.recommendation}")
        print(f"\nReasoning: {result.reasoning}")
        
        if result.risks:
            print(f"\nRisks ({result.risk_level.value} level):")
            for risk in result.risks:
                print(f"  - {risk}")
        
        print(f"\nConfidence: {result.confidence:.1%}")
        
        return {
            "agent": agent_name,
            "decision_type": result.decision_type.value,
            "top_choice": result.ranked_alternatives[0] if result.ranked_alternatives else ("None", 0.0),
            "risk_level": result.risk_level.value,
            "confidence": result.confidence,
            "statistics": agent.get_statistics()
        }


# Example usage
if __name__ == "__main__":
    print("=== Decision Support Agent Pattern Demo ===\n")
    
    # Create pattern
    pattern = DecisionSupportPattern()
    
    # Create agents
    print("1. Setting up decision support agents...")
    
    advisor = pattern.create_agent("StrategicAdvisor")
    
    # Define criteria
    print("\n2. Defining decision criteria...")
    
    criteria = [
        DecisionCriteria(name="ROI", weight=0.3, description="Return on Investment", is_benefit=True),
        DecisionCriteria(name="Risk", weight=0.2, description="Risk Level", is_benefit=False),
        DecisionCriteria(name="Timeline", weight=0.2, description="Implementation Time", is_benefit=False),
        DecisionCriteria(name="Impact", weight=0.3, description="Business Impact", is_benefit=True),
    ]
    
    # Define alternatives
    print("   Defining alternatives...")
    
    alternatives = [
        Alternative(
            name="Cloud Migration",
            description="Migrate infrastructure to cloud",
            scores={"ROI": 0.85, "Risk": 0.6, "Timeline": 0.7, "Impact": 0.9},
            cost=150000
        ),
        Alternative(
            name="In-House Development",
            description="Build custom solution internally",
            scores={"ROI": 0.7, "Risk": 0.4, "Timeline": 0.3, "Impact": 0.75},
            cost=200000
        ),
        Alternative(
            name="SaaS Solution",
            description="Adopt third-party SaaS",
            scores={"ROI": 0.8, "Risk": 0.2, "Timeline": 0.9, "Impact": 0.7},
            cost=50000
        ),
    ]
    
    # Perform analysis
    print("\n3. Performing decision analysis...")
    
    result = pattern.run("StrategicAdvisor", alternatives, criteria, DecisionType.STRATEGIC)
    
    print("\n" + "="*60 + "\n")
    
    # Investment decision
    print("4. Evaluating investment decision...")
    
    investment_alternatives = [
        Alternative(
            name="Stock Portfolio",
            description="Diversified stock investments",
            scores={"ROI": 0.75, "Risk": 0.7, "Timeline": 0.5, "Impact": 0.8},
            cost=100000
        ),
        Alternative(
            name="Real Estate",
            description="Commercial property",
            scores={"ROI": 0.65, "Risk": 0.4, "Timeline": 0.2, "Impact": 0.85},
            cost=500000
        ),
        Alternative(
            name="Bonds",
            description="Government bonds",
            scores={"ROI": 0.4, "Risk": 0.1, "Timeline": 0.8, "Impact": 0.5},
            cost=75000
        ),
    ]
    
    result2 = pattern.run("StrategicAdvisor", investment_alternatives, criteria, DecisionType.INVESTMENT)
    
    # Print statistics
    print("\n" + "="*60)
    print("\n5. Decision Support Statistics:")
    stats = result2["statistics"]
    for key, value in stats.items():
        print(f"   {key}: {value}")
    
    print("\nāœ“ Multi-criteria decision analysis with risk assessment complete!")

    

Agentic Architecture Pattern: Decision Support Agent

Pattern 240: 240 Decision Support Agent – Line-by-Line Explanation

Overview

Pattern 240: Decision Support Agent

Purpose: Multi-criteria decision analysis with risk assessment and recommendations. Helps evaluate alternatives, assess risks, and generate actionable decisions.


Code Structure Analysis

File Information

  • Pattern Number: 240
  • Pattern Name: 240 Decision Support Agent
  • File Path: patterns\domain_specific\240_decision_support_agent.py
  • Total Lines: 469

Detailed Code Explanation

Module Docstring

"""
Pattern 240: Decision Support Agent

Purpose: Multi-criteria decision analysis with risk assessment and recommendations.
Helps evaluate alternatives, assess risks, and generate actionable decisions.
"""

Explanation: This module docstring describes the purpose and key features of the 240 Decision Support Agent pattern.


Imports Section

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

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional: 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 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

DecisionType (Line 18)

class DecisionType(Enum):
    STRATEGIC
    OPERATIONAL
    INVESTMENT
    RISK_ASSESSMENT
    PRIORITIZATION
    SCENARIO_ANALYSIS

Purpose: Types of decisions.

Values: – STRATEGIC: Represents strategic – OPERATIONAL: Represents operational – INVESTMENT: Represents investment – RISK_ASSESSMENT: Represents risk assessment – PRIORITIZATION: Represents prioritization – SCENARIO_ANALYSIS: Represents scenario analysis

RiskLevel (Line 28)

class RiskLevel(Enum):
    LOW
    MEDIUM
    HIGH
    CRITICAL

Purpose: Risk level assessment.

Values: – LOW: Represents low – MEDIUM: Represents medium – HIGH: Represents high – CRITICAL: Represents critical


Classes

DecisionType (Line 18)

Description: Types of decisions.

RiskLevel (Line 28)

Description: Risk level assessment.

DecisionCriteria (Line 37)

Description: Decision criteria with weight.

Alternative (Line 46)

Description: Decision alternative.

DecisionResult (Line 56)

Description: Decision analysis result.

DecisionSupportAgent (Line 69)

Description: Decision Support Agent – Multi-criteria decision analysis.

Features: – Multi-criteria evaluation – Alternative ranking – Risk assessment – Scenario analysis – AI-powered recommendations – Trade-off analysis – Sensitivity analysis – Decision justification

Methods:

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

Purpose: No method docstring.

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

evaluate_decision (Line 96)
def evaluate_decision(self, alternatives, criteria, decision_type):
    """Evaluate alternatives based on criteria."""

Purpose: Evaluate alternatives based on criteria.

Parameters: – self: Instance reference – alternatives: Parameter for alternatives – criteria: Parameter for criteria – decision_type: Parameter for decision_type

_rank_alternatives (Line 141)
def _rank_alternatives(self, alternatives, criteria):
    """Rank alternatives using weighted scoring."""

Purpose: Rank alternatives using weighted scoring.

Parameters: – self: Instance reference – alternatives: Parameter for alternatives – criteria: Parameter for criteria

_assess_risks (Line 177)
def _assess_risks(self, alternatives, ranked, decision_type):
    """Assess risks for decision."""

Purpose: Assess risks for decision.

Parameters: – self: Instance reference – alternatives: Parameter for alternatives – ranked: Parameter for ranked – decision_type: Parameter for decision_type

_generate_recommendation (Line 214)
def _generate_recommendation(self, ranked, risks, decision_type):
    """Generate recommendation using LLM."""

Purpose: Generate recommendation using LLM.

Parameters: – self: Instance reference – ranked: Parameter for ranked – risks: Parameter for risks – decision_type: Parameter for decision_type

_generate_reasoning (Line 240)
def _generate_reasoning(self, alternatives, criteria, ranked):
    """Generate detailed reasoning."""

Purpose: Generate detailed reasoning.

Parameters: – self: Instance reference – alternatives: Parameter for alternatives – criteria: Parameter for criteria – ranked: Parameter for ranked

_calculate_confidence (Line 262)
def _calculate_confidence(self, alternatives, criteria, ranked):
    """Calculate confidence in recommendation."""

Purpose: Calculate confidence in recommendation.

Parameters: – self: Instance reference – alternatives: Parameter for alternatives – criteria: Parameter for criteria – ranked: Parameter for ranked

compare_scenarios (Line 291)
def compare_scenarios(self, scenarios, criteria):
    """Compare different scenarios."""

Purpose: Compare different scenarios.

Parameters: – self: Instance reference – scenarios: Parameter for scenarios – criteria: Parameter for criteria

get_statistics (Line 314)
def get_statistics(self):
    """Get decision support statistics."""

Purpose: Get decision support statistics.

Parameters: – self: Instance reference

DecisionSupportPattern (Line 322)

Description: Decision Support Pattern – Manage decision support agents.

Coordinates multiple decision support agents for different contexts.

Methods:

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

Purpose: No method docstring.

Parameters: – self: Instance reference

create_agent (Line 332)
def create_agent(self, name):
    """Create a new decision support agent."""

Purpose: Create a new decision support agent.

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

run (Line 339)
def run(self, agent_name, alternatives, criteria, decision_type):
    """Perform decision analysis using specified agent."""

Purpose: Perform decision analysis using specified agent.

Parameters: – self: Instance reference – agent_name: Parameter for agent_name – alternatives: Parameter for alternatives – criteria: Parameter for criteria – decision_type: Parameter for decision_type


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 = DecisionType(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 *