RAG(検索拡張生成)
LLMの知識はトレーニングされたデータに限定されています。 LLMにドメイン固有の知識や独自データを認識させたい場合、以下の方法があります:
- このセクションで説明するRAGを使用する
- 自分のデータでLLMをファインチューニングする
- RAGとファインチューニングの両方を組み合わせる
RAGとは?
簡単に言えば、RAGはLLMに送信する前に、プロンプト にデータから関連情報を見つけて挿入する方法です。 これによりLLMは(うまくいけば)関連情報を取得し、その情報を使用して回答できるようになり、 幻覚の可能性を減らすことができます。
関連情報はさまざまな情報検索方法で見つけることができます。 最も一般的なものは:
- 全文(キーワード)検索。この方法はTF-IDFやBM25などの技術を使用して、 クエリ(ユーザーが尋ねていること)のキーワードをドキュメントデータベースと照合して検索します。 各ドキュメント内のキーワードの頻度と関連性に基づいて結果をランク付けします。
- ベクトル検索(「意味検索」とも呼ばれる)。 テキストドキュメントは埋め込みモデルを使用して数値のベクトルに変換されます。 クエリベクトルとドキュメントベクトル間のコサイン類似度や その他の類似度/距離測定に基づいてドキュメントを検索しランク付けし、 より深い意味を捉えます。
- ハイブリッド。複数の検索方法(全文+ベクトルなど)を組み合わせると、通常は検索の効果が向上します。
現在、このページは主にベクトル検索に焦点を当てています。
全文検索とハイブリッド検索は現在、Azure AI Search統合でのみサポートされています。
詳細はAzureAiSearchContentRetriever
を参照してください。
近い将来、全文検索とハイブリッド検索を含むRAGツールボックスを拡張する予定です。
RAGの段階
RAGプロセスは、インデックス作成と検索という2つの明確な段階に分かれています。 LangChain4jは両方の段階のためのツールを提供しています。
インデックス作成
インデックス作成段階では、検索段階で効率的な検索を可能にするようにドキュメントが前処理されます。
このプロセスは使用される情報検索方法によって異なります。 ベクトル検索の場合、通常はドキュメントのクリーニング、追加データとメタデータによる強化、 小さなセグメント(チャンキングとも呼ばれる)への分割、これらのセグメントの埋め込み、 最後に埋め込みストア(ベクトルデータベースとも呼ばれる)への保存が含まれます。
インデックス作成段階は通常オフラインで行われ、エンドユーザーがその完了を待つ必要はありません。 これは例えば、週末に社内ドキュメントを週に一度再インデックス化するcronジョブを通じて実現できます。 インデックス作成を担当するコードは、インデックス作成タスクのみを処理する別のアプリケーションにすることもできます。
ただし、一部のシナリオでは、エンドユーザーがLLMがアクセスできるようにカスタムドキュメントをアップロードしたい場合があります。 この場合、インデックス作成はオンラインで行われ、メインアプリケーションの一部である必要があります。
検索
検索段階は通常、ユーザーがインデックス付きドキュメントを使用して回答すべき質問を送信したときに、オンラインで行われます。
このプロセスは使用される情報検索方法によって異なります。 ベクトル検索の場合、通常はユーザーのクエリ(質問)を埋め込み、 埋め込みストアで類似性検索を実行します。 関連するセグメント(元のドキュメントの一部)がプロンプトに挿入され、LLMに送信されます。
LangChain4jのRAGフレーバー
LangChain4jは3つのRAGフレーバーを提供しています:
- Easy RAG:RAGを始める最も簡単な方法
- Naive RAG:ベクトル検索を使用したRAGの基本的な実装
- Advanced RAG:クエリ変換、複数ソースからの検索、再ランキングなどの追加ステップを可能にするモジュラーRAGフレームワーク
Easy RAG
LangChain4jには、RAGを始めるのをできるだけ簡単にする「Easy RAG」機能があります。 埋め込みについて学んだり、ベクトルストアを選んだり、適切な埋め込みモデルを見つけたり、 ドキュメントの解析や分割方法を理解したりする必要はありません。 ドキュメントを指定するだけで、LangChain4jが魔法をかけます。
カスタマイズ可能なRAGが必要な場合は、次のセクションにスキップしてください。
Quarkusを使用している場合は、さらに簡単にEasy RAGを行う方法があります。 Quarkusのドキュメントをお読みください。
もちろん、このような「Easy RAG」の品質は、調整されたRAGセットアップよりも低くなります。 しかし、これはRAGについて学び始めたり、概念実証を作成したりする最も簡単な方法です。 後で、Easy RAGからより高度なRAGへスムーズに移行し、 より多くの側面を調整およびカスタマイズすることができます。
langchain4j-easy-rag
依存関係をインポートします:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.0.0-beta4</version>
</dependency>
- ドキュメントを読み込みましょう:
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");
これにより、指定されたディレクトリからすべてのファイルが読み込まれます。
内部で何が起きているのか?
幅広いドキュメントタイプをサポートするApache Tikaライブラリが、
ドキュメントタイプを検出して解析するために使用されます。
どのDocumentParser
を使用するかを明示的に指定しなかったため、
FileSystemDocumentLoader
はSPIを通じてlangchain4j-easy-rag
依存関係によって提供される
ApacheTikaDocumentParser
を読 み込みます。
ドキュメントの読み込みをカスタマイズする方法は?
すべてのサブディレクトリからドキュメントを読み込みたい場合は、loadDocumentsRecursively
メソッドを使用できます:
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/home/langchain4j/documentation");
さらに、globまたは正規表現を使用してドキュメントをフィルタリングできます:
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.pdf");
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation", pathMatcher);
loadDocumentsRecursively
メソッドを使用する場合、globで二重アスタリスク(単一ではなく)を使用することをお勧めします:glob:**.pdf
。
- 次に、ドキュメントを前処理し、特殊な埋め込みストア(ベクトルデータベースとも呼ばれる)に保存する必要があります。 これは、ユーザーが質問したときに関連情報をすばやく見つけるために必要です。 15以上のサポートされている埋め込みストアのいずれかを使用できますが、 簡単にするためにインメモリのものを使用します:
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
内部で何が起きているのか?
-
EmbeddingStoreIngestor
はSPIを通じてlangchain4j-easy-rag
依存関係からDocumentSplitter
を読み込みます。 各Document
は、それぞれが300トークン以下で30トークンのオーバーラップを持つ小さな部分(TextSegment
)に分割されます。 -
EmbeddingStoreIngestor
はSPIを通じてlangchain4j-easy-rag
依存関係からEmbeddingModel
を読み込みます。 各TextSegment
はEmbeddingModel
を使用してEmbedding
に変換されます。
Easy RAGのデフォルト埋め込みモデルとしてbge-small-en-v1.5を選択しました。 MTEBリーダーボードで印象的なスコアを達成し、 その量子化バージョンはわずか24メガバイトのスペースしか占めません。 したがって、ONNXランタイムを使用して、 簡単にメモリにロードし、同じプロセスで実行できます。
そうです、テキストを埋め込みに変換することは、外部サービスなしで、 同じJVMプロセス内で完全にオフラインで行うことができます。 LangChain4jは5つの人気のある埋め込みモデルを すぐに使える形で提供しています。
- すべての
TextSegment
-Embedding
ペアがEmbeddingStore
に保存されます。
- 最後のステップは、LLMへのAPIとして機能するAIサービスを作成することです:
interface Assistant {
String chat(String userMessage);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(ChatModel) // 任意のLLM
.contentRetriever(EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(EmbeddingModel) // 任意の埋め込みモデル
.maxResults(3)
.build())
.build();
String answer = assistant.chat("LangChain4jのRAGについて教えてください");
内部で何が起きているのか?
-
assistant.chat("LangChain4jのRAGについて教えてください")
が呼び出されると、EmbeddingStoreContentRetriever
はEmbeddingModel
を使用してクエリを埋め込みます。 -
EmbeddingStoreContentRetriever