RAG(検索拡張生成)
LLMの知識は、学習されたデータに限定されます。
LLMにドメイン固有の知識や独自データを認識させたい場合は、次の方法があります:
- RAGを使用する(このセクションで説明します)
- 独自データでLLMをファインチューニングする
- RAGとファインチューニングの両方を組み合わせる
RAG とは?
簡単に言うと、RAGとは、LLMにプロンプトを送信する前に、データから関連情報の断片を見つけてプロンプトに注入する方法です。
これにより、LLMは(できれば)関連情報を取得し、その情報を使って回答できるようになり、幻覚(ハルシネーション)の可能性を減らすことができます。
関連情報の断片は、さまざまな 情報検索 メソッドを使用して見つけることができます。 最も一般的なものは次のとおりです:
- 全文(キーワード)検索。この方法は、TF-IDFやBM25などの技術を使用して、 クエリ(例:ユーザーが尋ねている内容)内のキーワードをドキュメントのデータベースと照合してドキュメ ントを検索します。 各ドキュメント内のこれらのキーワードの頻度と関連性に基づいて結果をランク付けします。
- ベクトル検索(「セマンティック検索」とも呼ばれます)。 テキストドキュメントは、埋め込みモデルを使用して数値のベクトルに変換されます。 次に、クエリベクトルとドキュメントベクトル間のコサイン類似度 またはその他の類似度・距離尺度に基づいてドキュメントを検索・ランク付けし、 より深いセマンティックな意味を捉えます。
- ハイブリッド。複数の検索方法(例:全文+ベクトル)を組み合わせると、通常、検索の効果が向上します。
現在、このページは主にベクトル検索に焦点を当てています。
全文検索とハイブリッド検索は、現在、Azure AI Search統合とElasticsearchでのみサポートされています。
詳細については、AzureAiSearchContentRetriever と ElasticsearchContentRetriever を参照してください。
近い将来、RAGツールボックスに全文検索とハイブリッド検索を含める予定です。
RAG のステージ
RAGプロセスは、インデックス作成と検索の2つの明確なステージに分かれています。 LangChain4jは、両方のステージのツールを提供します。
インデックス作成
インデックス作成ステージでは、検索ステージ中に効率的な検索が可能になるように、ドキュメントが前処理されます。
このプロセスは、使用される情報検索方法によって異なります。 ベクトル検索の場合、通常、ドキュメントのクリーニング、追加データとメタデータの強化、 ドキュメントをより小さなセグメントに分割(チャンキングとも呼ばれる)、これらのセグメントの埋め込み、そして最後に埋め込みストア(ベクトルデータベースとも呼ばれる)への保存が含まれます。
インデックス作成ステージは通常オフラインで行われ、エンドユーザーが完了を待つ必要はありません。 これは、例えば、週末に社内ドキュメントを週に1回再インデックスするcronジョブによって実現できます。 インデックス作成を担当するコードは、インデックス作成タスクのみを処理する別のアプリケーションにすることもできます。
ただし、一部のシナリオでは、エンドユーザーが独自のドキュメントをアップロードしてLLMがアクセスできるようにしたい場合があります。 この場合、インデックス作成はオンラインで実行し、メインアプリケーションの一部にする必要があります。
インデックス作成ステージの簡略化された図は次のとおりです:

検索
検索ステージは通常オンラインで発生し、ユーザーがインデックスされたドキュメントを使用して回答すべき質問を送信したときに発生します。
このプロセスは、使用される情報検索方法によって異なります。 ベクトル検索の場合、通常、ユーザーのクエリ(質問)の埋め込みと、 埋め込みストアでの類似性検索の実行が含まれます。 関連するセグメント(元のドキュメントの一部)がプロンプトに注入され、LLMに送信されます。
検索ステージの簡略化された図は次のとおりです:

LangChain4j の RAG の種類
LangChain4jは、RAGの3つの種類を提供します:
- Easy RAG:RAGを始める最も簡単な方法
- Naive RAG:ベクトル検索を使用したRAGの基本的な実装
- Advanced RAG:クエリ変換、複数ソースからの検索、再ランキングなどの追加ステップを可能にするモジュール式RAGフレームワーク
Easy RAG
LangChain4jには「Easy RAG」機能があり、RAGをできるだけ簡単に始められます。 埋め込みについて学んだり、ベクトルストアを選択したり、適切な埋め込みモデルを見つけたり、 ドキュメントの解析方法や分割方法を理解したりする必要はありません。 ドキュメントを指定するだけで、LangChain4jが魔法のように処理します 。
カスタマイズ可能なRAGが必要な場合は、次のセクションに進んでください。
Quarkusを使用している場合は、Easy RAGをさらに簡単に行う方法があります。 Quarkusドキュメントをお読みください。
このような「Easy RAG」の品質は、もちろん、カスタマイズされたRAGセットアップよりも低くなります。 ただし、これはRAGについて学び始めたり、概念実証(PoC)を作成したりする最も簡単な方法です。 後で、Easy RAGからより高度なRAGへスムーズに移行し、さまざまな側面を調整およびカスタマイズできるようになります。
langchain4j-easy-rag依存関係をインポートします:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.18.1-beta28</version>
</dependency>
- ドキュメントを読み込みましょう:
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");
指定されたディレクトリからすべてのファイルを読み込みます。
内部では何が行われているのでしょうか?
多種多様なドキュメントタイプをサポートするApache Tikaライブラリを使用して、
ドキュメントタイプの検出と解析が行われます。
使用するDocumentParserを明示的に指定しなかったため、
FileSystemDocumentLoaderはlangchain4j-easy-rag依存関係からSPIを通じて提供される
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。
- 次に、ドキュメントを前処理し、専用の埋め込みストア(ベクターデータベースとも呼ばれます)に保存する必要がありま す。 これは、ユーザーが質問したときに、関連する情報をすばやく見つけるために必要です。 当社がサポートする30以上の埋め込みストアのいずれかを使用できますが、 ここでは簡単にするために、インメモリのものを使用します。
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 Runtimeを使用してメモリに簡単に読み込み、同じプロセス内で実行できます。
そうです、外部サービスを一切使わずに、同じJVMプロセス内で完全にオフラインでテキストを埋め込みに変換できるのです。 LangChain4jは、いくつかの人気のある埋め込みモデルを そのまま利用できる形で提供しています。
- すべての
TextSegmentとEmbeddingのペアは、EmbeddingStoreに保存されます。
- 最後のステップは、LLMへのAPIとして機能するAIサービスを作成することです。
interface Assistant {
String chat(String userMessage);
}
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
.build();
ここでは、Assistantがユーザーの質問に回答するためにOpenAI LLMを使用し、会話内の最新10件のメッセージを記憶し、ドキュメントを含むEmbeddingStoreから関連コンテンツを取得するように設定します。
- そして、これでチャットを始める準備ができました!
String answer = assistant.chat("How to do Easy RAG with LangChain4j?");
コア RAG API
LangChain4jは、シンプルなものから高度なものまで、カスタムRAGパイプラインを簡単に構築できるようにするための豊富なAPIセットを提供しています。 このセクションでは、主要なドメインクラスとAPIについて説明します。
Document
Documentクラスは、単一のPDFファイルやWebページなど、ドキュメント全体を表します。
現時点では、Documentはテキスト情報のみを表すことができますが、
将来のアップデートでは画像やテーブルもサポートする予定です。
便利なメソッド
Document.text()はDocumentのテキストを返しますDocument.metadata()はDocumentのMetadataを返します(下記の「Metadata」セクションを参照)Document.toTextSegment()はDocumentをTextSegmentに変換します(下記の「TextSegment」セクションを参照)Document.from(String, Metadata)はテキストとMetadataからDocumentを作成しますDocument.from(String)は空のMetadataを持つテキストからDocumentを作成します
Metadata
各DocumentにはMetadataが含まれています。
これは、ドキュメントの名前、ソース、最終更新日、所有者、
その他の関連する詳細情報など、Documentに関するメタ情報を格納します。
Metadataはキーと値のマップとして格納され、キーはString型で、
値はString、Integer、Long、Float、Double、UUIDのいずれかの型になります。
Metadataはいくつかの理由で役立ちます:
- LLMへのプロンプトに
Documentのコンテンツを含める際に、 メタデータエントリも含めることで、LLMに考慮すべき追加情報を提供できます。 例えば、Documentの名前とソースを提供することで、LLMのコンテンツ理解を向上させることができます。 - プロンプトに含める関連コンテンツを検索する際に、
Metadataエントリでフィルタリングできます。 例えば、セマンティック検索を特定の所有者に属するDocumentのみに絞り込むことができます。 Documentのソースが更新された場合(例えば、ドキュメントの特定のページ)、 メタデータエントリ(例えば、「id」、「source」など)によって対応するDocumentを簡単に見つけ出し、EmbeddingStore内のものも更新して同期を保つことができます。
便利なメソッド
Metadata.from(Map)はMapからMetadataを作成しますMetadata.put(String key, String value)/put(String, int)などは、Metadataにエントリを追加しますMetadata.putAll(Map)はMetadataに複数のエントリを追加しますMetadata.getString(String key)/getInteger(String key)などは、Metadataエントリの値を返し、必要な型にキャストしますMetadata.containsKey(String key)はMetadataに指定されたキーのエントリが含まれているかを確認しますMetadata.remove(String key)はキーによってMetadataからエントリを削除しますMetadata.copy()はMetadataのコピーを返しますMetadata.toMap()はMetadataをMapに変換しますMetadata.merge(Metadata)は現在のMetadataを別のMetadataとマージします
Document Loader
StringからDocumentを作成することもできますが、より簡単な方法は、ライブラリに含まれているドキュメントローダーのいずれかを使用することです:
langchain4jモジュールのFileSystemDocumentLoaderlangchain4jモジュールのClassPathDocumentLoaderlangchain4jモジュールのUrlDocumentLoaderlangchain4j-document-loader-amazon-s3モジュールのAmazonS3DocumentLoaderlangchain4j-document-loader-azure-storage-blobモジュールのAzureBlobStorageDocumentLoaderlangchain4j-document-loader-githubモジュールのGitHubDocumentLoaderlangchain4j-document-loader-google-cloud-storageモジュールのGoogleCloudStorageDocumentLoaderlangchain4j-document-loader-seleniumモジュールのSeleniumDocumentLoaderlangchain4j-document-loader-playwrightモジュールのPlaywrightDocumentLoaderlangchain4j-document-loader-tencent-cosモジュールのTencentCosDocumentLoader
Document Parser
Documentは、PDF、DOC、TXTなど、さまざまな形式のファイルを表すことができます。
これらの各形式を解析するために、ライブラリに含まれている複数の実装を持つDocumentParserインターフェースがあります:
langchain4jモジュールのTextDocumentParser。プレーンテキスト形式(TXT、HTML、MDなど)のファイルを解析できますlangchain4j-document-parser-apache-pdfboxモジュールのApachePdfBoxDocumentParser。PDFファイルを解析できますlangchain4j-document-parser-apache-poiモジュールのApachePoiDocumentParser。MS Officeファイル形式 (DOC、DOCX、PPT、PPTX、XLS、XLSXなど)を解析できますlangchain4j-document-parser-apache-tikaモジュールのApacheTikaDocumentParser。 ほぼすべての既存ファイル形式を自動的に検出して解析できますlangchain4j-document-parser-doclingモジュールのDoclingDocumentParser。 Docling JavaとDoclingを使用してドキュメントを処理しますlangchain4j-document-parser-markdownモジュールのMarkdownDocumentParser。 マークダウン形式のファイルを解析できますlangchain4j-document-parser-yamlモジュールのYamlDocumentParser。 YAML形式のファイルを解析できます
ファイルシステムから1つまたは複数のDocumentをロー ドする方法の例は次のとおりです:
// Load a single document
Document document = FileSystemDocumentLoader.loadDocument("/home/langchain4j/file.txt", new TextDocumentParser());
// Load all documents from a directory
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", new TextDocumentParser());
// Load all *.txt documents from a directory
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", pathMatcher, new TextDocumentParser());
// Load all documents from a directory and its subdirectories
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/home/langchain4j", new TextDocumentParser());
DocumentParserを明示的に指定せずにドキュメントを読み込むこともできます。
この場合、デフォルトのDocumentParserが使用されます。
デフォルトのパーサーはSPIを通じて読み込まれます(例:langchain4j-document-parser-apache-tikaやlangchain4j-easy-ragがインポートされている場合)。
SPIを通じてDocumentParserが見つからない場合は、フォールバックとしてTextDocumentParserが使用されます。
Document Transformer
DocumentTransformerの実装は、以下のようなさまざまなドキュメント変換を実行できます:
- クリーニング:
Documentのテキストから不要なノイズを除去します。これにより、トークンを節約し、ノイズによる干渉を減らすことができます。 - フィルタリング:特定の
Documentを検索対象から完全に除外します。 - エンリッチメント:検索結果を向上させる可能性がある追加情報を
Documentに付加します。 - 要約:
Documentを要約し、その短い要約をMetadataに保存して、後で各TextSegment(後述)に含めることで、検索を改善できる可能性があります。 - など。
この段階で、Metadataエントリの追加、変更、削除も行えます。
現在、標準で提供されている実装は、langchain4j-document-transformer-jsoupモジュールのHtmlToTextDocumentTransformerのみです。
これは、生のHTMLから必要なテキストコンテンツとメタデータエントリを抽出できます。
万能な解決策はないため、独自のデータに合わせてカスタマイズしたDocumentTransformerを独自に実装することをお勧めします。
Graph Transformer
GraphTransformerは、非構造化Documentオブジェクトを、ノードやリレーションシップなどのセマンティックグラフ要素を抽出して構造化GraphDocumentに変換するインターフェースです。
生のテキストを構造化されたセマンティックグラフに変換するのに最適です。
GraphTransformerは、生のドキュメントをGraphDocumentに変換します。これには以下が含まれます:
- テキスト内のエンティティや概念を表すノード(
GraphNode)のセット。 - それらのエンティティがどのように接続されているかを表すリレーションシップ(
GraphEdge)のセット。 - ソースとしての元の
Document。
デフォルトの実装はLLMGraphTransformerで、言語モデル(例:OpenAI)を使用して、プロンプトエンジニアリングにより自然言語からグラフ情報を抽出します。
主な利点
- エンティティとリレーションシップの抽出:主要な概念とその意味的なつながりを特定します。
- グラフ表現:出力はナレッジグラフやグラフデータベースへの統合にすぐに使用できます。
- モデル駆動型パース:大規模言語モデルを使用して、非構造化テキストから構造を推論します。
Maven依存関係
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-llm-graph-transformer</artifactId>
<version>${latest version here}</version>
</dependency>
使用例
import dev.langchain4j.data.document.Document;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.community.data.document.graph.GraphDocument;
import dev.langchain4j.community.data.document.graph.GraphNode;
import dev.langchain4j.community.data.document.graph.GraphEdge;
import dev.langchain4j.community.data.document.transformer.graph.GraphTransformer;
import dev.langchain4j.community.data.document.transformer.graph.llm.LLMGraphTransformer;
import java.time.Duration;
import java.util.Set;
public class GraphTransformerExample {
public static void main(String[] args) {
// Create a GraphTransformer backed by an LLM
GraphTransformer transformer = new LLMGraphTransformer(
OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.timeout(Duration.ofSeconds(60))
.build()
);
// Input document
Document document = Document.from("Barack Obama was born in Hawaii and served as the 44th President of the United States.");
// Transform the document
GraphDocument graphDocument = transformer.transform(document);
// Access nodes and relationships
Set<GraphNode> nodes = graphDocument.nodes();
Set<GraphEdge> relationships = graphDocument.relationships();
nodes.forEach(System.out::println);
relationships.forEach(System.out::println);
}
}
出力例
GraphNode(name=Barack Obama, type=Person)
GraphNode(name=Hawaii, type=Location)
GraphEdge(from=Barack Obama, predicate=was born in, to=Hawaii)
GraphEdge(from=Barack Obama, predicate=served as, to=President of the United States)