🪺

RAGに本気で向き合ってみた - 開発の苦労と工夫とヒント

2024/08/23に公開

はじめに

私はここ半年ほどプライベートでRAGに本気で向き合ってみました。
(※普段の仕事はアプリエンジニアなので、業務でLLMにはほぼ触れていません。)

RAGで精度が出ないという話はよく聞きます。
LLMがアプリケーションに載らないと、このままAIブームが終わってしまいます。
モデルの精度が高まった今、アプリの時代にしなければなりません。
全てのアプリエンジニアはLLMに目覚めてください。

この記事では、RAGの精度向上に向けて、自分が試したこととそのコスト・効果について書いていきます。

RAGとは

RAGについては、他の多くの記事で解説されているため、ここでは詳しくは述べません。
ざっくりいうと、LLMが持っていない知識を与えつつ質問することで、LLMが回答できる質問の範囲を広げる試み、あるいはシステムのことです。
例えば、社内のマニュアルなどの機密情報をもとに、従業員のサポートをするチャットボットなどに使うことができます。

https://www.nri.com/jp/knowledge/glossary/lst/alphabet/rag

インプットの工夫

人に質問をする時、抽象的な質問をしたり脈絡のないことを言ったりすると、頓珍漢な答えが返ってくることが多いと思います。LLMに質問をするときも同様で、質問や依頼が具体的で回答に必要な情報が揃っていることが重要になります。

プロンプトエンジニアリング

プロンプトを構造化したり、LLMに対して役割を与えてあげることで精度が向上するという話はよくあると思います。
この部分については、他の記事でも多く述べられていると思うのでここでは割愛します。

https://logmi.jp/tech/articles/330340#s6

質問の具体化

アプリを使うユーザーにとって、質問や依頼を詳しく正しく言語化するのは意外と大変なものです。
また、伝わると思って書かなかったことが、LLMの前提にないこともしばしば。こうなってしまうと、LLMの精度も期待できません。
これを解決するために、アプリケーションのフロント側で、事前に質問をしてその質問と回答内容をLLMに与えることができます。

ドキュメントの前処理

RAGの特徴である外部の知識を正確に提供することは、非常に重要です。
ただ、世の中の知識はPDFや文書・画像など構造化されていないプログラム上扱いづらい形式で存在することが多く、これらの情報を正しく扱いやすく扱いやすくすることが求められます。

ノイズ除去

PDFには、ページ番号・注釈・左右の端の索引?のようなものなど様々なノイズが含まれています。
これらは、データベースに格納して検索したり、LLMに投げたりする際に、無意味な文字となりやすいため最初に除去します。
自分の扱っているPDFは特に注釈が細かくページ下部についていたため、ページとページの間でコンテンツが繋がっているにも関わらずその間に割って入るような形で邪魔になっていました。
下記のテキスト抽出系のサービスは、ノイズも含めて全てのテキストを抽出してしまうので、事前に処理したいです。
そのために、モルフォロジー演算とかハフ変換とかよくわからない方法を使って、注釈の上部についていた少し太い線を検出しその下を削除するというアプローチを取りました。かなりローテクな感じがしますが、これが最も効果的でした。

テキスト抽出

PDFのテキスト抽出に関しては、昔からある技術ですしそんなに苦労はしないかなと思っていましたが、意外と良いツールが見つかりませんでした。
PyPDFやPyMuPDFなどPythonのOCRライブラリはありますが、これらは日本語の精度がイマイチでした。
また、自分が扱っているPDFがかなり表や画像を多用するもので、これらがノイズとなってうまく抽出できませんでした。

そこで、より精度が高くテキスト抽出できるツールやサービスを探し、色々と試してみました。それぞれの特徴と所感を下記に書きます。

  • llama parse

    • 出力がマークダウン形式。JSONではないため、ベクトルDBへの直接投入が困難。
    • 自然言語での解析指示が可能で興味深いが、精度に課題あり。特に日本語で顕著。
  • firecrawl

    • Webサイトがターゲットのため、PDFには不向き。
    • 日本語の精度も課題あり。
  • unstructured

    • 理念が良く、日本語の精度はほぼ問題なし。
    • 構造判定(TitleやNarrowTextなど)もある程度可能。
    • JSON形式で要素ごとに分割してくれる点が優秀。
    • ページ番号の判別、表の解析も良好。英語では表がHTML形式で返される。
    • データソース接続が安定せず、ナレッジDB(neo4jなど)との接続も未対応。
    • 構造の誤判定時に問題が残る。
  • Google Document AI

    • 日本語の精度、構造判定ともに良好。
    • 表の解釈は難しいが、おかしな解釈はしない。
    • 構造が変わっても文の意味を誤解しない点が優秀。

結果的に、Google Document AIを採用しました。こちらは比較的新しいGoogleのサービスで、文書処理と情報抽出に特化したAIサービスです。

ドキュメント参照の工夫

ドキュメントが扱いやすくなったら、今度はその情報をシステムに(主にはデータベースに)保存します。
RAGにおいてよく行われるのは、データベースに保存されたドキュメントの情報をベクトル検索を用いて抽出するというものです。
ベクトル検索は意外と精度がよくならないことも多く、ユーザーからの質問に正確に回答するための適切な情報をいかにとってこられるかもRAGを考える上で重要なポイントになります。

参照候補の絞り込み

ベクトル検索をする際に、全てのドキュメントから類似情報を取得しようとすると、候補が多すぎるあまり適切なドキュメントを選択できない可能性が高まります。
前もって、ドキュメントを絞り込めるのであれば、それを実践しないてはありません。

Graph RAG

ベクトルデータベースからベクトル検索を行い類似ドキュメントを取得するアプローチには弱点もあります。
それば、類似しているドキュメントであってもそのドキュメントから参照されている一見類似性の低そうなドキュメントが無視されてしまう可能性がある点です。
例えば、類似性の高いページが選ばれたとして、そのページが他のページを参照していた場合、ベクトル検索では参照先のページは選ばれる可能性が低くなってしまいます。

人間にとっての知識を考えたとき、知識はもっと構造化されていて関係性をもって繋がりのあるもののような気がしませんか。そのような、より構造的に知識を整理したデータベースがGraphデータベースです。
下記にGraphデータベースをRAGに導入したポイントと過程を書きます。

  • neo4jCypher を用いたナレッジグラフの構築。
  • Cypherの自動生成
  • ナレッジグラフの構築
    • LangChain LLMGraphTransformer の活用:
      • 150ページほどのPDFで15分程度かかり、LLMの制限に抵触
      • 途中で停止した場合、それまでの処理結果が得られず、コストが無駄に
      • 作成されたナレッジグラフの質に課題あり

https://blog.langchain.dev/enhancing-rag-based-applications-accuracy-by-constructing-and-leveraging-knowledge-graphs/

  • 地道なアプローチ:
    1. 目次から大きなナレッジを作成し、全ページを走査して細かなナレッジを紐づける方法
      • 紐付けの精度に課題があり断念
    2. 最初から全ページを走査し、全てのナレッジを一気に作成する方法
      • 一部誤りはあるが、全体的に良質なナレッジグラフが完成
      • 3ヶ月程度の時間を要し、10ファイル以上のPythonスクリプトを作成

  • ナレッジの抽出
    • 通常のベクトルデータベース同様、ベクトル検索が可能。
    • ベクトル検索前の事前フィルタリングも可能。
    • ドメイン知識に基づいて可能な限りフィルタリングすることで回答の精度が向上。

エージェント活用

エージェントを使えば、質問→知識取得→LLMというシンプルなRAGの構造をさらに発展させることができます。
エージェントとは、LLMを基盤とし外部ツールを活用した高度なタスク処理システムです。ここではその詳細については述べませんが、RAG向上におけるエージェントの活用方法についても触れたいと思います。
シンプルなRAGの場合、質問に対して知識を取得する方法は一つに決まっていますし、どのような質問に対しても同じ方法を用います。エージェントでは、様々な知識の取得方法を事前に用意しておき、質問の内容に応じて「いい感じ」に知識取得を行うことができます。
以下には、自分が活用したエージェント構築のためのツールやそれらを使った感想を述べます。

LangGraph

  • エージェントシステムを宣言的に構築するLangChainの一部
  • 複雑なワークフローを宣言的に構築可能
  • ワークフローが見やすく使いやすい

LangGraph Studio

  • LangGraph専用のIDE
  • シンプルな記述が可能で、処理の流れが明快
  • 使っていてとにかく楽しい

LangGraph Cloud

  • LangGraphアプリケーションのクラウド上でのホストとスケーリングが容易
  • Studioを使ったインタラクティブな構築、テスト、共有が可能
  • プロダクション環境でのデプロイにも対応
  • LLM専用のPaaS的な機能と、LangGraph構築・デバッグのUIを提供

開発体験向上

LangSmith

  • 実行されたノード、ツール、パラメータ、結果、処理時間などがわかりやすく表示。
  • デバッグが容易になり、開発効率が向上。

まとめ

RAGに本気で向き合うのはめっちゃ大変です。特に、ドキュメント前処理やナレッジグラフ構築は難しくて地味で時間がかかります。でも、やる価値はあるしやった分だけ精度は上がります。

RAGの精度向上にとって最もと感じたのは、参照ドキュメントの前処理と質問に応じたRAGの対象の絞り込みです。
どちらも比較的大変な作業ではない一方で、精度向上に与える効果は絶大な印象です。
一方、Graphデータベースの活用やエージェントの活用は、割と大変な一方で効果としては大きなものにはなりづらい印象です。自分が開発しているプロダクトの性質上かなり高い精度のアウトプットが求められるため、ここまでやる意味があるのですが、そこそこの精度で良いのであればここまでやる必要はない気がします。

RAG開発のためのツールも色々出てきているので、それらの恩恵も受けながらガンガン開発していきましょう!
この記事では、LangGraphなどの新しいツールの活用や、開発体験を向上させるためのTIPSも含めました。
今後RAG開発に取り組む開発者にとって実践的な洞察となれば嬉しいです。

immedioテックブログ

Discussion