The official Laravel integration for the Claude PHP SDK, providing seamless access to Anthropic's Claude API in your Laravel applications.
- PHP 8.2+
- Laravel 11.x or 12.x
Install the package via Composer:
composer require claude-php/claude-php-sdk-laravelThe package will automatically register the service provider and facade.
Publish the configuration file:
php artisan vendor:publish --tag=claude-configAdd your Anthropic API key to your .env file:
ANTHROPIC_API_KEY=your-api-key-here| 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 |
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
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
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'];<?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']
]);
}
}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?']
]
]);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..."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();
}
}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',
]);
}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.'
]
]
]
]
]);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.'
]
]
]
]
]);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)
]
]
]
]
]);
}
}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'];
}
}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);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 cheaperuse 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";
}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();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();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?']
]
]);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";
}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.
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]);
}
}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;
}
}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);
}
}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',
]);
}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
}<?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']
]);
}
}<?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;
}
}<?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'];
}
}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');
}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...
}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');
}Contributions are welcome! Please see CONTRIBUTING.md for details.
If you discover any security-related issues, please use the issue tracker.
The MIT License (MIT). Please see License File for more information.