Valutazione con DeepEval
Questa guida mostra come utilizzare DeepEval per valutare una pipeline Retrieval-Augmented Generation (RAG) costruita su Milvus.
Il sistema RAG combina un sistema di recupero con un modello generativo per generare nuovo testo sulla base di un prompt dato. Il sistema recupera prima i documenti rilevanti da un corpus utilizzando Milvus e poi utilizza un modello generativo per generare nuovo testo sulla base dei documenti recuperati.
DeepEval è un framework che aiuta a valutare le pipeline RAG. Esistono strumenti e framework che aiutano a costruire queste pipeline, ma valutarle e quantificarne le prestazioni può essere difficile. È qui che entra in gioco DeepEval.
Prerequisiti
Prima di eseguire questo notebook, assicuratevi di avere installato le seguenti dipendenze:
$ pip install --upgrade pymilvus openai requests tqdm pandas deepeval
Se si utilizza Google Colab, per abilitare le dipendenze appena installate potrebbe essere necessario riavviare il runtime (fare clic sul menu "Runtime" nella parte superiore dello schermo e selezionare "Restart session" dal menu a discesa).
In questo esempio utilizzeremo OpenAI come LLM. È necessario preparare la chiave api OPENAI_API_KEY
come variabile d'ambiente.
import os
os.environ["OPENAI_API_KEY"] = "sk-*****************"
Definire la pipeline RAG
Definiamo la classe RAG che utilizza Milvus come archivio vettoriale e OpenAI come LLM. La classe contiene il metodo load
, che carica i dati di testo in Milvus, il metodo retrieve
, che recupera i dati di testo più simili alla domanda data, e il metodo answer
, che risponde alla domanda data con la conoscenza recuperata.
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
Inizializziamo la classe RAG con i client OpenAI e Milvus.
openai_client = OpenAI()
milvus_client = MilvusClient(uri="./milvus_demo.db")
my_rag = RAG(openai_client=openai_client, milvus_client=milvus_client)
Per quanto riguarda l'argomento di MilvusClient
:
- L'impostazione di
uri
come file locale, ad esempio./milvus.db
, è il metodo più conveniente, poiché utilizza automaticamente Milvus Lite per memorizzare tutti i dati in questo file. - Se si dispone di una grande quantità di dati, è possibile configurare un server Milvus più performante su docker o kubernetes. In questa configurazione, utilizzare l'uri del server, ad esempio
http://localhost:19530
, comeuri
. - Se si desidera utilizzare Zilliz Cloud, il servizio cloud completamente gestito per Milvus, regolare
uri
etoken
, che corrispondono all'endpoint pubblico e alla chiave Api di Zilliz Cloud.
Eseguire la pipeline RAG e ottenere i risultati
Utilizziamo la guida allo sviluppo di Milvus come conoscenza privata nel nostro RAG, che è una buona fonte di dati per una semplice pipeline RAG.
Scaricarla e caricarla nella pipeline 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]
Definiamo una domanda di interrogazione sul contenuto della documentazione della guida allo sviluppo. Quindi utilizziamo il metodo answer
per ottenere la risposta e i testi contestuali recuperati.
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##"])
Ora prepariamo alcune domande con le corrispondenti risposte di verità. Otteniamo le risposte e i contesti dalla nostra pipeline 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]
domanda | contesti | risposta | verità_terra | |
---|---|---|---|---|
0 | quali sono le specifiche dei requisiti hardware... | [Requisiti hardware] Le seguenti specifiche... | Le specifiche dei requisiti hardware per... | Se si vuole costruire Milvus ed eseguirlo da sorgente... |
1 | Qual è il linguaggio di programmazione usato per scrivere... | [CMake & Conan] La libreria di algoritmi di Milvus... | Il linguaggio di programmazione utilizzato per scrivere Knowher... | Il linguaggio di programmazione utilizzato per scrivere Knowher... |
2 | Che cosa si deve garantire prima di eseguire la copertura del codice... | [Copertura del codice] Prima di inviare il pull ... | Prima di eseguire la copertura del codice, occorre assicurarsi ... | Prima di eseguire la copertura del codice, è necessario ... |
Valutazione di Retriever
Quando si valuta un retriever in sistemi di modelli linguistici di grandi dimensioni (LLM), è fondamentale valutare quanto segue:
Rilevanza del ranking: Quanto efficacemente il retriever dà priorità alle informazioni rilevanti rispetto ai dati irrilevanti.
Recupero contestuale: La capacità di catturare e recuperare informazioni contestualmente rilevanti in base all'input.
Equilibrio: La capacità del retriever di gestire le dimensioni dei pezzi di testo e la portata del recupero per ridurre al minimo le irrilevanze.
Insieme, questi fattori forniscono una comprensione completa del modo in cui il retriever stabilisce le priorità, cattura e presenta le informazioni più utili.
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(
Stai utilizzando l'ultima metrica di precisione contestuale di DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ Si sta eseguendo l'ultima metrica di richiamo contestuale di DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ Stai eseguendo l'ultima metrica di rilevanza contestuale di DeepEval! (usando 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]
✓ Test terminati 🎉! Eseguire 'deepeval login' per visualizzare i risultati della valutazione su Confident AI. ‼️ NOTA: È anche possibile eseguire valutazioni su TUTTE le metriche di deepeval direttamente su Confident AI.
Valutazione della generazione
Per valutare la qualità dei risultati generati nei modelli linguistici di grandi dimensioni (LLM), è importante concentrarsi su due aspetti chiave:
Pertinenza: Valutare se il prompt guida efficacemente l'LLM a generare risposte utili e contestualmente appropriate.
Fedeltà: Misurare l'accuratezza dell'output, assicurandosi che il modello produca informazioni corrette dal punto di vista fattuale e prive di allucinazioni o contraddizioni. Il contenuto generato deve essere in linea con le informazioni fattuali fornite nel contesto di ricerca.
L'insieme di questi fattori garantisce che gli output siano rilevanti e affidabili.
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
)
Stai utilizzando l'ultima metrica di pertinenza delle risposte di DeepEval! (usando gpt-4o, strict=False, async_mode=True)...
✨ Stai eseguendo l'ultima metrica di fedeltà di DeepEval! (usando 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]
✓ Test terminati 🎉! Eseguire 'deepeval login' per visualizzare i risultati della valutazione su Confident AI. ‼️ NOTA: È anche possibile eseguire valutazioni su TUTTE le metriche di deepeval direttamente su Confident AI.