Lección 5 de 7 35 min

RAG básico en PHP: combina tu base de datos con un LLM

RAG (Retrieval-Augmented Generation) es el patrón más útil para integrar IA en una app PHP empresarial: primero buscas información en tu base de datos, y después se la pasas al LLM como contexto para que responda. Resultado: el LLM responde con datos que sólo tu empresa tiene, sin entrenar ningún modelo propio.

Lo que vas a entender y construir

  1. Qué es RAG y por qué importa más que el "entrenamiento" para el 90% de casos reales.
  2. Esquema mental simple: buscar → enriquecer prompt → generar respuesta.
  3. RAG básico en 50 líneas: PHP + MySQL + Claude/OpenAI.
  4. Cuándo te basta búsqueda LIKE clásica y cuándo necesitas búsqueda semántica con embeddings.

Qué es RAG en una frase

RAG = el LLM no inventa, contesta con TUS datos que tú le pasaste antes en el prompt.

Por ejemplo, sin RAG:

Usuario: "¿Cuántos pedidos hizo Juan García en mayo?"
LLM (sin RAG): "Lo siento, no tengo acceso a vuestra base de datos. No puedo responder."

Con RAG:

Tu PHP: consulta MySQL → encuentra 12 pedidos de Juan García en mayo → pasa esa info al LLM como contexto.
LLM (con RAG): "Juan García hizo 12 pedidos en mayo por un total de 1.847 €. El más caro fue el del 18/05 (315 €)."

El LLM no "sabe" nada de tus datos. Tú se los pasas en el prompt cada vez que llamas. Esa es toda la magia.

Esquema mental del flujo RAG

Usuario pregunta algo
        ↓
[1. RETRIEVAL] Tu PHP busca en MySQL información relevante
        ↓
[2. AUGMENT] Construyes un prompt que incluye esa información como contexto
        ↓
[3. GENERATION] Llamas al LLM con el prompt enriquecido
        ↓
LLM devuelve respuesta basada en los datos que le diste
        ↓
Tu PHP devuelve la respuesta al usuario

RAG básico funcional en 50 líneas

Vamos a construir un asistente que responde sobre pedidos de una pyme. Asumimos que tienes una tabla pedidos en MySQL:

CREATE TABLE pedidos (
    id INT PRIMARY KEY AUTO_INCREMENT,
    cliente_nombre VARCHAR(200),
    producto VARCHAR(200),
    importe DECIMAL(10,2),
    fecha DATE
);

Crea rag.php:

<?php
require __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

use Symfony\Component\HttpClient\HttpClient;

// ─── 1. RETRIEVAL: buscar en MySQL ───────────────────────────────
function buscarPedidosCliente(PDO $pdo, string $nombreCliente): array
{
    $stmt = $pdo->prepare(
        "SELECT id, producto, importe, fecha
         FROM pedidos
         WHERE cliente_nombre LIKE :nombre
         ORDER BY fecha DESC
         LIMIT 20"
    );
    $stmt->execute([':nombre' => "%$nombreCliente%"]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// ─── 2. AUGMENT: construir prompt con contexto ──────────────────
function construirPrompt(string $preguntaUsuario, array $pedidos): string
{
    if (empty($pedidos)) {
        $contexto = "No hay pedidos para ese cliente en la base de datos.";
    } else {
        $lineas = array_map(fn($p) =>
            "- Pedido #{$p['id']}: {$p['producto']} · {$p['importe']}€ · {$p['fecha']}",
            $pedidos
        );
        $contexto = "Pedidos encontrados (más recientes primero):\n" . implode("\n", $lineas);
    }

    return "Eres un asistente que responde preguntas sobre pedidos de clientes.\n"
         . "Responde de forma concisa basándote SOLO en los datos siguientes. "
         . "Si la pregunta no se puede responder con los datos, dilo claramente.\n\n"
         . "DATOS:\n$contexto\n\n"
         . "PREGUNTA DEL USUARIO:\n$preguntaUsuario";
}

// ─── 3. GENERATION: llamar al LLM ───────────────────────────────
function preguntarLLM(string $prompt): string
{
    $client = HttpClient::create();
    $response = $client->request('POST', 'https://api.anthropic.com/v1/messages', [
        'headers' => [
            'x-api-key' => $_ENV['ANTHROPIC_API_KEY'],
            'anthropic-version' => '2023-06-01',
        ],
        'json' => [
            'model' => 'claude-haiku-4-5-20251001',
            'max_tokens' => 500,
            'messages' => [['role' => 'user', 'content' => $prompt]],
        ],
    ]);
    return $response->toArray()['content'][0]['text'];
}

// ─── ORQUESTACIÓN ───────────────────────────────────────────────
$pdo = new PDO(
    'mysql:host=localhost;dbname=mi_tienda;charset=utf8mb4',
    'root',
    'password',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

$pregunta = "¿Cuánto se ha gastado María López en total y cuál fue su pedido más caro?";
$nombreExtraido = 'María López';  // en producción extraerías esto con otro LLM o regex

$pedidos = buscarPedidosCliente($pdo, $nombreExtraido);
$prompt = construirPrompt($pregunta, $pedidos);
echo preguntarLLM($prompt);

Eso es RAG funcional. 50 líneas y tienes un asistente que responde sobre los datos reales de tu pyme.

Variantes del retrieval

Búsqueda LIKE / FULLTEXT (más simple)

Si tus consultas son por palabras exactas o por filtros estructurados (cliente, fecha, importe), basta con SQL clásico (LIKE, FULLTEXT INDEX de MySQL). Es lo que has visto arriba. Rápido, gratis, suficiente para el 80% de casos.

Búsqueda semántica con embeddings (más potente)

Si el usuario hace preguntas tipo "¿qué pedidos parecen problemáticos?" o "encuentra reseñas de clientes molestos", necesitas búsqueda por significado, no por palabras. Para eso usas embeddings: vectores numéricos que representan el significado del texto.

Flujo embeddings:

  1. Cada texto de tu BD se convierte en un vector (1 llamada API al servicio de embeddings).
  2. Guardas el vector en tu BD (columna JSON o extensión pgvector en PostgreSQL).
  3. Cuando llega una pregunta, conviertes la pregunta en vector también.
  4. Buscas los vectores más cercanos (similitud coseno) → esos son los resultados semánticamente relevantes.

Esto requiere más infra (BD vectorial). Para empezar, MySQL + LIKE basta. Cuando lo necesites, salta a Pinecone, Weaviate, Qdrant o PostgreSQL+pgvector.

Buenas prácticas RAG

  • Limita el contexto: no pases 10.000 filas. Pasa las 20 más relevantes. El LLM se confunde con contextos enormes.
  • Sé explícito en el prompt: "Responde SOLO basándote en los datos siguientes" reduce las alucinaciones.
  • Permite "no sé": di al modelo que si la respuesta no está en los datos, lo diga. Mucho mejor que invente.
  • Cita las fuentes: pide al modelo que devuelva qué IDs de pedido usó para componer la respuesta. Trazabilidad.

Checklist antes de marcar completada

  • Puedes explicar RAG en 2 frases: el LLM no sabe nada de tus datos, tú se los pasas en el prompt.
  • Entiendes el flujo: retrieval (busca en BD) → augment (construye prompt) → generation (LLM responde).
  • Has ejecutado rag.php (adaptado a tu BD si la tienes) y ves respuestas coherentes.
  • Sabes cuándo basta búsqueda LIKE clásica y cuándo necesitas embeddings.
  • Tienes interiorizadas las 4 buenas prácticas.

En la próxima lección aplicamos todo en un caso real completo: analizar el tono (positivo / negativo / neutro) de cientos de reseñas de clientes con IA y guardar los resultados en MySQL para que un dashboard los muestre. Esto es lo que vende a clientes finales.

📝 Comprueba lo aprendido

Responde estas 4 preguntas y acierta al menos el 60% para desbloquear el botón "Marcar como completada".

1 ¿Qué significa RAG en una frase?

2 En el flujo RAG, ¿qué hace la fase "RETRIEVAL"?

3 ¿Cuándo te basta búsqueda LIKE clásica en MySQL para el RAG y cuándo necesitas embeddings?

4 Buena práctica RAG: ¿qué incluyes SIEMPRE en el prompt?

Cuando termines, guarda tu avance. Así podrás continuar desde tu panel y desbloquear el certificado al completar el curso.