symfony/ai-open-ai-platform
Symfony integration for OpenAI Platform APIs, providing ready-to-use clients and tooling for text and chat generation, embeddings, and related AI features. Designed to fit Symfony apps with clean configuration and predictable HTTP handling.
composer require symfony/ai-open-ai-platform
.env:
AI_OPEN_AI_KEY="your-openai-api-key"
use Symfony\AI\OpenAI\Client\ChatCompletionClientInterface;
class ChatController extends AbstractController
{
public function __construct(
private ChatCompletionClientInterface $chatClient
) {}
public function ask(ChatMessage $message): Response
{
$response = $this->chatClient->complete(
'gpt-4',
[$message]
);
return $this->json($response->getContent());
}
}
// src/Controller/AiChatController.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class AiChatController extends AbstractController
{
public function __invoke(
Request $request,
ChatCompletionClientInterface $chatClient
): Response {
$prompt = $request->request->get('prompt');
$response = $chatClient->complete(
'gpt-3.5-turbo',
[new ChatMessage(ChatRole::User, $prompt)]
);
return $this->json(['reply' => $response->getContent()]);
}
}
Route it:
# config/routes.yaml
ai_chat:
path: /ai/chat
controller: Symfony\Component\HttpKernel\HttpCache\AiChatController
methods: POST
Chat Completions
// Single turn
$response = $chatClient->complete(
'gpt-4',
[new ChatMessage(ChatRole::User, 'Explain quantum computing')]
);
// Multi-turn conversation
$messages = [
new ChatMessage(ChatRole::System, 'You are a helpful assistant'),
new ChatMessage(ChatRole::User, 'What is PHP?'),
new ChatMessage(ChatRole::Assistant, 'PHP is a server-side scripting language...'),
new ChatMessage(ChatRole::User, 'Give me an example')
];
$response = $chatClient->complete('gpt-3.5-turbo', $messages);
Streaming Responses
use Symfony\AI\OpenAI\Client\StreamingChatCompletionClientInterface;
$stream = $streamingChatClient->stream(
'gpt-3.5-turbo',
[new ChatMessage(ChatRole::User, 'Tell me a joke')]
);
foreach ($stream as $delta) {
echo $delta->getContent();
}
Embeddings
$embeddingClient = $container->get(EmbeddingClientInterface::class);
$embeddings = $embeddingClient->create('text-embedding-ada-002', ['Your text here']);
Service Layer Abstraction
// src/Service/AiChatService.php
class AiChatService
{
public function __construct(
private ChatCompletionClientInterface $chatClient
) {}
public function generateResponse(string $prompt): string
{
$response = $this->chatClient->complete(
'gpt-3.5-turbo',
[new ChatMessage(ChatRole::User, $prompt)]
);
return $response->getContent();
}
}
Event-Driven Architecture
// Listen to chat completion events
$eventDispatcher->addListener(
ChatCompletionEvent::class,
function (ChatCompletionEvent $event) {
// Log or process the response
$this->logger->info('AI Response:', ['content' => $event->getContent()]);
}
);
Command Bus for Async Tasks
// src/Command/GenerateEmbeddingsCommand.php
class GenerateEmbeddingsCommand implements CommandInterface
{
public function __construct(
private EmbeddingClientInterface $embeddingClient
) {}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$texts = ['Text 1', 'Text 2'];
$embeddings = $this->embeddingClient->create('text-embedding-ada-002', $texts);
$output->writeln('Generated embeddings');
return Command::SUCCESS;
}
}
Environment-Specific Config
# config/packages/ai_open_ai.yaml
ai_open_ai:
client:
api_key: '%env(AI_OPEN_AI_KEY)%'
base_uri: '%env(AI_OPEN_AI_BASE_URI)%'
timeout: 30
retries: 3
model_routing:
default: 'gpt-3.5-turbo'
premium: 'gpt-4'
Model Routing
// Dynamically route models based on user tier
$model = $user->isPremium() ? 'gpt-4' : 'gpt-3.5-turbo';
$response = $chatClient->complete($model, $messages);
API Key Management
parameter_bag or environment variables# config/packages/ai_open_ai.yaml
ai_open_ai:
client:
api_key: '%env(AI_OPEN_AI_KEY)%'
Rate Limiting
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
try {
$response = $httpClient->request('POST', $url);
} catch (TransportExceptionInterface | ClientExceptionInterface $e) {
if ($e->getCode() === 429) {
sleep(2); // Simple backoff
retry();
}
}
Token Limits
$tokenCount = $this->countTokens($prompt);
if ($tokenCount > 4000) {
throw new \RuntimeException('Prompt too long');
}
Streaming Quirks
DeltaInterface objects, not raw strings$stream = $streamingChatClient->stream('gpt-3.5-turbo', [$message]);
$fullResponse = '';
foreach ($stream as $delta) {
$fullResponse .= $delta->getContent();
// Process partial content if needed
}
Enable Debug Mode
ai_open_ai:
client:
debug: true
var/log/dev.logInspect API Errors
try {
$response = $chatClient->complete('invalid-model', [$message]);
} catch (ApiErrorException $e) {
$error = $e->getError();
// Handle specific OpenAI errors
if ($error->getType() === 'invalid_request_error') {
// Model not found, etc.
}
}
Token Counting
use Symfony\AI\OpenAI\Tokenizer\TokenizerInterface;
$tokenizer = $container->get(TokenizerInterface::class);
$tokenCount = $tokenizer->countTokens($prompt);
Custom Model Clients
// src/OpenAI/CustomChatClient.php
class CustomChatClient implements ChatCompletionClientInterface
{
private ChatCompletionClientInterface $decorated;
public function __construct(ChatCompletionClientInterface $decorated)
{
$this->decorated = $decorated;
}
public function complete(string $model, array $messages, ?array $options = null): ChatCompletionResponseInterface
{
// Add custom logic (e.g., prompt validation)
if (str_contains($messages[0]->getContent(), 'hack')) {
throw new \RuntimeException('Blocked prompt');
}
return $this->decorated->complete($model, $messages, $options);
}
}
Register as service:
services:
Symfony\AI\OpenAI\Client\ChatCompletionClientInterface: '@App\OpenAI\CustomChatClient'
Response Transformers
// src/OpenAI/ResponseTransformer.php
class ResponseTransformer implements Response
How can I help you explore Laravel packages today?