RAGフレームワーク LlamaIndex の概要を整理してみる
はじめに
LLMは、トレーニングしたデータを使い推論するため、学習した時点の情報でしか推論できません。これを補うのがRAG(検索拡張生成 Retrieval-Augmented Generation)手法です。LLMへのプロンプトに、検索して取得した情報を追加することで、最新かつユーザが欲しい情報をもとに推論されます。
システム構成は、AzureでRAGを構築する際によくあるこのようなパターンです。
私が今まで構築してきたRAGシステムも、ドキュメントとユーザクエリのベクトル類似度が高いものを検索し、LLMで推論する方法です。この方法で、多量のドキュメントの中から類似度の高い情報から推論することができます。
この課題を解決する方法の一つとして、GraphDBを使ってドキュメントの関連性を表現することができると思います。ドキュメントを点とみなし、その点を結ぶ線が関連性を表せるようにデータを構築します。
GraphDBを使ったRAGは以下ののようなイメージです。
- まずユーザクエリに対して、類似度の高いドキュメントを検索します。
- そのドキュメントに関連するドキュメントも取得できます。
- 取得できた関連ドキュメントも一緒に、LLMで推論します。
このようなことを考えて調べていたら、こちらのMicorosftResearchブログを見つけました。
この記事では、上記ブログで紹介されているLlamaIndexの概要を整理してみます。
LlamaIndexとは
こちらがLlamaIndexのドキュメントです。
まず概要をまとめます。
- LlamaはRAGシステムを構築するためのフレームワーク
- PythonとTypescriptで利用可能
- 外部データをAPIやSQLを使って取り込むデータコネクタが用意されている
- データを構造化して保持するデータインデックスが用意されている
※様々な構造化パターンがあるので後述します。 - 構造化されたデータに自然言語でアクセスするためのエンジン
※検索のためのクエリエンジンや、マルチメッセージのためのチャットエンジン
RAGの流れ
LlamaIndexで用意されているツール群を把握するためには、それらのツール群がRAGのどのフェーズで使われるかを理解することが重要です。
- Loading : LlamaHubが用意されていて、ドキュメントの読み込み、ほかデータベースからのデータ取り込みが可能。
- Indexing : ドキュメントからメタデータを取得してベクトル化し、データ構造を作成
- Storing : Indexingして構造化したデータを保存する
- Quering : サブクエリ、マルチステップ クエリ、ハイブリッドクエリなどの種類があり、LLM と LlamaIndex データ構造を利用してクエリを実行できる。
- Evaluating : クエリに対する回答がどれだけ正確か評価する
1. Loadingフェーズ
LlamaIndexに格納するデータ(オブジェクト)の種類
LlamaIndexに格納するオブジェクトには、「Documentオブジェクト」と「Nodeオブジェクト」の2種類があります。
- Documentオブジェクト
- PDF、API出力、データベースから取得したデータなどから取得したデータ構造を格納するための汎用コンテナ。マルチモーダルに対応できるようにベータ版機能が公開されてます。(Excelのように、テキスト、グラフ、表、図などが混じっていてもデータ化できると嬉しい。)
- ドキュメントにはテキスト自体とメタデータが格納。
- metadata - テキストに追加する注釈辞書(タグ的な考え方)
- relationships - 他のドキュメントやノードとの関係を含む辞書
- ノードオブジェクト
- ドキュメントのチャンク単位がノード
- ドキュメントと同様にmetadataとrelationshipの情報が含まれる。
- ドキュメントから派生したすべてのノードは、ドキュメントのメタデータ(file_nameなど)を継承。
データコネクタ
データコネクトはLlamaHubで提供されている機能を使います。
いろいろありすぎますね。
図やグラフ、表を含むExcelやPowerpointにどこまで対応しているか、個人的には気になります。追々検証。。。
2. Indexingフェーズ
各Indexの仕組み
-
サマリーインデックス(旧リストインデックス)
- ノードをシーケンシャルチェーンとして格納
- ノードをシーケンシャルチェーンとして格納
-
ベクターストアインデックス
- 各ノードとそのベクトル値を格納
- 各ノードとそのベクトル値を格納
-
ツリーインデックス
- ノードを階層ツリーにして格納
- クエリ結果には、ルートノードからリーフノードへのノードが含まれる。
child_branch_factor
に設定された数に応じて、何個の子ノードを取得するかを設定する。
-
キーワードテーブルインデックス
- 各ノードからキーワードを抽出し、 各キーワードを、そのキーワードの対応するノードに設定する。
- クエリ時は、クエリからキーワードを抽出し、そのキーワードにマッチするノードを取得する。
-
Knowledge Graph Index
- KnowledgeGraphによるGraphDBの組み立て方を知りたかったのですが、、、、ドキュメントには解説がなかったため、のちの実検証記事で紹介します...!
https://docs.llamaindex.ai/en/stable/examples/index_structs/knowledge_graph/KnowledgeGraphDemo.html
- KnowledgeGraphによるGraphDBの組み立て方を知りたかったのですが、、、、ドキュメントには解説がなかったため、のちの実検証記事で紹介します...!
4. Queringフェーズ
Retriever
ユーザのクエリに対して最も関連性の高いコンテキストを取得する方法を定義します。
基本的には、RetrieverはIndexに定義されるものです。(個別定義もできるようです。)
RetrieverにはIndexに応じて複数のモードがあります。
- VectorIndex
- SummaryIndex
- TreeIndex
- KeywordTableIndex
- KnowledgeGraphIndex
- DocumentSummaryIndex
Router
Routerがナレッジベースから関連するコンテキストを取得するためにどのRetrieverを利用するかを決めます。(クエリを実行する 1 つ以上のRetrieverを選択する)
このような処理の流れになるようです。
- 多数のデータソースから最適なデータソースを選択する
- 要約やセマンティック検索を実行するか決める
- 一度に多数の選択肢を試し、その結果を組み合わせるかどうかを決める。(マルチルーティング機能)
Node Postprocessor
RouterやRetrieverで選択されたIndexで取得したノード群を取り込み、変換、フィルタリング、再ランク付けのロジックを実行する役割です。
Response Sysnthesizers
ユーザクエリとデータストアから取得しNodePostprocesserで変換されたテキストチャンクをのセットを使用し、LLMでユーザクエリに対する応答を生成する役割です。
応答を生成する方法もいくつかの種類があります。種類と概要をまとめます。
- refine
- 取得した各テキストチャンクを順番に調べて回答を作成し、絞り込む。取得したチャンクごとに個別のLLM呼び出しが行われる。
- compact
- refineとほぼ同じだが、事前にチャンクを圧縮(連結)するためLLM呼び出しを少なくできる。LLMの入力トークンに収まる分だけ、テキストチャンクを連結する。
- tree_summarize
- まずLLMの入力トークンに収まる分だけ、テキストチャンクを連結し、その数だけ推論する。
- 次に推論結果を連結し、さらにクエリを実行する。回答が1つになるまでこの処理を続ける。
- 要約に適している。
- simple_summarize
- すべてのテキストチャンクを切る捨てて、1つのLLMプロンプトに収める。
- 手っ取り早く要約したい場合に向いているが、切り捨てにより詳細情報が抜け落ちる可能性がある。
- no_text
- Retrieverのみを実行して、LLMに送信されるノードを取得する。
- accumulate
- テキストチャンクのセットに対し、ここにクエリを実行し、その結果の配列を変える。
- 各テキストチャンクに対して同じクエリを個別に実行したい場合に使う
- compact_accumulate
- accumulateとほぼ同じだが、事前にテキストを圧縮(連結)するためLLM呼び出しを少なくできる。LLMの入力トークンに収まる分だけ、テキストチャンクを連結する。
まとめ
まずはLlamaIndexのRAGフレームワークに使われる各モジュールの概要を整理しました。
今後、実際に動作検証していき、とくにKnowledgeGraphIndexについては、詳しく仕組みを調べていきたいと思います。
参考
実際に、Webサイトの情報をKnowledgeGraphにして、Neo4jに保管し、RAGをすることを試しました。
Discussion