Install the Bundle:
composer require symfony/ai-bundle
Enable in config/bundles.php:
return [
// ...
Symfony\AI\Bundle\AiBundle::class => ['all' => true],
];
Configure a Platform (config/packages/ai.yaml):
ai:
platforms:
openai:
client: 'ai.platform.openai'
model: 'gpt-4'
api_key: '%env(OPENAI_API_KEY)%'
First Use Case: Simple Chat Agent Create a controller to interact with the platform:
use Symfony\AI\Agent\AgentInterface;
use Symfony\AI\Agent\AgentFactoryInterface;
class ChatController
{
public function __construct(
private AgentFactoryInterface $agentFactory
) {}
public function chat(string $message): string
{
$agent = $this->agentFactory->createAgent('chat', [
'model' => 'gpt-4',
'platform' => 'openai',
'tools' => [],
'systemPrompt' => 'You are a helpful assistant.',
]);
$response = $agent->chat([$message]);
return $response->getContent();
}
}
Route the Controller:
# config/routes.yaml
chat:
path: /chat
controller: App\Controller\ChatController::chat
config/packages/ai.yaml for platform/store setup.src/Agent/ for custom agent logic.tests/ for integration patterns (e.g., AgentFactoryTest).ai.yaml with client aliases and models.
ai:
platforms:
anthropic:
client: 'ai.platform.anthropic'
model: 'claude-3'
api_key: '%env(ANTHROPIC_API_KEY)%'
Provider abstraction to route requests to the correct platform:
$provider = $this->container->get('ai.platform_provider');
$response = $provider->get('anthropic')->chat([$message]);
#[AsTool] for automatic registration:
use Symfony\AI\Agent\Attribute\AsTool;
class MyTools {
#[AsTool(name: 'fetch_weather', description: 'Fetches weather data')]
public function fetchWeather(string $location): string {
return "Weather in $location: Sunny";
}
}
use Symfony\AI\Agent\InputProcessorInterface;
class SanitizeInputProcessor implements InputProcessorInterface {
public function process(array $input): array {
return array_map('htmlspecialchars', $input);
}
}
Register in services.yaml:
services:
App\Agent\SanitizeInputProcessor:
tags: ['ai.agent.input_processor']
ai:
stores:
documents:
type: 'sqlite'
path: '%kernel.project_dir%/var/vector_store.sqlite'
$query = new Query('Symfony AI', [
'filter' => ['category' => 'framework'],
]);
$results = $store->search($query, 5);
use Symfony\AI\Agent\Attribute\IsGrantedTool;
class AdminTools {
#[IsGrantedTool('ROLE_ADMIN')]
#[AsTool(name: 'delete_user')]
public function deleteUser(int $userId): bool {
// ...
}
}
$agent->chat([$message]); // Logs to /_profiler/ai
$this->container->get('ai.data_collector')->collectTokens($agent, $response);
ScopingHttpClient for platform-specific API keys:
ai:
platforms:
openai:
client: 'ai.platform.openai'
http_client: 'ai.http_client.openai'
Configure in config/packages/http_client.yaml:
http_clients:
ai.openai:
base_uri: 'https://api.openai.com/v1'
auth_bearer: '%env(OPENAI_API_KEY)%'
PlatformBridgeInterface for unsupported providers:
use Symfony\AI\Platform\PlatformBridgeInterface;
class CustomPlatformBridge implements PlatformBridgeInterface {
public function chat(array $messages, array $options): string {
// Custom logic
}
}
Register in services.yaml:
services:
App\AI\CustomPlatformBridge:
tags: ['ai.platform_bridge']
MockPlatform for unit tests:
use Symfony\AI\Platform\MockPlatform;
$mockPlatform = new MockPlatform();
$mockPlatform->expects($this)->chat([$message])->willReturn('Mocked response');
$this->container->set('ai.platform.openai', $mockPlatform);
ai:
platforms:
openai:
cache: true
StreamingResponse for real-time interactions:
$response = $agent->chat([$message], ['stream' => true]);
foreach ($response->getDeltas() as $delta) {
echo $delta->getContent();
}
StoreFactory may change).
Workaround: Pin to a specific version (e.g., v0.8.0) and monitor Symfony AI issues.ai.platform.openai, the last one wins.
Workaround: Use explicit aliases in services.yaml:
services:
ai.platform.openai: '@ai.platform.openai_custom'
type: 'memory' for testing or upgrade to PostgreSQL.type: 'mariadb' with cosine enabled).#[IsGrantedTool] are accessible to all users.
Workaround: Always annotate tools or use #[IsGrantedTool('IS_AUTHENTICATED_FULLY')] as a default.OutputProcessorInterface to redact data:
class RedactPIIProcessor implements OutputProcessorInterface {
public function process(string $content): string {
return preg_replace('/\b\d{3}-\d{2}-\d{4}\b/', '[REDACTED]', $content);
}
}
RewindableGenerator) may break streaming.
Workaround: Convert to arrays or use iterator_to_array():
$deltas = iterator_to_array($response->getDeltas());
CompilerPass may fail if services depend on each other.
Workaround: Use onLoad() to defer registration:
public function process(ContainerBuilder $container) {
$container->setCompilerPass(new MyCompilerPass(), PassConfig::TYPE_OPTIMIZE);
}
How can I help you explore Laravel packages today?