34: Prompt Chaining and Workflows

Chapter 34: Prompt Chaining and Workflows
Section titled “Chapter 34: Prompt Chaining and Workflows”Overview
Section titled “Overview”Prompt chaining connects multiple AI operations in sequence, where each step’s output becomes the next step’s input. This enables complex workflows that exceed what a single prompt can accomplish, allowing you to build sophisticated AI pipelines with validation, transformation, and refinement at each stage.
This chapter teaches you to build production-ready workflow orchestration systems with sequential processing, conditional branching, loops, error recovery, and state management. You’ll learn to design reusable workflow components, implement retry logic, and optimize multi-step AI pipelines.
What You’ll Build
Section titled “What You’ll Build”By the end of this chapter, you will have created:
- Complete workflow orchestration framework with step definitions, conditional execution, and state management
- Reusable workflow steps including PromptStep, TransformStep, ValidationStep, LoopStep, and ParallelStep
- Production-ready workflow examples for content creation and code review pipelines
- Fluent workflow builder for creating workflows with a clean, chainable API
- Error handling and retry logic with exponential backoff and error recovery
- Workflow composition patterns for building complex multi-step AI pipelines
- State management system that maintains context across workflow steps
Prerequisites
Section titled “Prerequisites”Before starting, ensure you have:
- ✓ Completed Chapters 1-15 (Core API usage)
- ✓ Workflow pattern knowledge for orchestration
- ✓ State machine understanding for flow control
- ✓ Pipeline architecture experience for design
Estimated Time: 120-150 minutes
Verify your setup:
# Check PHP versionphp --version
# Verify Anthropic SDK is installedcomposer show anthropic/anthropic-sdk-php
# Check if API key is setecho $ANTHROPIC_API_KEY | cut -c1-10Quick Start
Section titled “Quick Start”Here’s a quick 5-minute example demonstrating a simple workflow:
<?phpdeclare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Workflow\WorkflowBuilder;use ClaudePhp\ClaudePhp;
// Initialize Claude$claude = new ClaudePhp([ 'apiKey' => $_ENV['ANTHROPIC_API_KEY']]);
// Create a simple 3-step workflow$workflow = (new WorkflowBuilder($claude, 'Quick Start')) ->prompt('analyze', 'Analyze this topic: {topic}', ['temperature' => 0.3]) ->transform('format', function($state) { $analysis = $state->get('analyze'); return "Analysis Summary:\n" . substr($analysis, 0, 200) . '...'; }) ->prompt('summarize', 'Create a brief summary of: {format}') ->build();
// Execute workflow$result = $workflow->execute(['topic' => 'PHP 8.4 Features']);
echo "Status: {$result->status}\n";echo "Duration: " . number_format($result->duration, 2) . "s\n";echo "Summary: " . ($result->output['summarize'] ?? 'N/A') . "\n";Run this example:
export ANTHROPIC_API_KEY=sk-ant-api03-your-key-herephp examples/quick-start-workflow.phpExpected output:
Status: completedDuration: 3.45sSummary: [Generated summary text]Objectives
Section titled “Objectives”By completing this chapter, you will:
- Understand prompt chaining patterns and when to use sequential vs parallel processing
- Build a reusable workflow orchestration framework with step definitions and state management
- Implement conditional execution and branching logic in workflows
- Create specialized workflow steps for prompts, transformations, validation, loops, and parallel tasks
- Design error handling and retry strategies with exponential backoff
- Compose complex workflows from reusable components
- Optimize multi-step AI pipelines for performance and reliability
- Apply workflow patterns to real-world use cases like content creation and code review
Workflow Framework
Section titled “Workflow Framework”<?phpdeclare(strict_types=1);
namespace App\Workflow;
class Workflow{ private array $steps = []; private array $executionLog = []; private WorkflowState $state;
public function __construct( private ClaudePhp\ClaudePhp $claude, private string $workflowId, private string $name ) { $this->state = new WorkflowState(); }
/** * Add a step to the workflow */ public function addStep(WorkflowStep $step): self { $this->steps[] = $step; return $this; }
/** * Execute the workflow */ public function execute(array $initialInput = []): WorkflowResult { $this->state->set('input', $initialInput); $this->state->set('current_step', 0);
$startTime = microtime(true);
try { foreach ($this->steps as $index => $step) { $this->state->set('current_step', $index);
// Check if step should be executed (conditional logic) if (!$this->shouldExecuteStep($step)) { $this->logStep($step, 'skipped', null); continue; }
// Execute step with retry logic $stepResult = $this->executeStepWithRetry($step);
// Update state with step output $this->state->set($step->getOutputKey(), $stepResult->output);
// Log execution $this->logStep($step, $stepResult->status, $stepResult);
// Check if workflow should continue if ($stepResult->status === 'stop') { break; }
// Handle errors if ($stepResult->status === 'error' && !$step->isContinueOnError()) { throw new WorkflowException( "Step '{$step->getName()}' failed: {$stepResult->error}" ); } }
$status = 'completed'; $output = $this->state->getAll();
} catch (\Exception $e) { $status = 'failed'; $output = ['error' => $e->getMessage()]; }
$duration = microtime(true) - $startTime;
return new WorkflowResult( workflowId: $this->workflowId, status: $status, output: $output, executionLog: $this->executionLog, duration: $duration ); }
/** * Execute step with retry logic */ private function executeStepWithRetry(WorkflowStep $step): StepResult { $maxRetries = $step->getMaxRetries(); $lastError = null;
for ($attempt = 0; $attempt <= $maxRetries; $attempt++) { try { if ($attempt > 0) { // Exponential backoff $delay = min(30, pow(2, $attempt - 1)); sleep($delay); }
$context = new StepContext( claude: $this->claude, state: $this->state, attempt: $attempt );
return $step->execute($context);
} catch (\Exception $e) { $lastError = $e->getMessage();
if ($attempt < $maxRetries) { continue; } } }
return new StepResult( status: 'error', output: null, error: $lastError ); }
/** * Check if step should execute based on conditions */ private function shouldExecuteStep(WorkflowStep $step): bool { $condition = $step->getCondition();
if ($condition === null) { return true; }
return $condition($this->state); }
/** * Log step execution */ private function logStep(WorkflowStep $step, string $status, ?StepResult $result): void { $this->executionLog[] = [ 'step' => $step->getName(), 'status' => $status, 'timestamp' => microtime(true), 'output' => $result?->output, 'error' => $result?->error ]; }
public function getState(): WorkflowState { return $this->state; }}Why It Works
Section titled “Why It Works”The Workflow class orchestrates step execution with several key features:
-
State Management: Each workflow maintains a
WorkflowStateobject that stores data between steps. Step outputs are automatically saved using theiroutputKey, making them available to subsequent steps via variable interpolation. -
Conditional Execution: The
shouldExecuteStep()method evaluates each step’s condition closure against the current state. If a condition returnsfalse, the step is skipped and logged. -
Retry Logic:
executeStepWithRetry()implements exponential backoff (1s, 2s, 4s, up to 30s max) for transient failures. Each retry attempt receives aStepContextwith the attempt number. -
Error Handling: Steps can be configured to
continueOnError(), allowing workflows to proceed even if a non-critical step fails. Critical steps will throwWorkflowExceptionand stop execution. -
Execution Logging: Every step execution is logged with status, timestamp, output, and errors. This provides complete observability for debugging and monitoring.
-
Result Tracking: The workflow returns a
WorkflowResultcontaining the final state, execution log, duration, and status, enabling comprehensive workflow analysis.
Workflow Step
Section titled “Workflow Step”<?phpdeclare(strict_types=1);
namespace App\Workflow;
abstract class WorkflowStep{ protected string $name; protected string $outputKey; protected int $maxRetries = 3; protected bool $continueOnError = false; protected ?\Closure $condition = null;
public function __construct(string $name, string $outputKey = null) { $this->name = $name; $this->outputKey = $outputKey ?? $name; }
/** * Execute the step */ abstract public function execute(StepContext $context): StepResult;
/** * Set condition for conditional execution */ public function when(\Closure $condition): self { $this->condition = $condition; return $this; }
/** * Set maximum retry attempts */ public function retry(int $maxRetries): self { $this->maxRetries = $maxRetries; return $this; }
/** * Continue workflow even if this step fails */ public function continueOnError(bool $continue = true): self { $this->continueOnError = $continue; return $this; }
public function getName(): string { return $this->name; }
public function getOutputKey(): string { return $this->outputKey; }
public function getMaxRetries(): int { return $this->maxRetries; }
public function isContinueOnError(): bool { return $this->continueOnError; }
public function getCondition(): ?\Closure { return $this->condition; }}Built-in Workflow Steps
Section titled “Built-in Workflow Steps”This section covers the core step types. Chapter 34 focuses on foundational patterns; for streaming, tool use, and vision workflows, see the integration patterns section below.
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;use App\Workflow\WorkflowState;
class PromptStep extends WorkflowStep{ public function __construct( string $name, private string $prompt, private array $options = [] ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { // Interpolate variables from state into prompt $prompt = $this->interpolatePrompt($this->prompt, $context->state);
$messages = []; if ($this->options['system'] ?? null) { $messages[] = [ 'role' => 'system', 'content' => $this->options['system'] ]; } $messages[] = [ 'role' => 'user', 'content' => $prompt ];
$response = $context->claude->messages()->create([ 'model' => $this->options['model'] ?? 'claude-sonnet-4-5-20250929', 'max_tokens' => $this->options['max_tokens'] ?? 4096, 'temperature' => $this->options['temperature'] ?? 0.7, 'messages' => $messages ]);
return new StepResult( status: 'success', output: $response->content[0]->text ); }
private function interpolatePrompt(string $prompt, WorkflowState $state): string { return preg_replace_callback('/\{(\w+)\}/', function($matches) use ($state) { $key = $matches[1]; $value = $state->get($key);
if (is_array($value)) { return json_encode($value, JSON_PRETTY_PRINT); }
return (string)$value; }, $prompt); }}Why It Works
Section titled “Why It Works”PromptStep executes Claude API calls with state interpolation:
-
Variable Interpolation: The
interpolatePrompt()method uses regex to find{key}placeholders and replaces them with values from the workflow state. Arrays are JSON-encoded for readability. -
State Access: Steps receive a
StepContextcontaining the Claude client and current state, enabling them to access previous step outputs. -
Configurable Options: Model selection, temperature, max tokens, and system prompts can be customized per step, allowing fine-tuned control over each AI interaction.
-
Error Propagation: API exceptions are caught by the workflow’s retry mechanism, enabling automatic recovery from transient failures.
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;use App\Workflow\WorkflowState;
class TransformStep extends WorkflowStep{ public function __construct( string $name, private \Closure $transformer ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { $transformer = $this->transformer; $output = $transformer($context->state);
return new StepResult( status: 'success', output: $output ); }}<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;use App\Workflow\WorkflowState;
class ValidationStep extends WorkflowStep{ public function __construct( string $name, private \Closure $validator, private ?string $errorMessage = null ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { $validator = $this->validator; $isValid = $validator($context->state);
if (!$isValid) { return new StepResult( status: 'error', output: false, error: $this->errorMessage ?? 'Validation failed' ); }
return new StepResult( status: 'success', output: true ); }}<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;use App\Workflow\Workflow;
class LoopStep extends WorkflowStep{ public function __construct( string $name, private array $items, private Workflow $itemWorkflow, private int $maxIterations = 100 ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { $results = []; $items = is_callable($this->items) ? ($this->items)($context->state) : $this->items;
$iterations = min(count($items), $this->maxIterations);
for ($i = 0; $i < $iterations; $i++) { $item = $items[$i];
// Execute sub-workflow for each item $result = $this->itemWorkflow->execute(['item' => $item, 'index' => $i]);
if ($result->status === 'completed') { $results[] = $result->output; } else { return new StepResult( status: 'error', output: $results, error: "Loop failed at iteration {$i}" ); } }
return new StepResult( status: 'success', output: $results ); }}<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;
class ParallelStep extends WorkflowStep{ public function __construct( string $name, private array $steps ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { // Note: True parallel execution would require async PHP or process forking // This is a simplified sequential execution that could be parallelized
$results = [];
foreach ($this->steps as $stepName => $step) { $stepResult = $step->execute($context);
if ($stepResult->status === 'error') { return new StepResult( status: 'error', output: $results, error: "Parallel step '{$stepName}' failed: {$stepResult->error}" ); }
$results[$stepName] = $stepResult->output; }
return new StepResult( status: 'success', output: $results ); }}Why It Works
Section titled “Why It Works”TransformStep enables data manipulation between AI calls:
- Pure Functions: Transformers receive the entire state and return new values, enabling data cleaning, formatting, extraction, and aggregation.
- No API Calls: Transform steps execute synchronously without network calls, making them fast and cost-free.
- State Mutation: The transformer’s output replaces the step’s output key in state, updating the workflow context.
ValidationStep ensures data quality:
- Early Failure: Invalid data stops workflow execution immediately (unless
continueOnError()is set), preventing downstream errors. - Custom Validators: Closures can implement complex validation logic, checking multiple state values and relationships.
- Error Messages: Custom error messages provide context for debugging validation failures.
LoopStep processes collections:
- Sub-workflows: Each item is processed by a separate workflow instance, enabling complex per-item processing pipelines.
- Iteration Limits:
maxIterationsprevents infinite loops and runaway costs. - Failure Handling: If any iteration fails, the loop stops and returns partial results.
ParallelStep executes independent tasks:
- Sequential Execution: This implementation runs steps sequentially. True parallelism requires async PHP (ReactPHP, Swoole) or process forking.
- Early Termination: If any parallel step fails, execution stops immediately (can be modified for partial success).
- Shared Context: All parallel steps share the same state context, enabling coordination when needed.
Workflow Examples
Section titled “Workflow Examples”<?phpdeclare(strict_types=1);
namespace App\Workflow\Workflows;
use App\Workflow\Workflow;use App\Workflow\Steps\PromptStep;use App\Workflow\Steps\ValidationStep;use App\Workflow\Steps\TransformStep;
class ContentCreationWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude, string $topic): Workflow { $workflow = new Workflow($claude, uniqid('workflow_'), 'Content Creation');
// Step 1: Research the topic $workflow->addStep( (new PromptStep('research', <<<PROMPTResearch the topic: {topic}
Provide:1. Key points to cover2. Target audience considerations3. Relevant examples4. Current trendsPROMPT, ['temperature' => 0.3])) ->retry(2) );
// Step 2: Create outline $workflow->addStep( new PromptStep('outline', <<<PROMPTBased on this research:
{research}
Create a detailed outline for an article about: {topic}
Include:- Introduction- Main sections (3-5)- Conclusion- Estimated word count for each sectionPROMPT, ['temperature' => 0.5]) );
// Step 3: Validate outline $workflow->addStep( (new ValidationStep( 'validate_outline', fn($state) => !empty($state->get('outline')) && strlen($state->get('outline')) > 100, 'Outline is too short or empty' ))->continueOnError(false) );
// Step 4: Write first draft $workflow->addStep( new PromptStep('draft', <<<PROMPTWrite a comprehensive article following this outline:
{outline}
Topic: {topic}
Requirements:- Engaging introduction- Well-structured sections- Concrete examples- Clear conclusion- Professional tonePROMPT, ['temperature' => 0.8, 'max_tokens' => 6000]) );
// Step 5: Review and improve $workflow->addStep( new PromptStep('review', <<<PROMPTReview and improve this article draft:
{draft}
Provide:1. Overall assessment2. Specific improvements made3. Final polished version
Focus on:- Clarity and readability- Flow and structure- Grammar and style- CompletenessPROMPT, ['temperature' => 0.4]) );
// Step 6: Extract final article $workflow->addStep( new TransformStep('extract_final', function($state) { $review = $state->get('review');
// Extract the final version from the review if (preg_match('/Final.*?Version:?\s*(.*)/is', $review, $matches)) { return trim($matches[1]); }
return $review; }) );
return $workflow; }}<?phpdeclare(strict_types=1);
namespace App\Workflow\Workflows;
use App\Workflow\Workflow;use App\Workflow\Steps\PromptStep;use App\Workflow\Steps\ValidationStep;
class CodeReviewWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude, string $code, string $language = 'php'): Workflow { $workflow = new Workflow($claude, uniqid('workflow_'), 'Code Review');
// Step 1: Initial code analysis $workflow->addStep( new PromptStep('analyze', <<<PROMPTAnalyze this {language} code:
```php{code}```
Provide:
1. Code structure overview2. Main functionality3. Dependencies identified4. Complexity assessment PROMPT, ['temperature' => 0.2]) );
// Step 2: Security review $workflow->addStep( new PromptStep('security', <<<PROMPT
Perform security review of this {language} code:
```php{code}```
Analysis: {analyze}
Identify:
1. Security vulnerabilities2. Input validation issues3. Authentication/authorization concerns4. Data exposure risks5. Recommendations PROMPT, ['temperature' => 0.2]) );
// Step 3: Performance review $workflow->addStep( new PromptStep('performance', <<<PROMPT
Review performance of this {language} code:
```php{code}```
Identify:
1. Performance bottlenecks2. Inefficient algorithms3. Resource usage concerns4. Optimization opportunities5. Recommendations PROMPT, ['temperature' => 0.2]) );
// Step 4: Best practices review $workflow->addStep( new PromptStep('best_practices', <<<PROMPT
Review best practices for this {language} code:
```php{code}```
Assess:
1. Code style and conventions2. Design patterns usage3. Error handling4. Documentation quality5. Testing considerations6. Recommendations PROMPT, ['temperature' => 0.3]) );
// Step 5: Synthesize final report $workflow->addStep( new PromptStep('report', <<<PROMPT
Create a comprehensive code review report based on:
Security Review:{security}
Performance Review:{performance}
Best Practices Review:{best_practices}
Provide:
1. Executive Summary2. Critical Issues (if any)3. Recommendations (prioritized)4. Overall Code Quality Score (1-10)5. Detailed Findings
Format as a professional code review report.PROMPT, ['temperature' => 0.4]));
return $workflow; }
}Why It Works
Section titled “Why It Works”ContentCreationWorkflow demonstrates a complete content generation pipeline:
- Research Phase: Low temperature (0.3) ensures factual, focused research output.
- Outline Generation: Medium temperature (0.5) balances creativity with structure.
- Validation Gate: Ensures outline quality before proceeding to expensive draft generation.
- Draft Writing: High temperature (0.8) and large token limit (6000) enable creative, comprehensive content.
- Review & Refinement: Lower temperature (0.4) focuses on editing and improvement.
- Extraction: Transform step extracts the final polished version from the review output.
CodeReviewWorkflow shows parallel analysis patterns:
- Multi-Perspective Review: Security, performance, and best practices are analyzed independently, then synthesized.
- Low Temperature: All review steps use low temperature (0.2-0.3) for consistent, objective analysis.
- Synthesis Step: Final report combines all perspectives into a comprehensive review.
- Reusable Pattern: This pattern works for any multi-faceted analysis task (documentation, architecture, etc.).
Workflow Builder (Fluent Interface)
Section titled “Workflow Builder (Fluent Interface)”<?phpdeclare(strict_types=1);
namespace App\Workflow;
use App\Workflow\Steps\PromptStep;use App\Workflow\Steps\TransformStep;use App\Workflow\Steps\ValidationStep;
class WorkflowBuilder{ private Workflow $workflow;
public function __construct( ClaudePhp\ClaudePhp $claude, string $name = 'Custom Workflow' ) { $this->workflow = new Workflow($claude, uniqid('workflow_'), $name); }
/** * Add a prompt step */ public function prompt( string $name, string $prompt, array $options = [] ): self { $this->workflow->addStep(new PromptStep($name, $prompt, $options)); return $this; }
/** * Add a transformation step */ public function transform(string $name, \Closure $transformer): self { $this->workflow->addStep(new TransformStep($name, $transformer)); return $this; }
/** * Add a validation step */ public function validate( string $name, \Closure $validator, ?string $errorMessage = null ): self { $this->workflow->addStep(new ValidationStep($name, $validator, $errorMessage)); return $this; }
/** * Add a conditional step */ public function when(\Closure $condition): ConditionalBuilder { return new ConditionalBuilder($this->workflow, $condition); }
/** * Build and return the workflow */ public function build(): Workflow { return $this->workflow; }}
class ConditionalBuilder{ public function __construct( private Workflow $workflow, private \Closure $condition ) {}
public function then(WorkflowStep $step): Workflow { $step->when($this->condition); $this->workflow->addStep($step); return $this->workflow; }}Why It Works
Section titled “Why It Works”The WorkflowBuilder provides a fluent, chainable API for constructing workflows:
- Method Chaining: Each method returns
$this, enabling readable, sequential workflow definition. - Type Safety: Methods accept typed parameters, providing IDE autocomplete and compile-time checking.
- Conditional Building: The
when()method returns aConditionalBuilderthat applies conditions to subsequent steps. - Immutability: Steps are added to the workflow but the builder itself remains unchanged, enabling reuse.
This pattern makes workflow creation more intuitive than manually instantiating and configuring steps.
Advanced Workflow Step Types
Section titled “Advanced Workflow Step Types”While the core framework covers basic operations, workflows can be extended with specialized steps for advanced features covered in other chapters. Here are the patterns for integration:
Tool Use Workflows
Section titled “Tool Use Workflows”Integrate tool calling (Chapter 12) into workflows:
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;
class ToolUseStep extends WorkflowStep{ public function __construct( string $name, private string $prompt, private array $tools = [], private array $options = [] ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { // Build tool definitions from the tools array $toolDefinitions = array_map(function($tool) { return [ 'name' => $tool['name'], 'description' => $tool['description'], 'input_schema' => $tool['schema'] ]; }, $this->tools);
// Call Claude with tools available $response = $context->claude->messages()->create([ 'model' => $this->options['model'] ?? 'claude-sonnet-4-5-20250929', 'max_tokens' => $this->options['max_tokens'] ?? 4096, 'tools' => $toolDefinitions, 'messages' => [[ 'role' => 'user', 'content' => $this->prompt ]] ]);
// Handle tool calls $toolResults = []; foreach ($response->content as $block) { if ($block->type === 'tool_use') { // Execute the tool $result = $this->executeTool($block->name, $block->input); $toolResults[] = $result; } }
return new StepResult( status: 'success', output: [ 'response' => $response->content, 'tool_results' => $toolResults ] ); }
private function executeTool(string $toolName, array $input): mixed { // This should be implemented based on your available tools // For now, return a placeholder return "Tool '{$toolName}' executed with input: " . json_encode($input); }}Vision Workflows
Section titled “Vision Workflows”Integrate image processing (Chapter 13) into workflows:
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;
class VisionStep extends WorkflowStep{ public function __construct( string $name, private string $prompt, private array $images = [], // Array of image URLs or base64 data private array $options = [] ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { // Build content with image blocks $content = [];
// Add images foreach ($this->images as $image) { if (str_starts_with($image, 'http')) { // URL-based image $content[] = [ 'type' => 'image', 'source' => [ 'type' => 'url', 'url' => $image ] ]; } else { // Base64-encoded image $content[] = [ 'type' => 'image', 'source' => [ 'type' => 'base64', 'media_type' => $this->options['media_type'] ?? 'image/jpeg', 'data' => $image ] ]; } }
// Add prompt text $content[] = [ 'type' => 'text', 'text' => $this->prompt ];
$response = $context->claude->messages()->create([ 'model' => $this->options['model'] ?? 'claude-sonnet-4-5-20250929', 'max_tokens' => $this->options['max_tokens'] ?? 4096, 'messages' => [[ 'role' => 'user', 'content' => $content ]] ]);
return new StepResult( status: 'success', output: $response->content[0]->text ); }}Streaming Workflows
Section titled “Streaming Workflows”For real-time streaming in long-running workflows:
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;
class StreamingStep extends WorkflowStep{ public function __construct( string $name, private string $prompt, private ?callable $onChunk = null, // Callback for each chunk private array $options = [] ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { $fullResponse = '';
// Stream the response $stream = $context->claude->messages()->stream([ 'model' => $this->options['model'] ?? 'claude-sonnet-4-5-20250929', 'max_tokens' => $this->options['max_tokens'] ?? 4096, 'messages' => [[ 'role' => 'user', 'content' => $this->prompt ]] ]);
foreach ($stream as $event) { if ($event->type === 'content_block_delta' && $event->delta->type === 'text_delta') { $chunk = $event->delta->text; $fullResponse .= $chunk;
// Call callback if provided if ($this->onChunk) { ($this->onChunk)($chunk); } } }
return new StepResult( status: 'success', output: $fullResponse ); }}Structured Output Workflows
Section titled “Structured Output Workflows”For schema-validated responses (Chapter 15):
<?phpdeclare(strict_types=1);
namespace App\Workflow\Steps;
use App\Workflow\WorkflowStep;use App\Workflow\StepContext;use App\Workflow\StepResult;
class StructuredStep extends WorkflowStep{ public function __construct( string $name, private string $prompt, private array $schema, private array $options = [] ) { parent::__construct($name); }
public function execute(StepContext $context): StepResult { $response = $context->claude->messages()->create([ 'model' => $this->options['model'] ?? 'claude-sonnet-4-5-20250929', 'max_tokens' => $this->options['max_tokens'] ?? 4096, 'messages' => [[ 'role' => 'user', 'content' => $this->prompt ]] ]);
// Use Extended Thinking for complex structured outputs $output = null; foreach ($response->content as $block) { if (property_exists($block, 'text')) { $output = json_decode($block->text, true); break; } }
return new StepResult( status: 'success', output: $output ); }}Complete Example
Section titled “Complete Example”<?phpdeclare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Workflow\WorkflowBuilder;use App\Workflow\Workflows\ContentCreationWorkflow;use App\Workflow\Workflows\CodeReviewWorkflow;
// Initialize Claudeuse ClaudePhp\ClaudePhp;
$claude = new ClaudePhp([ 'apiKey' => $_ENV['ANTHROPIC_API_KEY']]);
echo "=== Workflow Orchestration Demo ===\n\n";
// Example 1: Content Creation Workflowecho "Example 1: Content Creation Workflow\n";echo str_repeat('-', 80) . "\n\n";
$contentWorkflow = ContentCreationWorkflow::create($claude, 'Vector Databases in PHP');
$result = $contentWorkflow->execute([ 'topic' => 'Vector Databases in PHP']);
echo "Status: {$result->status}\n";echo "Duration: " . number_format($result->duration, 2) . "s\n";echo "Steps executed: " . count($result->executionLog) . "\n\n";
echo "Final Article:\n";echo $result->output['extract_final'] ?? 'N/A';echo "\n\n";
// Example 2: Code Review Workflowecho "\nExample 2: Code Review Workflow\n";echo str_repeat('-', 80) . "\n\n";
$codeToReview = <<<'PHP'function getUserData($id) { $db = new PDO('mysql:host=localhost;dbname=app', 'root', ''); $result = $db->query("SELECT * FROM users WHERE id = " . $id); return $result->fetch();}PHP;
$reviewWorkflow = CodeReviewWorkflow::create($claude, $codeToReview, 'php');
$result = $reviewWorkflow->execute([ 'code' => $codeToReview, 'language' => 'php']);
echo "Status: {$result->status}\n";echo "Duration: " . number_format($result->duration, 2) . "s\n\n";
echo "Code Review Report:\n";echo $result->output['report'] ?? 'N/A';echo "\n\n";
// Example 3: Custom Workflow with Fluent Builderecho "\nExample 3: Custom Workflow (Fluent Builder)\n";echo str_repeat('-', 80) . "\n\n";
$customWorkflow = (new WorkflowBuilder($claude, 'Email Summary')) ->prompt('extract_emails', 'Extract all email addresses from: {text}', ['temperature' => 0.1]) ->validate( 'check_emails', fn($state) => !empty($state->get('extract_emails')), 'No emails found' ) ->transform('count_emails', function($state) { $emails = $state->get('extract_emails'); preg_match_all('/[\w\.-]+@[\w\.-]+/', $emails, $matches); return count($matches[0]); }) ->prompt('summarize', 'Summarize: Found {count_emails} email addresses') ->build();
$result = $customWorkflow->execute([ 'text' => 'Contact us at info@example.com or support@example.com']);
echo "Result: " . json_encode($result->output, JSON_PRETTY_PRINT) . "\n";Why It Works
Section titled “Why It Works”This complete example demonstrates three workflow patterns:
-
Pre-built Workflows:
ContentCreationWorkflowandCodeReviewWorkflowshow how to encapsulate common patterns as reusable classes. -
Fluent Builder: The custom workflow example shows how
WorkflowBuilderenables rapid prototyping and ad-hoc workflow creation. -
State Flow: Notice how each step’s output becomes available to subsequent steps via
{variable}interpolation. -
Error Handling: The workflow framework handles retries, validation failures, and API errors automatically, returning structured results.
-
Observability: Execution logs provide complete visibility into step execution, timing, and outcomes.
Best Practices
Section titled “Best Practices”Workflow Design
Section titled “Workflow Design”Keep Steps Focused: Each step should have a single, well-defined responsibility. If a step does multiple things, consider splitting it.
Use Appropriate Temperatures:
- Low (0.1-0.3): Factual extraction, analysis, validation
- Medium (0.4-0.6): Balanced creativity and accuracy
- High (0.7-1.0): Creative generation, brainstorming
Validate Early: Place validation steps as early as possible to fail fast and avoid wasting API calls.
Limit Token Usage: Set max_tokens appropriately for each step. Large limits increase cost and latency.
Handle Errors Gracefully: Use continueOnError() for non-critical steps, but fail fast for critical validation.
State Management
Section titled “State Management”Use Descriptive Keys: Choose clear, consistent naming for state keys (e.g., research_output not r1).
Avoid State Pollution: Remove temporary data from state between steps to keep it manageable.
Document State Shape: Comment on what keys each step expects and produces.
Validate State Access: Check that required state keys exist before accessing them in prompts.
Performance Optimization
Section titled “Performance Optimization”Parallelize Independent Steps: Use ParallelStep for steps that don’t depend on each other.
Cache Expensive Operations: Store results of expensive steps in state for reuse.
Optimize Prompt Sizes: Keep prompts focused and remove unnecessary context to reduce token usage.
Profile Execution: Use execution logs to identify slow steps and optimize them.
Error Handling
Section titled “Error Handling”Set Appropriate Retries: Critical steps should retry more (3-5), optional steps less (1-2).
Use Exponential Backoff: The built-in backoff prevents overwhelming APIs during outages.
Log Everything: Execution logs are invaluable for debugging production issues.
Fail Fast: Don’t continue workflows with invalid data—validate early and fail immediately.
Data Structures
Section titled “Data Structures”<?phpdeclare(strict_types=1);
namespace App\Workflow;
/** * WorkflowState manages data shared between workflow steps. * Keys are arbitrary strings, values can be any PHP type. */class WorkflowState{ private array $data = [];
public function set(string $key, mixed $value): void { $this->data[$key] = $value; }
public function get(string $key, mixed $default = null): mixed { return $this->data[$key] ?? $default; }
public function has(string $key): bool { return isset($this->data[$key]); }
public function getAll(): array { return $this->data; }}
/** * StepContext provides execution context to workflow steps. * Contains the Claude client, current state, and retry attempt number. */readonly class StepContext{ public function __construct( public ClaudePhp\ClaudePhp $claude, public WorkflowState $state, public int $attempt = 0 ) {}}
/** * StepResult represents the outcome of a workflow step execution. * Status can be: 'success', 'error', 'stop', or 'skipped'. */readonly class StepResult{ public function __construct( public string $status, public mixed $output, public ?string $error = null ) {}}
/** * WorkflowResult contains the final outcome of workflow execution. * Status can be: 'completed', 'failed', or 'stopped'. * Output contains all state values at completion. */readonly class WorkflowResult{ public function __construct( public string $workflowId, public string $status, public array $output, public array $executionLog, public float $duration ) {}}
/** * WorkflowException is thrown when workflow execution fails critically. * Use this to distinguish workflow errors from step-level errors. */class WorkflowException extends \Exception {}Why It Works
Section titled “Why It Works”These data structures provide type safety and clear contracts:
-
WorkflowState: Simple key-value store with type hints for common operations. The
mixedtype allows flexibility while maintaining clarity. -
StepContext: Readonly class ensures immutability during step execution, preventing accidental state mutation.
-
StepResult: Standardized result format enables consistent error handling and logging across all step types.
-
WorkflowResult: Comprehensive result object provides everything needed for monitoring, debugging, and downstream processing.
-
WorkflowException: Custom exception type enables specific error handling for workflow failures vs. step failures.
Integrating with Other Claude Features
Section titled “Integrating with Other Claude Features”Chapter 34 focuses on workflow orchestration fundamentals. These patterns show how to integrate workflows with advanced features from other chapters:
Workflows with RAG (Chapter 31)
Section titled “Workflows with RAG (Chapter 31)”Combine retrieval steps with Claude calls:
<?php// Add a retrieval step that queries a vector database$workflow->addStep( (new PromptStep('retrieve_context', 'Search knowledge base for: {user_query}', ['temperature' => 0.3])) ->retry(2));
// Then use retrieved context in analysis$workflow->addStep( new PromptStep('analyze', <<<PROMPTBased on this context:
{retrieve_context}
Answer the user's question: {user_query}
Be specific and cite sources when relevant.PROMPT));Workflows with Tool Use (Chapter 12)
Section titled “Workflows with Tool Use (Chapter 12)”Execute tools between prompt steps:
<?php// Step 1: Claude decides which tools to use$workflow->addStep(new ToolUseStep( 'decide_action', 'Given this request: {request}, decide what to do', ['fetch_data', 'search_database', 'calculate_result']));
// Step 2: Process the tool results$workflow->addStep(new PromptStep( 'synthesize', 'Using these results: {decide_action}, provide final answer'));Workflows with Vision (Chapter 13)
Section titled “Workflows with Vision (Chapter 13)”Process images and documents in pipelines:
<?php// Extract information from documents$workflow->addStep(new VisionStep( 'analyze_document', 'Extract structured data: {extraction_prompt}', [$documentImageUrl] // Can be URLs or base64));
// Then process the extracted data$workflow->addStep(new PromptStep( 'classify', 'Classify this data: {analyze_document}'));Workflows with Streaming (Chapter 06)
Section titled “Workflows with Streaming (Chapter 06)”Stream responses for real-time UX:
<?php// Use streaming for long-running analysis$workflow->addStep(new StreamingStep( 'stream_analysis', 'Analyze in detail: {data}', onChunk: function($chunk) { // Send chunk to WebSocket client in real time echo $chunk; }));Workflows with Caching (Chapter 18)
Section titled “Workflows with Caching (Chapter 18)”Optimize costs in repeated workflows:
<?php// Pre-cache expensive prompts using a transform step$workflow->addStep(new TransformStep('setup_cache', function($state) { // Cache this context for reuse $cachedContext = 'long_system_prompt_or_context...'; return $cachedContext;}));
// Then use cached context in multiple steps$workflow->addStep( new PromptStep('analysis1', '{setup_cache}\n\nAnalyze: {data}'));$workflow->addStep( new PromptStep('analysis2', '{setup_cache}\n\nClassify: {data}'));Workflows with Batch Processing (Chapter 39)
Section titled “Workflows with Batch Processing (Chapter 39)”Process large datasets efficiently:
<?php// Use loop step for batch processing$workflow->addStep(new LoopStep( 'batch_process', fn($state) => $state->get('items'), // Large array of items $this->createItemProcessingWorkflow(), maxIterations: 1000 // Prevent runaway costs));
// Summarize results$workflow->addStep(new PromptStep( 'summarize', 'Summarize these results: {batch_process}'));Workflows with Extended Thinking
Section titled “Workflows with Extended Thinking”For complex reasoning in workflows:
<?php// Use thinking for complex multi-step reasoning$workflow->addStep(new StructuredStep( 'reason', 'Solve this complex problem: {problem}', schema: ['type' => 'object', 'properties' => [...]], options: ['thinking_budget' => 5000]));Scaling Workflows
Section titled “Scaling Workflows”Queue-Based Execution (Chapter 19)
Section titled “Queue-Based Execution (Chapter 19)”For long-running workflows, queue them for background processing:
<?php// Laravel example - dispatch workflow to queue$workflow = ContentCreationWorkflow::create($claude, 'AI Integration Patterns');
// Dispatch to queuedispatch(function() use ($workflow) { $result = $workflow->execute(['topic' => 'Advanced Patterns']); // Store result in database WorkflowResult::create([ 'workflow_id' => $result->workflowId, 'status' => $result->status, 'output' => $result->output, 'duration' => $result->duration ]);})->onQueue('workflows');WebSocket Streaming (Chapter 20)
Section titled “WebSocket Streaming (Chapter 20)”Stream workflow progress to connected clients:
<?php// Broadcast workflow progress in real-time$workflow = new Workflow($claude, uniqid(), 'Real-time Workflow');
$workflow->addStep(new StreamingStep( 'stream_content', 'Generate: {topic}', onChunk: function($chunk) use ($clientId) { // Stream to WebSocket client broadcast(new WorkflowChunkStreamed($clientId, $chunk)); }));
$result = $workflow->execute(['topic' => 'AI Architecture']);broadcast(new WorkflowCompleted($clientId, $result));Monitoring Workflows (Chapter 37)
Section titled “Monitoring Workflows (Chapter 37)”Track workflow execution and performance:
<?php// Log workflow metrics$result = $workflow->execute(['topic' => 'Advanced Techniques']);
Log::info('Workflow execution', [ 'workflow_id' => $result->workflowId, 'status' => $result->status, 'duration' => $result->duration, 'steps' => count($result->executionLog), 'step_timings' => array_map(fn($log) => [ 'step' => $log['step'], 'status' => $log['status'], 'timestamp' => $log['timestamp'] ], $result->executionLog)]);
// Alert on slow workflowsif ($result->duration > 60) { Log::warning('Slow workflow detected', ['workflow_id' => $result->workflowId]);}Cost Optimization (Chapter 39)
Section titled “Cost Optimization (Chapter 39)”Track and optimize workflow costs:
<?php// Calculate workflow costs based on model usageclass WorkflowCostCalculator{ private array $modelPricing = [ 'claude-haiku-4-5-20251001' => 0.80 / 1_000_000, // per input token 'claude-sonnet-4-5' => 3.00 / 1_000_000, 'claude-opus-4-1' => 15.00 / 1_000_000, ];
public function calculateCost(WorkflowResult $result): float { $totalCost = 0;
foreach ($result->executionLog as $log) { if ($log['status'] === 'success' && isset($log['output'])) { // Estimate tokens from response $tokens = strlen(json_encode($log['output'])) / 4; // Rough estimate $model = 'claude-sonnet-4-5'; // Track model per step $totalCost += $tokens * $this->modelPricing[$model]; } }
return $totalCost; }}Memory and Persistence (Chapter Memory Tool - Beta)
Section titled “Memory and Persistence (Chapter Memory Tool - Beta)”Workflows can leverage the Memory Tool for cross-workflow context:
<?php// Workflows can accumulate memory across executionsclass WorkflowWithMemory{ public function __construct( private ClaudePhp\ClaudePhp $claude, private string $userId ) {}
public function executeWithMemory(array $input): WorkflowResult { $workflow = new Workflow($this->claude, uniqid(), 'With Memory');
// Retrieve user's workflow memory $workflow->addStep(new PromptStep( 'load_context', 'What context should I know about {user_id}?' ));
// Main workflow steps using context $workflow->addStep(new PromptStep( 'process', 'With this context: {load_context}\n\nProcess: {request}' ));
// Store insights in memory for next time $workflow->addStep(new TransformStep('save_memory', function($state) { return 'Learned: ' . json_encode($state->get('process')); }));
return $workflow->execute(array_merge($input, ['user_id' => $this->userId])); }}Key Takeaways
Section titled “Key Takeaways”- ✓ Prompt chaining breaks complex tasks into manageable steps
- ✓ Each step’s output feeds into the next step’s input
- ✓ Conditional execution enables dynamic workflow paths
- ✓ Retry logic handles transient failures automatically
- ✓ Validation steps ensure data quality at each stage
- ✓ Transform steps enable data manipulation between prompts
- ✓ Loop steps process collections of items
- ✓ Parallel steps execute independent tasks concurrently
- ✓ Workflow composition creates reusable patterns
- ✓ State management maintains context across steps
Exercises
Section titled “Exercises”Exercise 1: Email Processing Workflow
Section titled “Exercise 1: Email Processing Workflow”Build a workflow that processes emails through multiple stages:
<?phpclass EmailProcessingWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude): Workflow { // TODO: Create workflow that: // 1. Extracts key information from email // 2. Categorizes email (support, sales, spam, etc.) // 3. Validates that critical fields are present // 4. Generates response draft if needed // 5. Routes to appropriate team/workflow
// Use WorkflowBuilder for fluent API }}Validation: Test with sample emails and verify each step produces expected output.
Exercise 2: Document Analysis Pipeline
Section titled “Exercise 2: Document Analysis Pipeline”Create a multi-stage document analysis workflow:
<?phpclass DocumentAnalysisWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude): Workflow { // TODO: Build workflow that: // 1. Extracts text from document (PDF, Word, etc.) // 2. Summarizes key points // 3. Identifies entities (people, organizations, dates) // 4. Extracts structured data (tables, lists) // 5. Generates analysis report // 6. Validates report completeness
// Include conditional steps based on document type }}Validation: Process different document types and verify structured output.
Exercise 3: Conditional Workflow with Branching
Section titled “Exercise 3: Conditional Workflow with Branching”Implement a workflow with conditional branching:
<?phpclass ConditionalWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude): Workflow { // TODO: Create workflow that: // 1. Analyzes input to determine complexity // 2. Branches based on complexity: // - Simple: Single-step processing // - Medium: Two-step refinement // - Complex: Multi-agent collaboration // 3. Validates output based on branch taken // 4. Logs decision path for debugging
// Use when() conditions for branching }}Validation: Test with inputs of varying complexity and verify correct branch execution.
Exercise 4: Workflow with Error Recovery
Section titled “Exercise 4: Workflow with Error Recovery”Build a robust workflow with comprehensive error handling:
<?phpclass ResilientWorkflow{ public static function create(ClaudePhp\ClaudePhp $claude): Workflow { // TODO: Implement workflow that: // 1. Attempts primary processing path // 2. Falls back to alternative path on failure // 3. Implements circuit breaker pattern // 4. Logs all errors for analysis // 5. Provides graceful degradation
// Use retry() and continueOnError() strategically }}Validation: Simulate failures and verify recovery mechanisms work correctly.
Troubleshooting
Section titled “Troubleshooting”Workflow hangs or times out?
- Check for infinite loops in conditional logic
- Verify maxIterations is set for LoopStep
- Review exponential backoff delays (may be too long)
- Check if parallel steps are blocking each other
- Monitor state size (large state can slow execution)
Steps not executing in expected order?
- Verify step conditions are not preventing execution
- Check that previous step outputs are available in state
- Review conditional logic in
shouldExecuteStep() - Ensure step dependencies are correctly ordered
- Check execution log for skipped steps
State not persisting between steps?
- Verify output keys match state keys being accessed
- Check that step results are being saved to state
- Review state key naming conventions
- Ensure state is not being reset between steps
- Check for key collisions overwriting values
Retry logic not working?
- Verify maxRetries is set correctly on steps
- Check that exceptions are being caught properly
- Review exponential backoff calculation
- Ensure retry conditions are appropriate
- Check if errors are transient vs permanent
Workflow performance issues?
- Consider parallelizing independent steps
- Review prompt sizes (large prompts slow execution)
- Optimize state management (remove unused data)
- Cache frequently used intermediate results
- Profile individual step execution times
Conditional steps always executing?
- Verify condition closures have correct logic
- Check that state values exist before evaluation
- Review condition return type (must be bool)
- Ensure state keys match what conditions expect
- Test conditions independently before adding to workflow
Further Reading
Section titled “Further Reading”- Official PHP SDK Documentation — The official Anthropic PHP SDK on GitHub
- Claude-PHP-SDK — Community resources and examples for Claude with PHP
- Anthropic API Documentation — Complete API reference and guides
- PHP SDK Composer Package — Official package on Packagist
Wrap-up
Section titled “Wrap-up”Congratulations! You’ve mastered prompt chaining and workflow orchestration. In this chapter, you’ve:
- ✓ Built a complete workflow framework with step definitions, state management, and execution engine
- ✓ Created reusable workflow steps for prompts, transformations, validation, loops, and parallel processing
- ✓ Implemented error handling with retry logic, exponential backoff, and graceful failure recovery
- ✓ Designed conditional execution that enables dynamic workflow paths based on state
- ✓ Composed complex workflows from simple, reusable components
- ✓ Applied workflow patterns to real-world use cases like content creation and code review
- ✓ Optimized workflows for performance, reliability, and maintainability
Workflow orchestration is a powerful pattern for building sophisticated AI applications. By breaking complex tasks into manageable steps, you can create systems that are easier to understand, debug, and maintain. The patterns you’ve learned here form the foundation for building production-ready AI pipelines.
Further Reading
Section titled “Further Reading”- Workflow Patterns — Comprehensive catalog of workflow patterns and anti-patterns
- State Machine Design Patterns — State pattern for workflow state management
- Retry Pattern — Best practices for implementing retry logic
- Pipeline Architecture — Martin Fowler’s guide to pipeline patterns
- PHP Design Patterns — Design patterns applicable to workflow systems
- Anthropic API Documentation — Official Claude API reference for building workflow steps
Continue to Chapter 35: Fine-tuning Strategies to learn when and how to fine-tune models.
💻 Code Samples
Section titled “💻 Code Samples”All code examples from this chapter are available in the GitHub repository:
Clone and run locally:
git clone https://github.com/dalehurley/codewithphp.gitcd codewithphp/code/claude-php/chapter-34composer installexport ANTHROPIC_API_KEY="sk-ant-your-key-here"php examples/workflow-demo.php