"""
Pattern 107: Multi-Objective Planning
Description:
The Multi-Objective Planning pattern enables agents to plan and make decisions
when multiple, often conflicting objectives must be balanced. Instead of
optimizing for a single goal, the agent must consider tradeoffs between
competing objectives like cost vs. quality, speed vs. accuracy, or risk vs.
reward.
This pattern is essential for realistic decision-making where perfect solutions
don't exist and every choice involves tradeoffs. The agent must evaluate plans
across multiple dimensions, understand Pareto-optimal solutions, and make
informed decisions about which tradeoffs are acceptable given the context.
Multi-objective planning involves objective weighting, constraint satisfaction,
Pareto optimization, preference elicitation, and tradeoff analysis. The agent
can present multiple plan options with different tradeoff profiles, allowing
humans or automated systems to select based on current priorities.
Key Components:
1. Objective Set: Multiple goals to optimize
2. Objective Weights: Relative importance of each objective
3. Plan Evaluator: Scores plans across all objectives
4. Pareto Filter: Identifies non-dominated solutions
5. Tradeoff Analyzer: Analyzes competing objectives
6. Constraint Checker: Ensures hard constraints met
7. Preference Engine: Incorporates user preferences
Objective Types:
1. Maximization: Quality, performance, completeness
2. Minimization: Cost, time, risk, resource usage
3. Target: Hit specific value (with tolerance)
4. Constraint: Must satisfy (hard requirement)
Common Objective Combinations:
- Cost vs. Quality vs. Speed (iron triangle)
- Risk vs. Reward
- Precision vs. Recall
- Exploration vs. Exploitation
- Short-term vs. Long-term goals
- Individual vs. Group benefit
Planning Approaches:
1. Weighted Sum: Combine objectives with weights
2. Lexicographic: Prioritize objectives in order
3. Pareto Optimal: Non-dominated solutions
4. Satisficing: Good enough on all objectives
5. Goal Programming: Meet targets with deviations
6. Multi-Criteria Decision Making (MCDM)
Use Cases:
- Resource allocation with constraints
- Project planning with multiple stakeholders
- Route planning (time vs. cost vs. comfort)
- Product design (features vs. complexity vs. cost)
- Investment decisions (risk vs. return)
- Scheduling (efficiency vs. fairness)
- Machine learning (accuracy vs. interpretability)
Advantages:
- Realistic modeling of complex decisions
- Explicit tradeoff consideration
- Multiple solution alternatives
- Stakeholder preference incorporation
- Better than single-objective optimization
- Transparent decision criteria
- Flexible priority adjustment
Challenges:
- Objective quantification difficulty
- Weight elicitation from users
- Computational complexity
- Visualizing multi-dimensional tradeoffs
- Handling conflicting constraints
- Determining Pareto frontier
- Explaining tradeoff decisions
LangChain Implementation:
This implementation uses LangChain for:
- LLM-based objective identification
- Plan evaluation across dimensions
- Tradeoff analysis and explanation
- Natural language objective specification
Production Considerations:
- Define clear, measurable objectives
- Implement efficient Pareto filtering
- Provide tradeoff visualizations
- Allow dynamic weight adjustment
- Cache evaluated plans
- Support constraint hierarchies
- Enable sensitivity analysis
- Log decision rationale
"""
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_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
class ObjectiveType(Enum):
"""Type of optimization objective."""
MAXIMIZE = "maximize"
MINIMIZE = "minimize"
TARGET = "target"
CONSTRAINT = "constraint"
@dataclass
class Objective:
"""
Represents an optimization objective.
Attributes:
name: Objective name
objective_type: Maximization, minimization, target, or constraint
weight: Relative importance (0-1)
target_value: Target value (for TARGET type)
constraint_value: Threshold (for CONSTRAINT type)
unit: Measurement unit
"""
name: str
objective_type: ObjectiveType
weight: float = 0.5
target_value: Optional[float] = None
constraint_value: Optional[float] = None
unit: str = ""
def evaluate(self, value: float) -> float:
"""
Evaluate objective given a value.
Args:
value: Actual value
Returns:
Normalized score (0-1), higher is better
"""
if self.objective_type == ObjectiveType.MAXIMIZE:
# Normalize to 0-1 (assuming reasonable range)
return min(1.0, max(0.0, value / 100.0))
elif self.objective_type == ObjectiveType.MINIMIZE:
# Lower is better, invert score
return max(0.0, 1.0 - (value / 100.0))
elif self.objective_type == ObjectiveType.TARGET:
# Closer to target is better
if self.target_value is None:
return 0.5
distance = abs(value - self.target_value)
return max(0.0, 1.0 - (distance / 50.0))
elif self.objective_type == ObjectiveType.CONSTRAINT:
# Binary: constraint satisfied or not
if self.constraint_value is None:
return 1.0
return 1.0 if value float:
"""Get normalized score for an objective."""
value = self.objective_values.get(objective.name, 0.0)
return objective.evaluate(value)
class MultiObjectivePlanner:
"""
Multi-objective planning system.
This planner evaluates and compares plans across multiple objectives,
identifies Pareto-optimal solutions, and analyzes tradeoffs.
"""
def __init__(self, temperature: float = 0.4):
"""
Initialize multi-objective planner.
Args:
temperature: LLM temperature
"""
self.llm = ChatOpenAI(temperature=temperature, model="gpt-3.5-turbo")
self.objectives: List[Objective] = []
self.plans: List[Plan] = []
self.plan_counter = 0
def add_objective(
self,
name: str,
objective_type: ObjectiveType,
weight: float = 0.5,
target_value: Optional[float] = None,
constraint_value: Optional[float] = None,
unit: str = ""
) -> Objective:
"""
Add an objective to optimize.
Args:
name: Objective name
objective_type: Type of objective
weight: Relative importance
target_value: Target value (for TARGET)
constraint_value: Constraint threshold
unit: Measurement unit
Returns:
Created objective
"""
objective = Objective(
name=name,
objective_type=objective_type,
weight=weight,
target_value=target_value,
constraint_value=constraint_value,
unit=unit
)
self.objectives.append(objective)
return objective
def create_plan(
self,
description: str,
objective_values: Dict[str, float]
) -> Plan:
"""
Create a plan with objective values.
Args:
description: Plan description
objective_values: Values for each objective
Returns:
Created plan
"""
self.plan_counter += 1
# Check constraint satisfaction
feasible = True
for obj in self.objectives:
if obj.objective_type == ObjectiveType.CONSTRAINT:
value = objective_values.get(obj.name, float('inf'))
if obj.constraint_value is not None and value > obj.constraint_value:
feasible = False
break
plan = Plan(
plan_id=f"plan_{self.plan_counter}",
description=description,
objective_values=objective_values,
feasible=feasible
)
self.plans.append(plan)
return plan
def evaluate_plan(self, plan: Plan) -> Dict[str, Any]:
"""
Evaluate plan across all objectives.
Args:
plan: Plan to evaluate
Returns:
Evaluation results
"""
scores = {}
weighted_sum = 0.0
total_weight = 0.0
for obj in self.objectives:
score = plan.get_score(obj)
scores[obj.name] = score
if obj.objective_type != ObjectiveType.CONSTRAINT:
weighted_sum += score * obj.weight
total_weight += obj.weight
overall_score = weighted_sum / total_weight if total_weight > 0 else 0.0
return {
"plan_id": plan.plan_id,
"feasible": plan.feasible,
"scores": scores,
"overall_score": overall_score,
"objective_values": plan.objective_values
}
def find_pareto_optimal(self) -> List[Plan]:
"""
Find Pareto-optimal (non-dominated) plans.
Returns:
List of Pareto-optimal plans
"""
feasible_plans = [p for p in self.plans if p.feasible]
if not feasible_plans:
return []
# Mark dominated plans
for plan in feasible_plans:
plan.dominated = False
for i, plan1 in enumerate(feasible_plans):
for plan2 in feasible_plans[i+1:]:
# Check if plan1 dominates plan2
plan1_better = False
plan2_better = False
for obj in self.objectives:
if obj.objective_type == ObjectiveType.CONSTRAINT:
continue
score1 = plan1.get_score(obj)
score2 = plan2.get_score(obj)
if score1 > score2:
plan1_better = True
elif score2 > score1:
plan2_better = True
# Mark dominated plans
if plan1_better and not plan2_better:
plan2.dominated = True
elif plan2_better and not plan1_better:
plan1.dominated = True
return [p for p in feasible_plans if not p.dominated]
def analyze_tradeoffs(
self,
plan1: Plan,
plan2: Plan
) -> Dict[str, Any]:
"""
Analyze tradeoffs between two plans.
Args:
plan1: First plan
plan2: Second plan
Returns:
Tradeoff analysis
"""
tradeoffs = []
for obj in self.objectives:
if obj.objective_type == ObjectiveType.CONSTRAINT:
continue
score1 = plan1.get_score(obj)
score2 = plan2.get_score(obj)
value1 = plan1.objective_values.get(obj.name, 0)
value2 = plan2.objective_values.get(obj.name, 0)
difference = score2 - score1
if abs(difference) > 0.05: # Significant difference
tradeoffs.append({
"objective": obj.name,
"plan1_value": value1,
"plan2_value": value2,
"plan1_score": score1,
"plan2_score": score2,
"difference": difference,
"winner": "plan2" if difference > 0 else "plan1"
})
return {
"plan1": plan1.plan_id,
"plan2": plan2.plan_id,
"tradeoffs": tradeoffs,
"num_tradeoffs": len(tradeoffs)
}
def generate_plan_with_priorities(
self,
task: str,
priority_objectives: List[str]
) -> Plan:
"""
Generate plan optimizing for priority objectives.
Args:
task: Task description
priority_objectives: High-priority objective names
Returns:
Generated plan
"""
# Create prompt emphasizing priority objectives
objectives_text = "\n".join([
f"- {obj.name} ({obj.objective_type.value})"
+ (f" - PRIORITY" if obj.name in priority_objectives else "")
for obj in self.objectives
])
prompt = ChatPromptTemplate.from_template(
"Create a plan for this task that optimizes the priority objectives:\n\n"
"Task: {task}\n\n"
"Objectives:\n{objectives}\n\n"
"Provide a brief plan description (one sentence).\n"
"Plan:"
)
chain = prompt | self.llm | StrOutputParser()
description = chain.invoke({
"task": task,
"objectives": objectives_text
}).strip()
# Generate estimated values (in practice, would come from simulation)
objective_values = {}
for obj in self.objectives:
# Favor priority objectives
if obj.name in priority_objectives:
if obj.objective_type == ObjectiveType.MAXIMIZE:
objective_values[obj.name] = 80.0
elif obj.objective_type == ObjectiveType.MINIMIZE:
objective_values[obj.name] = 20.0
else:
objective_values[obj.name] = 50.0
else:
# Non-priority gets moderate values
objective_values[obj.name] = 50.0
return self.create_plan(description, objective_values)
def rank_plans(self) -> List[Tuple[Plan, float]]:
"""
Rank plans by weighted overall score.
Returns:
List of (plan, score) tuples, sorted by score
"""
ranked = []
for plan in self.plans:
if not plan.feasible:
continue
eval_result = self.evaluate_plan(plan)
ranked.append((plan, eval_result["overall_score"]))
ranked.sort(key=lambda x: x[1], reverse=True)
return ranked
def get_summary(self) -> str:
"""Get summary of plans and objectives."""
lines = ["MULTI-OBJECTIVE PLANNING SUMMARY"]
lines.append("=" * 60)
# Objectives
lines.append("\nObjectives:")
for obj in self.objectives:
lines.append(
f" • {obj.name} ({obj.objective_type.value})"
f" - weight: {obj.weight:.2f}"
)
# Plans
lines.append(f"\nTotal Plans: {len(self.plans)}")
lines.append(f"Feasible Plans: {sum(1 for p in self.plans if p.feasible)}")
# Pareto optimal
pareto = self.find_pareto_optimal()
lines.append(f"Pareto-Optimal Plans: {len(pareto)}")
return "\n".join(lines)
def demonstrate_multi_objective_planning():
"""Demonstrate multi-objective planning pattern."""
print("=" * 80)
print("MULTI-OBJECTIVE PLANNING PATTERN DEMONSTRATION")
print("=" * 80)
# Example 1: Basic multi-objective planning
print("\n" + "=" * 80)
print("Example 1: Project Planning with Cost, Time, Quality")
print("=" * 80)
planner = MultiObjectivePlanner()
# Define objectives
planner.add_objective("cost", ObjectiveType.MINIMIZE, weight=0.4, unit="$")
planner.add_objective("time", ObjectiveType.MINIMIZE, weight=0.3, unit="days")
planner.add_objective("quality", ObjectiveType.MAXIMIZE, weight=0.3, unit="score")
print("\nObjectives:")
for obj in planner.objectives:
print(f" • {obj.name}: {obj.objective_type.value} (weight: {obj.weight})")
# Create alternative plans
plans_data = [
("Budget plan: Minimize costs, longer timeline", {"cost": 10000, "time": 60, "quality": 70}),
("Fast plan: Minimize time, higher costs", {"cost": 25000, "time": 30, "quality": 75}),
("Quality plan: Maximize quality, balanced cost/time", {"cost": 18000, "time": 45, "quality": 90}),
("Balanced plan: Moderate on all objectives", {"cost": 15000, "time": 45, "quality": 80}),
]
print("\nCreating alternative plans...")
for desc, values in plans_data:
plan = planner.create_plan(desc, values)
print(f" ✓ {plan.plan_id}: {desc}")
# Evaluate plans
print("\nPlan Evaluations:")
for plan in planner.plans:
eval_result = planner.evaluate_plan(plan)
print(f"\n {plan.plan_id}: {plan.description}")
print(f" Overall Score: {eval_result['overall_score']:.3f}")
for obj_name, score in eval_result['scores'].items():
value = plan.objective_values[obj_name]
print(f" {obj_name}: {value} → score: {score:.3f}")
# Example 2: Pareto optimal solutions
print("\n" + "=" * 80)
print("Example 2: Finding Pareto-Optimal Solutions")
print("=" * 80)
planner2 = MultiObjectivePlanner()
planner2.add_objective("performance", ObjectiveType.MAXIMIZE, weight=0.5)
planner2.add_objective("efficiency", ObjectiveType.MAXIMIZE, weight=0.5)
# Create plans with different tradeoffs
test_plans = [
("High performance, low efficiency", {"performance": 90, "efficiency": 40}),
("Low performance, high efficiency", {"performance": 40, "efficiency": 90}),
("Balanced", {"performance": 70, "efficiency": 70}),
("Dominated plan", {"performance": 50, "efficiency": 50}),
("Another balanced", {"performance": 75, "efficiency": 65}),
]
for desc, values in test_plans:
planner2.create_plan(desc, values)
print("\nAll Plans:")
for plan in planner2.plans:
print(f" {plan.plan_id}: {plan.description}")
print(f" Performance: {plan.objective_values['performance']}, "
f"Efficiency: {plan.objective_values['efficiency']}")
pareto_plans = planner2.find_pareto_optimal()
print(f"\nPareto-Optimal Plans ({len(pareto_plans)} found):")
for plan in pareto_plans:
print(f" ✓ {plan.plan_id}: {plan.description}")
print(f" Performance: {plan.objective_values['performance']}, "
f"Efficiency: {plan.objective_values['efficiency']}")
# Example 3: Tradeoff analysis
print("\n" + "=" * 80)
print("Example 3: Tradeoff Analysis Between Plans")
print("=" * 80)
planner3 = MultiObjectivePlanner()
planner3.add_objective("speed", ObjectiveType.MAXIMIZE, weight=0.4)
planner3.add_objective("accuracy", ObjectiveType.MAXIMIZE, weight=0.4)
planner3.add_objective("cost", ObjectiveType.MINIMIZE, weight=0.2)
plan_a = planner3.create_plan(
"Fast but less accurate",
{"speed": 90, "accuracy": 60, "cost": 20}
)
plan_b = planner3.create_plan(
"Slow but very accurate",
{"speed": 50, "accuracy": 95, "cost": 40}
)
print("\nComparing two plans:")
print(f" Plan A: {plan_a.description}")
print(f" Plan B: {plan_b.description}")
tradeoff = planner3.analyze_tradeoffs(plan_a, plan_b)
print(f"\nTradeoff Analysis ({tradeoff['num_tradeoffs']} significant differences):")
for t in tradeoff['tradeoffs']:
winner = "Plan A" if t['winner'] == 'plan1' else "Plan B"
print(f" • {t['objective']}:")
print(f" Plan A: {t['plan1_value']} (score: {t['plan1_score']:.3f})")
print(f" Plan B: {t['plan2_value']} (score: {t['plan2_score']:.3f})")
print(f" Winner: {winner}")
# Example 4: Constraints
print("\n" + "=" * 80)
print("Example 4: Hard Constraints")
print("=" * 80)
planner4 = MultiObjectivePlanner()
planner4.add_objective("profit", ObjectiveType.MAXIMIZE, weight=0.6)
planner4.add_objective("customer_satisfaction", ObjectiveType.MAXIMIZE, weight=0.4)
planner4.add_objective("budget", ObjectiveType.CONSTRAINT, constraint_value=50000)
print("\nObjectives:")
print(" • Maximize profit (weight: 0.6)")
print(" • Maximize customer satisfaction (weight: 0.4)")
print(" • Budget constraint: ≤ $50,000")
constraint_plans = [
("Within budget", {"profit": 30000, "customer_satisfaction": 80, "budget": 45000}),
("Over budget", {"profit": 50000, "customer_satisfaction": 90, "budget": 60000}),
("Tight budget", {"profit": 25000, "customer_satisfaction": 75, "budget": 50000}),
]
print("\nEvaluating plans:")
for desc, values in constraint_plans:
plan = planner4.create_plan(desc, values)
status = "✓ FEASIBLE" if plan.feasible else "✗ INFEASIBLE"
print(f" {status}: {desc}")
print(f" Budget: ${values['budget']:,}")
# Example 5: Ranking plans
print("\n" + "=" * 80)
print("Example 5: Ranking Plans by Overall Score")
print("=" * 80)
planner5 = MultiObjectivePlanner()
planner5.add_objective("innovation", ObjectiveType.MAXIMIZE, weight=0.3)
planner5.add_objective("feasibility", ObjectiveType.MAXIMIZE, weight=0.4)
planner5.add_objective("risk", ObjectiveType.MINIMIZE, weight=0.3)
ranking_plans = [
("Conservative approach", {"innovation": 40, "feasibility": 90, "risk": 10}),
("Innovative but risky", {"innovation": 90, "feasibility": 60, "risk": 70}),
("Moderate innovation", {"innovation": 70, "feasibility": 80, "risk": 30}),
("Safe and feasible", {"innovation": 50, "feasibility": 95, "risk": 15}),
]
for desc, values in ranking_plans:
planner5.create_plan(desc, values)
ranked = planner5.rank_plans()
print("\nPlans Ranked by Weighted Score:")
for i, (plan, score) in enumerate(ranked, 1):
print(f" {i}. {plan.description}")
print(f" Overall Score: {score:.3f}")
print(f" Innovation: {plan.objective_values['innovation']}, "
f"Feasibility: {plan.objective_values['feasibility']}, "
f"Risk: {plan.objective_values['risk']}")
# Example 6: Priority-based planning
print("\n" + "=" * 80)
print("Example 6: Priority-Based Plan Generation")
print("=" * 80)
planner6 = MultiObjectivePlanner()
planner6.add_objective("reliability", ObjectiveType.MAXIMIZE, weight=0.3)
planner6.add_objective("performance", ObjectiveType.MAXIMIZE, weight=0.3)
planner6.add_objective("maintainability", ObjectiveType.MAXIMIZE, weight=0.2)
planner6.add_objective("cost", ObjectiveType.MINIMIZE, weight=0.2)
print("\nGenerating plans with different priorities:")
# Priority on reliability
plan1 = planner6.generate_plan_with_priorities(
"Build production system",
["reliability"]
)
print(f"\n Priority: Reliability")
print(f" Plan: {plan1.description}")
# Priority on performance
plan2 = planner6.generate_plan_with_priorities(
"Build production system",
["performance"]
)
print(f"\n Priority: Performance")
print(f" Plan: {plan2.description}")
# Example 7: Weight sensitivity
print("\n" + "=" * 80)
print("Example 7: Objective Weight Sensitivity")
print("=" * 80)
planner7 = MultiObjectivePlanner()
obj1 = planner7.add_objective("feature_count", ObjectiveType.MAXIMIZE)
obj2 = planner7.add_objective("simplicity", ObjectiveType.MAXIMIZE)
test_plan = planner7.create_plan(
"Product design",
{"feature_count": 70, "simplicity": 60}
)
print("\nTesting different weight combinations:")
weight_combos = [
(0.8, 0.2, "Feature-focused"),
(0.5, 0.5, "Balanced"),
(0.2, 0.8, "Simplicity-focused"),
]
for w1, w2, label in weight_combos:
obj1.weight = w1
obj2.weight = w2
eval_result = planner7.evaluate_plan(test_plan)
print(f"\n {label} (weights: {w1:.1f}, {w2:.1f}):")
print(f" Overall Score: {eval_result['overall_score']:.3f}")
# Example 8: System summary
print("\n" + "=" * 80)
print("Example 8: Planning System Summary")
print("=" * 80)
planner8 = MultiObjectivePlanner()
planner8.add_objective("throughput", ObjectiveType.MAXIMIZE, weight=0.35)
planner8.add_objective("latency", ObjectiveType.MINIMIZE, weight=0.35)
planner8.add_objective("resource_usage", ObjectiveType.MINIMIZE, weight=0.30)
# Create several plans
for i in range(5):
planner8.create_plan(
f"Design option {i+1}",
{
"throughput": 50 + (i * 10),
"latency": 100 - (i * 15),
"resource_usage": 40 + (i * 5)
}
)
print(planner8.get_summary())
# Summary
print("\n" + "=" * 80)
print("SUMMARY: Multi-Objective Planning Pattern")
print("=" * 80)
summary = """
The Multi-Objective Planning pattern demonstrated:
1. MULTIPLE OBJECTIVES (Example 1):
- Cost, time, quality tradeoffs
- Weighted objective evaluation
- Overall score calculation
- Alternative plan comparison
- Objective value normalization
2. PARETO OPTIMALITY (Example 2):
- Non-dominated solutions
- Domination checking
- Efficient frontier identification
- Multiple optimal alternatives
- Tradeoff visualization
3. TRADEOFF ANALYSIS (Example 3):
- Comparing plan pairs
- Identifying differences
- Winner determination
- Quantifying tradeoffs
- Decision support
4. HARD CONSTRAINTS (Example 4):
- Constraint satisfaction
- Feasibility checking
- Plan filtering
- Requirement enforcement
- Infeasible plan rejection
5. PLAN RANKING (Example 5):
- Weighted overall scoring
- Sorted alternatives
- Best plan identification
- Multi-criteria comparison
- Decision making
6. PRIORITY OBJECTIVES (Example 6):
- LLM-guided plan generation
- Priority emphasis
- Context-aware planning
- Objective focus
- Adaptive generation
7. WEIGHT SENSITIVITY (Example 7):
- Dynamic weight adjustment
- Impact analysis
- Preference tuning
- Score variation
- Stakeholder alignment
8. SYSTEM INTEGRATION (Example 8):
- Comprehensive summary
- Statistics tracking
- Pareto analysis
- System overview
- Decision support
KEY BENEFITS:
✓ Realistic complex decision modeling
✓ Explicit tradeoff consideration
✓ Multiple solution alternatives
✓ Stakeholder preference integration
✓ Better than single-objective
✓ Transparent decision criteria
✓ Flexible priority adjustment
✓ Pareto-optimal solutions
USE CASES:
• Resource allocation with constraints
• Project planning (cost/time/quality)
• Route planning (time/cost/comfort)
• Product design tradeoffs
• Investment decisions (risk/return)
• Scheduling (efficiency/fairness)
• ML model selection (accuracy/interpretability)
• System design (performance/cost/reliability)
OBJECTIVE TYPES:
→ Maximize: Quality, performance, revenue
→ Minimize: Cost, time, risk, resource usage
→ Target: Hit specific value
→ Constraint: Must satisfy (hard requirement)
PLANNING APPROACHES:
→ Weighted Sum: Combine with weights
→ Lexicographic: Priority ordering
→ Pareto Optimal: Non-dominated set
→ Satisficing: Good enough
→ Goal Programming: Target achievement
BEST PRACTICES:
1. Define clear, measurable objectives
2. Implement efficient Pareto filtering
3. Provide tradeoff visualizations
4. Allow dynamic weight adjustment
5. Cache evaluated plans for reuse
6. Support constraint hierarchies
7. Enable sensitivity analysis
8. Log decision rationale
TRADE-OFFS:
• Thoroughness vs. computational cost
• Number of objectives vs. complexity
• Weight precision vs. simplicity
• Exact vs. approximate solutions
PRODUCTION CONSIDERATIONS:
→ Normalize objectives to comparable scales
→ Implement efficient Pareto algorithms
→ Provide interactive weight tuning
→ Visualize tradeoff spaces (2D/3D plots)
→ Cache evaluation results
→ Support what-if analysis
→ Enable constraint relaxation
→ Log all evaluation details
→ Provide explanation of tradeoffs
→ Support group decision making
This pattern enables agents to make realistic decisions when
multiple conflicting objectives must be balanced, providing
transparency into tradeoffs and multiple solution alternatives.
"""
print(summary)
if __name__ == "__main__":
demonstrate_multi_objective_planning()
Agentic Design Pattern: Multi Objective Planning
Pattern 107: Multi Objective Planning – Line-by-Line Explanation
Overview
This document provides a detailed line-by-line explanation of the Multi Objective Planning pattern implementation.
Pattern Description
Pattern 107: Multi-Objective Planning
Description: The Multi-Objective Planning pattern enables agents to plan and make decisions when multiple, often conflicting objectives must be balanced. Instead of optimizing for a single goal, the agent must consider tradeoffs between competing objectives like cost vs. quality, speed vs. accuracy, or risk vs. reward.
This pattern is essential for realistic decision-making where perfect solutions
don't exist and every choice involves tradeoffs. The agent must evaluate plans
across multiple dimensions, understand Pareto-optimal solutions, and make
informed decisions about which tradeoffs are acceptable given the context.
Multi-objective planning involves objective weighting, constraint satisfaction,
Pareto optimization, preference elicitation, and tradeoff analysis. The agent
can present multiple plan options with different tradeoff profiles, allowing
humans or automated systems to select based on current priorities.
Key Components: 1. Objective Set: Multiple goals to optimize 2. Objective Weights: Relative importance of each objective 3. Plan Evaluator: Scores plans across all objectives 4. Pareto Filter: Identifies non-dominated solutions 5. Tradeoff Analyzer: Analyzes competing objectives 6. Constraint Checker: Ensures hard constraints met 7. Preference Engine: Incorporates user preferences
Objective Types: 1. Maximization: Quality, performance, completeness 2. Minimization: Cost, time, risk, resource usage 3. Target: Hit specific value (with tolerance) 4. Constraint: Must satisfy (hard requirement)
Common Objective Combinations: – Cost vs. Quality vs. Speed (iron triangle) – Risk vs. Reward – Precision vs. Recall – Exploration vs. Exploitation – Short-term vs. Long-term goals – Individual vs. Group benefit
Planning Approaches: 1. Weighted Sum: Combine objectives with weights 2. Lexicographic: Prioritize objectives in order 3. Pareto Optimal: Non-dominated solutions 4. Satisficing: Good enough on all objectives 5. Goal Programming: Meet targets with deviations 6. Multi-Criteria Decision Making (MCDM)
Use Cases: – Resource allocation with constraints – Project planning with multiple stakeholders – Route planning (time vs. cost vs. comfort) – Product design (features vs. complexity vs. cost) – Investment decisions (risk vs. return) – Scheduling (efficiency vs. fairness) – Machine learning (accuracy vs. interpretability)
Advantages: – Realistic modeling of complex decisions – Explicit tradeoff consideration – Multiple solution alternatives – Stakeholder preference incorporation – Better than single-objective optimization – Transparent decision criteria – Flexible priority adjustment
Challenges: – Objective quantification difficulty – Weight elicitation from users – Computational complexity – Visualizing multi-dimensional tradeoffs – Handling conflicting constraints – Determining Pareto frontier – Explaining tradeoff decisions
LangChain Implementation: This implementation uses LangChain for: – LLM-based objective identification – Plan evaluation across dimensions – Tradeoff analysis and explanation – Natural language objective specification
Production Considerations: – Define clear, measurable objectives – Implement efficient Pareto filtering – Provide tradeoff visualizations – Allow dynamic weight adjustment – Cache evaluated plans – Support constraint hierarchies – Enable sensitivity analysis – Log decision rationale
File Statistics
- Total Lines: 899
- Classes: 4
- Functions: 1
- Constants: 0
Module Documentation
"""
Pattern 107: Multi-Objective Planning
Description:
The Multi-Objective Planning pattern enables agents to plan and make decisions
when multiple, often conflicting objectives must be balanced. Instead of
optimizing for a single goal, the agent must consider tradeoffs between
competing objectives like cost vs. quality, speed vs. accuracy, or risk vs.
reward.
This pattern is essential for realistic decision-making where perfect solutions
don't exist and every choice involves tradeoffs. The agent must evaluate plans
across multiple dimensions, understand Pareto-optimal solutions, and make
informed decisions about which tradeoffs are acceptable given the context.
Multi-objective planning involves objective weighting, constraint satisfaction,
Pareto optimization, preference elicitation, and tradeoff analysis. The agent
can present multiple plan options with different tradeoff profiles, allowing
humans or automated systems to select based on current priorities.
Key Components:
1. Objective Set: Multiple goals to optimize
2. Objective Weights: Relative importance of each objective
3. Plan Evaluator: Scores plans across all objectives
4. Pareto Filter: Identifies non-dominated solutions
5. Tradeoff Analyzer: Analyzes competing objectives
6. Constraint Checker: Ensures hard constraints met
7. Preference Engine: Incorporates user preferences
Objective Types:
1. Maximization: Quality, performance, completeness
2. Minimization: Cost, time, risk, resource usage
3. Target: Hit specific value (with tolerance)
4. Constraint: Must satisfy (hard requirement)
Common Objective Combinations:
- Cost vs. Quality vs. Speed (iron triangle)
- Risk vs. Reward
- Precision vs. Recall
- Exploration vs. Exploitation
- Short-term vs. Long-term goals
- Individual vs. Group benefit
Planning Approaches:
1. Weighted Sum: Combine objectives with weights
2. Lexicographic: Prioritize objectives in order
3. Pareto Optimal: Non-dominated solutions
4. Satisficing: Good enough on all objectives
5. Goal Programming: Meet targets with deviations
6. Multi-Criteria Decision Making (MCDM)
Use Cases:
- Resource allocation with constraints
- Project planning with multiple stakeholders
- Route planning (time vs. cost vs. comfort)
- Product design (features vs. complexity vs. cost)
- Investment decisions (risk vs. return)
- Scheduling (efficiency vs. fairness)
- Machine learning (accuracy vs. interpretability)
Advantages:
- Realistic modeling of complex decisions
- Explicit tradeoff consideration
- Multiple solution alternatives
- Stakeholder preference incorporation
- Better than single-objective optimization
- Transparent decision criteria
- Flexible priority adjustment
Challenges:
- Objective quantification difficulty
- Weight elicitation from users
- Computational complexity
- Visualizing multi-dimensional tradeoffs
- Handling conflicting constraints
- Determining Pareto frontier
- Explaining tradeoff decisions
LangChain Implementation:
This implementation uses LangChain for:
- LLM-based objective identification
- Plan evaluation across dimensions
- Tradeoff analysis and explanation
- Natural language objective specification
Production Considerations:
- Define clear, measurable objectives
- Implement efficient Pareto filtering
- Provide tradeoff visualizations
- Allow dynamic weight adjustment
- Cache evaluated plans
- Support constraint hierarchies
- Enable sensitivity analysis
- Log decision rationale
"""
Purpose: This module implements the Multi Objective Planning 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.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.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
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: ObjectiveType
Lines 110-115
Inherits from: Enum
Purpose:
Type of optimization objective.
Class: Objective
Lines 119-169
Purpose:
Represents an optimization objective.
Attributes:
name: Objective name
objective_type: Maximization, minimization, target, or constraint
weight: Relative importance (0-1)
target_value: Target value (for TARGET type)
constraint_value: Threshold (for CONSTRAINT type)
unit: Measurement unit
Methods:
evaluate(self, value)
Lines 138-169
Purpose:
Evaluate objective given a value.
Args:
value: Actual value
Returns:
Normalized score (0-1), higher is better
Parameters:
– value: Value
Returns: float
Class: Plan
Lines 173-193
Purpose:
Represents a plan with multiple objective values.
Attributes:
plan_id: Unique identifier
description: Plan description
objective_values: Values for each objective
feasible: Whether all constraints are satisfied
dominated: Whether dominated by another plan
Methods:
get_score(self, objective)
Lines 190-193
Purpose:
Get normalized score for an objective.
Parameters:
– objective: Objective
Returns: float
Class: MultiObjectivePlanner
Lines 196-499
Purpose:
Multi-objective planning system.
This planner evaluates and compares plans across multiple objectives,
identifies Pareto-optimal solutions, and analyzes tradeoffs.
Methods:
__init__(self, temperature)
Lines 204-214
Purpose:
Initialize multi-objective planner.
Args:
temperature: LLM temperature
Parameters:
– temperature: Sampling temperature (0-1)
add_objective(self, name, objective_type, weight, target_value, constraint_value, unit)
Lines 216-248
Purpose:
Add an objective to optimize.
Args:
name: Objective name
objective_type: Type of objective
weight: Relative importance
target_value: Target value (for TARGET)
constraint_value: Constraint threshold
unit: Measurement unit
Returns:
Created objective
Parameters:
– name: Name
– objective_type: Objective type
– weight: Weight
– target_value: Target value
– constraint_value: Constraint value
– unit: Unit
Returns: Objective
create_plan(self, description, objective_values)
Lines 250-284
Purpose:
Create a plan with objective values.
Args:
description: Plan description
objective_values: Values for each objective
Returns:
Created plan
Parameters:
– description: Description
– objective_values: Objective values
Returns: Plan
evaluate_plan(self, plan)
Lines 286-316
Purpose:
Evaluate plan across all objectives.
Args:
plan: Plan to evaluate
Returns:
Evaluation results
Parameters:
– plan: Plan
Returns: <ast.Subscript object at 0x000001A06D8107D0>
find_pareto_optimal(self)
Lines 318-358
Purpose:
Find Pareto-optimal (non-dominated) plans.
Returns:
List of Pareto-optimal plans
Parameters:
Returns: <ast.Subscript object at 0x000001A06E094690>
analyze_tradeoffs(self, plan1, plan2)
Lines 360-404
Purpose:
Analyze tradeoffs between two plans.
Args:
plan1: First plan
plan2: Second plan
Returns:
Tradeoff analysis
Parameters:
– plan1: Plan1
– plan2: Plan2
Returns: <ast.Subscript object at 0x000001A06D659890>
generate_plan_with_priorities(self, task, priority_objectives)
Lines 406-457
Purpose:
Generate plan optimizing for priority objectives.
Args:
task: Task description
priority_objectives: High-priority objective names
Returns:
Generated plan
Parameters:
– task: Task
– priority_objectives: Priority objectives
Returns: Plan
rank_plans(self)
Lines 459-476
Purpose:
Rank plans by weighted overall score.
Returns:
List of (plan, score) tuples, sorted by score
Parameters:
Returns: <ast.Subscript object at 0x000001A06D7FF210>
get_summary(self)
Lines 478-499
Purpose:
Get summary of plans and objectives.
Parameters:
Returns: str
Functions
demonstrate_multi_objective_planning()
Lines 502-894
Purpose:
Demonstrate multi-objective planning pattern.
Key Concepts Summary
Pattern Overview
The Multi Objective Planning 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
- Agent Class: Main implementation of the pattern logic
- Prompt Engineering: Carefully crafted prompts to guide LLM behavior
- Tool Integration: Optional tools for extended functionality
- Memory Management: State handling across interactions
- 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
.envfiles 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
- Prompt Optimization: Minimize tokens while maintaining clarity
- Caching: Cache repeated queries/responses
- Model Selection: Use smaller models when appropriate
- Batch Processing: Group similar requests
- 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
- Monitoring: Add logging, metrics, tracing
- Validation: Input/output schema validation
- Rate Limiting: Implement request throttling
- Caching: Redis or in-memory cache for responses
- Async Support: Non-blocking operations for scalability
Feature Extensions
- Multi-Model Support: Switch between providers
- Fallback Models: Graceful degradation strategy
- Custom Tools: Domain-specific function calling
- Advanced Memory: Vector stores for long-term memory
- Evaluation: Automated testing and benchmarking
Code Quality
- Testing: Unit tests, integration tests
- Documentation: API docs, usage examples
- Type Safety: Stricter type hints
- Configuration: Externalized config files
Related Patterns
- Pattern 106
- Pattern 108
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 Multi Objective Planning 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: 107_multi_objective_planning.py
Total Lines: 899
Pattern Category: AI Agent Design Pattern #107
Generated from actual implementation code
For educational and reference purposes
Leave a Reply