こんにちは、サイオステクノロジーの佐藤 陽です。
先日Ragasの大きめなアップデート(0.2.x)があり、色々と変更点が見られました。
今回はそのアップデート内容をチェックしていきたいと思います。
- Ragasって何?
- 今Ragas使ってるけど、どんな変更があったの?
- 公式ドキュメント読んでもよくわからないからサクっと教えて!
といった方は最後までご覧ください!
はじめに
Ragasって何?という方は以下の記事をご覧ください!
こちらの記事は前のバージョン(0.1.x)での紹介にはなりますが、概念的な部分は変わっていないので
一度読んでいただくことで、今回のアップデート内容も理解しやすいかと思います。
今回このRagasにバージョンアップがあり、0.1.xから0.2.xになりました。
そのアップデート内容の確認と、サンプルコードを交えて挙動の確認をしていきたいと思います。
なお、手元の環境では ragas==0.2.3
のバージョンを利用して検証行っています。
何が変わったのか?
公式にもまとめられていました。
変更点の概要としては以下の通りです。
- 評価用に与えるデータセットのクラス変更
- メトリクスの追加および、利用方法の変更
- テストセット生成のコスト効率改善
- プロンプトオブジェクトの変更
もう少し細かく見ていきたいと思います。
データセットの型変更
Ragasには以下の4つの値を渡すことで評価を行うことができます。
そしてそれらの名前にも変更がありました。
新名称 | 何? | 旧称 |
---|---|---|
user_input | ユーザーからの質問内容 | question |
response | RAGから返された回答内容 | answer |
retrieved_contexts | RAGが外部DBから取得し、参考にした内容 | contexts |
reference | user_inputに対する真の回答内容 | ground_truth |
これらの値をデータセットとして評価関数に渡し、評価を行ってもらいます。
そして、0.2.xへのアップデートによってこのデータセットのクラスにも変更がありました。
以前はHuggingFaceというライブラリのDatasets
というものを利用していましたが、0.2.xでは独自のEvaluationDatasetというクラスを利用しています。
このデータセットの用意の仕方を3つご紹介します。
Datasetsからの変換
前バージョンで利用していたDatasetsからEvaluationDatasetの変換方法が提供されています。
以下のような形で簡単に変換することが可能です。
from datasets import load_dataset
from ragas import EvaluationDataset
dataset = load_dataset("explodinggradients/amnesty_qa","english_v3")
eval_dataset = EvaluationDataset.from_hf_dataset(dataset["eval"])
CSVからの変換
EvaluationDataset->csvの変換の処理に関しても紹介されていました。
# load eva dataset
eval_dataset = EvaluationDataset.from_csv("path/to/save/dataset.csv")
Evaluation Sampleを利用した生成
ここが大きな変更点です。
恐らく0.2.x系からだと思うのですが、Evaluation Sample
という概念が出てきました。
Evaluation Sampleの定義を見てみると
An evaluation sample is a single structured data instance that is used to asses and measure the performance of your LLM application in specific scenarios.
とあり、翻訳すると以下の感じです。
評価サンプルは、特定のシナリオにおけるLLMアプリケーションのパフォーマンスを評価・測定するために使用される単一の構造化データインスタンスです。
要は、評価・測定するために使うデータの入れ物という事をいってます。
使い方としては以下のような感じです。
sample1 = SingleTurnSample(
user_input="日本の首都はどこですか?", #質問
retrieved_contexts=["東京(とうきょう、英: Tokyo)は、関東平野の南部に位置し、東京湾に面する都市。日本の首都である."], #RAGが外部DBから取得し、参考にした内容
response="日本の首都は東京です.", #RAGから返された回答内容
reference="東京", #user_inputに対する真の答え
)
「ん?さっき話に出してたEvaluationDatasetはどこいった?」という感じなのですが
Evaluation Datasetの定義を見てみると
An evaluation dataset is a homogeneous collection of data samples
とあり、Evaluation Sampleのコレクションである事がわかります。
実際サンプルコードなどを見ると以下のような感じで、コレクションの関係があることが分かります。
dataset = EvaluationDataset(samples=[sample1, sample2, sample3])
メトリクスの追加
これまで使われていたメトリクス(評価指標)は引き続き使えることに加えて、いくつか新たなメトリクスも加わっています。
ドキュメントを見ると、RAGの評価にとどまらずAgentや、自然言語の評価も行えるようになってますね。
RAG以外のメトリクスも非常に気になりますが、今回はひとまずRAGの評価の言及にとどめたいと思います。
このメトリクスの追加に合わせて、メトリクスの設定方法も若干変わっています。
メトリクスの設定方法の変更
以前は以下のような形で、利用するメトリクスを設定していました。(必要な部分のみ抜粋)
# import
from ragas.metrics import faithfulness
# list of metrics we're going to use
metrics = [
faithfulness
]
これが0.2.xのバージョンでは以下のように実装します。
メトリクスを選択する段階で利用するLLMを指定することを推奨するようです。
from ragas.metrics import Faithfulness
faithfulness_metric = Faithfulness(llm=your_evaluator_llm)
これにより、どのメトリクスで、どのLLMを使うのかを明確にすることができるようになりました。
評価方法の変更
また評価を行う部分の関数にも、いくつか変更が見られました。
今回はAzure OpenAI Serviceにデプロイしたモデルを使用する想定で実装します。
evaluate関数
まず、以前は以下のようにevaluate関数を使って評価を行っていました。
# import
from ragas.metrics import faithfulness
# list of metrics we're going to use
metrics = [
faithfulness
]
os.environ["AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY")
os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT")
os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION")
generator_llm = AzureChatOpenAI(
azure_deployment="gpt-35-turbo-16k",
)
embeddings = AzureOpenAIEmbeddings(
azure_deployment="text-embedding-ada-002"
)
result = evaluate(
ds, metrics=metrics, llm=chat_llm, embeddings=embeddings
)
ただ先程述べたようにmetrics
の生成方法が変わってるので、そのあたりの影響を受けてきます。
0.2.x系では以下のように書きます。
from ragas.metrics import Faithfulness
os.environ["AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY") #なぜかAZURE_OPENAI_API_KEYという環境変数名にすると反応してくれなかった。
os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT")
os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION")
evaluator_llm = LangchainLLMWrapper(AzureChatOpenAI(
azure_deployment="gpt-4o-mini",
temperature=0,
))
metrics = [
Faithfulness(llm=evaluator_llm)
]
results = evaluate(dataset=eval_dataset, metrics=metrics)
df = results.to_pandas()
もう少し深掘りしてみていきたいと思います。
0.2.x系でのevaluate()のソースコードは以下のようになっています。
def evaluate(
dataset: t.Union[Dataset, EvaluationDataset],
metrics: list[Metric] | None = None,
llm: t.Optional[BaseRagasLLM | LangchainLLM] = None,
embeddings: t.Optional[BaseRagasEmbeddings | LangchainEmbeddings] = None,
callbacks: Callbacks = None,
in_ci: bool = False,
run_config: RunConfig = RunConfig(),
token_usage_parser: t.Optional[TokenUsageParser] = None,
raise_exceptions: bool = False,
column_map: t.Optional[t.Dict[str, str]] = None,
show_progress: bool = True,
主な変更点としては
- datasetとして
EvaluationDataset
が追加されている - metricsの中身の
Metrics
の中身が変更されている - llm, embeddingsに関してBaseRagasLLM, BaseRagasEmbeddingsというものが追加されている
といった点が挙げられるかと思います。
先程Metricsごとに利用するLLMを指定できるといった説明をしましたが、仮に指定しない場合はevaluate関数で指定したLLMが使われるようです。
なお、優先度としてはevaluate関数の指定よりもMetricsの指定が高く、MetricsのLLMで上書きされるようです。
ascore関数
ascoreと呼ばれる関数にも変更が見られました。
Second is that metrics.ascore is now being deprecated in favor of metrics.single_score . You can make the transition as such
とあるので、ascore
というメソッドが非推奨となり、そのかわりsingle_score
を使ってくれ。ということです。
何のことかというと、以前書いたRagasの記事では、先程のようにevaluate()という関数を使って、複数のmetricsを一気に評価していました。
一方で、ascoreといったメソッドを使うことで、1つのデータに対して評価する方法も存在します。
このascoreという関数が0.2.xでは推奨されなくなり、single_turn_ascore
やmulti_turn_ascore
といった関数の使用が推奨されています。
このSingleやらMultiやらの話は後ほどしますので、とりあえず一旦使ってみます。
以下に実装のサンプルを載せます。 こちらのサンプルはとりあえず同期処理としてsingle_turn_score
を使っています。
os.environ["AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY")
os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT")
os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION")
evaluator_llm = LangchainLLMWrapper(AzureChatOpenAI(
azure_deployment="gpt-4o-mini",
temperature=0,
))
single_turn_sample = SingleTurnSample(
user_input="日本の首都はどこですか?", #質問
retrieved_contexts=["東京(とうきょう、英: Tokyo)は、関東平野の南部に位置し、東京湾に面する都市。日本の首都である."], #RAGが外部DBから取得し、参考にした内容
response="日本の首都は東京です.", #RAGから返された回答内容
reference="東京" #user_inputに対する真の答え
)
faithfulness = Faithfulness(llm=evaluator_llm)
metrics = [
Faithfulness(llm=evaluator_llm)
]
result = faithfulness.single_turn_score(single_turn_sample)
# score = 1.0
メトリクスの分類
先程single_turnやmulti_turnといったものが出てきましたので、これについて見ていきたいと思います。
そもそも今回メトリクスは
- 評価にLLMを利用するか
- SingleTurnか、MultiTurnか
の2軸で分類され、合計4パターンで分類されます。 Ragasの公式にもメトリクスの分類が分かりやすくまとめてありました。
この分類に合わせ、実装を切り替える必要があります。
Faithfulnessのメトリクスを例に、定義を見てみたいと思います。
@dataclass
class Faithfulness(MetricWithLLM, SingleTurnMetric):
name: str = "faithfulness" # type: ignore
と書かれています。
MetricWithLLMを継承しているため、このFaithfulnessの評価にはLLMが利用されることが分かります。
そのため、Faithfulnessのコンストラクタ引数としてはLLMを与えてあげる必要があります。
他にはMetricWithEmbeddingsを継承しているメトリクスもありますが
これは評価の際にEmbeddingsモデルを利用することを表します。
また、FaithfulnessのはSingleTurnMetricというクラスも継承しています。
これはユーザーとAI間の1回のやり取り(Interaction)に基づき評価を行うことを表します。
SingleTurnMetricを継承しているMetricsに関してはsingle_turn_ascore
や、single_turn_score
のメソッドを利用して評価を行います。
Agentの評価に用いるMetricsだとMulti Turnなものが多いかもしれないですね。
テストセット生成のコスト効率改善
テストセットの仕組みが変わったようなのですが、すいません、正直まだ理解しきれていません…。
どうやらKnowledge Graphを使うようになったりして、大幅にコスト削減できるようになったようです。
このあたりは別の機会にまた試してみたいと思います。
プロンプトオブジェクトの変更
最後、プロンプトオブジェクトの変更についてです。
まず、変更点を先に述べると0.1.xではPromptというオブジェクトが利用されていましたが
0.2.xではPydanticPromptというオブジェクトが使われるようになりました。
ただ恥ずかしながら、自分もこの変更点見るまではRagasにおける「プロンプト」といったものを認識しておらず
「Ragasの中でそんなプロンプトを意識するところあるの?」といった感じでした。
それでソースコードを見てみると、色々なところで使われてたので少しご紹介します。
先程のFaithfulnessの例を挙げます。
Faithfulnessのコンストラクタの中にPydanticPrompt
で定義されたプロンプトオブジェクトが含まれていました。
class Faithfulness(MetricWithLLM, SingleTurnMetric):
name: str = "faithfulness" # type: ignore
_required_columns: t.Dict[MetricType, t.Set[str]] = field(
default_factory=lambda: {
MetricType.SINGLE_TURN: {
"user_input",
"response",
"retrieved_contexts",
}
}
)
nli_statements_message: PydanticPrompt = field(default_factory=NLIStatementPrompt) #←ここ!!
statement_prompt: PydanticPrompt = field(default_factory=LongFormAnswerPrompt) #←ここ!!
sentence_segmenter: t.Optional[HasSegmentMethod] = None
max_retries: int = 1
_reproducibility: int = 1
今回デフォルトで使われているNLIStatementPrompt
とLongFormAnswerPrompt
の2つについて、それぞれの中身を確認してみました。
英語かつ少々長いですが、一番分かりやすい点としてinstruction
の文章を確認してみます。
instructionはいわばAIへの命令文です。
まずはNLIStatementPromptのほうから。
Your task is to judge the faithfulness of a series of statements based on a given context. For each statement you must return verdict as 1 if the statement can be directly inferred based on the context or 0 if the statement can not be directly inferred based on the context.
翻訳すると以下の感じです
あなたの仕事は、与えられた文脈に基づいて一連の声明の忠実さを判断することです。各声明に対して、文脈に基づいて直接推論できる場合は1、文脈に基づいて直接推論できない場合は0の判決を返さなければなりません。
次に、LongFormAnswerPromptの方は以下の内容でした。
Given a question, an answer, and sentences from the answer analyze the complexity of each sentence given under ‘sentences’ and break down each sentence into one or more fully understandable statements while also ensuring no pronouns are used in each statement. Format the outputs in JSON.
“質問、回答、および回答からの文が与えられた場合、’sentences’ にある各文の複雑さを分析し、各文を一つ以上の完全に理解可能なステートメントに分解してください。また、各ステートメントには代名詞を使用しないようにしてください。出力はJSON形式でフォーマットしてください。
これを見てると、Faithfulnessの算出方法が頭をよぎります。
Faithfulnessのスコア算出方法は、以下の2ステップです。
- 得られた回答(response)を分解
- 分解された回答のなかで外部から取得した情報(retrieved_contexts)から導かれた個数/step1で分解された数
まさに、STEP1に該当するのがLongFormAnswerPromp
で、STEP2に該当するのがNLIStatementPrompt
と分かります。
つまり、最終的なFaithfulnessの算出に必要な要素を、これらのプロンプトを使って算出していることが読み取れました。
このようにRagasの内部で評価を行う際にも、プロンプトエンジニアリングのテクニックは活用されていることが分かりました。
そして、もちろんこのプロンプトの内容を自分で書き換えること(=新たなPydanticPromptを作成し、置き換えること)も可能です。
ただ、LLMを評価するためのRagasの中で、評価するためのプロンプトを書き直すとなってくると、そのプロンプトが適切か、かみたいな話にもなってきますね…
一般的なソフトウェアのテストにおいても、「テストコードが正しいことはどうやって証明しますか?」といった議題が上がりますが
それに似た、もしくはそれ以上にややこしい問題な気もします。
まとめ
今回はRagasの0.2.x系のバージョンアップ内容をさらっとおさらいしてみました。
おさらいする中で知らなかった事も多々あったので、そのあたりは改めて勉強して記事にしていきたいと思います。
ではまた!