El ecosistema PHP ha dado un paso importante con la llegada de Symfony AI, un conjunto de componentes oficiales que te permite integrar modelos de lenguaje (LLMs) directamente en tus aplicaciones Symfony, sin tener que salir del entorno PHP ni renunciar a la arquitectura limpia que tanto nos gusta.
Hasta hace poco, si querías trabajar con IA en un proyecto Symfony, lo más habitual era delegar esa parte a un microservicio en Python o Node.js. Symfony AI cambia eso: te ofrece una alternativa sólida y nativa para muchos casos de uso reales. No pretende reemplazar el ecosistema de Python (especialmente en investigación, fine-tuning o tareas muy avanzadas), pero sí te permite construir funcionalidades inteligentes dentro de tu aplicación Symfony manteniendo todo en el mismo stack.
Nota importante: En el momento de publicar este artículo, Symfony AI se encuentra en estado experimental (última versión estable:
v0.6.0). Esto significa que la API puede sufrir cambios y que no está cubierta por la promesa de compatibilidad hacia atrás de Symfony. Puedes usarlo en producción, pero con esa precaución en mente.
1. ¿Qué es Symfony AI y qué problema resuelve?
Symfony AI no es simplemente un wrapper de la API de OpenAI. Es una capa de abstracción pensada con la filosofía de Symfony: desacoplamiento, extensibilidad e integración profunda con el Service Container.
Sus componentes principales son:
- Platform: una interfaz unificada para conectarte a más de 25 proveedores de modelos (OpenAI, Anthropic, Google Gemini, Azure, Mistral, Ollama, etc.).
- Agent: el framework para crear agentes que razonan, usan herramientas y gestionan flujos complejos.
- Chat: una API de alto nivel para manejar conversaciones con historial.
- Store: abstracción sobre bases de datos vectoriales para implementar RAG.
- AI Bundle: la integración de todo lo anterior con el contenedor de servicios de Symfony.
Gracias a esto, puedes cambiar de proveedor (por ejemplo, de OpenAI a Anthropic) solo modificando la configuración, sin tocar ni una línea de tu lógica de negocio.
2. Los cuatro pilares en detalle
Platform – La conexión con los modelos
El componente Platform se encarga de la comunicación con los distintos proveedores de IA. Ofrece una interfaz común (PlatformInterface) que oculta las diferencias entre las APIs de cada uno.
Puedes enviarle un MessageBag con mensajes del sistema y del usuario, y recibir una respuesta estructurada. Además, soporta streaming mediante Server-Sent Events, lo que permite mostrar las respuestas en tiempo real al usuario (tema que desarrollaremos en el próximo artículo).
Agent – El núcleo lógico
El Agent es el cerebro del sistema. Recibe los mensajes, decide si necesita usar alguna herramienta, ejecuta los ciclos necesarios y devuelve la respuesta final. Es el componente más completo: soporta memoria, procesadores de entrada y salida, subagentes, tolerancia a fallos en las herramientas y mucho más.
Tools – La acción sobre tu código
Aquí está, para mí, una de las ideas más potentes de Symfony AI.
Las tools son clases PHP normales que decoras con el atributo #[AsTool]. El agente puede llamarlas automáticamente cuando detecta que las necesita.
Lo mejor es que Symfony AI genera automáticamente el esquema JSON de cada herramienta a partir de los tipos de los parámetros y los comentarios del docblock. No tienes que escribir descripciones manuales complicadas.
Store – La memoria a largo plazo
El componente Store es una abstracción sobre bases de datos vectoriales (ChromaDB, Pinecone, Weaviate, pgvector, etc.). Es la pieza fundamental para implementar RAG (Retrieval-Augmented Generation): antes de responder, el agente recupera los fragmentos más relevantes de tu propia documentación o base de conocimiento y los incluye en el contexto de la conversación.
3. ¿Cuándo usar Symfony AI (y cuándo no)?
Como con cualquier herramienta, es importante saber dónde encaja y dónde no.
Casos donde funciona muy bien:
- Asistentes conversacionales integrados en una aplicación Symfony ya existente.
- Funcionalidades que necesitan acceder a datos internos (base de datos, APIs propias) durante la conversación.
- Búsqueda semántica o sistemas de preguntas y respuestas sobre documentación interna.
- Flujos automatizados en los que el LLM orquesta acciones sobre tu propio sistema.
Casos donde probablemente no sea la mejor opción:
- Investigación, fine-tuning o entrenamiento de modelos (ahí Python sigue siendo el rey).
- Integraciones muy específicas con SDKs de proveedores que aún no están cubiertas por Symfony AI.
- Proyectos donde la estabilidad de la API es crítica y no puedes permitirte posibles breaking changes (recuerda que el bundle sigue siendo experimental).
4. Ejemplo práctico: un asistente de productos
Vamos a construir un asistente sencillo pero realista: uno que pueda responder preguntas sobre productos consultando datos reales de nuestra aplicación.
Paso 1: Instalación
composer require symfony/ai-bundle
Paso 2: Configuración
# config/packages/ai.yaml
ai:
platform:
openai:
api_key: '%env(OPENAI_API_KEY)%'
agent:
default:
model: 'gpt-4o-mini'
El bundle registra automáticamente el agente por defecto con el alias AgentInterface $defaultAgent.
¿No tienes acceso a una API de pago? No hay problema. Symfony AI soporta varias opciones gratuitas o de coste cero, y lo mejor es que el código de tu aplicación no cambia en absoluto. Solo modificas la configuración del proveedor.
Opción A: Ollama — modelos locales y sin coste
Ollama te permite ejecutar modelos directamente en tu máquina. Es ideal para desarrollo local: sin claves API, sin límites de peticiones y sin latencia de red.
# Bridge oficial
composer require symfony/ai-ollama-platform
# Descargar un modelo (fuera de Symfony)
ollama pull llama3.2
# config/packages/ai.yaml
ai:
platform:
ollama:
host_url: '%env(OLLAMA_HOST_URL)%'
agent:
default:
model: 'llama3.2'
# .env.local
OLLAMA_HOST_URL=http://localhost:11434
Requiere una máquina con suficiente RAM (mínimo 8 GB recomendados para modelos de 7B), pero para desarrollo y aprendizaje es excelente.
Opción B: Google Gemini — free tier generoso
Google Gemini ofrece un tier gratuito bastante usable (gemini-1.5-flash permite unas 1.500 peticiones diarias gratis). No pide tarjeta de crédito.
ai:
platform:
gemini:
api_key: '%env(GEMINI_API_KEY)%'
agent:
default:
model: 'gemini-1.5-flash'
La clave se obtiene gratis en aistudio.google.com.
Opción C: OpenRouter
Proxy unificado a muchos modelos. Útil para probar opciones específicas, aunque su capa gratuita suele saturarse fácilmente.
Opción D: Groq – velocidad extrema y gratuita
Si buscas la mejor combinación actual de velocidad + tool calling gratuito, Groq es difícil de superar. Usa chips LPU especializados.
ai:
platform:
generic:
groq:
base_url: 'https://api.groq.com/openai'
api_key: '%env(GROQ_API_KEY)%'
model_catalog: 'ai.platform.model_catalog.generic.fallback'
agent:
default:
platform: 'ai.platform.generic.groq'
model: 'llama-3.3-70b-versatile'
Nota sobre el catálogo de modelos (Generic Bridge):
Cuando usas el bridge generic, Symfony AI intenta validar el modelo contra su catálogo interno. Si no lo encuentra, falla. La solución es registrar un catálogo comodín:
# config/services.yaml
services:
ai.platform.model_catalog.generic.fallback:
class: Symfony\AI\Platform\Bridge\Generic\FallbackModelCatalog
Resumen de opciones gratuitas y de pago:
| Opción | Coste | Tool Calling | Ideal para |
|---|---|---|---|
| OpenAI (gpt-4o-mini) | De pago (~0.15$/1M tokens) | Producción | |
| Groq (llama-3.3-70b) | Gratis (LPU) | Desarrollo / Agentes rápidos | |
| Ollama (llama3.2) | Gratis (local) | Desarrollo local y privado | |
| Google Gemini | Gratis (hasta límite diario) | Proyectos pequeños |
Nota sobre Tool Calling: No todos los modelos lo soportan bien. En Ollama y Groq, elige modelos como
llama-3.3-70bomistral-nemo. Los modelos muy pequeños suelen fallar al generar el formato de las herramientas.
Paso 3: Crear una Tool
<?php
namespace App\AI\Tool;
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
#[AsTool(
name: 'product_price',
description: 'Returns the current price of a product given its SKU',
)]
final class ProductPriceTool
{
public function __construct(
private readonly ProductRepository $productRepository,
) {}
/**
* @param string $sku The product SKU identifier
*/
public function __invoke(string $sku): string
{
$product = $this->productRepository->findBySku($sku);
if ($product === null) {
return sprintf('No product found with SKU "%s".', $sku);
}
return sprintf(
'The product "%s" (SKU: %s) costs %.2f EUR.',
$product->getName(),
$sku,
$product->getPrice(),
);
}
}
Symfony genera automáticamente el esquema JSON a partir del tipo y el docblock. Puedes inyectar cualquier servicio de Symfony (repositorios, clientes HTTP, etc.).
Paso 4: Crear el servicio de chat
<?php
namespace App\AI;
use Symfony\AI\Agent\AgentInterface;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
final readonly class ProductChatService
{
public function __construct(
private AgentInterface $defaultAgent,
) {}
public function ask(string $userQuestion): string
{
$messages = new MessageBag(
Message::forSystem(
'You are a helpful assistant for an online store. ' .
'Answer questions about products concisely and accurately. ' .
'If you need to check a price or product details, use the available tools.'
),
Message::ofUser($userQuestion),
);
$result = $this->defaultAgent->call($messages);
return $result->getContent();
}
}
Paso 5: Usarlo desde un controlador
#[Route('/api/chat', methods: ['POST'])]
public function chat(Request $request, ProductChatService $chatService): JsonResponse
{
$body = json_decode($request->getContent(), true);
$question = $body['question'] ?? '';
if (empty($question)) {
return $this->json(['error' => 'Question is required'], 400);
}
$answer = $chatService->ask($question);
return $this->json(['answer' => $answer]);
}
5. Qué ocurre internamente
Cuando el usuario pregunta algo como «¿Cuánto cuesta el producto con SKU laptop-pro-1?», el flujo es el siguiente:
- El
Agentenvía el mensaje al modelo junto con el esquema de las herramientas disponibles. - El modelo decide que necesita información externa y responde pidiendo una llamada a la herramienta
product_priceconsku: "laptop-pro-1". - El
AgentProcessorejecutaProductPriceTool::__invoke()y añade el resultado al contexto. - El modelo genera la respuesta final usando esa información real.
Este ciclo puede repetirse varias veces en una sola petición si es necesario. El agente lo gestiona automáticamente.
6. Observabilidad y testing
Symfony Profiler: El bundle añade un panel específico en el Profiler donde puedes ver los prompts enviados, las respuestas recibidas y el consumo de tokens. Es una herramienta invaluable durante el desarrollo.
Testing: Symfony AI incluye InMemoryPlatform, una implementación falsa que te permite probar tu lógica sin llamar a ninguna API real:
use Symfony\AI\Platform\Test\InMemoryPlatform;
$platform = new InMemoryPlatform('The product costs 999.99 EUR.');
$agent = new Agent($platform, $model);
Para cerrar: lo que logramos y lo que viene después
Symfony AI llena un hueco importante en el ecosistema PHP: nos da una forma estructurada, desacoplada y bien integrada de incorporar inteligencia artificial en aplicaciones Symfony.
No es la solución universal, pero para asistentes conversacionales, RAG, automatización de procesos internos o cualquier funcionalidad que necesite combinar LLMs con tus datos y lógica de negocio, ofrece una base muy sólida y agradable de trabajar.
El ejemplo que vimos en este artículo funciona correctamente, pero tiene una limitación clara: devuelve la respuesta completa de una sola vez. Si el modelo tarda varios segundos, la experiencia de usuario no es la ideal.
Puedes descargar y probar el código completo del ejemplo desarrollado en este artículo en el siguiente repositorio:
Repositorio del ejemplo: https://github.com/jure-ve/symfony-ai-product-agent
En el próximo artículo veremos cómo resolver exactamente eso: construiremos un chat en tiempo real con streaming via Server-Sent Events, una interfaz JavaScript para consumir el stream desde el navegador y la gestión del historial de conversación con el componente Chat de Symfony AI.