mozex/anthropic-php
Community-maintained PHP SDK for the Anthropic API. Send messages, stream responses, call tools, use extended thinking, web search, code execution, files, and batches. PSR-18 compatible, works with any HTTP client; Laravel wrapper available.
Installation:
composer require mozex/anthropic-php
The package auto-discovers a PSR-18 HTTP client (Guzzle/Symfony by default).
First API Call:
use Anthropic\Anthropic;
$client = Anthropic::client(config('services.anthropic.key'));
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'messages' => [['role' => 'user', 'content' => 'Hello']],
]);
echo $response->content[0]->text;
Key Config:
Store your API key in .env:
ANTHROPIC_KEY=your_api_key_here
Then reference it in config/services.php:
'anthropic' => [
'key' => env('ANTHROPIC_KEY'),
],
Create a ChatService facade:
// app/Services/ChatService.php
namespace App\Services;
use Anthropic\Anthropic;
use Illuminate\Support\Facades\Facade;
class ChatService extends Facade {
protected static function getFacadeAccessor() {
return 'anthropic';
}
}
Register the service provider:
// config/app.php
'providers' => [
// ...
Mozex\AnthropicLaravel\AnthropicServiceProvider::class,
],
Now use it in a controller:
use App\Services\ChatService;
class ChatController extends Controller {
public function ask(string $question) {
$response = ChatService::messages()->create([
'model' => 'claude-sonnet-4-6',
'messages' => [['role' => 'user', 'content' => $question]],
]);
return response()->json(['answer' => $response->content[0]->text]);
}
}
Pattern: Wrap Anthropic calls in domain-specific services to:
Example:
// app/Services/CustomerSupportService.php
class CustomerSupportService {
public function __construct(protected AnthropicClient $client) {}
public function handleTicket(string $ticketContent): string {
$response = $this->client->messages()->create([
'model' => 'claude-opus-4-6',
'max_tokens' => 2048,
'messages' => [
['role' => 'system', 'content' => 'You are a customer support agent.'],
['role' => 'user', 'content' => $ticketContent],
],
]);
return $response->content[0]->text;
}
}
Pattern: Process streaming responses in real-time for:
Example with Laravel Echo:
// app/Http/Controllers/StreamController.php
class StreamController extends Controller {
public function generateStream(string $prompt) {
$stream = ChatService::messages()->createStreamed([
'model' => 'claude-sonnet-4-6',
'messages' => [['role' => 'user', 'content' => $prompt]],
]);
return response()->stream(function () use ($stream) {
foreach ($stream as $chunk) {
if ($chunk->type === 'content_block_delta' &&
$chunk->delta->type === 'text_delta') {
echo $chunk->delta->text;
flush();
}
}
});
}
}
Pattern: Use tools for:
Example:
// app/Services/InventoryService.php
class InventoryService {
public function checkStock(string $productId): array {
$response = ChatService::messages()->create([
'model' => 'claude-sonnet-4-6',
'tools' => [
[
'name' => 'get_inventory',
'description' => 'Check stock levels for a product',
'input_schema' => [
'type' => 'object',
'properties' => [
'product_id' => ['type' => 'string'],
],
'required' => ['product_id'],
],
],
],
'messages' => [
['role' => 'user', 'content' => 'What is the stock level for product ' . $productId],
],
]);
// Handle tool call
if (isset($response->content[0]) && $response->content[0]->name === 'get_inventory') {
return $this->fetchFromDatabase($response->content[0]->input['product_id']);
}
return ['error' => 'No tool call detected'];
}
}
Pattern: Process multiple requests efficiently:
Example:
// app/Jobs/ProcessUserQueries.php
class ProcessUserQueries implements ShouldQueue {
public function handle() {
$queries = UserQuery::where('processed', false)->get();
$batch = ChatService::batches()->create([
'model' => 'claude-sonnet-4-6',
'messages' => $queries->map(fn($q) => [
'role' => 'user',
'content' => $q->query,
])->toArray(),
]);
foreach ($batch->results as $index => $result) {
$queries[$index]->update(['response' => $result->content[0]->text]);
}
}
}
Pattern: Upload and reference files:
Example:
// app/Services/DocumentProcessor.php
class DocumentProcessor {
public function processDocument(string $filePath, string $prompt) {
$file = ChatService::files()->upload([
'file' => fopen($filePath, 'r'),
]);
$response = ChatService::messages()->create([
'model' => 'claude-opus-4-6',
'betas' => ['files-api-2025-04-14'],
'messages' => [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => $prompt],
['type' => 'document', 'source' => [
'type' => 'file',
'file_id' => $file->id,
]],
],
],
],
]);
return $response->content[0]->text;
}
}
Pattern: Centralize error responses:
Example:
// app/Exceptions/Handler.php
class Handler extends ExceptionHandler {
public function render($request, Throwable $exception) {
if ($exception instanceof \Anthropic\Exceptions\AnthropicException) {
return response()->json([
'error' => $exception->getMessage(),
'type' => $exception->getType(),
'code' => $exception->getCode(),
'meta' => $exception->getMeta(),
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
}
$response->meta() before making subsequent calls.meta()->requestLimit and meta()->tokenLimit:
$remainingRequests = $response->meta()->requestLimit->remaining;
$remainingTokens = $response->meta()->tokenLimit->remaining;
class RateLimitedClient {
public function __construct(protected AnthropicClient $client) {}
public function safeCreate(array $parameters) {
$response = $this->client->messages()->create($parameters);
if ($response->meta()->requestLimit->remaining < 5) {
sleep($response->meta()->requestLimit->resetAt->getTimestamp() - time() + 1);
}
return $response;
}
}
tool_use_delta).foreach ($stream as $chunk) {
if ($chunk->type === 'content_block_delta' &&
$chunk->delta->type === 'text_delta') {
echo $chunk->delta->text;
}
// Handle other delta types (e.g., tool_use_delta)
}
response()->stream() for large streams to avoid memory issues.$toolInput
How can I help you explore Laravel packages today?