symfony/ai-generic-platform
A generic platform bridge for Symfony AI that connects the framework's AI components to a common platform abstraction, simplifying configuration and enabling interoperable model integrations within Symfony applications.
Installation Add the package via Composer:
composer require symfony/ai-generic-platform
Ensure your project uses Symfony 7.0+ (or PHP 8.2+). For Laravel, this bridges cleanly into your service container.
Basic Setup
Register the bridge in your config/packages/ai.yaml (Symfony) or bind it manually in AppServiceProvider (Laravel):
framework:
ai:
platforms:
generic: true
First Use Case: Embedding Generation
Use the AiClient to interact with generic AI platforms:
use Symfony\AI\Client\AiClient;
use Symfony\AI\Client\AiClientInterface;
// In a service or controller
public function __construct(private AiClientInterface $aiClient) {}
public function generateEmbedding(string $text): array
{
$response = $this->aiClient->embed([
'model' => 'generic-embedding-model',
'input' => $text,
]);
return $response->getEmbedding();
}
Where to Look Next
src/Symfony/AI/Client/GenericPlatformClient.php for low-level API details.EdenAI platform config if you want a multi-provider switchboard instead of hitting a single vendor.Embedding Generation for Search
// Generate embeddings for a list of documents
$documents = ['doc1', 'doc2', 'doc3'];
$embeddings = collect($documents)->map(fn($doc) =>
$this->aiClient->embed(['input' => $doc])->getEmbedding()
);
Chat Completions with Context
// Use chat history for context-aware responses
$history = [
['role' => 'user', 'content' => 'Previous question'],
['role' => 'assistant', 'content' => 'Previous answer'],
];
$response = $this->aiClient->chat([
'model' => 'generic-chat-model',
'messages' => array_merge($history, [['role' => 'user', 'content' => 'New question']]),
]);
Tool / Function Calling (Stable in v0.14)
Tool call handling is now consistent across finish reasons — you no longer need to special-case a stop finish reason to retrieve tool calls:
$response = $this->aiClient->chat([
'model' => 'generic-chat-model',
'messages' => [['role' => 'user', 'content' => 'What is the weather in Paris?']],
'tools' => [[
'type' => 'function',
'function' => [
'name' => 'get_weather',
'description' => 'Get current weather for a city',
'parameters' => [
'type' => 'object',
'properties' => ['city' => ['type' => 'string']],
'required' => ['city'],
],
],
]],
]);
// Tool calls are now populated on ANY finish reason (fix #2543, #2551)
foreach ($response->getToolCalls() as $toolCall) {
$result = $this->execute($toolCall->getName(), $toolCall->getArguments());
// Feed back as a tool message in the next turn
}
Batch Processing
// Process embeddings in parallel (using Symfony Messenger or similar)
$embeddingTasks = array_map(fn($text) => new GenerateEmbeddingTask($text), $texts);
$dispatcher->dispatch($embeddingTasks);
Laravel Service Providers
Bind the AiClientInterface in AppServiceProvider:
$this->app->bind(AiClientInterface::class, function ($app) {
return new GenericPlatformClient($app['config']['ai.platform']);
});
Configuration Management Use Laravel’s config system to switch between platforms, including the new Eden AI gateway:
// config/ai.php
return [
'platform' => env('AI_PLATFORM', 'generic'),
'platforms' => [
'generic' => [
'endpoint' => 'https://api.generic-ai-provider.com/v1',
'api_key' => env('AI_GENERIC_API_KEY'),
],
'eden' => [
'endpoint' => 'https://api.edenai.run/v2',
'api_key' => env('EDEN_AI_API_KEY'),
],
],
];
Cost Tracking (New: TokenUsage model)
TokenUsage now exposes the model name, so you can attribute cost per model without re-parsing the request payload:
$response = $this->aiClient->chat([...]);
$usage = $response->getUsage();
Cost::create([
'model' => $usage->getModel(), // New in v0.14
'input_tokens' => $usage->getInputTokens(),
'output_tokens' => $usage->getOutputTokens(),
'cost' => $this->pricing->for($usage->getModel())
->calculate($usage->getInputTokens(), $usage->getOutputTokens()),
]);
Carbon Footprint Metadata (Albert) Albert responses now expose carbon footprint data via result metadata — useful for ESG reporting or green dashboards:
$response = $this->aiClient->chat([...]);
$carbon = $response->getMetadata()['carbon_footprint'] ?? null;
if ($carbon) {
GreenMetric::record($carbon->getGramsCO2e());
}
Caching Responses Cache embeddings/chat responses to avoid redundant API calls:
$cacheKey = 'embedding_'.md5($text);
return Cache::remember($cacheKey, now()->addHours(1), fn() =>
$this->aiClient->embed(['input' => $text])->getEmbedding()
);
Error Handling Wrap API calls in try-catch blocks:
try {
$response = $this->aiClient->embed(['input' => $text]);
} catch (AiException $e) {
Log::error('AI Embedding Failed', ['error' => $e->getMessage()]);
throw new \RuntimeException('Failed to generate embedding', 0, $e);
}
Platform-Specific Quirks
GenericPlatformClient:
class CustomGenericClient extends GenericPlatformClient {
protected function decodeResponse(array $data): mixed {
// Override to handle custom responses
return $data['custom_key'] ?? parent::decodeResponse($data);
}
}
Tool Call Finish Reasons (Fixed in v0.14)
stop finish reason instead of tool_calls. Upgrade to v0.14 to get consistent tool-call extraction across all finish reasons. If you pinned an older version, guard your parsing logic explicitly by inspecting getFinishReason().Rate Limiting
use Symfony\Component\AI\Exception\RateLimitExceededException;
try {
$response = $this->aiClient->chat($prompt);
} catch (RateLimitExceededException $e) {
sleep(2 ** $this->retryCount++);
retry();
}
Cost Management
if (strlen($text) > 5000) {
throw new \InvalidArgumentException('Input too long for embedding');
}
TokenUsage::getModel() when persisting usage so cost aggregation is accurate even when you route across multiple models.Dependency Conflicts
symfony/ai and symfony/http-client versions are compatible. Avoid:
composer require symfony/ai:^1.0 symfony/http-client:^6.4
(Check Symfony’s docs for version pairs.)Eden AI Gateway Caveats (New in v0.14)
usage block and metadata keys returned for your chosen combination before relying on TokenUsage or carbon footprint fields.Enable API Logging
Configure HTTP client logging in config/packages/http_client.yaml:
framework:
http_client:
logging: true
Validate API Responses
Use dd() to inspect raw responses:
$response = $this->aiClient->embed(['input' => $text]);
dd($response->toArray()); // Debug raw data
Mocking for Tests
Use Symfony\AI\Client\MockAiClient in PHPUnit:
$mockClient = new MockAiClient();
$mockClient->expects('embed')->andReturn(new EmbeddingResponse([1, 2, 3]));
$this->app->instance(AiClientInterface::class, $mockClient);
Custom Platform Clients
Extend GenericPlatformClient for provider-specific logic:
class OpenAIPlatformClient extends GenericPlatformClient {
protected function getEndpoint(): string {
return 'https://api.openai.com/v1';
}
}
Middleware for AI Requests Add request/response modifiers:
$client = new GenericPlatformClient($config, [
new AddAuthHeaderMiddleware('Bearer %env(AI_TOKEN)%'),
new RetryMiddleware(),
]);
Event Listeners
Subscribe to AI events (e.g., AiClientEvent):
$dispatcher->addListener(AiClientEvent::PRE_REQUEST, function (AiClientEvent $event) {
if ($event->getRequest()->getUri() === 'embed') {
$event->setRequest($event->getRequest()->withHeader('X-Custom-Header', 'value'));
}
});
Laravel Scout Integration Use embeddings with Laravel Scout for vector search:
use Laravel\Scout\Searchable;
class Post extends Model implements Searchable {
public function toSearchableArray() {
return [
'title' => $this->title,
'embedding' => $this->aiClient->embed(['input' => $this->content])->getEmbedding(),
];
}
}
How can I help you explore Laravel packages today?