The UtilityBasedAgent is a decision-making agent that evaluates and selects actions by maximizing a utility function. It supports multi-objective optimization, constraint-based filtering, and sophisticated trade-off analysis to make optimal decisions in complex scenarios.
- Utility Function Optimization: Select actions that maximize utility scores
- Multi-Objective Support: Balance multiple competing objectives with weighted scores
- Constraint Filtering: Enforce hard constraints to filter infeasible actions
- Trade-off Analysis: Make informed decisions when objectives conflict
- Flexible Configuration: Custom utility functions and objectives
- Action Generation: Automatically generates possible actions using Claude
- Comprehensive Evaluation: Detailed analysis of all alternatives
┌─────────────────────────────────────┐
│ UtilityBasedAgent │
├─────────────────────────────────────┤
│ - ClaudePhp client │
│ - Utility function │
│ - Objectives (weighted) │
│ - Constraints (boolean predicates) │
└──────────┬──────────────────────────┘
│
├─── Action Generation
│ └─── Claude generates possible actions
│
├─── Constraint Filtering
│ └─── Filter actions that violate constraints
│
├─── Utility Evaluation
│ ├─── Single utility function
│ └─── Multi-objective weighted sum
│
├─── Action Selection
│ └─── Choose action with highest utility
│
└─── Result Formatting
└─── Detailed decision report
The main agent class that implements utility-based decision making.
Namespace: ClaudeAgents\Agents
Implements: AgentInterface
Properties:
client- Claude API clientname- Agent nameutilityFunction- Callable to compute utility scoresobjectives- Array of objective functions with weightsconstraints- Array of constraint predicateslogger- PSR-3 logger
use ClaudeAgents\Agents\UtilityBasedAgent;
use ClaudePhp\ClaudePhp;
$client = new ClaudePhp(apiKey: getenv('ANTHROPIC_API_KEY'));
$agent = new UtilityBasedAgent($client);
// Run a decision-making task
$result = $agent->run('Choose the best database for our application');
if ($result->isSuccess()) {
echo $result->getAnswer();
// Access metadata
$metadata = $result->getMetadata();
echo "Actions evaluated: {$metadata['actions_evaluated']}\n";
echo "Best utility: {$metadata['best_utility']}\n";
}$agent = new UtilityBasedAgent($client, [
// Optional
'name' => 'decision_maker', // Agent name
'utility_function' => fn($action) => 0.0, // Custom utility function
'objectives' => [], // Initial objectives
'constraints' => [], // Initial constraints
'logger' => $psrLogger, // PSR-3 logger
]);When no objectives are defined, the agent uses a single utility function:
$agent->setUtilityFunction(function($action) {
$value = $action['estimated_value'] ?? 50;
$cost = $action['estimated_cost'] ?? 50;
// Maximize value, minimize cost
$valueScore = $value / 100;
$costScore = (100 - $cost) / 100;
// Return weighted combination
return ($valueScore * 0.7) + ($costScore * 0.3);
});
$result = $agent->run('Choose the best option');For complex decisions with multiple competing goals:
// Add objectives with different weights
$agent->addObjective(
'value',
fn($action) => $action['estimated_value'] ?? 50,
weight: 0.5 // 50% of total utility
);
$agent->addObjective(
'cost_efficiency',
fn($action) => 100 - ($action['estimated_cost'] ?? 50),
weight: 0.3 // 30% of total utility
);
$agent->addObjective(
'risk_tolerance',
fn($action) => match($action['risk'] ?? 'medium') {
'low' => 100,
'medium' => 60,
'high' => 30,
},
weight: 0.2 // 20% of total utility
);
$result = $agent->run('Select the best cloud provider');Constraints filter out infeasible actions before evaluation:
// Budget constraint
$agent->addConstraint(
'budget_limit',
fn($action) => ($action['estimated_cost'] ?? 100) <= 70
);
// Risk constraint
$agent->addConstraint(
'acceptable_risk',
fn($action) => in_array($action['risk'] ?? 'high', ['low', 'medium'])
);
// Minimum quality constraint
$agent->addConstraint(
'quality_threshold',
fn($action) => ($action['estimated_value'] ?? 0) >= 50
);
$result = $agent->run('Choose within budget and risk tolerance');The run() method returns an AgentResult object:
$result = $agent->run('task');
// Standard properties
$result->isSuccess(); // bool
$result->getAnswer(); // string (formatted decision)
$result->getError(); // string (if failed)
$result->getIterations(); // int (always 1)
// Metadata
$metadata = $result->getMetadata();
$metadata['actions_evaluated']; // Number of actions evaluated
$metadata['best_action']; // The selected action details
$metadata['best_utility']; // Utility score of selected action
$metadata['all_evaluations']; // All evaluated actions with scoresThe agent uses Claude to generate 3-5 possible actions for the task:
Task: Choose the best database
Generated Actions:
1. PostgreSQL (value: 80, cost: 60, risk: low)
2. MongoDB (value: 75, cost: 50, risk: medium)
3. DynamoDB (value: 85, cost: 70, risk: medium)
Actions are filtered based on constraints:
// With constraint: cost <= 60
$agent->addConstraint('budget', fn($a) => $a['estimated_cost'] <= 60);
// DynamoDB filtered out (cost: 70)
// Remaining: PostgreSQL, MongoDBEach remaining action is scored:
// With objectives:
// - value (50% weight)
// - cost_efficiency (30% weight)
// - risk (20% weight)
PostgreSQL: (80 * 0.5) + (40 * 0.3) + (100 * 0.2) = 72
MongoDB: (75 * 0.5) + (50 * 0.3) + (60 * 0.2) = 64.5The action with the highest utility is selected:
Selected: PostgreSQL (utility: 72.0)
$agent = new UtilityBasedAgent($client);
$agent->addObjective('features', fn($a) => $a['estimated_value'] ?? 50, 0.4);
$agent->addObjective('cost', fn($a) => 100 - ($a['estimated_cost'] ?? 50), 0.3);
$agent->addObjective('maturity', fn($a) => match($a['risk'] ?? 'high') {
'low' => 90, 'medium' => 60, 'high' => 30
}, 0.3);
$result = $agent->run('Choose a web framework for our startup');$agent = new UtilityBasedAgent($client);
$agent->addObjective('market_share', fn($a) => $a['estimated_value'] ?? 50, 0.4);
$agent->addObjective('profit_margin', fn($a) => $a['estimated_value'] ?? 50, 0.3);
$agent->addObjective('speed_to_market', fn($a) => 100 - ($a['estimated_cost'] ?? 50), 0.3);
$agent->addConstraint('budget', fn($a) => ($a['estimated_cost'] ?? 100) <= 75);
$result = $agent->run('Choose our go-to-market strategy');$agent = new UtilityBasedAgent($client);
$agent->addObjective('impact', fn($a) => $a['estimated_value'] ?? 50, 0.5);
$agent->addObjective('effort', fn($a) => 100 - ($a['estimated_cost'] ?? 50), 0.3);
$agent->addObjective('urgency', fn($a) => 75, 0.2);
$result = $agent->run('Which project should the team work on next?');$agent = new UtilityBasedAgent($client);
$agent->addObjective('features', fn($a) => $a['estimated_value'] ?? 50, 0.25);
$agent->addObjective('pricing', fn($a) => 100 - ($a['estimated_cost'] ?? 50), 0.25);
$agent->addObjective('reliability', fn($a) => 80, 0.25);
$agent->addObjective('support', fn($a) => 70, 0.25);
$agent->addConstraint('sla', fn($a) => ($a['risk'] ?? 'high') !== 'high');
$result = $agent->run('Select a payment processing vendor');$agent = new UtilityBasedAgent($client);
$agent->addObjective('business_value', fn($a) => $a['estimated_value'] ?? 50, 0.4);
$agent->addObjective('user_impact', fn($a) => $a['estimated_value'] ?? 50, 0.3);
$agent->addObjective('ease_of_implementation', fn($a) => 100 - ($a['estimated_cost'] ?? 50), 0.3);
$agent->addConstraint('time_limit', fn($a) => ($a['estimated_cost'] ?? 100) <= 70);
$result = $agent->run('Which feature should we build first?');// ✅ Good - Specific, measurable objectives
$agent->addObjective('conversion_rate', fn($a) => $a['estimated_value'] ?? 0, 0.4);
$agent->addObjective('customer_satisfaction', fn($a) => $a['quality_score'] ?? 0, 0.3);
// ❌ Avoid - Vague objectives
$agent->addObjective('general_goodness', fn($a) => 50, 1.0);// ✅ Good - Weights sum to 1.0 (or close to it)
$agent->addObjective('speed', fn($a) => ..., 0.4);
$agent->addObjective('quality', fn($a) => ..., 0.35);
$agent->addObjective('cost', fn($a) => ..., 0.25);
// ❌ Avoid - Weights don't reflect priorities
$agent->addObjective('speed', fn($a) => ..., 1.0);
$agent->addObjective('quality', fn($a) => ..., 1.0);// ✅ Good - Hard constraints as constraints
$agent->addConstraint('legal', fn($a) => $a['compliant'] ?? false);
$agent->addConstraint('budget', fn($a) => ($a['cost'] ?? 1000) <= 500);
// ❌ Avoid - Hard requirements as objectives
$agent->addObjective('maybe_legal', fn($a) => $a['compliant'] ? 100 : 0, 0.1);// ✅ Good - Scores normalized to 0-100 range
$agent->addObjective('performance', fn($action) => {
$score = $action['benchmark_result'] ?? 0;
return min(100, max(0, $score * 10));
}, 0.5);
// ❌ Avoid - Unnormalized scores
$agent->addObjective('performance', fn($a) => $a['raw_score'] ?? 0, 0.5);// Test with missing action fields
$testAction = ['description' => 'Test'];
$utility = $utilityFunction($testAction); // Should not crash
// Test with extreme values
$extremeAction = [
'estimated_value' => 1000,
'estimated_cost' => -50,
];use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('utility_agent');
$logger->pushHandler(new StreamHandler('/var/log/agent.log', Logger::INFO));
$agent = new UtilityBasedAgent($client, [
'logger' => $logger,
]);class DynamicConstraintAgent
{
private UtilityBasedAgent $agent;
private float $budgetLimit = 100;
public function adjustBudget(float $newLimit): void
{
$this->budgetLimit = $newLimit;
// Re-add constraint with new limit
$this->agent->addConstraint(
'budget',
fn($a) => ($a['estimated_cost'] ?? 100) <= $this->budgetLimit
);
}
}function createAgentForContext(string $context): UtilityBasedAgent
{
global $client;
$agent = new UtilityBasedAgent($client);
if ($context === 'startup') {
$agent->addObjective('speed', fn($a) => ..., 0.5);
$agent->addObjective('cost', fn($a) => ..., 0.5);
} elseif ($context === 'enterprise') {
$agent->addObjective('reliability', fn($a) => ..., 0.4);
$agent->addObjective('security', fn($a) => ..., 0.4);
$agent->addObjective('scalability', fn($a) => ..., 0.2);
}
return $agent;
}// First decide on approach
$strategicAgent = new UtilityBasedAgent($client);
$strategicAgent->addObjective('alignment', fn($a) => ..., 1.0);
$strategy = $strategicAgent->run('Choose overall approach');
// Then decide on implementation
$tacticalAgent = new UtilityBasedAgent($client);
$tacticalAgent->addObjective('feasibility', fn($a) => ..., 0.6);
$tacticalAgent->addObjective('timeline', fn($a) => ..., 0.4);
$tactics = $tacticalAgent->run("Implement the {$strategy} strategy");function compareOptions(array $options): array
{
global $client;
$results = [];
foreach ($options as $option) {
$agent = new UtilityBasedAgent($client);
// Configure agent for this option
$result = $agent->run("Evaluate {$option}");
$results[$option] = $result->getMetadata()['best_utility'];
}
arsort($results);
return $results;
}- Claude generates 3-5 actions per task
- Generation time: ~1-3 seconds
- Consider caching for repeated decisions
- Time complexity: O(n * m) where:
- n = number of actions
- m = number of objectives
- Typical: 5 actions × 3 objectives = 15 evaluations
- Very fast (< 1ms total)
// 1. Reduce API calls by caching action generation
$cachedActions = getFromCache($taskHash);
if (!$cachedActions) {
$result = $agent->run($task);
saveToCache($taskHash, $result->getMetadata()['all_evaluations']);
}
// 2. Use simpler utility functions when possible
$agent->setUtilityFunction(fn($a) => ($a['value'] ?? 0) - ($a['cost'] ?? 0));
// 3. Add constraints early to filter actions
$agent->addConstraint('quick_filter', fn($a) => ($a['cost'] ?? 100) <= 50);./vendor/bin/phpunit tests/Unit/Agents/UtilityBasedAgentTest.php# Basic usage
php examples/utility_based_agent.php
# Advanced patterns
php examples/advanced_utility_based_agent.phppublic function __construct(
ClaudePhp $client,
array $options = []
)Options:
name(string): Agent name (default: 'utility_agent')utility_function(callable): Utility function (default: returns 0.0)objectives(array): Initial objectives (default: [])constraints(array): Initial constraints (default: [])logger(LoggerInterface): PSR-3 logger (default: NullLogger)
Execute decision-making task.
Returns: AgentResult with metadata including actions_evaluated, best_action, best_utility, all_evaluations
Set the utility function.
Parameters:
function: Callable with signaturefn(array $action): float
Add an objective for multi-objective optimization.
Parameters:
name: Objective namefunction: Objective functionfn(array $action): floatweight: Weight in [0,1] (default: 1.0)
Add a constraint.
Parameters:
name: Constraint namepredicate: Constraint predicatefn(array $action): bool
Get the agent name.
See the /examples directory for complete working examples:
utility_based_agent.php- Basic utility-based decision makingadvanced_utility_based_agent.php- Advanced patterns and real-world scenarios
MIT License - See LICENSE file for details.