Evaluación con Arize Pheonix
Esta guía muestra cómo utilizar Arize Pheonix 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 texto nuevo 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.
Arize Pheonix es un marco de trabajo que le ayuda a evaluar sus canalizaciones RAG. Existen herramientas y marcos de trabajo que le ayudan a construir estas canalizaciones, pero evaluarlas y cuantificar su rendimiento puede ser difícil. Aquí es donde entra Arize Pheonix.
Requisitos previos
Antes de ejecutar este cuaderno, asegúrate de tener instaladas las siguientes dependencias:
$ pip install --upgrade pymilvus openai requests tqdm pandas "arize-phoenix>=4.29.0" nest_asyncio
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:12<00:00, 3.84it/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 are:\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.04s/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... | El lenguaje de programación utilizado para escribir Knowher... | ¿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 con Arize Phoenix
Utilizamos Arize Phoenix para evaluar nuestro canal de generación de recuperación aumentada (RAG), centrándonos en dos métricas clave:
Evaluación de alucinaciones: Determina si el contenido es factual o alucinatorio (información no basada en el contexto), garantizando la integridad de los datos.
- Explicación de la alucinación: Explica por qué una respuesta es objetiva o no.
Evaluación dela garantía de calidad: Evalúa la precisión de las respuestas del modelo a las consultas de entrada.
- Explicación de la GC: Detalla por qué una respuesta es correcta o incorrecta.
Descripción general del seguimiento de Phoenix
Phoenix proporciona rastreo compatible con OTEL para aplicaciones LLM, con integraciones para frameworks como Langchain, LlamaIndex, y SDKs como OpenAI y Mistral. El rastreo captura todo el flujo de peticiones, ofreciendo información sobre:
- Latencia de la aplicación: Identifica y optimiza las invocaciones LLM lentas y el rendimiento de los componentes.
- Uso de tokens: Desglosa el consumo de tokens para optimizar costes.
- Excepciones en tiempo de ejecución: Capture problemas críticos como la limitación de velocidad.
- Documentos recuperados: Analice la recuperación, la puntuación y el orden de los documentos.
Al utilizar el rastreo de Phoenix, puede identificar cuellos de botella, optimizar recursos y garantizar la fiabilidad del sistema en varios marcos de trabajo y lenguajes.
import phoenix as px
from phoenix.trace.openai import OpenAIInstrumentor
# To view traces in Phoenix, you will first have to start a Phoenix server. You can do this by running the following:
session = px.launch_app()
# Initialize OpenAI auto-instrumentation
OpenAIInstrumentor().instrument()
🌍 To view the Phoenix app in your browser, visit http://localhost:6006/
📖 For more information on how to use Phoenix, check out https://docs.arize.com/phoenix
Texto Alt
import nest_asyncio
from phoenix.evals import HallucinationEvaluator, OpenAIModel, QAEvaluator, run_evals
nest_asyncio.apply() # This is needed for concurrency in notebook environments
# Set your OpenAI API key
eval_model = OpenAIModel(model="gpt-4o")
# Define your evaluators
hallucination_evaluator = HallucinationEvaluator(eval_model)
qa_evaluator = QAEvaluator(eval_model)
# We have to make some minor changes to our dataframe to use the column names expected by our evaluators
# for `hallucination_evaluator` the input df needs to have columns 'output', 'input', 'context'
# for `qa_evaluator` the input df needs to have columns 'output', 'input', 'reference'
df["context"] = df["contexts"]
df["reference"] = df["contexts"]
df.rename(columns={"question": "input", "answer": "output"}, inplace=True)
assert all(
column in df.columns for column in ["output", "input", "context", "reference"]
)
# Run the evaluators, each evaluator will return a dataframe with evaluation results
# We upload the evaluation results to Phoenix in the next step
hallucination_eval_df, qa_eval_df = run_evals(
dataframe=df,
evaluators=[hallucination_evaluator, qa_evaluator],
provide_explanation=True,
)
run_evals |██████████| 6/6 (100.0%) | ⏳ 00:03<00:00 | 1.64it/s
results_df = df.copy()
results_df["hallucination_eval"] = hallucination_eval_df["label"]
results_df["hallucination_explanation"] = hallucination_eval_df["explanation"]
results_df["qa_eval"] = qa_eval_df["label"]
results_df["qa_explanation"] = qa_eval_df["explanation"]
results_df.head()
entrada | contextos | salida | verdad_subyacente | contexto | referencia | alucinación_evaluación | alucinación_explicación | qa_eval | explicación_qa | |
---|---|---|---|---|---|---|---|---|---|---|
0 | ¿cuál es la especificación de los ... | [Requisitos de hardware\nLa siguiente especif... | La especificación de requisitos de hardware ... | Si desea crear Milvus y ejecutarlo desde la fuente... | [Requerimientos de Hardware La siguiente especific... | [Requisitos de hardware\nLa siguiente especif... | factual | Para determinar si la respuesta es fac... | correcta | Para determinar si la respuesta es correcta, necesitamos... |
1 | ¿Cuál es el lenguaje de programación utilizado para escribir... | [CMake & Conan\n\nLa biblioteca de algoritmos de Mil... | El lenguaje de programación utilizado para escribir Knowher... | El lenguaje de programación utilizado para escribir Knowher... | [CMake & Conan\nLa biblioteca de algoritmos de Mil... | [CMake & Conan La biblioteca de algoritmos de Mil... | factual | Determinar si la respuesta es factual o hallu... | correcto | Para determinar si la respuesta es correcta, necesitamos... |
2 | ¿Qué hay que asegurarse antes de ejecutar la ... | [Cobertura de código\nAntes de enviar su pull ... | Antes de ejecutar la cobertura de código, debe ens... | Antes de ejecutar la cobertura de código, debe ... | [Cobertura de código Antes de enviar su pull ... | [Antes de enviar su pull ... | fáctico | El texto de referencia especifica que antes de ejecutar ... | correcta | Para determinar si la respuesta es correcta, necesitamos... |