RAG: Recuperación Aumentada por Generación

¿Te has encontrado alguna vez con estas situaciones?

  • Tienes un manual de producto de cientos de páginas, un usuario hace una pregunta muy concreta y, después de buscar durante un buen rato, no encuentras dónde está la respuesta.

  • Tu empresa ha acumulado durante varios años documentos internos, actas de reuniones y especificaciones técnicas. Un nuevo empleado quiere conocer una regla de negocio y no tiene idea de por dónde empezar a buscar.

  • Tienes un montón de libros electrónicos, informes de investigación y artículos académicos. Quieres que la IA responda preguntas basándose en ese contenido, pero si le pegas el libro entero, no cabe en la ventana de contexto.

Eso es exactamente lo que RAG pretende resolver.

RAG (Retrieval-Augmented Generation, Recuperación Aumentada por Generación) es una técnica que permite a los modelos de lenguaje grandes responder preguntas basándose en una base de conocimiento específica.。

La idea central de RAG es simple: primero encuentra el contenido más relevante en tu base de conocimiento y luego envía ese contenido junto con la pregunta del usuario al LLM, para que el LLM responda basándose en este contexto.

Sin RAG, el LLM solo conoce el contenido de sus datos de entrenamiento; con RAG, el LLM puede utilizar tus datos privados y más recientes.


¿Por qué necesitamos RAG?

RAG no es la única solución a estos problemas, pero es una de las soluciones más prácticas y de menor costo en la actualidad.

Problema de la fecha límite de conocimiento de los LLM

  • Todos los LLM tienen una fecha límite de conocimiento: los datos de entrenamiento solo llegan hasta cierto punto en el tiempo; lo que ocurre después, no lo saben.

  • Por ejemplo, el conocimiento de GPT-4 llega hasta octubre de 2023. Si le preguntas por noticias de 2024, solo puede decir que no lo sabe.

  • Más importante aún, tus datos privados (documentos internos, manuales de producto, normas de empresa) nunca han aparecido en los datos de entrenamiento del LLM. ¿Cómo podría saberlos?

Los datos privados no se pueden introducir directamente en el LLM

Quizás pienses: ¿no puedo simplemente pegarle el documento al LLM?

La respuesta es: con documentos cortos sí, con documentos largos no.

  • En primer lugar, la ventana de contexto del LLM es limitada. Por ejemplo, GPT-3.5 solo tiene 16K tokens, más o menos 10,000 caracteres. Un manual de cientos de páginas simplemente no cabe.

  • En segundo lugar, incluso si la ventana de contexto fuera lo suficientemente grande, meter el documento completo no daría buenos resultados. El LLM tiende a perderse en textos largos y no encuentra la información realmente relevante.

  • Por último, el costo. El contexto de 128K de GPT-4 cuesta 10 dólares por millón de tokens de entrada. Meter el libro completo cada vez hará llorar a tu cartera.

RAG vs. Ajuste fino: cómo elegir

Otro método para que el LLM use datos nuevos es el ajuste fino (Fine-tuning): seguir entrenando el modelo con datos nuevos.

Pero el ajuste fino y RAG tienen diferencias fundamentales y escenarios de aplicación completamente distintos:

Elemento de comparaciónRAGAjuste fino
Actualidad de los datosSe actualiza en cualquier momento, disponible al instanteRequiere reentrenamiento, ciclo largo
Volumen de datos adecuadoMuy grande (millones de documentos)Medio (de miles a decenas de miles de muestras)
Fuente de la citaPuede mostrar de qué documento proviene la respuestaNo se puede rastrear la fuente
Problema de alucinacionesReduce las alucinaciones al proporcionar contextoTodavía puede generar alucinaciones
Modificación del conocimientoBasta con eliminar o actualizar documentos directamenteRequiere reentrenamiento, es difícil "olvidar"
Barrera técnicaBaja, basta con usar frameworks existentesAlta, se necesitan GPU y experiencia en entrenamiento
CostoPrincipalmente bases de datos vectoriales y embeddingsCosto de entrenamiento alto, y también alto el costo de inferencia

Un principio simple para decidir: si quieres que el LLM "sepa" ciertos conocimientos factuales (como descripciones de productos, normas de empresa), usa RAG; si quieres que el LLM "aprenda" un estilo o habilidad (como estilo de escritura, convenciones de código), usa ajuste fino.


Visión general de la arquitectura RAG

RAG se divide en dos fases: la fase de indexación fuera de línea y la fase de recuperación en línea.

Primero veamos un diagrama completo de la arquitectura:

RAG 整体架构流程图

Fase de indexación fuera de línea (preparación de datos)

Esta fase se ejecuta en segundo plano; el usuario no la ve directamente.

Su tarea es procesar tus documentos en un formato que se pueda recuperar rápidamente.

Los pasos son los siguientes:

  • 1. Carga de documentos: Lee documentos en varios formatos como PDF, TXT, DOCX, páginas web, etc.

  • 2. División de documentos: Divide los documentos largos en bloques de texto pequeños (chunks), normalmente de unos cientos a mil palabras.

  • 3. Vectorización: Usa un modelo de embedding para convertir cada bloque de texto en un vector (una serie de números).

  • 4. Almacenamiento: Almacena los vectores junto con el texto original en una base de datos vectorial.

Esta fase solo necesita hacerse una vez, o volver a ejecutarse cuando los documentos se actualicen.

Fase de recuperación en línea (consulta del usuario)

Esta fase ocurre en tiempo real cuando el usuario hace una pregunta.

Los pasos son los siguientes:

  • 1. Vectorización de la pregunta: Usa el mismo modelo de embedding para convertir la pregunta del usuario en un vector.

  • 2. Búsqueda por similitud: Encuentra en la base de datos vectorial los bloques de texto más cercanos al vector de la pregunta.

  • 3. Construcción del Prompt: Usa los bloques de texto recuperados como contexto y ensámblalos junto con la pregunta del usuario en un Prompt.

  • 4. Generación con LLM: Envía el Prompt al LLM y deja que responda basándose en el contexto.

  • 5. Devolución de la respuesta: Devuelve la respuesta del LLM al usuario, generalmente adjuntando las fuentes de documentos citadas.


Introducción a las bases de datos vectoriales

La base de datos vectorial es uno de los componentes centrales de RAG. Para entenderla, primero hay que entender qué es un vector.

¿Qué es un vector (Embedding)?

Un vector (embedding) es una serie de números en la que se convierte un texto.

Por ejemplo, la palabra "gato" podría convertirse en un vector de varios cientos de dimensiones como [0.23, -0.45, 0.12, 0.89, ...].

La clave es:Los textos con significado similar tienen vectores muy cercanos entre sí en el espacio.。

Por ejemplo:

  • Los vectores de "gato" y "gatito" están muy cerca
  • Los vectores de "gato" y "perro" están más cerca que los de "gato" y "automóvil"
  • Los vectores de "me gustan los gatos" y "amo a los gatitos" están muy cerca

Esta es la base de la "búsqueda semántica": no se empareja por palabras clave, sino por significado.

Principio de cálculo de la similitud semántica

La similitud entre dos vectores generalmente se calcula con la "similitud del coseno" (Cosine Similarity).

La similitud del coseno tiene un rango de -1 a 1:

  • 1 indica que son idénticos
  • 0 indica que no están relacionados
  • -1 indica que son completamente opuestos

En la práctica, normalmente solo nos interesa la similitud positiva; cuanto más cerca de 1, más relevante.

Cuando haces una pregunta, la base de datos vectorial calcula rápidamente la similitud entre el vector de la pregunta y todos los vectores de los documentos, y devuelve los primeros más relevantes.

Bases de datos vectoriales más populares

Hay muchas bases de datos vectoriales en el mercado, y cada una tiene sus propias características:

Base de datosTipoCaracterísticasCasos de uso
ChromaLocal/Código abiertoLigero, fácil de usar, amigable con PythonDesarrollo de prototipos, aplicaciones a pequeña escala
PineconeServicio en la nube/SaaSServicio gestionado, sin mantenimiento, escalabilidad elásticaEntornos de producción, aplicaciones a gran escala
WeaviateCódigo abierto/GestionadoRico en funciones, compatible con GraphQLEscenarios que requieren funciones avanzadas
QdrantCódigo abierto/GestionadoAlto rendimiento, escrito en RustEscenarios con altos requisitos de rendimiento
MilvusCódigo abierto/GestionadoFunciones completas, nivel empresarialAplicaciones empresariales a gran escala
FAISSBiblioteca localCreado por Facebook, extremadamente rápidoEscenarios que no requieren persistencia

Para principiantes, recomendamos empezar con Chroma: es fácil de instalar, no requiere configuración adicional y es muy adecuado para el aprendizaje y el desarrollo de prototipos.


Flujo de procesamiento de documentos

El procesamiento de documentos es una parte de RAG que a menudo se pasa por alto, pero que en realidad es muy importante.

La calidad de tus resultados de recuperación depende en gran medida de qué tan bien proceses los documentos.

Formatos de documento compatibles

LangChain admite muchos formatos de documentos:

  • Texto plano: .txt, .md
  • PDF:.pdf
  • Office:.docx, .pptx, .xlsx
  • Páginas web: HTML, URL
  • Código: .py, .js, .java, etc.
  • JSON:.json, .jsonl
  • CSV:.csv

Cada formato tiene su cargador correspondiente, por lo que es muy fácil de usar.

Estrategias de división de documentos

La fragmentación de documentos (Chunking) es el proceso de dividir documentos largos en fragmentos pequeños.

Parece simple, pero en realidad hay muchas consideraciones.

  • Si se divide demasiado pequeño: puede perder contexto, un significado completo se corta.
  • Si se divide demasiado grande: puede contener información irrelevante y desperdiciar la ventana de contexto.

Estrategias de fragmentación comunes:

EstrategiaPrincipioCasos de uso
Por número de caracteresSimplemente divide por número de caracteres, por ejemplo, un fragmento cada 500 caracteres.Escenarios simples, sin altos requisitos de contexto
Por párrafosDivide por saltos de línea, manteniendo los párrafos lo más completos posible.Documentos con formato bien definido
Fragmentación recursivaPrioriza párrafos, luego frases si es demasiado largo y finalmente palabras.La opción preferida en la mayoría de los escenarios
Fragmentación semánticaDivide según la similitud semántica para garantizar la integridad semántica.Escenarios con altos requisitos semánticos

En la mayoría de los casos, usar RecursiveCharacterTextSplitter de LangChain es suficiente.

Consejos para elegir el tamaño de división

El tamaño del fragmento (Chunk Size) es un parámetro que debe ajustarse según el escenario.

Algunos principios de referencia:

  • Escenarios de preguntas y respuestas: 500-1000 caracteres, suficiente para contener un par completo de pregunta-respuesta.
  • Escenarios de resumen: 1000-2000 caracteres, se necesita más contexto.
  • Escenarios de código: se puede dividir por funciones o clases para mantener los bloques de código completos.

Otro parámetro importante: la superposición (Overlap): el número de caracteres que se superponen entre dos bloques de texto adyacentes.

La superposición evita que información importante quede dividida en dos bloques. Normalmente se establece entre el 10% y el 20% del tamaño del fragmento.

No persigas ciegamente los parámetros de fragmentación "óptimos". Comienza con 500 caracteres y 50 de superposición, prueba los resultados reales y luego ajusta.


Modelos de embedding

El modelo de embedding es un modelo que convierte texto en vectores.

Su calidad afecta directamente los resultados de recuperación: un buen modelo de embedding entiende la semántica con precisión y coloca los documentos relevantes en los primeros lugares.

¿Qué es un modelo de embedding?

Un modelo de embedding es una red neuronal preentrenada cuya entrada es texto y cuya salida es un vector.

Su objetivo de entrenamiento es que los textos con semántica similar produzcan vectores similares.

Por ejemplo:

  • "Cómo aprender Python" y "Métodos de aprendizaje de Python" → vectores cercanos
  • "Los gatos son adorables" y "Los perros son adorables" → vectores con cierta distancia, pero relativamente cercanos
  • "Cómo aprender Python" y "Los gatos son adorables" → vectores muy distantes

Serie OpenAI text-embedding-3

OpenAI ofrece los modelos de embedding más populares actualmente:

ModeloDimensiónPrecio (por cada millón de tokens)Características
text-embedding-3-small1536$0.02Buena relación calidad-precio, suficiente para la mayoría de los casos
text-embedding-3-large3072$0.13Mejor rendimiento, adecuado para necesidades de alta precisión

text-embedding-3-small es la opción preferida actual: es mejor que la generación anterior text-embedding-ada-002 y más económico.

Modelos de embedding de código abierto

Si no quieres usar OpenAI, o tienes requisitos de privacidad de datos, también hay muchas opciones de código abierto:

ModeloDimensiónCaracterísticas
sentence-transformers/all-MiniLM-L6-v2384Ligero, rápido y con buenos resultados
sentence-transformers/all-mpnet-base-v2768Mejores resultados, pero más lento y grande
BAAI/bge-large-zh-v1.51024Optimizado específicamente para chino
Alibaba-NLP/gte-large-zh1024Creado por Alibaba, buenos resultados en chino

Para escenarios en chino, se recomienda usar la serie BAAI/bge o la serie Alibaba-NLP/gte: están optimizadas específicamente para chino y ofrecen mejores resultados que los modelos generales.


Estrategias de recuperación

La recuperación no es tan simple como "encontrar los documentos más relevantes": diferentes estrategias se adaptan a diferentes escenarios.

Búsqueda por similitud vectorial

Es el método de recuperación más común: calcula la similitud entre el vector de la pregunta y los vectores de todos los documentos, y devuelve los K más relevantes.

Ventajas: entiende la semántica, puede igualar sinónimos y palabras relacionadas.

Desventajas: puede ser menos preciso que la recuperación por palabras clave para términos exactos y nombres propios.

Por ejemplo, si el usuario pregunta "tutorial de Python de ejemplo", la recuperación vectorial entiende que "tutorial" y "curso" son similares, pero si el usuario pregunta "documentación de Python3.10 de ejemplo", la recuperación por palabras clave puede ser más precisa.

Búsqueda por palabras clave (BM25)

Es el método utilizado por los motores de búsqueda tradicionales: compara palabras clave y puntúa según la frecuencia de términos, la frecuencia inversa de documentos, etc.

  • Ventajas: funciona bien para coincidencias exactas de términos, nombres de productos, modelos, etc.

  • Desventajas: no puede entender sinónimos ni relaciones semánticas.

Por ejemplo, si el usuario pregunta cómo aprender programación, la recuperación por palabras clave solo encuentra documentos que contienen "programación", y no documentos que dicen "escribir código" pero significan lo mismo.

Búsqueda híbrida (Hybrid Search)

Ya que ambos métodos tienen ventajas y desventajas, ¿por qué no usarlos juntos?

La recuperación híbrida realiza simultáneamente la recuperación vectorial y la recuperación por palabras clave, y luego combina y reordena los resultados.

De esta manera, se puede aprovechar la comprensión semántica y garantizar la coincidencia exacta.

Muchas bases de datos vectoriales admiten recuperación híbrida, como Weaviate, Pinecone, Qdrant, etc.

Si no estás seguro de cuál usar, elige la recuperación híbrida: en la mayoría de los casos, es la que mejores resultados da.


Optimización de la fase de generación

Recuperar los documentos relevantes es solo el primer paso; también hay muchas técnicas para lograr que el LLM genere buenas respuestas basadas en estos documentos.

Diseño de plantillas de Prompt

La plantilla de prompt es un factor clave que afecta la calidad de la salida en RAG.

Un buen prompt de RAG generalmente incluye:

  • Definición del rol: decirle al LLM qué rol tiene
  • Contexto: el contenido de los documentos recuperados
  • Instrucción de la tarea: indicar qué debe hacer el LLM
  • Restricciones: por ejemplo, "responde solo basándote en el contexto", "si no lo sabes, di que no lo sabes"
  • Formato de salida: cómo quieres que responda el LLM

Una plantilla clásica:

Ejemplo

# Una plantilla típica de prompt de RAG
RAG_PROMPT_TEMPLATE = """Eres un {role} profesional.
Responde a la pregunta del usuario basándote en el siguiente contexto. Si el contexto no contiene información relevante, di "Lo siento, no he encontrado información relacionada".

Contexto:
{context}

Pregunta del usuario: {question}

Responde con un lenguaje conciso y preciso:"""

Cómo reducir las alucinaciones

La alucinación (Hallucination) es el fenómeno por el cual el LLM inventa hechos: suena convincente, pero todo es falso.

RAG puede reducir las alucinaciones, pero no puede eliminarlas por completo.

Algunas técnicas para reducir las alucinaciones:

  • Enfatizar en el prompt que se debe "responder solo basándose en el contexto"
  • Hacer que el LLM cite fragmentos del texto original en sus respuestas
  • Si los resultados de la búsqueda no son relevantes, hacer que el LLM diga "no lo sé"
  • Usa múltiples LLM para validar respuestas de forma cruzada
  • Haz que el LLM primero encuentre el contenido relevante en el contexto y luego responda

Anotación de fuentes citadas

Es importante que el usuario sepa de dónde proviene la respuesta: no solo aumenta la credibilidad, sino que también facilita la verificación.

Prácticas comunes:

  • Marca la fuente en la respuesta, por ejemplo: "según el documento A, página 2"
  • Al final de la respuesta, enumera los títulos y enlaces de los documentos de referencia
  • Permite que el usuario haga clic para saltar a la ubicación del texto original

Algunos marcos avanzados de LangChain (como LlamaIndex) pueden hacer esto automáticamente por ti.


Práctica: construir un sistema de preguntas y respuestas sobre documentos con LangChain

Dicho todo esto, vamos a construir un sistema RAG completo.

Usaremos LangChain + Chroma + OpenAI para construir un sistema de preguntas y respuestas sobre documentos.

Preparación del entorno

Primero instala las bibliotecas necesarias:

pip install langchain langchain-openai langchain-chroma python-dotenv

Luego crea un archivo .env en el directorio del proyecto y completa tu clave de API de OpenAI:

OPENAI_API_KEY=你的-api-key-在这里

Implementación completa del código

Este es un sistema RAG completo, que incluye funciones de creación de índices y consulta:

Ejemplo

# ============================================
# Implementación completa del sistema de preguntas y respuestas RAG sobre documentos
# ============================================

import os
from dotenv import load_dotenv
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.schema import Document


def load_documents_from_directory(directory: str) -> list[Document]:
    """Cargar documentos desde el directorio
Aquí, para la demostración, creamos manualmente algunos documentos
En proyectos reales, puedes usar DirectoryLoader para cargar archivos reales
    """

    documents = [
        Document(
            page_content="""El sitio web de tutoriales EJEMPLO fue fundado en 2013, creado por el equipo de Rookie Tutorial.
El objetivo del sitio web es proporcionar tutoriales de programación simples y fáciles de entender para principiantes en programación.
Ofrecemos tutoriales para múltiples lenguajes de programación como HTML, CSS, JavaScript, Python, Java, C++, etc."""
,
            metadata={"source": "ejemplo_intro.txt", "page": 1}
        ),
        Document(
            page_content="""El tutorial de Python de EJEMPLO cubre todos los puntos de conocimiento importantes de Python.
Incluye: sintaxis básica de Python, tipos de datos, sentencias de control, funciones, módulos, operaciones de archivos, etc.
El tutorial proporciona una gran cantidad de ejemplos de código, cada uno cuidadosamente diseñado y probado.
También ofrecemos un editor de Python en línea, donde puedes ejecutar código directamente en tu navegador."""
,
            metadata={"source": "python_tutorial.txt", "page": 1}
        ),
        Document(
            page_content="""Las características de los tutoriales de EJEMPLO son contenido conciso, ejemplos abundantes y énfasis en la práctica.
Creemos que "la mejor manera de aprender a programar es escribir código".
Por lo tanto, cada punto de conocimiento viene con ejemplos de código ejecutables, para que puedas practicar mientras lees.
Nuestros tutoriales son completamente gratuitos y cualquier persona puede acceder y aprender."""
,
            metadata={"source": "features.txt", "page": 1}
        ),
        Document(
            page_content="""Además de los tutoriales de texto, EJEMPLO también ofrece tutoriales en video.
Los tutoriales en video son más intuitivos y adecuados para usuarios que aprenden visualmente.
Nuestros tutoriales en video cubren temas populares como Python, desarrollo web y análisis de datos.
Puedes encontrar nuestros videos en Bilibili y YouTube."""
,
            metadata={"source": "video_course.txt", "page": 1}
        ),
    ]
    print(f"Se han cargado {len(documents)} documentos")
    return documents


def split_documents(documents: list[Document], chunk_size: int = 500, chunk_overlap: int = 50) -> list[Document]:
    """Dividir documentos"""
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
    )
    splits = text_splitter.split_documents(documents)
    print(f"Se han dividido {len(documents)} documentos en {len(splits)} bloques de texto")
    return splits


def create_vector_store(splits: list[Document], persist_directory: str = "./chroma_db") -> Chroma:
    """Crear base de datos vectorial"""
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = Chroma.from_documents(
        documents=splits,
        embedding=embeddings,
        persist_directory=persist_directory,
    )
    print(f"Base de datos vectorial creada, con {vector_store._collection.count()} registros en total")
    return vector_store


def load_vector_store(persist_directory: str = "./chroma_db") -> Chroma:
    """Cargar la base de datos vectorial existente"""
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings,
    )
    print(f"Base de datos vectorial cargada, con {vector_store._collection.count()} registros en total")
    return vector_store


def create_rag_chain(vector_store: Chroma):
    """Crear cadena de preguntas y respuestas RAG"""

    # Plantilla de Prompt personalizada
    template = """Eres un asistente de servicio al cliente profesional de EJEMPLO.
Responde a la pregunta del usuario basándote en el siguiente contexto. Si no hay información relevante en el contexto, di "Lo siento, no he encontrado información relevante".
Las respuestas deben ser concisas y claras, en chino.

Contexto:
{context}

Pregunta del usuario: {question}

Respuesta:"""


    prompt = PromptTemplate(
        template=template,
        input_variables=["context", "question"],
    )

    llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

    # Crear cadena de recuperación
    rag_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",
        retriever=vector_store.as_retriever(search_kwargs={"k": 3}),
        chain_type_kwargs={"prompt": prompt},
        return_source_documents=True,
    )

    return rag_chain


def main():
    """Función principal"""
    # Cargar variables de entorno
    load_dotenv()

    # Comprobar si la base de datos ya existe
    db_exists = os.path.exists("./chroma_db") and os.path.isdir("./chroma_db")

    if db_exists:
        print("Se encontró una base de datos existente, cargando...")
        vector_store = load_vector_store()
    else:
        print("Creando nueva base de datos...")
        # 1. Cargar documentos
        docs = load_documents_from_directory("./data")
        # 2. Dividir documentos
        splits = split_documents(docs)
        # 3. Crear base de datos vectorial
        vector_store = create_vector_store(splits)

    # 4. Crear cadena RAG
    rag_chain = create_rag_chain(vector_store)

    # 5. Probar consultas
    test_questions = [
        "¿Cuándo fue fundado EJEMPLO?",
        "¿Cuáles son las características del tutorial de Python de EJEMPLO?",
        "¿EJEMPLO tiene tutoriales en video?",
        "¿Cómo aprender C++ en EJEMPLO?",
        "¿Los tutoriales de EJEMPLO requieren pago?",
    ]

    for question in test_questions:
        print(f"\n{'='*60}")
        print(f"Pregunta: {question}")
        print(f"{'='*60}")

        result = rag_chain.invoke({"query": question})
        answer = result["result"]
        sources = result["source_documents"]

        print(f"Respuesta: {answer}")
        print("\nFuentes de referencia:")
        for i, doc in enumerate(sources, 1):
            print(f" [{i}] {doc.metadata['source']} (página {doc.metadata['page']})")
            print(f" Fragmento: {doc.page_content[:50]}...")


if __name__ == "__main__":
    main()

Al ejecutar este programa, verás una salida similar a esta:

创建新的数据库...
已加载 4 个文档
已将 4 个文档切分为 4 个文本块
向量数据库已创建,共 4 条记录

============================================================
问题:EJEMPLO 是什么时候创立的?
============================================================
回答:EJEMPLO 教程网站创立于 2013 年。

参考来源:
  [1] ejemplo_intro.txt (第 1 页)
      片段:EJEMPLO 教程网站创立于 2013 年,由Ejemplo团队创建。...
  [2] features.txt (第 1 页)
      片段:除了文字教程,EJEMPLO 还提供了视频教程。...

============================================================
问题:EJEMPLO 的 Python 教程有什么特点?
============================================================
回答:EJEMPLO 的 Python 教程涵盖了 Python 的所有重要知识点,包括基础语法、数据类型、控制语句、函数、模块、文件操作等,并且提供了大量经过精心设计和测试的代码示例,还提供了在线 Python 编辑器,可以直接在浏览器中运行代码。

参考来源:
  [1] python_tutorial.txt (第 1 页)
      片段:EJEMPLO 的 Python 教程涵盖了 Python 的所有重要知识点。...
  [2] features.txt (第 1 页)
      片段:EJEMPLO 教程的特点是内容简洁、示例丰富、注重实践。...

¡Felicitaciones! Has construido con éxito un sistema completo de preguntas y respuestas sobre documentos RAG.


Optimización avanzada: Re-ranking, HyDE

El RAG básico ya funciona, pero si quieres mejores resultados, puedes probar estos métodos de optimización avanzados.

Re-ranking (reordenamiento)

Los resultados devueltos por la recuperación vectorial se ordenan por similitud, pero una alta similitud no significa necesariamente que sea el más adecuado para responder la pregunta.

Re-ranking consiste en: primero usar la recuperación vectorial para devolver los primeros K resultados (por ejemplo, 20), luego usar un modelo más preciso para reordenar estos resultados y seleccionar los mejores para enviarlos al LLM.

Modelos de Re-ranking comúnmente utilizados:

  • cross-encoder/ms-marco-MiniLM-L-6-v2
  • BAAI/bge-reranker-large

Aunque añade un paso adicional, la mejora es evidente y vale la pena hacerlo.

HyDE(Hypothetical Document Embedding)

HyDE es una idea muy ingeniosa: primero usa el LLM para generar una "respuesta hipotética" a la pregunta del usuario, y luego usa esta respuesta hipotética para la recuperación.

¿Por qué funciona esto? Porque las preguntas suelen ser cortas y con baja densidad de información; mientras que las respuestas hipotéticas son más largas, más parecidas al estilo de los documentos, y tienen mayor similitud con los documentos reales.

Por ejemplo:

  • Pregunta del usuario: "¿Cuándo fue fundado EJEMPLO?"
  • Respuesta hipotética: "El sitio web de tutoriales EJEMPLO fue fundado en 2013, creado por el equipo de Rookie Tutorial, con el objetivo de proporcionar tutoriales de calidad para principiantes en programación."

Usar la respuesta hipotética para la recuperación suele encontrar documentos más relevantes.

Otras extensiones