RAG sin la mística: recuperación y luego generación
Un camino sencillo que va desde el procesamiento en bloques e índices hasta respuestas fundamentadas, con operadores de citación que permiten realizar auditorías.
Utilícelo como una versión reestructurada dirigida a los operadores de las ideas presentadas en “RAG, Explicado como Ojalá Alguien Me Lo Hubiera Explicado”: etapas claras, espacios ordenados para el código y notas de recuperación que sobreviven al traspaso de tareas. La visión general funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
1. La analogía que hace comprender RAG
Para 1. La analogía que hace funcionar RAG, defina las entradas, 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. 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. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.
2. Las dos partes de todo sistema RAG
En 2. Las dos partes de todo sistema RAG, 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. 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 pueden distinguir entre alucinaciones y brechas en el indexado.
3. Etapa 1: Indexación: Convertir documentos en algo buscable
En la Etapa 1 — Indexación: Convertir documentos en algo buscable, 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. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Cite los pasajes que realmente sirvieron de base para la respuesta; sin citas, los operadores no pueden distinguir entre alucinaciones y deficiencias en la indexación.
Etapa 1: Cargar sus documentos
Para Paso 1: Cargar sus documentos, 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 secretos y las banderas de funcionalidad deben encontrarse en un lugar que los operadores puedan auditar sin necesidad de leer todo el sistema. Mencione las secciones que realmente sirvieron como base para la respuesta. Sin citaciones, los operadores no podrán distinguir entre una alucinación y una laguna en el indexado.
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("company_policy.pdf")
documents = loader.load()
print(f"Loaded {len(documents)} pages")
Paso 2: Dividirlo en fragmentos — y este paso es más importante de lo que la gente cree
Para el Paso 2: Dividirlo en fragmentos —y este paso es más importante de lo que la gente cree—, 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. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # characters per chunk
chunk_overlap=80, # overlap between consecutive chunks
separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks")
Paso 3: Incorporar los fragmentos
Para Paso 3: Incrustar los fragmentos, 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. Cite los pasajes que realmente sustentan la respuesta. Sin citaciones, los operadores no pueden distinguir entre alucinaciones y brechas en el indexado.
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
# under the hood, this is what happens per chunk:
vector = embedding_model.embed_query("30-day refund window for annual plans")
print(len(vector)) # e.g. 1536 numbers representing this sentence's meaning
"refund policy" •
\
• "money-back guarantee"
"vacation days" •
\
• "paid time off"
Paso 4: Almacenar los vectores en una base de datos vectorial
Para Paso 4: Almacenar los vectores en una base de datos vectorial, 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. 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 podrán distinguir entre alucinaciones y lagunas en el indexado.
from langchain_community.vectorstores import FAISS
vectorstore = FAISS.from_documents(chunks, embedding_model)
vectorstore.save_local("faiss_index")
4. Etapa 2 — Recuperación + Generación: Responder a una pregunta real
Para 4. Etapa 2 — Recuperación + Generación: Responder a una pregunta real, 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. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. 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. Para 4. Etapa 2 — Recuperación + Generación: Responder a una pregunta real, 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. Documente tanto la ruta óptima como la ruta de recuperación. Retr
Las puertas humanas y el manejo de correos no entregados forman parte del producto, no son mejoras posteriores.Paso 1: Incrustar la consulta — con el mismo modelo
Al trabajar en Paso 1: Incrustar la consulta — con el mismo modelo, 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. 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. Almacene en caché las instrucciones del sistema estables y los esquemas de herramientas. Reenviar un preámbulo idéntico es una causa común de sobrecarga.
query = "Can I get a refund on my annual subscription?"
Paso 2: Obtener los fragmentos más relevantes
Al trabajar en Paso 2: Obtener los fragmentos más relevantes, 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. Considere 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 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.
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
relevant_chunks = retriever.invoke(query)
for chunk in relevant_chunks:
print(chunk.page_content[:100], "...")
Paso 3: Incluir los fragmentos en un prompt
Al trabajar en Paso 3: Incluir los fragmentos en un prompt, anote primero el contrato: entradas requeridas, 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. 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 se pasa de entornos de demostración a entornos compartidos. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de gastos excesivos. Al trabajar en Paso 3: Incluir los fragmentos en un prompt, anote primero el contrato: entradas requeridas, 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. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("""
Answer the question using ONLY the context below.
If the answer isn't in the context, say "I don't have that information."
Context:
{context}
Question: {question}
""")
Paso 4: Unirlo todo
Paso 4: Unirlo todo 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. 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. 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.
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini")
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("Can I get a refund on my annual subscription?")
print(answer)
5. Por qué esto es mejor que simplemente pegar todo el documento en la instrucción
5. Por qué esto es mejor que simplemente pegar todo el documento en la solicitud: 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. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de forma excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas.
6. Errores comunes (aprendidos a la fuerza)
6. Errores comunes (aprendidos a costa de dificultades) funciona mejor cuando se trata como una superficie medible. Capture un transcripte ideal, 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 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. 6. Errores comunes (aprendidos a costa de dificultades) funciona mejor cuando se trata como una superficie medible. Capture un transcripte ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.
7. Hacia dónde ir a partir de aquí
En 7. ¿A dónde ir a partir de aquí?, 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 y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta; sin citas, los operadores no pueden distinguir entre alucinaciones y lagunas en el indexado.
Lista de verificación operativa
Al trabajar con la lista de verificación operativa, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista garantiza que los cambios posteriores en el código sean transparentes.
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 único lugar que los operadores puedan auditar sin tener que leer todo el sistema.
Mida la tasa de recuperación 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.
Fije las versiones de las dependencias y registre el resumen de la imagen que se utilizó para ejecutar la demostración. La reproducibilidad es mejor que el conocimiento basado en prácticas internas.
Documente tanto la ruta óptima como la ruta de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son algo que se añade posteriormente.
Mida la tasa de recuperación 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.
Antes de promocionar el conjunto de herramientas, congele las versiones, capture una transcripción de referencia para la ruta crítica y confirme los pasos para revertir cambios. Los entornos compartidos requieren límites de velocidad, verificaciones de asignación y un responsable claro para la rotación de credenciales secretas. Prefiera una fiabilidad sólida a demostraciones ingeniosas pero puntuales.
Nota para el lote 372916c7a432: mantenga las claves del proveedor fuera del repositorio, establezca un límite para los tokens por sesión y almacene las transcripciones junto a los archivos de prueba para que los cambios en los modelos posteriores sigan siendo comparables.
Lecturas relacionadas
- Explicación de la Generación Aumentada con Recuperación: Arreglando las Fallas en el Conocimiento de los LLM — Aprenda por qué los LLM generan información errónea y se vuelven obsoletos, y luego vea paso a paso cómo la técnica RAG recupera datos, los divide en fragmentos, los incrusta y mejora las solicitudes para solucionarlo.