Arize Pheonixによる評価
このガイドでは、Arize Pheonixを使用して、Milvusをベースに構築されたRAG(Retrieval-Augmented Generation)パイプラインを評価する方法を示します。
RAGシステムは、検索システムと生成モデルを組み合わせて、与えられたプロンプトに基づいて新しいテキストを生成します。このシステムは、まずMilvusを使ってコーパスから関連文書を検索し、次に生成モデルを使って検索された文書に基づいて新しいテキストを生成する。
Arize Pheonixは、RAGパイプラインの評価を支援するフレームワークである。パイプラインの構築を支援する既存のツールやフレームワークはありますが、パイプラインを評価し、パイプラインのパフォーマンスを定量化することは困難です。そこでArize Pheonixの登場です。
前提条件
このノートブックを実行する前に、以下の依存関係がインストールされていることを確認してください:
$ pip install --upgrade pymilvus openai requests tqdm pandas "arize-phoenix>=4.29.0" nest_asyncio
Google Colabを使用している場合、インストールしたばかりの依存関係を有効にするには、ランタイムを再起動する必要があるかもしれません(画面上部の "Runtime "メニューをクリックし、ドロップダウンメニューから "Restart session "を選択してください)。
この例では、LLMとしてOpenAIを使います。api key OPENAI_API_KEY
を環境変数として用意してください。
import os
# os.environ["OPENAI_API_KEY"] = "sk-*****************"
RAGパイプラインの定義
Milvusをベクトルストアとして、OpenAIをLLMとして使用するRAGクラスを定義します。このクラスには、テキストデータをMilvusにロードするload
メソッド、与えられた質問に最も類似したテキストデータを検索するretrieve
メソッド、検索された知識を用いて与えられた質問に回答するanswer
メソッドが含まれます。
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
RAGクラスをOpenAIとMilvusクライアントで初期化してみよう。
openai_client = OpenAI()
milvus_client = MilvusClient(uri="./milvus_demo.db")
my_rag = RAG(openai_client=openai_client, milvus_client=milvus_client)
MilvusClient
の引数については以下の通り:
uri
の引数をローカルファイル、例えば./milvus.db
に設定するのが最も便利である。- データ規模が大きい場合は、dockerやkubernetes上に、よりパフォーマンスの高いMilvusサーバを構築することができます。このセットアップでは、
http://localhost:19530
などのサーバ uri をuri
として使用してください。 - MilvusのフルマネージドクラウドサービスであるZilliz Cloudを使用する場合は、Zilliz CloudのPublic EndpointとApi keyに対応する
uri
とtoken
を調整してください。
RAGパイプラインの実行と結果の取得
Milvusの開発ガイドをRAGのプライベートナレッジとして使用します。
ダウンロードし、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]
開発ガイドのドキュメントの内容に関するクエリの質問を定義します。そして、answer
メソッドを使用して、答えと取得したコンテキストテキストを取得します。
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##"])
それでは、いくつかの質問とそれに対応するグランドトゥルースの答えを用意しましょう。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]
質問 | コンテキスト | 答え | 真実 | |
---|---|---|---|---|
0 | ハードウェア要件とは何ですか? | [Hardware Requirementsn以下の仕様がある。 | Milvusをビルドするためのハードウェア要件は何ですか? | Milvusをビルドし、ソースから実行する場合、... |
1 | Milvusをビルドしてソースコードから実行したいのですが、プログラミング言語は何ですか? | [Milvusのアルゴリズムライブラリは、CMakeとConan... | Knowherを記述するために使用されるプログラミング言語... | Knowherを記述するために使用されるプログラミング言語... |
2 | コードカバレッジを実行する前に確認すべきことは? | [Code coveragenBefore submitting your pull ... | コードカバレッジを実行する前に、... | コードカバレッジを実行する前に、... |
Arize Phoenixによる評価
我々は、Arize Phoenixを使用して、2つの主要なメトリクスに焦点を当てて、検索支援生成(RAG)パイプラインを評価する:
幻覚評価:コンテンツの内容が事実か幻覚(文脈に基づかない情報)かを判断し、データの整合性を確保します。
- 幻覚説明:回答が事実に基づいているか否かの理由を説明する。
QA評価:入力クエリに対するモデルの回答の正確性を評価する。
- QA説明:回答が正しいか正しくないかの理由を詳細に説明します。
Phoenixトレースの概要
PhoenixはOTEL互換のトレースをLLMアプリケーションに提供し、Langchainや LlamaIndexのようなフレームワーク、OpenAIや MistralのようなSDKを統合します。トレースはリクエストフロー全体をキャプチャし、以下のような洞察を提供します:
- アプリケーション遅延:遅いLLMの呼び出しとコンポーネントのパフォーマンスを特定し、最適化します。
- トークンの使用状況:コスト最適化のためにトークン消費を分解します。
- ランタイム例外:レート制限などの重大な問題を捕捉します。
- 取得ドキュメントドキュメントの検索、スコア、順序を分析します。
Phoenixのトレースを活用することで、ボトルネックの特定、リソースの最適化、さまざまなフレームワークや言語にわたるシステムの信頼性の確保が可能になります。
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
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()
入力 | コンテキスト | 出力 | 真実 | コンテキスト | 参照 | 幻覚 | 幻覚説明 | 幻覚 | qa_説明 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | ハードウェア要件とは何ですか? | [Hardware Requirementsn以下の仕様がある。 | Milvusをビルドするためのハードウェア要件は以下の通りです。 | Milvusをビルドしてソースから実行する場合、以下のハードウェア要件が必要です。 | [Hardware Requirements The following specif... | [Hardware Requirements次の仕様が必要です。 | factual | 答えがfactualかhallu... かを判断する。 | 正解 | 答えが正しいかどうかを判断するには... |
1 | プログラミング言語は何ですか? | [CMakeとConanMil... | Knowherを記述するために使用されるプログラミング言語... | Knowherを書くためのプログラミング言語は? | [Mil... The algorithm library of Mil... | [Mil... 【CMake & Conan】Mil... | 事実 | 答えが事実か事実でないかを判断する。 | 正解 | 答えが正しいかどうかを判断するには、... |
2 | コード・カバレッジを実行する前に確認すべきことは? | [コード・カバレッジを実行する前に、コード・カバレッジを確実にする必要がある。 | コードカバレッジを実行する前に、... | コードカバレッジを実行する前に、... | [Code coverageBfore submitting your pull ... | [Code coveragenBefore submitting your pull ... | factual | 参考テキストでは、コードカバレッジを実行する前に... | 正しい | 答えが正しいかどうか判断するには、... |