"""
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