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
Requires
- php: ^8.2
- claude-php/claude-php-sdk: ^0.5.1|^1.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.40
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.0
README
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.