Time: 45 minutes | Difficulty: Intermediate
You've learned how to build agents, handle errors, and implement advanced patterns. Now let's make your code truly production-ready using industry-standard design patterns.
By the end of this tutorial, you'll be able to:
- Use the Factory Pattern for consistent agent creation
- Apply the Builder Pattern for type-safe configuration
- Implement the Observer Pattern for event-driven monitoring
- Use the Strategy Pattern for flexible response parsing
- Apply the Template Method Pattern for structured prompts
- Combine patterns for production-quality code
- Create reusable agent templates
We'll refactor a basic agent into a production-ready system using:
- Factory Pattern - Centralized agent creation
- Builder Pattern - Type-safe configuration
- Observer Pattern - Event-driven monitoring
- Strategy Pattern - Flexible response parsing
- Template Method - Structured prompts
- Error Handler Pattern - Resilient execution
Make sure you have:
- Completed Tutorial 4: Production Patterns
- Understanding of production systems
- Basic knowledge of design patterns (helpful but not required)
- Claude PHP Agent Framework installed
// ❌ Without patterns: Hard to maintain, test, and scale
$agent1 = new ReactAgent($client, AgentConfig::create('agent1'), $logger);
$agent2 = new ReactAgent($client, AgentConfig::create('agent2'), $logger);
// What if constructor changes?
// How do we ensure consistent configuration?
// How do we mock for testing?
// How do we monitor execution?
// ✅ With patterns: Maintainable, testable, scalable
$factory = new AgentFactory($client, $logger, $eventDispatcher);
$config = AgentConfigBuilder::create()
->name('agent1')
->maxIterations(10)
->build();
$agent = $factory->createReactAgent($config->toArray());
// Easy to maintain, test, and monitor!The Factory Pattern centralizes object creation.
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use ClaudeAgents\Factory\AgentFactory;
use ClaudePhp\ClaudePhp;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Load environment
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../..');
$dotenv->load();
// Setup
$client = new ClaudePhp(apiKey: $_ENV['ANTHROPIC_API_KEY']);
$logger = new Logger('tutorial');
$logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
// Create factory
$factory = new AgentFactory($client, $logger);
echo "✅ Created AgentFactory\n\n";// Generic creation
$agent = $factory->create('react', [
'name' => 'customer_support',
'max_iterations' => 10,
]);
echo "✅ Created ReAct agent via factory\n";
// Type-safe creation (recommended)
$reactAgent = $factory->createReactAgent([
'name' => 'react_agent',
'max_iterations' => 15,
]);
$dialogAgent = $factory->createDialogAgent([
'name' => 'dialog_agent',
'temperature' => 0.7,
]);
echo "✅ Created agents with type-safe methods\n\n";- ✅ Consistent configuration across all agents
- ✅ Centralized dependency injection
- ✅ Easy to test with mocks
- ✅ Type-safe factory methods
The Builder Pattern provides fluent, validated configuration.
use ClaudeAgents\Builder\AgentConfigBuilder;
$config = AgentConfigBuilder::create()
->name('production_agent')
->model('claude-sonnet-4-20250514')
->maxIterations(15)
->temperature(0.7)
->maxTokens(4096)
->systemPrompt('You are a helpful assistant')
->build();
echo "✅ Built configuration with fluent API\n\n";
// Use with factory
$agent = $factory->createReactAgent($config->toArray());try {
$invalidConfig = AgentConfigBuilder::create()
->name('test')
->temperature(5.0) // Invalid! Must be 0.0-1.0
->build();
} catch (InvalidArgumentException $e) {
echo "❌ Caught validation error: {$e->getMessage()}\n";
}
try {
$invalidConfig2 = AgentConfigBuilder::create()
->name('test')
->maxIterations(200) // Invalid! Must be 1-100
->build();
} catch (InvalidArgumentException $e) {
echo "❌ Caught validation error: {$e->getMessage()}\n\n";
}class AgentTemplates
{
public static function customerSupport(): AgentConfig
{
return AgentConfigBuilder::create()
->name('support')
->model('claude-sonnet-4-20250514')
->temperature(0.7)
->maxIterations(5)
->systemPrompt('You are a helpful customer support agent with expertise in resolving issues.')
->build();
}
public static function dataAnalyst(): AgentConfig
{
return AgentConfigBuilder::create()
->name('analyst')
->model('claude-opus-4-20250514')
->temperature(0.3) // More deterministic
->maxIterations(15)
->systemPrompt('You are a data analyst with expertise in statistics and visualization.')
->build();
}
public static function codeReviewer(): AgentConfig
{
return AgentConfigBuilder::create()
->name('reviewer')
->model('claude-opus-4-20250514')
->temperature(0.2) // Very deterministic
->maxIterations(10)
->systemPrompt('You are an expert code reviewer focusing on security and best practices.')
->build();
}
}
// Usage
$supportConfig = AgentTemplates::customerSupport();
$supportAgent = $factory->createDialogAgent($supportConfig->toArray());
echo "✅ Created agent from template\n\n";- ✅ IDE autocomplete and type checking
- ✅ Automatic validation (temperature 0-1, iterations 1-100)
- ✅ Clear, self-documenting code
- ✅ Reusable configuration templates
The Observer Pattern enables decoupled monitoring.
use ClaudeAgents\Events\EventDispatcher;
use ClaudeAgents\Events\{AgentStartedEvent, AgentCompletedEvent, AgentFailedEvent};
$dispatcher = new EventDispatcher();
echo "✅ Created EventDispatcher\n\n";// Event 1: Agent started
$dispatcher->listen(AgentStartedEvent::class, function($event) {
echo "🚀 Agent '{$event->getAgentName()}' started\n";
echo " Task: {$event->getTask()}\n";
});
// Event 2: Agent completed
$dispatcher->listen(AgentCompletedEvent::class, function($event) {
echo "✅ Agent '{$event->getAgentName()}' completed\n";
echo " Duration: " . round($event->getDuration(), 2) . "s\n";
echo " Iterations: {$event->getIterations()}\n";
});
// Event 3: Agent failed
$dispatcher->listen(AgentFailedEvent::class, function($event) {
echo "❌ Agent '{$event->getAgentName()}' failed\n";
echo " Error: {$event->getError()}\n";
});
echo "✅ Subscribed to lifecycle events\n\n";class MetricsCollector
{
private array $metrics = [
'total_runs' => 0,
'successful_runs' => 0,
'failed_runs' => 0,
'total_duration' => 0.0,
];
public function onAgentCompleted(AgentCompletedEvent $event): void
{
$this->metrics['total_runs']++;
$this->metrics['successful_runs']++;
$this->metrics['total_duration'] += $event->getDuration();
}
public function onAgentFailed(AgentFailedEvent $event): void
{
$this->metrics['total_runs']++;
$this->metrics['failed_runs']++;
}
public function getMetrics(): array
{
return [
...$this->metrics,
'success_rate' => $this->metrics['total_runs'] > 0
? $this->metrics['successful_runs'] / $this->metrics['total_runs']
: 0,
'avg_duration' => $this->metrics['successful_runs'] > 0
? $this->metrics['total_duration'] / $this->metrics['successful_runs']
: 0,
];
}
}
$metrics = new MetricsCollector();
$dispatcher->listen(AgentCompletedEvent::class, [$metrics, 'onAgentCompleted']);
$dispatcher->listen(AgentFailedEvent::class, [$metrics, 'onAgentFailed']);
echo "✅ Registered metrics collector\n\n";// Create factory with event dispatcher
$factoryWithEvents = new AgentFactory($client, $logger, $dispatcher);
// All agents created will automatically dispatch events
$monitoredAgent = $factoryWithEvents->createReactAgent([
'name' => 'monitored_agent',
]);
echo "✅ Created factory with automatic event dispatching\n\n";- ✅ Agents don't know about monitoring code
- ✅ Add/remove monitoring without changing agents
- ✅ Multiple listeners per event
- ✅ Real-time metrics and alerts
Let's build a production-ready system using all patterns:
echo "═══ Production-Ready Setup ═══\n\n";
// 1. Create event dispatcher for monitoring
$productionDispatcher = new EventDispatcher();
// 2. Set up metrics collection
$productionMetrics = new MetricsCollector();
$productionDispatcher->listen(AgentCompletedEvent::class, [$productionMetrics, 'onAgentCompleted']);
$productionDispatcher->listen(AgentFailedEvent::class, [$productionMetrics, 'onAgentFailed']);
// 3. Set up console logging
$productionDispatcher->listen(AgentStartedEvent::class, function($event) {
echo "[" . date('H:i:s') . "] Agent started: {$event->getAgentName()}\n";
});
$productionDispatcher->listen(AgentCompletedEvent::class, function($event) {
echo "[" . date('H:i:s') . "] Agent completed in " . round($event->getDuration(), 2) . "s\n";
});
// 4. Create factory with all dependencies
$productionFactory = new AgentFactory($client, $logger, $productionDispatcher);
// 5. Build configuration with builder
$productionConfig = AgentConfigBuilder::create()
->name('production_agent')
->model('claude-sonnet-4-20250514')
->maxIterations(10)
->temperature(0.7)
->systemPrompt('You are a production AI assistant.')
->build();
// 6. Create agent via factory
$productionAgent = $productionFactory->createReactAgent($productionConfig->toArray());
// 7. Add tools
$productionAgent->withTool(
Tool::create('get_weather')
->description('Get current weather')
->stringParam('location', 'City name')
->handler(function($input) {
return "Weather in {$input['location']}: 72°F, sunny";
})
);
echo "✅ Production setup complete!\n\n";
// 8. Run agent (events dispatch automatically)
echo "═══ Running Production Agent ═══\n\n";
$result = $productionAgent->run('What is the weather in San Francisco?');
if ($result->isSuccess()) {
echo "\n📊 Result: " . substr($result->getAnswer(), 0, 100) . "...\n\n";
}
// 9. Display metrics
echo "═══ Metrics ═══\n\n";
$stats = $productionMetrics->getMetrics();
echo "Total Runs: {$stats['total_runs']}\n";
echo "Successful: {$stats['successful_runs']}\n";
echo "Failed: {$stats['failed_runs']}\n";
echo "Success Rate: " . round($stats['success_rate'] * 100, 1) . "%\n";
echo "Avg Duration: " . round($stats['avg_duration'], 2) . "s\n\n";use ClaudeAgents\Parsers\ResponseParserChain;
use ClaudeAgents\Parsers\{JsonParser, MarkdownParser, XmlParser};
$parserChain = new ResponseParserChain([
new JsonParser(),
new MarkdownParser(),
new XmlParser(),
]);
// Automatically tries parsers until one succeeds
$jsonResponse = '```json{"status": "success", "data": {"id": 1}}```';
$parsed = $parserChain->parse($jsonResponse);
echo "✅ Parsed JSON response: " . json_encode($parsed) . "\n\n";use ClaudeAgents\Prompts\PromptBuilder;
$prompt = PromptBuilder::create()
->addContext('You are a helpful coding assistant')
->addTask('Review the following PHP code')
->addCode('<?php function add($a, $b) { return $a + $b; } ?>', 'php')
->addConstraint('Limit feedback to 3 key points')
->addInstructions('Be concise and actionable')
->build();
echo "✅ Built structured prompt\n\n";
echo "Prompt preview:\n";
echo substr($prompt, 0, 200) . "...\n\n";You now have a production-ready agent system with:
- ✅ Centralized creation (Factory)
- ✅ Type-safe configuration (Builder)
- ✅ Automatic monitoring (Observer)
- ✅ Flexible parsing (Strategy)
- ✅ Structured prompts (Template Method)
❌ Don't:
$agent = new ReactAgent($client, AgentConfig::create('test'), $logger);✅ Do:
$agent = $factory->createReactAgent(['name' => 'test']);❌ Don't:
$config = ['name' => 'agent', 'model' => 'opus', 'max_iterations' => 20];✅ Do:
$config = AgentConfigBuilder::create()
->name('agent')
->model('claude-opus-4-20250514')
->maxIterations(20)
->build();❌ Don't:
// Agent code knows about monitoring
$agent->run($task);
$metrics->record($agent->getStats());✅ Do:
// Monitoring via events
$dispatcher->listen(AgentCompletedEvent::class, [$metrics, 'record']);
$agent->run($task); // Events dispatched automatically❌ Don't:
// Repeated configuration
$agent1 = $factory->create('react', ['name' => 'a1', 'model' => 'opus', ...]);
$agent2 = $factory->create('react', ['name' => 'a2', 'model' => 'opus', ...]);✅ Do:
// Reusable template
$template = AgentTemplates::production();
$agent1 = $factory->create('react', [...$template->toArray(), 'name' => 'a1']);
$agent2 = $factory->create('react', [...$template->toArray(), 'name' => 'a2']);You've completed the Design Patterns tutorial! You now know how to:
✅ Use Factory Pattern for consistent agent creation
✅ Apply Builder Pattern for type-safe configuration
✅ Implement Observer Pattern for event-driven monitoring
✅ Use Strategy Pattern for flexible response parsing
✅ Apply Template Method for structured prompts
✅ Combine patterns for production-quality code
✅ Tutorial 0: Concepts
✅ Tutorial 1: First Agent
✅ Tutorial 2: ReAct Loop
✅ Tutorial 3: Multi-Tool
✅ Tutorial 4: Production
✅ Tutorial 5: Advanced Patterns
✅ Tutorial 6: Design Patterns
→ You're now a production AI agent expert!
- Design Patterns Guide - Comprehensive pattern reference
- Factory Pattern - Factory documentation
- Builder Pattern - Builder documentation
- Event System - Observer pattern documentation
- Best Practices - Production patterns
- Complete Demo - Working example
- Factory centralizes creation - Consistency and testability
- Builder ensures type safety - IDE support and validation
- Events decouple monitoring - Extensibility and flexibility
- Strategy enables flexibility - Handle variable formats
- Templates reduce boilerplate - DRY principle
- Combine patterns - Greater than the sum of parts
Use these patterns in your next project:
- Start with Factory - Always create agents via factory
- Add Builder - For complex configurations
- Add Events - When you need monitoring
- Add Strategy - When dealing with variable formats
- Create Templates - As patterns emerge
class AgentService
{
public function __construct(
private AgentFactory $factory,
private EventDispatcher $dispatcher,
private MetricsCollector $metrics
) {
// Register event handlers
$this->dispatcher->listen(
AgentCompletedEvent::class,
[$this->metrics, 'onAgentCompleted']
);
}
public function createSupportAgent(): Agent
{
$config = AgentTemplates::customerSupport();
return $this->factory->createDialogAgent($config->toArray());
}
public function createAnalystAgent(): Agent
{
$config = AgentTemplates::dataAnalyst();
return $this->factory->createReactAgent($config->toArray());
}
public function getMetrics(): array
{
return $this->metrics->getMetrics();
}
}
// Usage
$service = new AgentService($factory, $dispatcher, $metrics);
$agent = $service->createSupportAgent();You're now ready to build world-class AI agents! 🚀
Share your projects, ask questions, and contribute back to the community!
Last Updated: December 2024
Framework Version: 2.0+