claude-php/claude-php-sdk-laravel

Laravel integration for the Claude PHP SDK - Anthropic Claude API

Fund package maintenance!
claude-php

Installs: 4

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/claude-php/claude-php-sdk-laravel

v1.0.1 2025-11-26 22:07 UTC

This package is auto-updated.

Last update: 2025-11-26 22:10:51 UTC


README

Latest Version Total Downloads License PHP Version Laravel Version

The official Laravel integration for the Claude PHP SDK, providing seamless access to Anthropic's Claude API in your Laravel applications.

Requirements

  • PHP 8.2+
  • Laravel 11.x or 12.x

Installation

Install the package via Composer:

composer require claude-php/claude-php-sdk-laravel

The package will automatically register the service provider and facade.

Configuration

Publish the configuration file:

php artisan vendor:publish --tag=claude-config

Add your Anthropic API key to your .env file:

ANTHROPIC_API_KEY=your-api-key-here

Available Configuration Options

Option Environment Variable Default Description
api_key ANTHROPIC_API_KEY null Your Anthropic API key
base_url ANTHROPIC_BASE_URL https://api.anthropic.com/v1 API base URL
timeout ANTHROPIC_TIMEOUT 30.0 Request timeout in seconds
max_retries ANTHROPIC_MAX_RETRIES 2 Maximum retry attempts
headers - [] Custom headers for requests

📚 More Examples & Tutorials

This Laravel package wraps the Claude PHP SDK. The main SDK contains 80+ examples and 15 comprehensive tutorials covering advanced topics like:

  • 🤖 Agentic Patterns: ReAct, Chain-of-Thought, Tree-of-Thoughts, Plan-and-Execute
  • 🔧 Tool Use: Function calling, Computer Use, Bash tools, MCP integration
  • 🧠 Extended Thinking: Deep reasoning with thinking budgets
  • 📄 Document Processing: PDFs, images, citations
  • âš¡ Streaming: Real-time responses with event handling
  • 📦 Batch Processing: High-volume request handling

Using SDK Examples with Laravel

All examples in the main SDK can be adapted for Laravel by replacing the client initialization with the Facade:

SDK Example (standalone):

use ClaudePhp\ClaudePhp;

$client = new ClaudePhp(apiKey: $_ENV['ANTHROPIC_API_KEY']);
$response = $client->messages()->create([...]);

Laravel (using Facade):

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([...]);

Laravel (using Dependency Injection):

use ClaudePhp\ClaudePhp;

public function __construct(private ClaudePhp $claude) {}

// Then use: $this->claude->messages()->create([...]);

👉 Browse Examples | Read Tutorials

Usage Examples

Basic Message

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        ['role' => 'user', 'content' => 'Hello, Claude!']
    ]
]);

echo $response['content'][0]['text'];

Using in a Controller

<?php

namespace App\Http\Controllers;

use ClaudePhp\ClaudePhp;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class ChatController extends Controller
{
    public function __construct(
        private ClaudePhp $claude
    ) {}

    public function chat(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'message' => 'required|string|max:10000',
        ]);

        $response = $this->claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 1024,
            'messages' => [
                ['role' => 'user', 'content' => $validated['message']]
            ]
        ]);

        return response()->json([
            'response' => $response['content'][0]['text'],
            'usage' => $response['usage']
        ]);
    }
}

System Prompts

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'system' => 'You are a helpful assistant that speaks like a pirate. Always end responses with "Arrr!"',
    'messages' => [
        ['role' => 'user', 'content' => 'What is Laravel?']
    ]
]);

Multi-Turn Conversations

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        ['role' => 'user', 'content' => 'My name is Alice.'],
        ['role' => 'assistant', 'content' => 'Hello Alice! Nice to meet you. How can I help you today?'],
        ['role' => 'user', 'content' => 'What is my name?']
    ]
]);

// Response: "Your name is Alice..."

Streaming Responses

use ClaudePhp\Laravel\Facades\Claude;

$stream = Claude::messages()->stream([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        ['role' => 'user', 'content' => 'Tell me a story about a brave knight']
    ]
]);

foreach ($stream as $event) {
    $type = $event['type'] ?? '';

    if ($type === 'content_block_delta') {
        echo $event['delta']['text'] ?? '';
        flush();
    }
}

Streaming with Laravel Response

use ClaudePhp\Laravel\Facades\Claude;
use Symfony\Component\HttpFoundation\StreamedResponse;

public function streamChat(Request $request): StreamedResponse
{
    return response()->stream(function () use ($request) {
        $stream = Claude::messages()->stream([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'messages' => [
                ['role' => 'user', 'content' => $request->input('message')]
            ]
        ]);

        foreach ($stream as $event) {
            if (($event['type'] ?? '') === 'content_block_delta') {
                echo "data: " . json_encode(['text' => $event['delta']['text'] ?? '']) . "\n\n";
                ob_flush();
                flush();
            }
        }
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'X-Accel-Buffering' => 'no',
    ]);
}

Vision - Analyzing Images

use ClaudePhp\Laravel\Facades\Claude;

// From URL
$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        [
            'role' => 'user',
            'content' => [
                [
                    'type' => 'image',
                    'source' => [
                        'type' => 'url',
                        'url' => 'https://example.com/image.jpg'
                    ]
                ],
                [
                    'type' => 'text',
                    'text' => 'What do you see in this image?'
                ]
            ]
        ]
    ]
]);

// From base64
$imageData = base64_encode(file_get_contents(storage_path('app/photo.jpg')));

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        [
            'role' => 'user',
            'content' => [
                [
                    'type' => 'image',
                    'source' => [
                        'type' => 'base64',
                        'media_type' => 'image/jpeg',
                        'data' => $imageData
                    ]
                ],
                [
                    'type' => 'text',
                    'text' => 'Describe this image in detail.'
                ]
            ]
        ]
    ]
]);

PDF Document Analysis

use ClaudePhp\Laravel\Facades\Claude;

$pdfData = base64_encode(file_get_contents(storage_path('app/document.pdf')));

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 4096,
    'messages' => [
        [
            'role' => 'user',
            'content' => [
                [
                    'type' => 'document',
                    'source' => [
                        'type' => 'base64',
                        'media_type' => 'application/pdf',
                        'data' => $pdfData
                    ]
                ],
                [
                    'type' => 'text',
                    'text' => 'Summarize the key points of this document.'
                ]
            ]
        ]
    ]
]);

Tool Use (Function Calling)

use ClaudePhp\Laravel\Facades\Claude;

$tools = [
    [
        'name' => 'get_weather',
        'description' => 'Get the current weather in a given location',
        'input_schema' => [
            'type' => 'object',
            'properties' => [
                'location' => [
                    'type' => 'string',
                    'description' => 'City and country, e.g. London, UK'
                ],
                'unit' => [
                    'type' => 'string',
                    'enum' => ['celsius', 'fahrenheit'],
                    'description' => 'Temperature unit'
                ]
            ],
            'required' => ['location']
        ]
    ],
    [
        'name' => 'search_products',
        'description' => 'Search for products in the catalog',
        'input_schema' => [
            'type' => 'object',
            'properties' => [
                'query' => ['type' => 'string', 'description' => 'Search query'],
                'category' => ['type' => 'string', 'description' => 'Product category'],
                'max_price' => ['type' => 'number', 'description' => 'Maximum price']
            ],
            'required' => ['query']
        ]
    ]
];

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'tools' => $tools,
    'messages' => [
        ['role' => 'user', 'content' => "What's the weather in Tokyo?"]
    ]
]);

// Check if Claude wants to use a tool
foreach ($response['content'] as $block) {
    if ($block['type'] === 'tool_use') {
        $toolName = $block['name'];
        $toolInput = $block['input'];
        $toolUseId = $block['id'];

        // Execute your tool and get result
        $result = match($toolName) {
            'get_weather' => getWeather($toolInput['location'], $toolInput['unit'] ?? 'celsius'),
            'search_products' => searchProducts($toolInput),
            default => ['error' => 'Unknown tool']
        };

        // Continue conversation with tool result
        $followUp = Claude::messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 1024,
            'tools' => $tools,
            'messages' => [
                ['role' => 'user', 'content' => "What's the weather in Tokyo?"],
                ['role' => 'assistant', 'content' => $response['content']],
                [
                    'role' => 'user',
                    'content' => [
                        [
                            'type' => 'tool_result',
                            'tool_use_id' => $toolUseId,
                            'content' => json_encode($result)
                        ]
                    ]
                ]
            ]
        ]);
    }
}

Extended Thinking (Deep Reasoning)

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 16000,
    'thinking' => [
        'type' => 'enabled',
        'budget_tokens' => 10000  // Tokens allocated for reasoning
    ],
    'messages' => [
        ['role' => 'user', 'content' => 'Prove that there are infinitely many prime numbers.']
    ]
]);

foreach ($response['content'] as $block) {
    if ($block['type'] === 'thinking') {
        // Claude's internal reasoning process
        echo "Thinking: " . substr($block['thinking'], 0, 500) . "...\n\n";
    } elseif ($block['type'] === 'text') {
        // Final answer
        echo "Answer: " . $block['text'];
    }
}

Structured Output (JSON Mode)

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        [
            'role' => 'user',
            'content' => 'Extract the following info as JSON:
                Name: John Smith
                Email: john@example.com
                Age: 30
                Skills: PHP, Laravel, JavaScript

                Return a JSON object with keys: name, email, age, skills (as array)'
        ]
    ]
]);

$data = json_decode($response['content'][0]['text'], true);

Prompt Caching

use ClaudePhp\Laravel\Facades\Claude;

// Cache large system prompts or context for efficiency
$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'system' => [
        [
            'type' => 'text',
            'text' => $largeSystemPrompt, // Your extensive instructions
            'cache_control' => ['type' => 'ephemeral']
        ]
    ],
    'messages' => [
        ['role' => 'user', 'content' => 'Process this request...']
    ]
]);

// Subsequent requests with same cached content are faster and cheaper

Listing Available Models

use ClaudePhp\Laravel\Facades\Claude;

$models = Claude::models()->list();

foreach ($models['data'] as $model) {
    echo "Model: {$model['id']}\n";
    echo "  Display Name: {$model['display_name']}\n";
    echo "  Created: {$model['created_at']}\n\n";
}

Async Operations

use ClaudePhp\Laravel\Facades\Claude;

// Get the async proxy for concurrent operations
$async = Claude::async();

// Create multiple requests concurrently
$promise1 = $async->messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [['role' => 'user', 'content' => 'Summarize quantum physics']]
]);

$promise2 = $async->messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [['role' => 'user', 'content' => 'Explain machine learning']]
]);

// Wait for both to complete
$response1 = $promise1->await();
$response2 = $promise2->await();

Beta Features - Message Batches

use ClaudePhp\Laravel\Facades\Claude;

// Create a batch of requests for high-volume processing
$batch = Claude::beta()->messages()->batches()->create([
    'requests' => [
        [
            'custom_id' => 'request-1',
            'params' => [
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 1024,
                'messages' => [
                    ['role' => 'user', 'content' => 'Translate to French: Hello world']
                ]
            ]
        ],
        [
            'custom_id' => 'request-2',
            'params' => [
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 1024,
                'messages' => [
                    ['role' => 'user', 'content' => 'Translate to Spanish: Hello world']
                ]
            ]
        ]
    ]
]);

// Check batch status
$status = Claude::beta()->messages()->batches()->retrieve($batch['id']);

// List all batches
$batches = Claude::beta()->messages()->batches()->list();

Web Search Integration

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 4096,
    'tools' => [
        [
            'type' => 'web_search_20250305',
            'name' => 'web_search',
            'max_uses' => 5
        ]
    ],
    'messages' => [
        ['role' => 'user', 'content' => 'What are the latest developments in PHP 8.4?']
    ]
]);

Token Counting

use ClaudePhp\Laravel\Facades\Claude;

$response = Claude::messages()->create([
    'model' => 'claude-sonnet-4-5-20250929',
    'max_tokens' => 1024,
    'messages' => [
        ['role' => 'user', 'content' => 'Hello, Claude!']
    ]
]);

$usage = $response['usage'];
echo "Input tokens: {$usage['input_tokens']}\n";
echo "Output tokens: {$usage['output_tokens']}\n";
echo "Total tokens: " . ($usage['input_tokens'] + $usage['output_tokens']) . "\n";

// With caching
if (isset($usage['cache_creation_input_tokens'])) {
    echo "Cache creation tokens: {$usage['cache_creation_input_tokens']}\n";
}
if (isset($usage['cache_read_input_tokens'])) {
    echo "Cache read tokens: {$usage['cache_read_input_tokens']}\n";
}

🤖 Agentic Laravel

Build intelligent AI agents that can reason, use tools, and solve complex problems autonomously.

📚 For comprehensive tutorials on agentic patterns, see the main SDK tutorials.

ReAct Agent Loop

The ReAct (Reason-Act-Observe) pattern enables iterative problem solving:

<?php

namespace App\Services;

use ClaudePhp\ClaudePhp;

class ReActAgent
{
    private array $tools;
    private array $messages = [];

    public function __construct(
        private ClaudePhp $claude,
        private int $maxIterations = 10
    ) {
        $this->tools = $this->defineTools();
    }

    public function run(string $task): string
    {
        $this->messages = [['role' => 'user', 'content' => $task]];
        $iteration = 0;

        while ($iteration < $this->maxIterations) {
            $iteration++;

            // REASON: Ask Claude to analyze the situation
            $response = $this->claude->messages()->create([
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 4096,
                'system' => 'You are a helpful assistant. Use tools when needed to solve problems.',
                'messages' => $this->messages,
                'tools' => $this->tools
            ]);

            // Add assistant response to history
            $this->messages[] = ['role' => 'assistant', 'content' => $response['content']];

            // COMPLETE: Check if task is finished
            if ($response['stop_reason'] === 'end_turn') {
                return $this->extractText($response);
            }

            // ACT: Execute any requested tools
            if ($response['stop_reason'] === 'tool_use') {
                $toolResults = $this->executeTools($response['content']);

                // OBSERVE: Add tool results to conversation
                $this->messages[] = ['role' => 'user', 'content' => $toolResults];

                // Continue loop to get Claude's response to tool results
                continue;
            }

            // Unexpected stop reason - break to avoid infinite loop
            break;
        }

        return 'Max iterations reached without completion.';
    }

    private function executeTools(array $content): array
    {
        $results = [];

        foreach ($content as $block) {
            if ($block['type'] === 'tool_use') {
                $result = $this->executeTool($block['name'], $block['input']);
                $results[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block['id'],
                    'content' => is_string($result) ? $result : json_encode($result)
                ];
            }
        }

        return $results;
    }

    private function executeTool(string $name, array $input): mixed
    {
        return match ($name) {
            'calculate' => $this->calculate($input['expression']),
            'search_database' => $this->searchDatabase($input['query']),
            'get_current_time' => now()->toDateTimeString(),
            default => "Unknown tool: {$name}"
        };
    }

    private function calculate(string $expression): string
    {
        // Use a safe math parser in production!
        try {
            $result = eval("return {$expression};");
            return (string) $result;
        } catch (\Throwable $e) {
            return "Error: {$e->getMessage()}";
        }
    }

    private function searchDatabase(string $query): array
    {
        // Your database search logic
        return \App\Models\Product::search($query)->take(5)->get()->toArray();
    }

    private function defineTools(): array
    {
        return [
            [
                'name' => 'calculate',
                'description' => 'Perform mathematical calculations',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'expression' => [
                            'type' => 'string',
                            'description' => 'Math expression (e.g., "25 * 4 + 10")'
                        ]
                    ],
                    'required' => ['expression']
                ]
            ],
            [
                'name' => 'search_database',
                'description' => 'Search the product database',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'query' => [
                            'type' => 'string',
                            'description' => 'Search query'
                        ]
                    ],
                    'required' => ['query']
                ]
            ],
            [
                'name' => 'get_current_time',
                'description' => 'Get the current date and time',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => []
                ]
            ]
        ];
    }

    private function extractText(array $response): string
    {
        foreach ($response['content'] as $block) {
            if ($block['type'] === 'text') {
                return $block['text'];
            }
        }
        return '';
    }
}

Usage:

use App\Services\ReActAgent;

class AgentController extends Controller
{
    public function solve(Request $request, ReActAgent $agent)
    {
        $result = $agent->run($request->input('task'));

        return response()->json(['result' => $result]);
    }
}

Multi-Tool Agent with Laravel Services

Integrate Laravel services as tools for your agent:

<?php

namespace App\Services;

use App\Models\Order;
use App\Models\Customer;
use ClaudePhp\ClaudePhp;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;

class CustomerServiceAgent
{
    private array $tools;

    public function __construct(
        private ClaudePhp $claude
    ) {
        $this->tools = [
            [
                'name' => 'lookup_customer',
                'description' => 'Look up customer information by email or ID',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'identifier' => ['type' => 'string', 'description' => 'Customer email or ID']
                    ],
                    'required' => ['identifier']
                ]
            ],
            [
                'name' => 'get_order_history',
                'description' => 'Get recent orders for a customer',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'customer_id' => ['type' => 'integer', 'description' => 'Customer ID'],
                        'limit' => ['type' => 'integer', 'description' => 'Number of orders (default 5)']
                    ],
                    'required' => ['customer_id']
                ]
            ],
            [
                'name' => 'check_inventory',
                'description' => 'Check product inventory levels',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'product_sku' => ['type' => 'string', 'description' => 'Product SKU']
                    ],
                    'required' => ['product_sku']
                ]
            ],
            [
                'name' => 'create_support_ticket',
                'description' => 'Create a support ticket for the customer',
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'customer_id' => ['type' => 'integer'],
                        'subject' => ['type' => 'string'],
                        'description' => ['type' => 'string'],
                        'priority' => ['type' => 'string', 'enum' => ['low', 'medium', 'high']]
                    ],
                    'required' => ['customer_id', 'subject', 'description']
                ]
            ]
        ];
    }

    public function handleInquiry(string $inquiry, ?int $customerId = null): array
    {
        $context = $customerId
            ? "You are helping customer ID: {$customerId}. "
            : "You are a customer service agent. ";

        $messages = [['role' => 'user', 'content' => $inquiry]];
        $iterations = 0;

        while ($iterations < 8) {
            $iterations++;

            $response = $this->claude->messages()->create([
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 4096,
                'system' => $context . 'Use available tools to help resolve customer inquiries. Be helpful and thorough.',
                'messages' => $messages,
                'tools' => $this->tools
            ]);

            $messages[] = ['role' => 'assistant', 'content' => $response['content']];

            // COMPLETE: Task finished
            if ($response['stop_reason'] === 'end_turn') {
                return [
                    'response' => $this->extractText($response),
                    'iterations' => $iterations,
                    'tools_used' => $this->countToolUses($messages)
                ];
            }

            // ACT & OBSERVE: Execute tools and continue
            if ($response['stop_reason'] === 'tool_use') {
                $results = [];
                foreach ($response['content'] as $block) {
                    if ($block['type'] === 'tool_use') {
                        $results[] = [
                            'type' => 'tool_result',
                            'tool_use_id' => $block['id'],
                            'content' => json_encode($this->executeTool($block['name'], $block['input']))
                        ];
                    }
                }
                $messages[] = ['role' => 'user', 'content' => $results];
                continue; // Get Claude's response to tool results
            }

            // Unexpected stop reason
            break;
        }

        return ['response' => 'Unable to complete request.', 'iterations' => $iterations];
    }

    private function executeTool(string $name, array $input): mixed
    {
        return match ($name) {
            'lookup_customer' => $this->lookupCustomer($input['identifier']),
            'get_order_history' => $this->getOrderHistory($input['customer_id'], $input['limit'] ?? 5),
            'check_inventory' => $this->checkInventory($input['product_sku']),
            'create_support_ticket' => $this->createTicket($input),
            default => ['error' => 'Unknown tool']
        };
    }

    private function lookupCustomer(string $identifier): array
    {
        $customer = Customer::where('email', $identifier)
            ->orWhere('id', $identifier)
            ->first();

        return $customer ? $customer->toArray() : ['error' => 'Customer not found'];
    }

    private function getOrderHistory(int $customerId, int $limit): array
    {
        return Order::where('customer_id', $customerId)
            ->with('items')
            ->latest()
            ->take($limit)
            ->get()
            ->toArray();
    }

    private function checkInventory(string $sku): array
    {
        return Cache::remember("inventory_{$sku}", 300, function () use ($sku) {
            // Your inventory check logic
            return ['sku' => $sku, 'quantity' => rand(0, 100), 'status' => 'in_stock'];
        });
    }

    private function createTicket(array $input): array
    {
        $ticket = \App\Models\SupportTicket::create([
            'customer_id' => $input['customer_id'],
            'subject' => $input['subject'],
            'description' => $input['description'],
            'priority' => $input['priority'] ?? 'medium',
            'status' => 'open'
        ]);

        return ['ticket_id' => $ticket->id, 'status' => 'created'];
    }

    private function extractText(array $response): string
    {
        foreach ($response['content'] as $block) {
            if ($block['type'] === 'text') {
                return $block['text'];
            }
        }
        return '';
    }

    private function countToolUses(array $messages): int
    {
        $count = 0;
        foreach ($messages as $msg) {
            if (is_array($msg['content'] ?? null)) {
                foreach ($msg['content'] as $block) {
                    if (($block['type'] ?? '') === 'tool_use') {
                        $count++;
                    }
                }
            }
        }
        return $count;
    }
}

Agentic Framework with State Management

Build a complete agent framework with task decomposition:

<?php

namespace App\Services\Agents;

use ClaudePhp\ClaudePhp;
use Illuminate\Support\Facades\Log;

class AgentFramework
{
    private array $state = [];
    private array $agents = [];

    public function __construct(
        private ClaudePhp $claude
    ) {}

    public function registerAgent(string $name, array $tools, string $systemPrompt): self
    {
        $this->agents[$name] = [
            'tools' => $tools,
            'system' => $systemPrompt
        ];
        return $this;
    }

    public function execute(string $goal): array
    {
        Log::info("Agent Framework: Starting goal", ['goal' => $goal]);

        // Step 1: Decompose the goal into subtasks
        $subtasks = $this->decompose($goal);

        // Step 2: Execute each subtask
        $results = [];
        foreach ($subtasks as $i => $subtask) {
            Log::info("Executing subtask", ['index' => $i, 'task' => $subtask['task']]);

            $agentName = $subtask['agent'] ?? 'default';
            $result = $this->runAgent($agentName, $subtask['task']);

            $results[] = $result;
            $this->state["subtask_{$i}"] = $result;
        }

        // Step 3: Synthesize final result
        $finalResult = $this->synthesize($goal, $results);

        return [
            'goal' => $goal,
            'subtasks' => $subtasks,
            'results' => $results,
            'final_result' => $finalResult,
            'state' => $this->state
        ];
    }

    private function decompose(string $goal): array
    {
        $response = $this->claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'thinking' => ['type' => 'enabled', 'budget_tokens' => 3000],
            'messages' => [[
                'role' => 'user',
                'content' => "Break this goal into 2-4 concrete subtasks. Format as numbered list.\n\nGoal: {$goal}"
            ]]
        ]);

        return $this->parseSubtasks($this->extractText($response));
    }

    private function runAgent(string $agentName, string $task): string
    {
        $agent = $this->agents[$agentName] ?? $this->agents['default'] ?? null;

        if (!$agent) {
            return "No agent available for: {$agentName}";
        }

        $messages = [['role' => 'user', 'content' => $task]];
        $iteration = 0;

        while ($iteration < 10) {
            $iteration++;

            $params = [
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 4096,
                'system' => $agent['system'],
                'messages' => $messages,
            ];

            if (!empty($agent['tools'])) {
                $params['tools'] = $agent['tools'];
            }

            $response = $this->claude->messages()->create($params);
            $messages[] = ['role' => 'assistant', 'content' => $response['content']];

            // COMPLETE: Task finished
            if ($response['stop_reason'] === 'end_turn') {
                return $this->extractText($response);
            }

            // ACT & OBSERVE: Execute tools and continue loop
            if ($response['stop_reason'] === 'tool_use') {
                $results = $this->executeTools($response['content']);
                $messages[] = ['role' => 'user', 'content' => $results];
                continue;
            }

            // Unexpected stop reason
            break;
        }

        return 'Agent reached iteration limit.';
    }

    private function synthesize(string $goal, array $results): string
    {
        $resultsText = collect($results)
            ->map(fn($r, $i) => ($i + 1) . ". {$r}")
            ->implode("\n\n");

        $response = $this->claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'messages' => [[
                'role' => 'user',
                'content' => "Original goal: {$goal}\n\nSubtask results:\n{$resultsText}\n\nProvide a synthesized final answer."
            ]]
        ]);

        return $this->extractText($response);
    }

    private function executeTools(array $content): array
    {
        $results = [];
        foreach ($content as $block) {
            if ($block['type'] === 'tool_use') {
                // Implement your tool execution logic
                $results[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block['id'],
                    'content' => json_encode(['status' => 'executed'])
                ];
            }
        }
        return $results;
    }

    private function parseSubtasks(string $text): array
    {
        $subtasks = [];
        foreach (explode("\n", $text) as $line) {
            if (preg_match('/^\d+\.\s+(.+)$/', trim($line), $matches)) {
                $subtasks[] = ['task' => $matches[1], 'agent' => 'default'];
            }
        }
        return $subtasks ?: [['task' => $text, 'agent' => 'default']];
    }

    private function extractText(array $response): string
    {
        foreach ($response['content'] as $block) {
            if ($block['type'] === 'text') {
                return $block['text'];
            }
        }
        return '';
    }

    public function getState(): array
    {
        return $this->state;
    }
}

Usage in a Controller:

use App\Services\Agents\AgentFramework;

class ResearchController extends Controller
{
    public function analyze(Request $request, AgentFramework $framework)
    {
        // Register specialized agents
        $framework
            ->registerAgent('default', [], 'You are a helpful research assistant.')
            ->registerAgent('calculator', [
                [
                    'name' => 'calculate',
                    'description' => 'Perform calculations',
                    'input_schema' => [
                        'type' => 'object',
                        'properties' => [
                            'expression' => ['type' => 'string']
                        ],
                        'required' => ['expression']
                    ]
                ]
            ], 'You are a math specialist.');

        $result = $framework->execute($request->input('goal'));

        return response()->json($result);
    }
}

Streaming Agent Responses

For real-time agent feedback:

use ClaudePhp\Laravel\Facades\Claude;
use Symfony\Component\HttpFoundation\StreamedResponse;

public function streamAgent(Request $request): StreamedResponse
{
    return response()->stream(function () use ($request) {
        $task = $request->input('task');
        $messages = [['role' => 'user', 'content' => $task]];
        $tools = $this->getTools();

        for ($i = 0; $i < 10; $i++) {
            // Stream: Send iteration marker
            echo "data: " . json_encode(['type' => 'iteration', 'number' => $i + 1]) . "\n\n";
            ob_flush(); flush();

            $stream = Claude::messages()->stream([
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 4096,
                'messages' => $messages,
                'tools' => $tools
            ]);

            $fullContent = [];
            $stopReason = null;

            foreach ($stream as $event) {
                $type = $event['type'] ?? '';

                if ($type === 'content_block_delta' && isset($event['delta']['text'])) {
                    // Stream text to client
                    echo "data: " . json_encode([
                        'type' => 'text',
                        'content' => $event['delta']['text']
                    ]) . "\n\n";
                    ob_flush(); flush();
                }

                if ($type === 'message_delta') {
                    $stopReason = $event['delta']['stop_reason'] ?? null;
                }

                if ($type === 'content_block_start' || $type === 'content_block_delta') {
                    // Accumulate for history
                    // ... accumulation logic
                }
            }

            if ($stopReason === 'end_turn') {
                echo "data: " . json_encode(['type' => 'complete']) . "\n\n";
                break;
            }

            if ($stopReason === 'tool_use') {
                echo "data: " . json_encode(['type' => 'tool_execution']) . "\n\n";
                ob_flush(); flush();

                // Execute tools and continue loop
                // ... tool execution logic
            }
        }

        echo "data: [DONE]\n\n";
        ob_flush(); flush();
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'X-Accel-Buffering' => 'no',
    ]);
}

Agent with Extended Thinking

For complex reasoning tasks:

use ClaudePhp\Laravel\Facades\Claude;

class ReasoningAgent
{
    public function solve(string $problem): array
    {
        $messages = [['role' => 'user', 'content' => $problem]];
        $thinkingHistory = [];
        $iteration = 0;

        while ($iteration < 5) {
            $iteration++;

            $response = Claude::messages()->create([
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => 16000,
                'thinking' => [
                    'type' => 'enabled',
                    'budget_tokens' => 8000
                ],
                'messages' => $messages,
                'tools' => $this->getTools()
            ]);

            // Capture thinking for analysis
            foreach ($response['content'] as $block) {
                if ($block['type'] === 'thinking') {
                    $thinkingHistory[] = [
                        'iteration' => $iteration,
                        'thinking' => $block['thinking']
                    ];
                }
            }

            $messages[] = ['role' => 'assistant', 'content' => $response['content']];

            // COMPLETE: Problem solved
            if ($response['stop_reason'] === 'end_turn') {
                return [
                    'answer' => $this->extractText($response),
                    'thinking_steps' => count($thinkingHistory),
                    'thinking_history' => $thinkingHistory,
                    'iterations' => $iteration
                ];
            }

            // ACT & OBSERVE: Execute tools and continue reasoning
            if ($response['stop_reason'] === 'tool_use') {
                $results = $this->executeTools($response['content']);
                $messages[] = ['role' => 'user', 'content' => $results];
                continue;
            }

            // Unexpected stop reason
            break;
        }

        return ['answer' => 'Could not solve within iteration limit.'];
    }

    // ... helper methods
}

Laravel-Specific Patterns

Using with Jobs

<?php

namespace App\Jobs;

use ClaudePhp\ClaudePhp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessWithClaude implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private string $content,
        private int $userId
    ) {}

    public function handle(ClaudePhp $claude): void
    {
        $response = $claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'messages' => [
                ['role' => 'user', 'content' => $this->content]
            ]
        ]);

        // Store result...
        \App\Models\AiResponse::create([
            'user_id' => $this->userId,
            'response' => $response['content'][0]['text']
        ]);
    }
}

Using with Artisan Commands

<?php

namespace App\Console\Commands;

use ClaudePhp\ClaudePhp;
use Illuminate\Console\Command;

class AskClaude extends Command
{
    protected $signature = 'claude:ask {question}';
    protected $description = 'Ask Claude a question';

    public function handle(ClaudePhp $claude): int
    {
        $question = $this->argument('question');

        $this->info("Asking Claude: {$question}");
        $this->newLine();

        $response = $claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'messages' => [
                ['role' => 'user', 'content' => $question]
            ]
        ]);

        $this->line($response['content'][0]['text']);

        return Command::SUCCESS;
    }
}

Using with Service Classes

<?php

namespace App\Services;

use ClaudePhp\ClaudePhp;
use Illuminate\Support\Facades\Cache;

class AiService
{
    public function __construct(
        private ClaudePhp $claude
    ) {}

    public function summarize(string $text, int $maxLength = 200): string
    {
        $cacheKey = 'summary_' . md5($text);

        return Cache::remember($cacheKey, 3600, function () use ($text, $maxLength) {
            $response = $this->claude->messages()->create([
                'model' => 'claude-sonnet-4-5-20250929',
                'max_tokens' => $maxLength,
                'system' => 'You are a summarization expert. Provide concise summaries.',
                'messages' => [
                    ['role' => 'user', 'content' => "Summarize this: {$text}"]
                ]
            ]);

            return $response['content'][0]['text'];
        });
    }

    public function translate(string $text, string $targetLanguage): string
    {
        $response = $this->claude->messages()->create([
            'model' => 'claude-sonnet-4-5-20250929',
            'max_tokens' => 2048,
            'messages' => [
                ['role' => 'user', 'content' => "Translate to {$targetLanguage}: {$text}"]
            ]
        ]);

        return $response['content'][0]['text'];
    }
}

Testing

Mocking the Facade

use ClaudePhp\Laravel\Facades\Claude;

public function test_chat_endpoint(): void
{
    Claude::shouldReceive('messages->create')
        ->once()
        ->andReturn([
            'id' => 'msg_123',
            'type' => 'message',
            'role' => 'assistant',
            'content' => [
                ['type' => 'text', 'text' => 'Mocked response']
            ],
            'model' => 'claude-sonnet-4-5-20250929',
            'stop_reason' => 'end_turn',
            'usage' => [
                'input_tokens' => 10,
                'output_tokens' => 5
            ]
        ]);

    $response = $this->postJson('/api/chat', [
        'message' => 'Hello'
    ]);

    $response->assertOk()
        ->assertJsonPath('response', 'Mocked response');
}

Using Fake API Responses

use ClaudePhp\ClaudePhp;
use Mockery;

public function test_with_dependency_injection(): void
{
    $mockClaude = Mockery::mock(ClaudePhp::class);
    $mockMessages = Mockery::mock();

    $mockClaude->shouldReceive('messages')->andReturn($mockMessages);
    $mockMessages->shouldReceive('create')->andReturn([
        'content' => [['type' => 'text', 'text' => 'Test response']]
    ]);

    $this->app->instance(ClaudePhp::class, $mockClaude);

    // Your test...
}

Error Handling

use ClaudePhp\Laravel\Facades\Claude;
use ClaudePhp\Exceptions\AuthenticationException;
use ClaudePhp\Exceptions\RateLimitException;
use ClaudePhp\Exceptions\ApiException;
use ClaudePhp\Exceptions\InvalidRequestException;
use Illuminate\Support\Facades\Log;

try {
    $response = Claude::messages()->create([
        'model' => 'claude-sonnet-4-5-20250929',
        'max_tokens' => 1024,
        'messages' => [
            ['role' => 'user', 'content' => 'Hello!']
        ]
    ]);
} catch (AuthenticationException $e) {
    // Invalid API key
    Log::error('Claude authentication failed', ['error' => $e->getMessage()]);
    abort(500, 'AI service configuration error');

} catch (RateLimitException $e) {
    // Rate limited - implement retry logic
    $retryAfter = $e->retryAfter ?? 60;
    Log::warning('Claude rate limited', ['retry_after' => $retryAfter]);

    // Optionally dispatch to queue for later
    ProcessWithClaude::dispatch($content)->delay(now()->addSeconds($retryAfter));

} catch (InvalidRequestException $e) {
    // Bad request parameters
    Log::error('Invalid Claude request', ['error' => $e->getMessage()]);
    abort(400, 'Invalid request');

} catch (ApiException $e) {
    // General API error
    Log::error('Claude API error', [
        'message' => $e->getMessage(),
        'code' => $e->getCode()
    ]);
    abort(503, 'AI service temporarily unavailable');
}

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

Security

If you discover any security-related issues, please use the issue tracker.

License

The MIT License (MIT). Please see License File for more information.