Evaluación con DeepEval
Esta guía muestra cómo utilizar DeepEval para evaluar un sistema de generación mejorada por recuperación (RAG) basado en Milvus.
El sistema RAG combina un sistema de recuperación con un modelo generativo para generar nuevo texto basado en una petición dada. En primer lugar, el sistema recupera documentos relevantes de un corpus utilizando Milvus y, a continuación, utiliza un modelo generativo para generar un nuevo texto basado en los documentos recuperados.
DeepEval es un marco de trabajo que le ayuda a evaluar sus procesos GAR. Existen herramientas y marcos de trabajo que ayudan a construir estos pipelines, pero evaluarlos y cuantificar su rendimiento puede ser difícil. Aquí es donde entra DeepEval.
Requisitos previos
Antes de ejecutar este cuaderno, asegúrate de tener instaladas las siguientes dependencias:
$ pip install --upgrade pymilvus openai requests tqdm pandas deepeval
Si estás utilizando Google Colab, para habilitar las dependencias que acabas de instalar, es posible que tengas que reiniciar el tiempo de ejecución (haz clic en el menú "Tiempo de ejecución" en la parte superior de la pantalla, y selecciona "Reiniciar sesión" en el menú desplegable).
En este ejemplo utilizaremos OpenAI como LLM. Deberás preparar la clave api OPENAI_API_KEY
como variable de entorno.
import os
os.environ["OPENAI_API_KEY"] = "sk-*****************"
Definir el pipeline RAG
Definiremos la clase RAG que utiliza Milvus como almacén de vectores, y OpenAI como LLM. La clase contiene el método load
, que carga los datos de texto en Milvus, el método retrieve
, que recupera los datos de texto más similares a la pregunta dada, y el método answer
, que responde a la pregunta dada con el conocimiento recuperado.
from typing import List
from tqdm import tqdm
from openai import OpenAI
from pymilvus import MilvusClient
class RAG:
"""
RAG(Retrieval-Augmented Generation) class built upon OpenAI and Milvus.
"""
def __init__(self, openai_client: OpenAI, milvus_client: MilvusClient):
self._prepare_openai(openai_client)
self._prepare_milvus(milvus_client)
def _emb_text(self, text: str) -> List[float]:
return (
self.openai_client.embeddings.create(input=text, model=self.embedding_model)
.data[0]
.embedding
)
def _prepare_openai(
self,
openai_client: OpenAI,
embedding_model: str = "text-embedding-3-small",
llm_model: str = "gpt-4o-mini",
):
self.openai_client = openai_client
self.embedding_model = embedding_model
self.llm_model = llm_model
self.SYSTEM_PROMPT = """
Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.
"""
self.USER_PROMPT = """
Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.
<context>
{context}
</context>
<question>
{question}
</question>
"""
def _prepare_milvus(
self, milvus_client: MilvusClient, collection_name: str = "rag_collection"
):
self.milvus_client = milvus_client
self.collection_name = collection_name
if self.milvus_client.has_collection(self.collection_name):
self.milvus_client.drop_collection(self.collection_name)
embedding_dim = len(self._emb_text("demo"))
self.milvus_client.create_collection(
collection_name=self.collection_name,
dimension=embedding_dim,
metric_type="IP",
consistency_level="Strong",
)
def load(self, texts: List[str]):
"""
Load the text data into Milvus.
"""
data = []
for i, line in enumerate(tqdm(texts, desc="Creating embeddings")):
data.append({"id": i, "vector": self._emb_text(line), "text": line})
self.milvus_client.insert(collection_name=self.collection_name, data=data)
def retrieve(self, question: str, top_k: int = 3) -> List[str]:
"""
Retrieve the most similar text data to the given question.
"""
search_res = self.milvus_client.search(
collection_name=self.collection_name,
data=[self._emb_text(question)],
limit=top_k,
search_params={"metric_type": "IP", "params": {}}, # inner product distance
output_fields=["text"], # Return the text field
)
retrieved_texts = [res["entity"]["text"] for res in search_res[0]]
return retrieved_texts[:top_k]
def answer(
self,
question: str,
retrieval_top_k: int = 3,
return_retrieved_text: bool = False,
):
"""
Answer the given question with the retrieved knowledge.
"""
retrieved_texts = self.retrieve(question, top_k=retrieval_top_k)
user_prompt = self.USER_PROMPT.format(
context="\n".join(retrieved_texts), question=question
)
response = self.openai_client.chat.completions.create(
model=self.llm_model,
messages=[
{"role": "system", "content": self.SYSTEM_PROMPT},
{"role": "user", "content": user_prompt},
],
)
if not return_retrieved_text:
return response.choices[0].message.content
else:
return response.choices[0].message.content, retrieved_texts
Inicialicemos la clase RAG con los clientes OpenAI y Milvus.
openai_client = OpenAI()
milvus_client = MilvusClient(uri="./milvus_demo.db")
my_rag = RAG(openai_client=openai_client, milvus_client=milvus_client)
En cuanto al argumento de MilvusClient
:
- Establecer el
uri
como un archivo local, por ejemplo./milvus.db
, es el método más conveniente, ya que utiliza automáticamente Milvus Lite para almacenar todos los datos en este archivo. - Si tiene una gran escala de datos, puede configurar un servidor Milvus más eficiente en docker o kubernetes. En esta configuración, por favor utilice la uri del servidor, por ejemplo
http://localhost:19530
, como suuri
. - Si desea utilizar Zilliz Cloud, el servicio en la nube totalmente gestionado para Milvus, ajuste
uri
ytoken
, que corresponden al punto final público y a la clave Api en Zilliz Cloud.
Ejecutar la canalización RAG y obtener resultados
Utilizamos la guía de desarrollo de Milvus como conocimiento privado en nuestra RAG, que es una buena fuente de datos para una canalización RAG sencilla.
Descárguela y cárguela en la tubería RAG.
import urllib.request
import os
url = "https://raw.githubusercontent.com/milvus-io/milvus/master/DEVELOPMENT.md"
file_path = "./Milvus_DEVELOPMENT.md"
if not os.path.exists(file_path):
urllib.request.urlretrieve(url, file_path)
with open(file_path, "r") as file:
file_text = file.read()
text_lines = file_text.split("# ")
my_rag.load(text_lines)
Creating embeddings: 100%|██████████| 47/47 [00:20<00:00, 2.26it/s]
Definamos una pregunta de consulta sobre el contenido de la documentación de la guía de desarrollo. Y luego utilicemos el método answer
para obtener la respuesta y los textos contextuales recuperados.
question = "what is the hardware requirements specification if I want to build Milvus and run from source code?"
my_rag.answer(question, return_retrieved_text=True)
('The hardware requirements specification to build and run Milvus from source code is as follows:\n\n- 8GB of RAM\n- 50GB of free disk space',
['Hardware Requirements\n\nThe following specification (either physical or virtual machine resources) is recommended for Milvus to build and run from source code.\n\n```\n- 8GB of RAM\n- 50GB of free disk space\n```\n\n##',
'Building Milvus on a local OS/shell environment\n\nThe details below outline the hardware and software requirements for building on Linux and MacOS.\n\n##',
"Software Requirements\n\nAll Linux distributions are available for Milvus development. However a majority of our contributor worked with Ubuntu or CentOS systems, with a small portion of Mac (both x86_64 and Apple Silicon) contributors. If you would like Milvus to build and run on other distributions, you are more than welcome to file an issue and contribute!\n\nHere's a list of verified OS types where Milvus can successfully build and run:\n\n- Debian/Ubuntu\n- Amazon Linux\n- MacOS (x86_64)\n- MacOS (Apple Silicon)\n\n##"])
Ahora vamos a preparar algunas preguntas con sus correspondientes respuestas ground truth. Obtenemos las respuestas y los contextos de nuestra canalización RAG.
from datasets import Dataset
import pandas as pd
question_list = [
"what is the hardware requirements specification if I want to build Milvus and run from source code?",
"What is the programming language used to write Knowhere?",
"What should be ensured before running code coverage?",
]
ground_truth_list = [
"If you want to build Milvus and run from source code, the recommended hardware requirements specification is:\n\n- 8GB of RAM\n- 50GB of free disk space.",
"The programming language used to write Knowhere is C++.",
"Before running code coverage, you should make sure that your code changes are covered by unit tests.",
]
contexts_list = []
answer_list = []
for question in tqdm(question_list, desc="Answering questions"):
answer, contexts = my_rag.answer(question, return_retrieved_text=True)
contexts_list.append(contexts)
answer_list.append(answer)
df = pd.DataFrame(
{
"question": question_list,
"contexts": contexts_list,
"answer": answer_list,
"ground_truth": ground_truth_list,
}
)
rag_results = Dataset.from_pandas(df)
df
/Users/eureka/miniconda3/envs/zilliz/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
Answering questions: 100%|██████████| 3/3 [00:03<00:00, 1.06s/it]
pregunta | contextos | respuesta | verdad_base | |
---|---|---|---|---|
0 | ¿cuál es la especificación de los ... | [Requisitos de hardware\nespecificaci... | La especificación de requisitos de hardware ... | Si desea crear Milvus y ejecutarlo desde la fuente... |
1 | ¿Cuál es el lenguaje de programación utilizado... | [CMake & Conan\nLa biblioteca de algoritmos de Mil... | ¿Cuál es el lenguaje de programación utilizado... | ¿Cuál es el lenguaje de programación utilizado... |
2 | ¿Qué debe garantizarse antes de ejecutar la cobertur... | [Antes de enviar su pull ... | Antes de ejecutar la cobertura de có... | Antes de ejecutar la cobertura de código, debe ... |
Evaluación del Recuperador
Cuando se evalúa un recuperador en sistemas de grandes modelos lingüísticos (LLM), es crucial evaluar lo siguiente:
Relevancia de la clasificación: La eficacia con la que el recuperador prioriza la información relevante sobre los datos irrelevantes.
Recuperación contextual: La capacidad de capturar y recuperar información contextualmente relevante basada en la entrada.
Equilibrio: Cómo gestiona el recuperador el tamaño de los fragmentos de texto y el alcance de la recuperación para minimizar las irrelevancias.
Juntos, estos factores proporcionan una comprensión global de cómo el recuperador prioriza, captura y presenta la información más útil.
from deepeval.metrics import (
ContextualPrecisionMetric,
ContextualRecallMetric,
ContextualRelevancyMetric,
)
from deepeval.test_case import LLMTestCase
from deepeval import evaluate
contextual_precision = ContextualPrecisionMetric()
contextual_recall = ContextualRecallMetric()
contextual_relevancy = ContextualRelevancyMetric()
test_cases = []
for index, row in df.iterrows():
test_case = LLMTestCase(
input=row["question"],
actual_output=row["answer"],
expected_output=row["ground_truth"],
retrieval_context=row["contexts"],
)
test_cases.append(test_case)
# test_cases
result = evaluate(
test_cases=test_cases,
metrics=[contextual_precision, contextual_recall, contextual_relevancy],
print_results=False, # Change to True to see detailed metric results
)
/Users/eureka/miniconda3/envs/zilliz/lib/python3.9/site-packages/deepeval/__init__.py:49: UserWarning: You are using deepeval version 1.1.6, however version 1.2.2 is available. You should consider upgrading via the "pip install --upgrade deepeval" command.
warnings.warn(
✨ ¡Está ejecutando la última métrica de precisión contextual de DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ ¡Estás ejecutando la última métrica de recuperación contextual de DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ ¡Está ejecutando la última métrica de relevancia contextual de DeepEval! (using gpt-4o, strict=False, async_mode=True)...
Event loop is already running. Applying nest_asyncio patch to allow async execution...
Evaluating 3 test case(s) in parallel: |██████████|100% (3/3) [Time Taken: 00:11, 3.91s/test case]
✓ ¡Pruebas finalizadas 🎉! Ejecuta 'deepeval login' para ver los resultados de la evaluación en Confident AI. ‼️ NOTA: También puedes ejecutar evaluaciones de TODAS las métricas de deepeval directamente en Confident AI en su lugar.
Evaluación de la generación
Para evaluar la calidad de los resultados generados en grandes modelos lingüísticos (LLM), es importante centrarse en dos aspectos clave:
Relevancia: Evaluar si la instrucción guía eficazmente al LLM para generar respuestas útiles y adecuadas al contexto.
Fidelidad: Mide la exactitud del resultado, asegurándote de que el modelo produce información objetivamente correcta y libre de alucinaciones o contradicciones. El contenido generado debe coincidir con la información factual proporcionada en el contexto de recuperación.
Todos estos factores garantizan que los resultados sean pertinentes y fiables.
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric
from deepeval.test_case import LLMTestCase
from deepeval import evaluate
answer_relevancy = AnswerRelevancyMetric()
faithfulness = FaithfulnessMetric()
test_cases = []
for index, row in df.iterrows():
test_case = LLMTestCase(
input=row["question"],
actual_output=row["answer"],
expected_output=row["ground_truth"],
retrieval_context=row["contexts"],
)
test_cases.append(test_case)
# test_cases
result = evaluate(
test_cases=test_cases,
metrics=[answer_relevancy, faithfulness],
print_results=False, # Change to True to see detailed metric results
)
✨ ¡Estás utilizando la última métrica de relevancia de respuestas de DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ ¡Estás ejecutando la última métrica de fidelidad de DeepEval! (using gpt-4o, strict=False, async_mode=True)...
Event loop is already running. Applying nest_asyncio patch to allow async execution...
Evaluating 3 test case(s) in parallel: |██████████|100% (3/3) [Time Taken: 00:11, 3.97s/test case]
✓ ¡Pruebas finalizadas 🎉! Ejecuta 'deepeval login' para ver los resultados de la evaluación en Confident AI. ‼️ NOTA: También puedes ejecutar evaluaciones en TODAS las métricas de deepeval directamente en Confident AI en su lugar.