「どんな質問をしたいのか?」別に考える、適切なRAG技法 〜RAG曼荼羅〜
Nishika データサイエンティストの髙山です。
弊社では、RAG(Retrieval Augmented Generation:検索拡張生成)をはじめとするLLM関連の相談を多くいただいています。LLMは日々進化し、RAGも様々な手法が発表され続けています。
このような多数のナレッジで溢れている状況では、実務でRAGのシステムを構築する際にどのような手法が適切か、分かりにくくなっています。
今回は、弊社でこれまで蓄積してきたRAGの知見をもとに、「質問の内容」ごとに適切なRAGの手法、ナレッジ整理の施策を紹介します。
RAGについておさらい
RAGとはRetrieval Augmented Generationの略で、日本語では「検索拡張生成」といい、社内ドキュメントを「検索」して、自分の知識を企業内情報まで「拡張」して、回答を「生成」したい、これを実現する仕組みです。GPTをはじめとした生成AI(LLM)は、自分達が学習した時点までの情報(ナレッジカットオフ)に基づいてしか回答できませんが、社内ドキュメントのデータを入力することで企業内データも知識として与えた上で回答させることが可能になります。
RAGは2つのステップで構成され、RAGの性能の大半は検索の仕組みに依存します。
- 質問が入力されたら、関連する文書を検索する:検索モデル(埋め込みモデルなど)
- 検索してヒットした文書の情報も踏まえ、回答を生成する(生成AI)
「質問で使われている言葉は、検索モデルが知っている言葉か?」「質問で使われている言葉と、回答根拠となる文書で使われている言葉は似ているか?」が、性能に影響する二大論点であると考えています。
※実際には、「最新の情報も取得して回答して欲しい」や「図表も解釈して欲しい」など、他にも多数の「質問の仕方」があるのでその点には留意します。
RAG曼荼羅に沿って適切な手法を検討する
結論を先に話しますと、以下のRAG曼荼羅に沿って、質問のユースケースごとに検討するRAGの手法を検討するのが良いと考えています。
Qと回答根拠の文書のスタイルが似ているのか否か、検索用の言語モデル(検索モデルとする)が知っている言葉が使われるのかの2点が主な判断軸となるかと思います。
そのうえで個別のユースケースに合わせてRAGの精度向上の工夫を検討していくのが望ましいと考えています。
こちらの内容については、新しい手法や知見が増えると思うのでこれから更新してブラッシュアップしていきたいと思います。
ではこれからRAG曼荼羅に沿って、質問内容別にどういった手法を検討するのかを詳しく見ていきましょう。
質問内容別の手法
ユーザーから入力される1つの質問内容をユースケースごとに分けて、どのようなRAGの手法を採用すべきか、ナレッジ整理の施策のポイントをまとめます。
マニュアルに関する質問
質問例:
- 「新商品登録画面はどこから行ける?」
- 「ユーザー管理画面で登録できない」
社内のマニュアル・手順書について社員が問い合わせする、最もオーソドックスなユースケースです。ナレッジの形式を統一し、文書化されていないものは文書化することが重要になります。
質問も回答根拠の文書もユーザーが知っている一般的な単語から成り、目線の近い書かれ方をされていることが想定されます。
RAG適用のアイデア
- 質問も回答根拠の文書も一般的な単語から成るため、検索用の言語モデルで意味を解釈できる語彙が多いことが想定されるので、質問に意味が近いナレッジをヒットさせるセマンティック検索を使用する
- ベクトル化する際に基本的には文書単位でベクトル化するか、見出し単位などでベクトル化することで1つの手順やトピックが途中で分割されないような工夫をする。分割単位をLLMに提案してもらうことも有効
- 例:登録手順が以下の通りにあったときに1~3と4が2つのチャンクに分かれると手順を聞かれた際に1~4を回答できず、1~3までしか回答できない問題が発生する可能性があります。
- トップページに遷移: ...
- 登録ボタンクリック: ...
- 必要事項入力: ...
- 完了ボタンクリック: ...
- 例:登録手順が以下の通りにあったときに1~3と4が2つのチャンクに分かれると手順を聞かれた際に1~4を回答できず、1~3までしか回答できない問題が発生する可能性があります。
ナレッジ整理の施策
- PowerPoint, Excel, Word, NotionなどのWikiツールなどの形式が混在していると扱いにくいため、可能であればWikiツールに集約する
文書の形式が増えるごとに実装のコストは増える傾向にあります。簡単にすぐできることではありませんが、できる範囲で形式を合わせることで実装コストも下がり、回答の精度も向上させることを狙います。
規程・法令・規則に関する質問
質問例:
- 「有給休暇の取得ルールについて教えてください。」
- 「出張の際の手当はどこまで適用されますか?」
質問も回答根拠の文書もユーザーが知っている一般的な単語から成りますが、質問は口語に近く文書はやや堅苦しい書き方になっているでしょう。
RAG適用のアイデア
- 質問も回答根拠の文書も一般的な単語から成るため、検索用の言語モデルで意味を解釈できる語彙が多いことが想定されるので、質問に意味が近いナレッジをヒットさせるセマンティック検索を使用する
- 事前整理済みのQAを対象に検索する
- 質問は口語に近く、文書はやや堅苦しいスタイルでギャップがあり、意図した内容を検索しやすいように工夫する
- あらかじめ擬似的な質問(ダミークエリ)を作成しそれを検索する
- こちらも質問と文書のスタイルのギャップを埋めるための施策
- HyDE(Hypothetical Document Embeddings:LLMが作成した仮の文書の埋め込みで検索する手法)をも同様に有効
ナレッジ整理の施策
- QAを事前に整理する
質問の平易なスタイルと規定の文書の堅いスタイルのギャップを埋める手段としてはあらかじめ擬似的なクエリを作成しておき検索することも有効ですが、QAを事前に整理しておくことも有効です。
社内の技術文書・提案書に関する質問
質問例:
- 「施策A導入後のリキャップタイムの変化について教えてください」
- 「過去の案件でデプスチャートの中で特に技術的なボトルネックとなる箇所はどこですか?」
質問はユーザーが知っている一般的な単語から成りますが、参照元の文書は一般的な言語モデルが知らない、社内の専門単語などから成る場合が多いでしょう。
質問としては専門用語を尋ねるものもあれば、おおまかな内容として「〇〇だった事例を知りたい」といった形で実際の文書よりは抽象度を上げた形で尋ねるものもあります。
RAG適用のアイデア
- 専門用語の多い文書から、平易な単語から成る擬似的な質問(ダミークエリ)を作成し、それを検索する
- HyDEを採用することも検討
- 頻出の社内の独自の略語を拡張してキーワード検索する
- 専門用語について尋ねるときには専用のDBをキーワード検索する
回答根拠の文書については、検索用の言語モデルが知らない語彙が多く意味を解釈できない可能性が高いため、質問と回答根拠の文書の語彙のギャップを埋めるための工夫が必要になります。
RAGに頼らないアイデア
- 文書の内容の分類・タグなどのメタ情報を検索する
- ユーザーが見たい案件・技術情報のカテゴリの文書をヒットさせる場合に適する
- 分類やタグがない場合は、LLMに出力させ付与させることを検討する
ナレッジ整理の施策
- 専門用語および略語について専用のDBをあらかじめ整理する
- 熟練者の知見を引き出しテキスト化する
- 文書作成
- インタビューして文字起こし
熟練者の技術や知見を経験の浅い人に伝承することは、RAGシステムの構築の目的になることも多いです。根幹である技術や知見のナレッジを充実させることが言うまでもなく重要であり、どのようにナレッジを用意することがハードルになります。繰り返しになりますが簡単にすぐできることではない中でどこまでコストを割けるかを鑑みてナレッジを熟練者から提供してもらうかが検討のポイントになります。
型番、IDなどに関する質問
質問例:
- 「型番223344の寸法を教えて」
- 「No23456の請求書の詳細の内容を教えて」
型番、IDなどの数字や英字の意味を持たない文字列や製品名・会社名などの固有名詞で検索する場合にセマンティック検索は機能しにくいためキーワード検索を検討します。
RAG適用のアイデア
- BM25などの表層ベースの検索手法を採用する
- キーワード検索をした後に上位のものについて意味的に近いもの順番になるようにリランク(Re Rank)※する
※リランク(Re Rank): 検索を行った後に2段階目の処理で上位のものについて意味的に関連度が高い順位を再計算する
RAGに頼らないアイデア
- メタ情報での検索などの手法を選択
ナレッジ整理のポイント
- キーワード検索の精度が向上するように名寄せを行う
- 製品情報などのメタ情報を整理する
キーワード検索を必要に応じて活用する点については「実務で後一歩使えない」を解決するLLM・RAG ~質問回答に必要なドキュメントを適切に検索する~にて解説しています。
日本語であればOkapi BM25などを使用して、固有名詞のキーワード検索の精度を向上させることを検討します。
多様な質問内容に対応する手法
LLMが知らない最新情報を元に回答する
LLMは直近1,2年の情報までは学習していますが、最新の情報をもとに回答することは難しい場合が多いです。
質問例:
- 「今後導入される予定の労働時間に関する新しい法律や規制について、最新の情報を教えてください。」
- 「最近の食品メーカーで生成AIが導入された事例を教えてください。
最新情報を求める質問については、Webの情報を取得して回答することが望ましいです。
RAG適用のアイデア
- Webを検索し取得した情報に基づいて回答するモジュールを実装
- Webの情報取得ツール化してLLMがエージェントとして呼び出す
- 検索キーワードについてクエリの内容をそのまま入力すると精度が良くない
- Webから情報を取得し回答する処理の流れは以下の通りで、BingSearchAPIなど検索用のAPIを利用できるように準備し実装
- LLMが最適と判断したキーワードでWeb検索
- 質問の内容、検索結果の上位n件のタイトル、URL、ページのスニペット(短い説明文や要約)をもとにLLMが最適なURLを選択
- URLのページの内容をスクレイピング
- 質問とスクレイピング結果をもとにLLMが回答
コードのイメージ
# 質問とナレッジの検索結果をもとに回答できるか、回答内容をLLMが生成
search_document_response = answer_by_document(query, db.as_retriever(search_kwargs={"k": 3}))
answer = search_document_response["answer"]
# ナレッジの検索結果で回答できないと判断したら、Webの情報を取得して回答する
if search_document_response["answer_able"] == "no":
# クエリをもとに検索ワードを設定
search_results = bing_search(keyword)
# 最適と見られるURLをLLMが選択
url = choose_url(query, search_results)["url"]
# 選択したURLの内容をスクレイピング
page_info = get_page_info_by_scraping(url)
# スクレイピングの内容をもとに質問の内容に回答内容を生成
answer = answer_by_web_page(query, page_info)
Webの情報に基づくLLMの回答については、注意点があります。
- スクレイピング結果をLLMに入力するのでトークン数の大きい入力も可能なLLMが望ましく、そうでない場合は分割して判断させて回答結果を集約する必要がある
- Web検索を使用するため、回答の精度がクエリの最適な抽出と検索エンジンの返す結果に依存する部分が大きい
図形などの画像情報の内容を踏まえて回答する
ナイーブに文書をLLMに読み込むと、図形が複数使用されているなど、相対的な位置関係・テキストなどの画像を把握して解釈する必要がある文書の場合普通にテキスト情報をパースしてもうまく解釈できないため工夫が必要です。
画像入力可能なLLMやOCRの技術を用いて、画像の情報をテキスト化することが重要です。
詳細は「実務で後一歩使えない」を解決するLLM・RAG ~画像・図形付きのスライドを理解する~にて紹介しております。
文章の背景情報も踏まえて回答する
ナイーブにRAGを適用した際に、文書の断片的な内容をLLMに入力し、意図しない回答をしてしまうことがあります。それを防ぎ回答するためにベクトル化する際に文脈の情報を追加することが重要です。
質問例
- SMCのDB設計を教えて
(回答は「SMC」フォルダ配下の「DB設計」ドキュメントにある)
RAG適用のアイデア
- Wikiが階層化されている場合、文書の階層の情報も含めてチャンク化してベクトル化することで、文書の前提やトピックが欠落しないようにする。
- 例:「/XX事業部/申請/案件申請手順」という階層構造の文書があり、文書内に「XX事業部」の文言がない場合、階層情報を含めないと「XX事業部」の案件申請手順に関するものだという文書の前提が欠落してしまう
- 長い文書をいくつかのチャンクに分ける場合に、ファイル全体の短い要約を追加することで、文脈を補足することも有効
ナレッジ整理の施策
- 文書のタイトルや階層構造を整理する
専門用語・造語について全体的な構造を解釈した上で回答する
LLMが知らない語彙について全体的な構造の理解を必要とする質問に対応するために、GraphRAGの適用を検討します。
GraphRAGとはあらかじめ文書についてナレッジグラフを構築しておき、質問についてキーワード検索し、ヒットしたノードに関するグラフを取得し、文書の内容だけではなく回答のヒントとして構造情報も追加でLLMに入力する手法です。
質問例:ERPの情報が社内のユーザーが参照する流れを教えて
入力されるナレッジグラフの情報:
ERP-データの同期->CRM
ERP-データのバックアップ->Cloud Storage
CRM-データのレポート->BI
BI-示唆出し->社内ユーザー
事前構築済みのナレッジグラフのイメージ
間違った内容を含まないように回答する
LLMを使うにあたってはハルシネーションはつきもので、書類に記載の数字の情報について確認したり、規程・ルールについて回答をする際など厳密性が重視される場合も少なくないです。
その場合には誤りを防止するプロンプトの工夫やUXの工夫が重要になります
質問例
- 昨年の日本橋イベントの売上・利益はいくら?
プロンプトの工夫
- 回答の理由を合わせてLLMに出力させる
UXの工夫
- 検索でヒットした文書の出典を表示するようにする
- 間違った回答によるリスクが許容できない場合は、LLMによる回答生成は行わず、検索結果の表示にとどめる
質問の形式に対応する手法
ここまでは質問内容のユースケースごとにRAGの手法とナレッジ整理の施策を紹介していきました。
一問一答で質問に対して回答する場合は、質問内容ごとの工夫を考慮すれば良いですが、そうでない場合は別途実装を工夫する必要があります。
複数の質問を持つ質問
質問例:
- 「おはようございます。HACCPに基づく衛生管理の手順を教えてください。また、定期的な監査の対象エリアも教えてもらえますか?」
ユーザーの実際の質問では、質問ではない内容が含まれていたり、質問が複数含まれている場合があります。この質問についてはそのまま検索すると期待する内容が得られないことが考えられます。
RAG適用のアイデア
- LLMによって質問を1つずつ抽出する
質問例1では質問に無関係なあいさつと質問2つから質問を2つ抽出し、質問例2では質問に無関係なあいさつと質問1つから質問を1つ抽出する正解を示しています。その上で構造化して出力するように指示しています。
このような場合には前処理をして質問ごとにナレッジを検索し回答を生成することが有効です。
質問例の場合は、以下のように質問を抽出することが望ましいです。
- 質問1:「HACCPに基づく衛生管理の手順を教えてください。」
- 質問2:「定期的な監査の対象エリアも教えてもらえますか?」
質問をする際のプロンプト例は以下のイメージです。具体例を2つ用意してfew-shotで生成AIに教えることで、質問を的確に抽出します。
質問が与えられます。次の指示に従い修正してください。
指示1:挨拶などの不要な文言を除去する。
指示2:質問が複数ある場合は分割し以下の例に従いJSONフォーマットで修正した内容を返します。
----------
[質問例1]:
お疲れ様です。NishikaのXXXです。注文管理の画面から注文を行うにはどうすれば良いでしょうか。またパスワードがロックされた場合はどの画面から解除すれば良いでしょうか。
[回答例1]:
{{
"questions":[
"注文管理の画面から注文を行うにはどうすれば良いでしょうか。",
"パスワードがロックされた場合はどの画面から解除すれば良いでしょうか。"
]
}}
[質問例2]:
お疲れ様です。NishikaのXXXです。納品完了報告はどこから上げれれば良いでしょうか。
[回答例2]:
{{
"questions":[
"納品完了報告はどこから上げれれば良いでしょうか。"
]
}}
LLMによる質問の分割を行うと回答生成までの時間が延び、実装コストも増えます。RAGのアプリとして質問を1つだけ入力するようにユーザーに促すUIとして提供することも1つのアイデアです。
利用するユーザーにクエリを工夫してもらうことが望ましいです。
対話的な質問
例:
ユーザー:「新しいプロジェクトのための予算申請はどう進めればいいか?」
AI:「予算申請の手順は、まず部署内での承認を得てから、財務部門に提出する流れです。詳細な申請書類の準備も必要になります。具体的にどのステップについて知りたいですか?」
ユーザー:「どんな書類が必要になりますか?」
例のように対話的なチャットボットとして、会話した場合は考慮するポイントが増えます。
会話の流れを記憶することと、必要に応じてナレッジを参照して回答することの2つが主に重要になります。
ユーザーの内容が必ずしも質問ではない場合もありますし、「どんな書類が必要になりますか?」のような質問は会話の流れを把握していないと予算申請に関するものであると理解できません。
RAG適用のアイデア
- 会話の履歴を保持できるメモリ機能を実装する
- エージェント機能を実装して、クエリの内容に近いナレッジを検索するかしないかをLLMが判断するようにする
- LangChainであれば以下のようなイメージ
- langchain.agents.openai_functions_agent.agent_token_buffer_memory.AgentTokenBufferMemoryで会話のメモリを宣言
- langchain.tools.toolでナレッジを検索するモジュールをエージェントが使用できるツール化
- langchain.agents.AgentExecutorでそのツールを使えるエージェントを宣言する
コードのイメージ
from langchain.agents import AgentExecutor
from langchain.agents.openai_functions_agent.agent_token_buffer_memory import AgentTokenBufferMemory
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import MessagesPlaceholder
from langchain.pydantic_v1 import BaseModel, Field
from langchain.schema import SystemMessage
from langchain.tools import tool
from langchain_community.chat_models.openai import ChatOpenAI
from langchain_community.vectorstores import FAISS
## ツール
# @toolの引数でreturn_direct = Trueとすることで回答をそのまま返すことも可能
@tool("get_external_info", args_schema=...)
def get_external_info(query: str, keyword: str) -> str:
"""独自の社内の文書のナレッジを確認できるツール
...
"""
# RAG処理を定義
...
## エージェント
tools = [get_external_info]
system_message = SystemMessage(
content=(
"""
# 指示
あなたは企業Yの熟練の社員です。... ユーザーが社内のナレッジについて質問するので丁寧に簡潔に回答してください。
# 注意点
- 社内のナレッジについてはget_external_infoツールを使用して回答する
- わからない内容については、わからない旨を回答すること
- 社内ナレッジ以外について尋ねられた場合は、事前知識でわかる範囲で回答する
"""
)
)
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=system_message,
extra_prompt_messages=[MessagesPlaceholder(variable_name="history")],
)
llm = ChatOpenAI(model="gpt-4-0125-preview", streaming=True)
memory = AgentTokenBufferMemory(memory_key="history", llm=llm)
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
return_intermediate_steps=True,
)
まとめ
いかがでしたでしょうか。今回は質問のユースケースごとにRAGを適用するポイントを紹介いたしました。簡潔にいうと、質問と回答根拠になる文書の内容・スタイル・語彙のギャップをどう埋めるかを考えて、手法を選択することが大事だと感じます。
RAGを使用した仕組みを実装・運用する際には細かい手法の検討も重要ですが、ユースケースをユーザーにヒアリングした上でデータソースである文書を整理することが非常に重要です。
対象とする文書の種類やユースケースを増やしたり、RAGの工夫の実装を増やすほどに大きく複雑なシステムになります。またLLMの処理を増やすと回答までの時間が長くなる点にも注意が必要です。
ユーザーが文書の整理にどこまでコストを割けるか、質問文の内容を工夫してもらうことが可能なのかも合わせて考えるようにしたいです。
Nishikaについて
Nishikaは2019年に創業、「テクノロジーですべての人が誇りを持てる社会の実現」をビジョンに掲げ、「テクノロジーを、普段テクノロジーからは縁の遠い人にとっても当たり前の存在としていき、皆の仕事の付加価値・業務効率を向上させることに貢献したい」と考え、活動しています。
AIプロダクト事業/AIコンサルティング・開発事業/AI人材事業を手掛け、AIコンサルティング・開発事業では「生成AIを使うと何が嬉しいのか、通り一遍ではない使い方を知りたい」という段階のお客様から、伴走してご支援するアプローチを強みとしています。
We're hiring!
Nishikaテックチームでは、「テクノロジーを、普段テクノロジーからは縁の遠い人にとっても当たり前の存在としていく」を目指し、音声AIプロダクトの開発・生成AIを活用した課題解決ソリューションの構築を行なっています。
興味をお持ちいただけた方は、以下リンクからご応募お待ちしています。インターンも募集しております!
Discussion