RAGを使ってAIの回答精度を向上させる一連の流れを、Spring AIで理解する:Spring AIで始める生成AIプログラミング(6)
Java×Spring AIで始めるAIプログラミングの入門連載。前回はベクトルストアの基本的な使い方を説明しました。今回は、RAGを使った質問から回答の流れを説明していきます。
本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。
目次
Spring AIにおけるRAGとは
RAGとはRetrieval(検索)+Augmented(拡張)+Generation(生成)の略で、日本語では「検索拡張生成」と訳されます。ただし、開発者の立場から見ると、この言葉だけでは具体的に何を指しているのか分かりにくい部分があります。そこで、Spring AIにおけるRAGの仕組みを概略として示したのが図1です。
このように、利用者から入力された質問を基に、AIが回答しやすい形に質問を言い換えたり、その回答を補強するためにクローズドなデータベースなどから必要な参考情報を取り出したりする仕組みがRAGの役割です。Spring AIにおけるRAGとは、これらの目的を実現するための一連の処理の流れを定義したもの、と言えます。
Spring AIで提供されているRAG関連のクラス
Spring AIでは、RAG用のクラスとして以下の2つのクラスが提供されています。
- QuestionAnswerAdvisor:単純なQAシステムを作成するためのクラス
- RetrievalAugmentationAdvisor:さまざまな目的を実現するためにカスタマイズ可能なクラス
これらについて、具体的に説明していきます。
【補足】本稿で使用するサンプルデータについて
今回使用するデータは、前回も扱った国交省から配布されているマンション規約のひな型文章です。前回、データを既に作成している方はこの部分は読み飛ばして問題ありません。なお、このPDFファイルを用いてVectorStoreを作成するには、前回のサンプルコードから以下のコマンドを実行してください。
shell:>elt-extract-pdf --filename 001965185.pdf
シンプルなRAGを実現する(QuestionAnswerAdvisor)
まず紹介するのは、QuestionAnswerAdvisorクラスです。ここでは、QuestionAnswerAdvisorと前回紹介したVectorStoreを組み合わせて、ユーザーの質問に答えるプログラムを作ってみましょう。
[1]依存関係を設定する
QuestionAnswerAdvisorを利用するには、ビルドファイルに依存関係を追加します(リスト1)。
dependencies { // 省略 implementation("org.springframework.ai:spring-ai-advisors-vector-store") }
[2]質問に答えるプログラムを作る
では、QuestionAnswerAdvisorを使ったRAGプログラムを作っていきましょう。QuestionAnswerAdvisorは第2回で説明したAdvisorの一種で、ChatClientに設定するだけで利用できます。ただし、RAGとして利用する場合には留意すべき点があるので、そこに注目しながらサンプルも見ていきましょう(リスト2)。
@ShellMethod(key = "qa-prompt") public String qaPrompt(String message,String keyword){ // (1) 検索リクエストを作成する SearchRequest searchRequest = SearchRequest.builder() .topK(10).similarityThreshold(0.5) // 上位10件、類似度の閾値は0.5 // .query(message) (2) ここで指定することは意味がない .build(); // (3) QuestionAnswerAdvisorのインスタンスを作成 QuestionAnswerAdvisor advisor = QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(searchRequest) .build(); // (4) システムのプロンプト用の文字列 String template = """ あなたはマンションの管理人です。丁寧にやさしい口調で答えてください。 """; SystemPromptTemplate tmpl = new SystemPromptTemplate(template); var prompt = Prompt.builder().messages( tmpl.createMessage(), // (5) SystemMessageを作成 // (6) 入力された質問メッセージ UserMessage.builder().text(message).build() ).build(); // (7) advisorsに設定する var response = client.prompt(prompt).advisors(advisor).call(); return response.content(); }
(1)では、VectorStoreで検索条件を設定するためのSearchRequestを作成します。第4回でSearchRequestを作成する際には、実際にベクトルデータとして検索する文字列を(2)のように設定していました。
しかし、QuestionAnswerAdvisorを使う場合には、この検索文字列は入力される質問(サンプルコードではmessage変数の値)で後から上書きされるので不要です。なぜなら、Advisorが後からユーザーの質問(message変数)を使って検索するため、ここで指定しても上書きされてしまうからです。つまり、VectorStoreで検索する質問文と、AIプロンプトで使う質問文を別々に設定することはできません。
(3)では、VectorStoreを指定し、必要に応じて追加の検索条件(今回の例ではtopKやsimilarityThreshold)を設定して、QuestionAnswerAdvisorのインスタンスを作成します。これは、AIが質問応答する際のアドバイザーとして機能します。
AIへの回答を生成する際、質問文以外にも追加の指示や説明をプロンプトに含めたい場合があります。その場合は(4)や(5)のように、SystemPromptTemplateを使って追加テキストをSystemMessageとして作成します。ここで注意すべき点は、追加テキストをUserMessageや質問文に直接連結してしまうと、そのテキストもVectorStoreへの検索クエリとして扱われてしまうことです。必ずSystemMessageとして分離して設定しましょう。
(6)でユーザーから入力された質問文をUserMessageとして設定し、(7)でAdvisorをChatClientに渡してAIから回答を取得します。
この流れによって、検索条件やプロンプトの役割を明確に分けて、AIから意図した通りの応答を得ることができます。
[3]サンプルを実行する
以上のコードを実行すると、以下のような結果が得られます。
shell:>qa-prompt 駐車場を使う場合にはどうしたらいいですか 駐車場を使用される場合には、まず「駐車場使用細則」をご確認いただく必要がございます。また、駐車場使用契約を締結することが求められています。具体的には、常時駐車する車両の所有者や車両番号、車種をあらかじめ管理組合に届け出ていただくことが必要です。 さらに、駐車場の使用者の選定は、公平な方法で行われることが望まれていますので、抽選や申込順にて行われることがあります。詳細については、管理規約に基づいて進めることになりますので、何かご不明な点がございましたら、お気軽にお尋ねくださいね。
この結果が期待したものかどうかを確認する必要があるでしょう。その場合には、リスト3のようにログ出力をすることで実際にSpring AI側で最終的に作成しているプロンプトを確認できます。
logging.level.org.springframework.web.client.DefaultRestClient=DEBUG
こちらを設定し、再度処理を実行すると、リスト4のような内容が確認できます。
駐車場を使う場合にはどうしたらいいですか Context information is below, surrounded by ---------------------(日本語訳:コンテキスト情報は以下「-----------------」の間にあります) --------------------- 「駐車場使用細則」を別途定めるものとする。また、駐車場使用契約の内 容(契約書の様式)についても駐車場使用細則に位置付け、あらかじめ総 (省略:VectorStoreからの検索結果が挿入) --------------------- Given the context and provided history information and not prior knowledge, reply to the user comment. If the answer is not in the context, inform the user that you can't answer the question.(日本語訳:提示された状況と提供された履歴情報に基づいて、ユーザーのコメントに返信してください。もし回答がコンテキストにない場合は、質問に答えられないことをユーザーに伝えてください。)
これらの内容を見ながら、いろいろと調整していく流れになります。
より自由な制御が可能なRAGを実現する(RetrievalAugmentationAdvisor)
実際にQuestionAnswerAdvisorを使ってみると、調整できるのは主にVectorStoreへの検索条件だけであり、検索に使うクエリ文字列自体は変更できません。そのため、自由度が低いと感じる方もいるかもしれません。そうした場合に活用できるのが、もうひとつのRetrievalAugmentationAdvisorクラスです。
ただし、いきなりカスタマイズ方法を説明するよりも、実際に使いながら理解する方が分かりやすいでしょう。そこで、先ほどのサンプルと同じ内容をRetrievalAugmentationAdvisorを使って書き換えてみます。
[1]依存関係を設定する
RetrievalAugmentationAdvisorを利用するには、ビルドファイルに依存関係を追加します(リスト5)。
dependencies { // 省略 implementation("org.springframework.ai:spring-ai-rag") }
[2]質問に答えるプログラムを作る
では、先ほどのQuestionAnswerAdvisorを使ったコードを、RetrievalAugmentationAdvisorで書き換えてみましょう(リスト6)。
@ShellMethod(key = "rag-qa-prompt") public String ragQaPrompt(String message){ // (1) QuestionAnswerAdvisorのインスタンスを作成 RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder() // (2) 参考情報を設定するための方法 .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.5) .topK(10) .build() ).build(); // リスト2と同じコードのため省略 // (3) advisorsに設定する var response = client.prompt(prompt).advisors(advisor).call(); return response.content(); }
(1)では、RetrievalAugmentationAdvisorのインスタンスを作成しています。先ほどのQuestionAnswerAdvisorの場合は、VectorStoreの指定が必須でしたが、今回のコードでは(2)のようにdocumentRetrieverという項目で、新たにVectorStoreDocumentRetrieverクラスを使ってVectorStoreを設定しています。
一見すると、QuestionAnswerAdvisorよりも手順が増えて面倒に感じるかもしれません。しかし、これはさまざまな方法で検索や情報取得の仕組みをカスタマイズできるように設計されているためです。より柔軟な設定や拡張が可能になる分、記述が複雑になるという特徴があります。
[3]サンプルを実行する
以上のコードを実行すると、以下のような結果が得られます。
shell:>rag-qa-prompt 駐車場を使う場合にはどうしたらいいですか 駐車場を使用する場合には、まず駐車場使用契約を結ぶ必要があります。契約期間や使用料については、事前に確認しておくことが大切です。また、駐車場使用細則に従って利用することが求められます。具体的な手続きや条件については、管理組合にお問合せいただくと良いでしょう。
多少、内容が異なりますが、おおよそは同じような回答になりました。また、実際にログからプロンプトを確認するとリスト7のような内容でした。
Context information is below.(日本語訳:コンテキスト情報は以下です) --------------------- 近時、駐車場の需要が減少しており、空き区画が生じているケースもあ (省略:VectorStoreからの検索結果が挿入) --------------------- Given the context information and no prior knowledge, answer the query.(日本語訳:提供されたコンテキスト情報のみに基づいて、質問に答えてください。) Follow these rules:(日本語訳:以下のルールに従ってください) 1. If the answer is not in the context, just say that you don't know.(日本語訳:もし答えがコンテキストにない場合は、「わかりません」とだけ答えてください。) 2. Avoid statements like "Based on the context..." or "The provided information...".(日本語訳:「コンテキストに基づくと…」や「提供された情報によると……」といった表現は避けてください。) Query: 駐車場を使う場合にはどうしたらいいですか Answer:
今回は、実際に生成されたプロンプトの内容が若干異なっており、また、VectorStoreから取得した情報の順番も変化しました。結果、最終的に得られる回答の方向性も変化したように見えます。
RetrievalAugmentationAdvisorでは何をしているのか?
では、RetrievalAugmentationAdvisorを使うと、どのようなカスタマイズができるのかについて説明していきます。まずは、RetrievalAugmentationAdvisor内部での「質問から最終的にプロンプトを作成するまでの流れ」を図示してみましょう(図2)。
この図のように、質問文からプロンプトを作るまでに5つの工程を経て作成され、それぞれに以下のインタフェースが関わっています。以下は、そのインタフェースとデフォルト設定をまとめたものです。これらの設定を上書き、もしくは追加することで目的のRAGシステムを構築できるというわけです。
- QueryTransformer:設定なし。設定がない場合には、入力された質問をそのまま利用
- QueryExpander:設定なし。設定がない場合には、入力された質問をそのまま利用
- DocumentRetriever:設定なし。ただし、何らかの指定は必須
- DocumentJoiner:ConcatenationDocumentJoinerクラス(単純に連結する)
- QueryAugmenter:ContextualQueryAugmenterクラス
QuestionAnswerAdvisorと同じ使い方であれば、DocumentRetrieverにVectorStoreを使った方法を設定するだけで済みます。QuestionAnswerAdvisorでは、VectorStoreへの検索クエリが変更できませんでしたが、これを変更したければ、QueryTransformerにて変更するということになります。
また、DocumentRetrieverでは、必ずしもベクトルデータを使う必要はなく、全文検索や独自の検索システムの結果を用いることも可能です。
この流れに沿ってプログラムを書くことで、RAGシステムが作りやすくなっています。次回は、これらのインタフェースごとに用意されているクラスなどを用いて、RetrievalAugmentationAdvisorならではのカスタマイズ方法を紹介します。
まとめ
今回は、RAGシステムを構築する上で必要な全体を扱う2つのクラスについて説明しました。ただし、QuestionAnswerAdvisorはあくまで簡易なアドバイザーで、自由度は限られます。RAGシステムでよりよい結果を得るためには、さまざまな調整が必要になることもあるため、(QuestionAnswerAdvisorではなく)最初からRetrievalAugmentationAdvisorを使ってしまう方がよいでしょう。
次回は、そのRetrievalAugmentationAdvisorを利用する際の、用途ごとに用意されているクラスの使い方などを紹介します。
筆者紹介
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・X: @WingsPro_info(https://x.com/WingsPro_info)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.