Avaliação com Ragas
Este guia demonstra como utilizar o Ragas para avaliar um pipeline Retrieval-Augmented Generation (RAG) baseado no Milvus.
O sistema RAG combina um sistema de recuperação com um modelo generativo para gerar novo texto com base num determinado pedido. O sistema começa por recuperar documentos relevantes de um corpus utilizando o Milvus e, em seguida, utiliza um modelo generativo para gerar novo texto com base nos documentos recuperados.
O Ragas é um quadro que ajuda a avaliar as condutas RAG. Existem ferramentas e estruturas que ajudam a construir estas condutas, mas avaliá-las e quantificar o seu desempenho pode ser difícil. É aqui que entra o Ragas (Avaliação RAG).
Pré-requisitos
Antes de executar este notebook, certifique-se de ter as seguintes dependências instaladas:
$ pip install --upgrade pymilvus milvus-lite openai requests tqdm pandas ragas
Se estiver a utilizar o Google Colab, para ativar as dependências que acabou de instalar, poderá ter de reiniciar o tempo de execução (clique no menu "Tempo de execução" na parte superior do ecrã e selecione "Reiniciar sessão" no menu pendente).
Neste exemplo, vamos utilizar o OpenAI como LLM. Você deve preparar a chave api OPENAI_API_KEY como uma variável de ambiente.
import os
os.environ["OPENAI_API_KEY"] = "sk-***********"
Definir o pipeline do RAG
Vamos definir a classe RAG que usa o Milvus como armazenamento de vectores e o OpenAI como LLM. A classe contém o método load, que carrega os dados de texto no Milvus, o método retrieve, que recupera os dados de texto mais semelhantes à pergunta dada, e o método answer, que responde à pergunta dada com o conhecimento 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-3.5-turbo",
):
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("foo"))
self.milvus_client.create_collection(
collection_name=self.collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Bounded", # Strong consistency level
)
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
Vamos inicializar a classe RAG com os clientes OpenAI e Milvus.
openai_client = OpenAI()
milvus_client = MilvusClient(uri="./milvus_demo.db")
my_rag = RAG(openai_client=openai_client, milvus_client=milvus_client)
Quanto ao argumento de MilvusClient:
- Definir o
uricomo um ficheiro local, por exemplo./milvus.db, é o método mais conveniente, pois utiliza automaticamente o Milvus Lite para armazenar todos os dados neste ficheiro. - Se tiver uma grande escala de dados, pode configurar um servidor Milvus mais eficiente em docker ou kubernetes. Nesta configuração, utilize o uri do servidor, por exemplo,
http://localhost:19530, como o seuuri. - Se pretender utilizar o Zilliz Cloud, o serviço de nuvem totalmente gerido para o Milvus, ajuste
urietoken, que correspondem ao Public Endpoint e à chave Api no Zilliz Cloud.
Executar o pipeline RAG e obter resultados
Utilizamos o guia de desenvolvimento do Milvus para ser o conhecimento privado no nosso RAG, que é uma boa fonte de dados para um pipeline RAG simples.
Descarregue-o e carregue-o no pipeline RAG.
import os
import urllib.request
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()
# We simply use "# " to separate the content in the file, which can roughly separate the content of each main part of the markdown file.
text_lines = file_text.split("# ")
my_rag.load(text_lines) # Load the text data into RAG pipeline
Creating embeddings: 100%|██████████| 27/27 [00:20<00:00, 1.34it/s]
Vamos definir uma pergunta de consulta sobre o conteúdo da documentação do guia de desenvolvimento. E, em seguida, usar o método answer para obter a resposta e os textos de contexto 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 for building Milvus and running it 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```yaml\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##"])
Agora, vamos preparar algumas perguntas com as respectivas respostas verdadeiras. Obtemos respostas e contextos do nosso pipeline RAG.
from ragas import EvaluationDataset
from datasets import Dataset
import pandas as pd
user_input_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?",
]
reference_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.",
]
retrieved_contexts_list = []
response_list = []
for user_input in tqdm(user_input_list, desc="Answering questions"):
response, retrieved_context = my_rag.answer(user_input, return_retrieved_text=True)
retrieved_contexts_list.append(retrieved_context)
response_list.append(response)
df = pd.DataFrame(
{
"user_input": user_input_list,
"retrieved_contexts": retrieved_contexts_list,
"response": response_list,
"reference": reference_list,
}
)
rag_results = EvaluationDataset.from_pandas(df)
df
Answering questions: 100%|██████████| 3/3 [00:04<00:00, 1.37s/it]
| user_input | contextos recuperados | resposta | referência | |
|---|---|---|---|---|
| 0 | quais são as especificações dos requisitos de hardware... | [Requisitos de hardware\n\nAs seguintes especificaç... | A especificação dos requisitos de hardware para a constru... | Se quiser construir o Milvus e correr a partir da fonte... |
| 1 | Qual é a linguagem de programação usada para escrever... | [CMake & Conan\n\nA biblioteca de algoritmos de Mil... | A linguagem de programação usada para escrever o Knowher... | A linguagem de programação usada para escrever o Knowher... |
| 2 | O que deve ser assegurado antes de executar a cobertura de... | [Cobertura de código\n\nAntes de submeter o seu pull ... | Antes de executar a cobertura de código, deve ser... | Antes de executar a cobertura de código, deve ser ... |
Avaliação com Ragas
Usamos o Ragas para avaliar o desempenho dos resultados do nosso pipeline RAG.
O Ragas fornece um conjunto de métricas que é fácil de usar. Usamos Answer relevancy, Faithfulness, Context recall e Context precision como métricas para avaliar nosso pipeline RAG. Para obter mais informações sobre as métricas, consulte as Métricas Ragas.
from ragas import evaluate
from ragas.metrics import AnswerRelevancy, Faithfulness, ContextRecall, ContextPrecision
from ragas.llms import LangchainLLMWrapper
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
evaluator_llm = LangchainLLMWrapper(llm)
results = evaluate(
dataset=rag_results,
metrics=[
AnswerRelevancy(llm=evaluator_llm),
Faithfulness(llm=evaluator_llm),
ContextRecall(llm=evaluator_llm),
ContextPrecision(llm=evaluator_llm),
],
)
results
Evaluating: 100%|██████████| 12/12 [00:10<00:00, 1.11it/s]
{'answer_relevancy': 0.9894, 'faithfulness': 1.0000, 'context_recall': 1.0000, 'context_precision': 1.0000}