Inicio / Artículos / Notas prácticas: Explicación de RAG: cómo las aplicaciones permiten que la IA acceda a sus propios datos

Notas prácticas: Explicación de RAG: cómo las aplicaciones permiten que la IA acceda a sus propios datos

Guía práctica paso a paso: Explicación de RAG: cómo las aplicaciones permiten que la IA acceda a sus propios contratos, cheques y espacios para código, dirigida a los equipos que implementan este patrón.

2693 palabras

Úselo como una versión reestructurada dirigida a operadores de las ideas presentadas en “RAG Explained: How Applications Give AI Access to Their Own Data”: etapas claras, espacios ordenados para el código y notas de recuperación que perduran tras un traspaso de tareas. La etapa de Resumen funciona mejor cuando se considera como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado.

¿Qué es la Generación Aumentada por Recuperación de Información (RAG)?

En la etapa de ¿Qué es la generación reforzada por recuperación?, defina las entradas, el responsable de dicha etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sirvieron de base para la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

Without RAG:
[User Question] ──> [LLM] ──> [Answer based on generic training data]
With RAG:
[User Question] ──> [Search Engine / Vector DB]
                          │
                          └──> [Relevant Documents]
                                     │
[User Question] + [Relevant Documents] ──> [LLM] ──> [Factual Answer]

Por qué es importante RAG: las limitaciones que resuelve

Para entender por qué es importante RAG, se debe definir la etapa a seguir, las entradas necesarias, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Se deben registrar los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad sobre los costos desde el principio evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Es necesario citar los fragmentos que realmente sirvieron de base para la respuesta; sin estas referencias, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

¿Cómo funciona RAG en secreto?

En la etapa de “¿Cómo funciona RAG?”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos confidenciales y las banderas de funcionalidad deben encontrarse en un lugar que los operadores puedan auditar sin necesidad de leer todo el sistema. Cite los pasajes que realmente sirvieron como base para la respuesta. Sin citaciones, los operadores no podrán distinguir entre alucinaciones y lagunas en el indexado. En la etapa de “¿Cómo funciona RAG?”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado.

1. Ingestion Pipeline (Offline / Background):
[Raw Documents] ──> [Chunking] ──> [Embedding Model] ──> [Vector Database]
2. Query Pipeline (Runtime):
[User Question] ──> [Embedding Model] ──> [Vector Search in DB]
                                                  │
                                                  ▼
[User Question] + [Top Matching Chunks] ──> [LLM] ──> [Final Answer]

1. Fragmentación

Al trabajar en la etapa de 1 Fragmentación, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

2. Embeddings

Al trabajar en la etapa de 2 Embeddings, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

3. Almacenamiento vectorial

Al trabajar en la etapa de Almacenamiento Vectorial 3, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Mida la tasa de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en la etapa de Almacenamiento Vectorial 3, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado.

4. Búsqueda Vectorial (Similitud Coseno)

La etapa de búsqueda vectorial por coseno funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

5. Aumento de prompts

La etapa de 5 ampliaciones de prompts funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de manera intensiva; los límites estrictos impiden que las demostraciones se conviertan en facturas inesperadas.

Implementación práctica en Node.js

La implementación práctica en la etapa de Node funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Separe la política de particionamiento de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. La implementación práctica en la etapa de Node funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.

Paso 1: Ingestión y vectorización de documentos

En la etapa 1 de ingestión y procesamiento, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Cite los pasajes que realmente sustentan la respuesta; sin citas, los operadores no podrán distinguir entre alucinaciones y lagunas en el indexado.

// ingest.js
import { OpenAI } from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// In a real application, you would load these from Markdown files or a CMS
const documents = [
  {
    id: "doc_1",
    text: "Enterprise customers can request a full refund within 30 days of contract signing. Contact enterprise-support@example.com."
  },
  {
    id: "doc_2",
    text: "Monthly self-serve subscriptions are non-refundable once the billing cycle begins. Users can cancel anytime to avoid future charges."
  },
  {
    id: "doc_3",
    text: "Custom engineering work and onboarding packages are strictly non-refundable once the kickoff meeting has taken place."
  }
];
async function createEmbedding(text) {
  const response = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: text,
  });
  return response.data[0].embedding;
}
export async function buildKnowledgeBase() {
  const embeddedDocs = [];
  for (const doc of documents) {
    const vector = await createEmbedding(doc.text);
    embeddedDocs.push({ ...doc, vector });
  }
  return embeddedDocs;
}

Etapa 2: Encontrar el contexto relevante

En la etapa 2, “Buscar información relevante”, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando la tarea pasa de un entorno de demostración a uno compartido. Cite los pasajes que realmente sirvieron como base para la respuesta; sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.

// search.js
function cosineSimilarity(vecA, vecB) {
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < vecA.length; i++) {
    dotProduct += vecA[i] * vecB[i];
    normA += vecA[i] * vecA[i];
    normB += vecB[i] * vecB[i];
  }
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
export function findTopMatches(queryVector, knowledgeBase, topK = 2) {
  return knowledgeBase
    .map(doc => ({
      ...doc,
      score: cosineSimilarity(queryVector, doc.vector)
    }))
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

Paso 3: Mejorar el prompt y generar una respuesta

En el Paso 3 de Mejora de la etapa, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea código o una llamada a una herramienta. En el Paso 3 de Mejora de la etapa, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad en lugar de un proceso complicado.

// answer.js
import { OpenAI } from "openai";
import { createEmbedding, buildKnowledgeBase } from "./ingest.js";
import { findTopMatches } from "./search.js";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function askKnowledgeBase(userQuestion, knowledgeBase) {
  // 1. Convert user question into an embedding
  const questionVector = await createEmbedding(userQuestion);
  // 2. Retrieve top matching chunks
  const matches = findTopMatches(questionVector, knowledgeBase, 1);
  const contextText = matches.map(m => m.text).join("\n---\n");
  // 3. Augment prompt with retrieved context
  const systemPrompt = `
You are a helpful customer service assistant.
Answer the user's question using ONLY the context provided below.
If the context does not contain the answer, say "I do not have enough information to answer that."
Context:
${contextText}
`;
  // 4. Generate answer
  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: systemPrompt },
      { role: "user", content: userQuestion }
    ],
    temperature: 0.2, // Low temperature minimizes creative liberties
  });
  return completion.choices[0].message.content;
}
// Example usage:
const knowledgeBase = await buildKnowledgeBase();
const answer = await askKnowledgeBase("I signed an enterprise deal 2 weeks ago, can I get my money back?", knowledgeBase);
console.log(answer);
// Output: Yes, enterprise customers can request a full refund within 30 days of contract signing. You can email enterprise-support@example.com to initiate the process.

Errores comunes que cometen los desarrolladores con RAG

Al trabajar en la etapa de Errores comunes que cometen los desarrolladores, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Mida el recuerdo con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

1. Fragmentación ingenua

Al trabajar en la etapa 1 de Fragmentación Naiva, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mida el rendimiento en un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

2. Depender únicamente de la búsqueda vectorial

Al trabajar en la fase de “Depender únicamente de 2 elementos”, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Mida la capacidad de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente. Al trabajar en la fase de “Depender únicamente de 2 elementos”, anote primero el contrato: los datos de entrada necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe indicar una única responsabilidad y no un proceso complicado.

3. Incluir demasiado contexto (el problema de quedar atrapado en el medio)

La etapa de “Incluir demasiado contexto” funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

¿Cuándo debería usar RAG en lugar de ajuste fino?

La etapa “¿Cuándo debería usarse?” funciona mejor si se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Separe la política de fragmentación de la política de recuperación; cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad.

Conclusión

La etapa de Conclusión funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Separe la política de fragmentación de la política de recuperación. Cambiar una no debe obligar a reescribir la otra cuando cambian las métricas de calidad. La etapa de Conclusión funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.

Lista de verificación operativa

Al trabajar en la fase de lista de verificación operativa, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código.

Documente tanto el camino óptimo como el de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

Mida el rendimiento de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Fije las versiones de las dependencias y registre el resumen de la imagen que se utilizó para la demostración. La reproducibilidad es mejor que el conocimiento basado en prácticas internas.

Prefiera unidades pequeñas y verificables a scripts extensos. Cuando falla un paso, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado.

Mida el rendimiento de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.

Antes de promocionar el stack, congele las versiones, capture una transcripción de referencia para la ruta crítica y confirme los pasos de reversión. Los entornos compartidos requieren límites de velocidad, verificaciones de tenencia y un responsable claro para la rotación de secretos. Prefiera una fiabilidad sencilla a demostraciones ingeniosas pero puntuales.

Nota para el lote 2c8785c21af6: mantenga las claves del proveedor fuera del repositorio, establezca un límite máximo para tokens por sesión y almacene las transcripciones junto a los fixtures de evaluación para que los cambios posteriores en el modelo sigan siendo comparables.