Join us at MongoDB.local London on 7 May to unlock new possibilities for your data. Use WEB50 to save 50%.
Register now >
Docs Menu
Docs Home
/ /

Evalúa tu aplicación RAG

En este tutorial, aprenderás cómo evaluar un RAG application. La evaluación te ayuda a elegir el modelo adecuado, a garantizar que el rendimiento de tu modelo se traslade del prototipo a la producción y a detectar regresiones en el rendimiento.

En concreto, realizas las siguientes acciones:

  • Configura el entorno.

  • Descarga un conjunto de datos para su evaluación.

  • Crea fragmentos y vectores de documentos.

  • Incorpora los embeddings en Atlas.

  • Compara modelos de embedding para recuperación.

  • Compara modelos de finalización para generación.

  • Medir el rendimiento general de RAG.

  • Rastree el rendimiento a lo largo del tiempo con MongoDB Charts.

Nota

Este tutorial se centra en la evaluación de aplicaciones LLM, no de modelos LLM. La evaluación de modelos LLM implica medir el rendimiento de un modelo dado en diferentes tareas. La evaluación de la aplicación LLM consiste en evaluar diferentes componentes de una aplicación LLM, como indicaciones y recuperadores, así como el sistema en su conjunto.

Trabaja con una versión ejecutable de este tutorial como Notebook de Python.

Este tutorial utiliza el marco de evaluación de código abierto RAGAS para medir el rendimiento de RAG con las siguientes métricas:

  • Métricas de recuperación: La precisión de contexto y el recall de contexto miden cuán bien tu recuperador encuentra información relevante.

  • Métricas de generación: La fidelidad y la relevancia de las respuestas miden qué tan bien tu LLM genera respuestas precisas y relevantes.

  • Métricas generales: La similitud y corrección de las respuestas se utiliza para comparar las respuestas generadas con la verdad fundamental.

Para obtener más información sobre estas métricas, consulte Métricas RAGAS en la documentación de RAGAS.

Este tutorial utiliza el conjunto de datos ragas-wikiqa de Hugging Face, que contiene aproximadamente 230 preguntas de conocimientos generales con sus respuestas correctas.

Para completar este tutorial, debes tener lo siguiente:

  • Un clúster de MongoDB Atlas ejecutando MongoDB versión 6.0.11 o posterior. Asegúrate de que tu dirección IP esté en tu lista de accesodel proyecto.

  • Una clave API de OpenAI para utilizar los modelos de incrustaciones y chat de OpenAI.

  • Un terminal configurado con lo siguiente:

1

Ejecute el siguiente comando para instalar las librerías necesarias:

pip install -qU datasets ragas langchain langchain-mongodb langchain-openai \
pymongo pandas tqdm matplotlib seaborn nest_asyncio
2

Ejecuta el siguiente código en tu cuaderno para configurar tu cadena de conexión MongoDB y la clave API de OpenAI:

import getpass
import os
from openai import OpenAI
MONGODB_URI = getpass.getpass("Enter your MongoDB connection string:")
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key:")
openai_client = OpenAI()

Descarga el ragas-wikiqa conjunto de datos de Hugging Face y conviértelo a un DataFrame de pandas:

from datasets import load_dataset
import pandas as pd
data = load_dataset("explodinggradients/ragas-wikiqa", split="train")
df = pd.DataFrame(data)

El conjunto de datos contiene las siguientes columnas:

  • questionPregunta del usuario

  • correct_answer: Respuestas del terreno

  • context: Lista de textos de referencia para responder a las preguntas

Divide los textos de referencia en fragmentos más pequeños antes de la integración:

from langchain.text_splitter import RecursiveCharacterTextSplitter
# Split text by tokens using the tiktoken tokenizer
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", keep_separator=False, chunk_size=200, chunk_overlap=30
)
def split_texts(texts):
chunked_texts = []
for text in texts:
chunks = text_splitter.create_documents([text])
chunked_texts.extend([chunk.page_content for chunk in chunks])
return chunked_texts
# Split the context field into chunks
df["chunks"] = df["context"].apply(lambda x: split_texts(x))
# Aggregate list of all chunks
all_chunks = df["chunks"].tolist()
docs = [item for chunk in all_chunks for item in chunk]

Tip

Experimenta con diferentes estrategias de fragmentación al evaluar la recuperación. Este tutorial se centra en la evaluación de modelos de incrustación.

Incorporar los documentos fragmentados e integrarlos en Atlas. Crea colecciones separadas para cada modelo de embedding que quieras comparar:

1

Cree una función para generar incrustaciones utilizando la API de OpenAI:

from typing import List
def get_embeddings(docs: List[str], model: str) -> List[List[float]]:
"""Generate embeddings using the OpenAI API."""
docs = [doc.replace("\n", " ") for doc in docs]
response = openai_client.embeddings.create(input=docs, model=model)
return [r.embedding for r in response.data]
2

Incrusta e ingiere los documentos particionados en colecciones de Atlas:

from pymongo import MongoClient
from tqdm.auto import tqdm
client = MongoClient(MONGODB_URI)
DB_NAME = "ragas_evals"
db = client[DB_NAME]
batch_size = 128
EVAL_EMBEDDING_MODELS = ["text-embedding-ada-002", "text-embedding-3-small"]
for model in EVAL_EMBEDDING_MODELS:
embedded_docs = []
print(f"Getting embeddings for the {model} model")
for i in tqdm(range(0, len(docs), batch_size)):
end = min(len(docs), i + batch_size)
batch = docs[i:end]
batch_embeddings = get_embeddings(batch, model)
batch_embedded_docs = [
{"text": batch[i], "embedding": batch_embeddings[i]}
for i in range(len(batch))
]
embedded_docs.extend(batch_embedded_docs)
collection = db[model]
collection.delete_many({})
collection.insert_many(embedded_docs)
print(f"Finished inserting embeddings for the {model} model")
3

Cree un índice de búsqueda vectorial de MongoDB para cada colección. Utilice la siguiente definición de índice con el nombre del índice vector_index:

{
"fields": [
{
"numDimensions": 1536,
"path": "embedding",
"similarity": "cosine",
"type": "vector"
}
]
}

Para aprender cómo crear el índice, consulta Crea un índice de MongoDB Vector Search.

Tip

Tanto text-embedding-ada-002 como text-embedding-3-small tienen 1536 dimensiones, por lo que la misma definición de índice funciona para ambas colecciones.

Para asegurarse de recuperar el contexto adecuado para el LLM, compare diferentes modelos de incrustación. Este tutorial compara text-embedding-ada-002 y text-embedding-3-small.

1

Crea una función para obtener un recuperador del almacén vectorial usando LangChain y MongoDB Atlas:

from langchain_openai import OpenAIEmbeddings
from langchain_mongodb import MongoDBAtlasVectorSearch
from langchain_core.vectorstores import VectorStoreRetriever
def get_retriever(model: str, k: int) -> VectorStoreRetriever:
"""
Get a vector store retriever for a given embedding model.
Args:
model (str): Embedding model to use
k (int): Number of results to retrieve
Returns:
VectorStoreRetriever: A vector store retriever object
"""
embeddings = OpenAIEmbeddings(model=model)
vector_store = MongoDBAtlasVectorSearch.from_connection_string(
connection_string=MONGODB_URI,
namespace=f"{DB_NAME}.{model}",
embedding=embeddings,
index_name="vector_index",
text_key="text",
)
retriever = vector_store.as_retriever(
search_type="similarity", search_kwargs={"k": k}
)
return retriever
2

Extraiga las preguntas y las respuestas fidedignas de su conjunto de datos:

QUESTIONS = df["question"].to_list()
GROUND_TRUTH = df["correct_answer"].tolist()
3

Use las métricas context_precision y context_recall de la librería RAGAS para evaluar cada modelo de embedding:

from datasets import Dataset
from ragas import evaluate, RunConfig
from ragas.metrics import context_precision, context_recall
import nest_asyncio
# Allow nested use of asyncio (used by RAGAS)
nest_asyncio.apply()
for model in EVAL_EMBEDDING_MODELS:
data = {"question": [], "ground_truth": [], "contexts": []}
data["question"] = QUESTIONS
data["ground_truth"] = GROUND_TRUTH
retriever = get_retriever(model, 2)
# Get relevant documents for the evaluation dataset
for i in tqdm(range(0, len(QUESTIONS))):
data["contexts"].append(
[doc.page_content for doc in retriever.invoke(QUESTIONS[i])]
)
# RAGAS expects a Dataset object
dataset = Dataset.from_dict(data)
# RAGAS runtime settings to avoid hitting OpenAI rate limits
run_config = RunConfig(max_workers=4, max_wait=180)
result = evaluate(
dataset=dataset,
metrics=[context_precision, context_recall],
run_config=run_config,
raise_exceptions=False,
)
print(f"Result for the {model} model: {result}")

Los resultados de la evaluación de los modelos de embedding en el conjunto de datos de muestra son los siguientes:

Modelo
Precisión contextual
Recuperación de contexto

text-embedding-ada-002

0.9310

0.8561

text-embedding-3-pequeño

0.9116

0.8826

En base a estos resultados, text-embedding-ada-002 clasifica los resultados más relevantes en una posición más alta, pero text-embedding-3-small recupera contextos que están más alineados con las respuestas de referencia. Para este tutorial, utiliza text-embedding-3-small como el modelo de incrustación.

Ahora que ha seleccionado el mejor modelo de embedding, compare modelos de completion para el componente de generación de su aplicación RAG.

1

Crea una función que construya una cadena RAG usando LangChain:

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.base import RunnableSequence
from langchain_core.output_parsers import StrOutputParser
def get_rag_chain(retriever: VectorStoreRetriever, model: str) -> RunnableSequence:
"""
Create a basic RAG chain.
Args:
retriever (VectorStoreRetriever): Vector store retriever object
model (str): Chat completion model to use
Returns:
RunnableSequence: A RAG chain
"""
# Generate context using the retriever, and pass the user question through
retrieve = {
"context": retriever
| (lambda docs: "\n\n".join([d.page_content for d in docs])),
"question": RunnablePassthrough(),
}
template = """Answer the question based only on the following context: \
{context}
Question: {question}
"""
# Define the chat prompt
prompt = ChatPromptTemplate.from_template(template)
# Define the model for chat completion
llm = ChatOpenAI(temperature=0, model=model)
# Parse output as a string
parse_output = StrOutputParser()
# RAG chain
rag_chain = retrieve | prompt | llm | parse_output
return rag_chain
2

Utiliza las métricas faithfulness y answer_relevancy para evaluar diferentes modelos de finalización:

from ragas.metrics import faithfulness, answer_relevancy
for model in ["gpt-3.5-turbo-1106", "gpt-3.5-turbo"]:
data = {"question": [], "ground_truth": [], "contexts": [], "answer": []}
data["question"] = QUESTIONS
data["ground_truth"] = GROUND_TRUTH
# Use the best embedding model from the retriever evaluation
retriever = get_retriever("text-embedding-3-small", 2)
rag_chain = get_rag_chain(retriever, model)
for i in tqdm(range(0, len(QUESTIONS))):
question = QUESTIONS[i]
data["answer"].append(rag_chain.invoke(question))
data["contexts"].append(
[doc.page_content for doc in retriever.invoke(question)]
)
# RAGAS expects a Dataset object
dataset = Dataset.from_dict(data)
# RAGAS runtime settings to avoid hitting OpenAI rate limits
run_config = RunConfig(max_workers=4, max_wait=180)
result = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy],
run_config=run_config,
raise_exceptions=False,
)
print(f"Result for the {model} model: {result}")

Los resultados de la evaluación de los modelos de finalización en el conjunto de datos de muestra son los siguientes:

Modelo
Fidelidad
Relevancia de la respuesta

gpt-3.5-turbo

0.9714

0.9087

gpt-3.5-turbo-1106

0.9671

0.9105

Según estos resultados, la versión más reciente de gpt-3.5-turbo genera resultados más coherentes a nivel fáctico, mientras que la versión anterior proporciona respuestas más pertinentes al prompt dado. Para este tutorial, utilice gpt-3.5-turbo como el modelo de finalización.

Tip

Si no desea elegir entre métricas, considere crear métricas consolidadas utilizando una suma ponderada, o personalizar las indicaciones utilizadas para la evaluación.

Evalúa el rendimiento general de tu aplicación RAG utilizando los modelos de mejor rendimiento:

from ragas.metrics import answer_similarity, answer_correctness
data = {"question": [], "ground_truth": [], "answer": []}
data["question"] = QUESTIONS
data["ground_truth"] = GROUND_TRUTH
# Use the best embedding model from the retriever evaluation
retriever = get_retriever("text-embedding-3-small", 2)
# Use the best completion model from the generator evaluation
rag_chain = get_rag_chain(retriever, "gpt-3.5-turbo")
for question in tqdm(QUESTIONS):
data["answer"].append(rag_chain.invoke(question))
dataset = Dataset.from_dict(data)
run_config = RunConfig(max_workers=4, max_wait=180)
result = evaluate(
dataset=dataset,
metrics=[answer_similarity, answer_correctness],
run_config=run_config,
raise_exceptions=False,
)
print(f"Overall metrics: {result}")

Esta evaluación muestra que la cadena RAG produce una similitud de respuesta de 0.8873 y una corrección de respuesta de 0.5922 en el conjunto de datos de muestra.

Para investigar más a fondo los resultados, conviértalos en un dataframe de pandas y aplique un filtro a las respuestas con baja puntuación:

result_df = result.to_pandas()
result_df[result_df["answer_correctness"] < 0.7]

Para un análisis visual, genera un mapa de calor de preguntas frente a métricas:

import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
sns.heatmap(
result_df[1:10].set_index("question")[["answer_similarity", "answer_correctness"]],
annot=True,
cmap="flare",
)
plt.show()

El código anterior produce el siguiente mapa de calor:

Mapa de calor que visualiza el rendimiento de una aplicación RAG

Heatmap que visualiza el rendimiento de la aplicación RAG

Al investigar los resultados de baja puntuación, podría encontrar:

  • Algunas respuestas de la verdad fundamental en el conjunto de datos de evaluación son incorrectas. Aunque la respuesta generada por el LLMes correcta, no concuerda con la verdad objetiva, lo que resulta en una baja puntuación.

  • Algunas respuestas de la verdad de base son oraciones completas, mientras que la respuesta generada por LLMes una sola palabra o número.

Estos hallazgos enfatizan la importancia de revisar aleatoriamente las evaluaciones de LLM y curar conjuntos de datos de evaluación precisos.

La evaluación no debe ser un evento de una sola vez. Cada vez que cambies un componente en tu sistema, evalúa los cambios para determinar cómo tienen un impacto en el rendimiento. Una vez que su aplicación se encuentre en producción, supervise el rendimiento en tiempo real y detecte cambios.

Usa Charts para monitorear el rendimiento de tu aplicación LLM. Escribe los resultados de la evaluación y cualquier métrica de retroalimentación que quieras rastrear en una colección de Atlas:

from datetime import datetime
result["timestamp"] = datetime.now()
collection = db["metrics"]
collection.insert_one(result)

Este código añade un campo timestamp al resultado de la evaluación y lo escribe en una colección metrics en la base de datos ragas_evals. El documento en Atlas se ve así:

{
"answer_similarity": 0.8873,
"answer_correctness": 0.5922,
"timestamp": "2024-04-07T23:27:30.655+00:00"
}

Crea un tablero en MongoDB Charts para visualizar tus métricas a lo largo del tiempo. Para aprender a compilar gráficas y tableros, consulta Compila gráficas.

En este tutorial, aprendiste cómo evaluar una aplicación RAG utilizando el marco RAGAS y MongoDB Atlas. Se compararon modelos de embeddings para recuperación, modelos de compleción para generación y se midió el rendimiento general de la aplicación. También aprendiste a rastrear el rendimiento a lo largo del tiempo usando MongoDB Charts.

Para obtener más información sobre cómo crear aplicaciones RAG con MongoDB, consulta los siguientes recursos:

Volver

Queries en lenguaje natural