ghdj / laravel-ai-integration
Laravel package for AI provider abstraction with support for OpenAI, Claude, and Gemini
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/ghdj/laravel-ai-integration
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- larastan/larastan: ^2.0|^3.0
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2025-12-14 12:21:45 UTC
README
A Laravel package for seamless integration with multiple AI providers (OpenAI, Claude, Gemini) featuring rate limiting, cost tracking, and prompt templating.
Requirements
- PHP 8.1+
- Laravel 10.0+
Installation
composer require ghdj/laravel-ai-integration
Publish the configuration file:
php artisan vendor:publish --tag=ai-config
Configuration
Add your API keys to .env:
AI_DEFAULT_PROVIDER=openai OPENAI_API_KEY=your-openai-key OPENAI_DEFAULT_MODEL=gpt-4o CLAUDE_API_KEY=your-claude-key CLAUDE_DEFAULT_MODEL=claude-sonnet-4-20250514 GEMINI_API_KEY=your-gemini-key GEMINI_DEFAULT_MODEL=gemini-1.5-pro
Basic Usage
Simple Chat
use Ghdj\AIIntegration\Facades\AI; // Using default provider $response = AI::provider()->chat([ ['role' => 'user', 'content' => 'Hello, how are you?'] ]); echo $response->getContent();
Switching Providers
// OpenAI $response = AI::provider('openai')->chat([ ['role' => 'user', 'content' => 'Explain quantum computing'] ]); // Claude $response = AI::provider('claude')->chat([ ['role' => 'user', 'content' => 'Explain quantum computing'] ]); // Gemini $response = AI::provider('gemini')->chat([ ['role' => 'user', 'content' => 'Explain quantum computing'] ]);
With System Message
$response = AI::provider('openai')->chat([ ['role' => 'system', 'content' => 'You are a helpful coding assistant.'], ['role' => 'user', 'content' => 'Write a function to reverse a string in PHP'] ]);
Using Options
$response = AI::provider('openai')->chat( messages: [ ['role' => 'user', 'content' => 'Write a haiku about programming'] ], options: [ 'model' => 'gpt-4o', 'temperature' => 0.7, 'max_tokens' => 150, ] );
Text Completion
$response = AI::provider('openai')->complete( 'Translate to French: Hello, world!', ['temperature' => 0.3] ); echo $response->getContent();
Streaming Responses
$stream = AI::provider('openai')->chatStream([ ['role' => 'user', 'content' => 'Tell me a story'] ]); foreach ($stream as $chunk) { echo $chunk; // Output chunks as they arrive flush(); } // Get final usage stats echo "Tokens used: " . $stream->getTotalTokens();
Embeddings
// Single text $response = AI::provider('openai')->embed('Hello world'); $vector = $response->getFirstEmbedding(); // Multiple texts $response = AI::provider('openai')->embed([ 'First text to embed', 'Second text to embed', ]); $vectors = $response->getEmbeddings(); // Gemini batch embeddings $response = AI::provider('gemini')->embed([ 'Text one', 'Text two', 'Text three', ]);
Note: Claude does not support embeddings. Use OpenAI or Gemini.
Tool/Function Calling
OpenAI Tools
$tools = [ [ 'type' => 'function', 'function' => [ 'name' => 'get_weather', 'description' => 'Get the current weather for a location', 'parameters' => [ 'type' => 'object', 'properties' => [ 'location' => [ 'type' => 'string', 'description' => 'City name', ], ], 'required' => ['location'], ], ], ], ]; $response = AI::provider('openai')->chatWithTools( messages: [ ['role' => 'user', 'content' => 'What is the weather in Paris?'] ], tools: $tools ); if ($response->hasToolCalls()) { foreach ($response->getToolCalls() as $toolCall) { $name = $toolCall['function']['name']; $args = json_decode($toolCall['function']['arguments'], true); // Execute tool and continue conversation $result = executeYourTool($name, $args); // Send tool result back $response = AI::provider('openai')->chat([ ['role' => 'user', 'content' => 'What is the weather in Paris?'], ['role' => 'assistant', 'content' => null, 'tool_calls' => $response->getToolCalls()], ['role' => 'tool', 'tool_call_id' => $toolCall['id'], 'content' => json_encode($result)], ]); } }
Claude Tools
$tools = [ [ 'name' => 'calculator', 'description' => 'Perform basic math operations', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'operation' => ['type' => 'string', 'enum' => ['add', 'subtract', 'multiply', 'divide']], 'a' => ['type' => 'number'], 'b' => ['type' => 'number'], ], 'required' => ['operation', 'a', 'b'], ], ], ]; $response = AI::provider('claude')->chatWithTools( messages: [['role' => 'user', 'content' => 'What is 25 * 4?']], tools: $tools );
JSON Mode (OpenAI)
$response = AI::provider('openai')->chat( messages: [ ['role' => 'user', 'content' => 'List 3 colors as JSON array'] ], options: [ 'response_format' => ['type' => 'json_object'], ] ); $data = json_decode($response->getContent(), true);
Prompt Templating
Basic Templates
// Register a template AI::registerPrompt('greeting', 'Hello {{ name }}, welcome to {{ place }}!'); // Render the template $text = AI::renderPrompt('greeting', [ 'name' => 'John', 'place' => 'Laravel' ]); // Output: Hello John, welcome to Laravel!
Templates with Defaults
AI::registerPrompt('welcome', [ 'template' => 'Welcome {{ name | ucfirst }}, your role is {{ role }}.', 'defaults' => ['role' => 'user'], ]); echo AI::renderPrompt('welcome', ['name' => 'john']); // Output: Welcome John, your role is user.
System Prompts
AI::registerPrompt('assistant', [ 'template' => 'Help me with: {{ task }}', 'system' => 'You are a helpful {{ specialty }} assistant.', 'defaults' => ['specialty' => 'general'], ]); $messages = AI::promptToMessages('assistant', [ 'task' => 'Write a unit test', 'specialty' => 'PHP', ]); // Returns: // [ // ['role' => 'system', 'content' => 'You are a helpful PHP assistant.'], // ['role' => 'user', 'content' => 'Help me with: Write a unit test'], // ] $response = AI::provider('openai')->chat($messages);
Available Filters
AI::registerPrompt('filters', ' Upper: {{ text | upper }} Lower: {{ text | lower }} Capitalize: {{ text | ucfirst }} '); echo AI::renderPrompt('filters', ['text' => 'hello WORLD']); // Upper: HELLO WORLD // Lower: hello world // Capitalize: Hello WORLD
Loading Templates from Files
Create template files in your configured prompts directory:
prompts/summarize.txt
Summarize the following text in {{ style }} style:
{{ content }}
prompts/code-review.json
{
"template": "Review this {{ language }} code:\n\n```{{ language }}\n{{ code }}\n```",
"system": "You are an expert code reviewer.",
"defaults": {
"language": "php"
}
}
prompts/translate.php
<?php return [ 'template' => 'Translate to {{ target_language }}: {{ text }}', 'defaults' => [ 'target_language' => 'English', ], ];
Load and use:
$promptManager = AI::prompts(); // Load from file $template = $promptManager->load('summarize'); echo $template->render(['style' => 'concise', 'content' => $article]); // Load JSON template $template = $promptManager->load('code-review'); $messages = $template->with(['code' => $code])->toMessages();
Rate Limiting
Rate limiting is built-in and configurable per provider:
// config/ai.php 'rate_limiting' => [ 'enabled' => true, 'default_limit' => 60, 'default_window' => 60, // seconds 'providers' => [ 'openai' => ['limit' => 100, 'window' => 60], 'claude' => ['limit' => 50, 'window' => 60], 'gemini' => ['limit' => 60, 'window' => 60], ], ],
Manual rate limit checking:
use Ghdj\AIIntegration\Services\RateLimiter; $rateLimiter = app(RateLimiter::class); // Check remaining requests $remaining = $rateLimiter->remaining('openai'); // Reset limits $rateLimiter->reset('openai');
Cost Tracking
Track API usage and costs:
use Ghdj\AIIntegration\Services\CostTracker; $tracker = app(CostTracker::class); // After making requests, check costs $openaiCost = $tracker->getTotalCost('openai'); $claudeCost = $tracker->getTotalCost('claude'); $totalCost = $tracker->getTotalCost(); // All providers // Get detailed usage $usage = $tracker->getUsage('openai'); foreach ($usage as $request) { echo "Model: {$request['model']}, Cost: \${$request['cost']}\n"; } // Reset tracking $tracker->reset();
Configure pricing in config/ai.php:
'cost_tracking' => [ 'enabled' => true, 'pricing' => [ 'openai' => [ 'gpt-4o' => ['prompt' => 0.0025, 'completion' => 0.01], 'gpt-4o-mini' => ['prompt' => 0.00015, 'completion' => 0.0006], 'text-embedding-3-small' => ['prompt' => 0.00002, 'completion' => 0], ], 'claude' => [ 'claude-sonnet-4-20250514' => ['prompt' => 0.003, 'completion' => 0.015], 'claude-opus-4-20250514' => ['prompt' => 0.015, 'completion' => 0.075], ], 'gemini' => [ 'gemini-1.5-pro' => ['prompt' => 0.00125, 'completion' => 0.005], 'gemini-1.5-flash' => ['prompt' => 0.000075, 'completion' => 0.0003], ], ], ],
Custom Providers
Register your own AI provider:
use Ghdj\AIIntegration\Contracts\AIProviderInterface; class MyCustomProvider implements AIProviderInterface { public function __construct(private array $config) {} public function getName(): string { return 'my-custom'; } public function getModels(): array { return ['custom-model-v1', 'custom-model-v2']; } public function supportsStreaming(): bool { return false; } public function chat(array $messages, array $options = []): AIResponseInterface { // Your implementation } // Implement other interface methods... } // Register the provider AI::extend('my-custom', function (array $config) { return new MyCustomProvider($config); }); // Add configuration config(['ai.providers.my-custom' => [ 'api_key' => env('MY_CUSTOM_API_KEY'), ]]); // Use it $response = AI::provider('my-custom')->chat([ ['role' => 'user', 'content' => 'Hello!'] ]);
Response Object
All chat responses implement AIResponseInterface:
$response = AI::provider('openai')->chat($messages); $response->getContent(); // The response text $response->getRole(); // 'assistant' $response->getModel(); // 'gpt-4o' $response->getFinishReason(); // 'stop', 'length', 'tool_calls' $response->getPromptTokens(); // Input tokens used $response->getCompletionTokens(); // Output tokens used $response->getTotalTokens(); // Total tokens $response->getUsage(); // ['prompt_tokens' => X, 'completion_tokens' => Y, 'total_tokens' => Z] $response->hasToolCalls(); // bool $response->getToolCalls(); // Array of tool calls $response->getRaw(); // Raw API response $response->toArray(); // Array representation
Error Handling
use Ghdj\AIIntegration\Exceptions\AIException; use Ghdj\AIIntegration\Exceptions\APIException; use Ghdj\AIIntegration\Exceptions\OpenAIException; use Ghdj\AIIntegration\Exceptions\ClaudeException; use Ghdj\AIIntegration\Exceptions\GeminiException; use Ghdj\AIIntegration\Exceptions\RateLimitExceededException; use Ghdj\AIIntegration\Exceptions\ProviderNotFoundException; try { $response = AI::provider('openai')->chat($messages); } catch (RateLimitExceededException $e) { // Rate limit exceeded (internal or API) Log::warning('Rate limit hit', ['provider' => 'openai']); } catch (OpenAIException $e) { if ($e->isRateLimitError()) { // API rate limit } elseif ($e->isQuotaError()) { // Quota exceeded } elseif ($e->isAuthenticationError()) { // Invalid API key } } catch (ClaudeException $e) { if ($e->isOverloaded()) { // Claude API overloaded (529) } } catch (APIException $e) { // General API error $statusCode = $e->getCode(); $response = $e->getResponse(); } catch (AIException $e) { // Base exception for all AI errors }
Testing
composer test
License
MIT License. See LICENSE for details.