PythonでRAGと知識検索を実装
この章では、検索拡張生成(Retrieval-Augmented Generation、RAG)技術を紹介します。
RAGは、知識集約型エージェントを構築するための中核技術の一つです。
ベクトル検索と言語生成モデルを組み合わせることで、RAGはエージェントに外部知識へのアクセスと活用能力を与え、モデルパラメータに「記憶」されたコンテンツだけに依存する必要をなくします。
RAGの基本原理
RAGの核となる考え方は、「情報検索」と「言語生成」という2つのプロセスを組み合わせて、完全なQAチェーンを形成することです。
ユーザーが質問すると、システムはまず知識ベースから質問に関連するコンテンツを検索します。
次に、システムは検索されたコンテンツをコンテキストとして生成モデルに提供し、より正確で根拠のある回答の生成を支援します。
なぜRAGが必要か
言語モデルには知識の期限があり、トレーニング完了後に起こったことを認識できません。
モデルのパラメータは有限であり、世界のすべての知識をパラメータに記憶することは不可能です。
モデルがパラメータから知識を「思い出す」ことに完全に依存すると、正確に覚えていない、またはまったく学習していないコンテンツに遭遇した場合、モデルはしばしば真面目にでっち上げた回答を生成します。これがよく言われる「幻覚」(hallucination)です。
RAGの手法は、モデルが回答する前に実際の資料を調べさせ、「記憶による回答」を「オープンブックでの回答」に変えることです。知識ソースもいつでも更新でき、モデルを再トレーニングする必要はありません。
コアアーキテクチャ
完全なRAGシステムには通常、3つの中核コンポーネントが含まれます:
検索器(Retriever):知識ベースから質問に最も関連する情報断片を見つけ出します。
ベクトルデータベース(Vector Store):ドキュメントコンテンツに対応するベクトル表現を保存し、大規模で低遅延の類似度検索をサポートします。
生成器(Generator):検索されたコンテンツとユーザーの元の質問に基づいて、最終的な自然言語の回答を生成します。
3つの関係は次のように理解できます:検索器は「資料を探す」役割、ベクトルデータベースは「資料を保存し、類似資料を素早く見つける」役割、生成器は「資料を見ながら回答をまとめる」役割を担います。
ワークフロー
第1ステップ、インデックス作成段階。元のドキュメントを複数のテキストチャンク(chunks)に分割し、それぞれベクトル化してベクトルデータベースに保存します。このステップは通常、オフラインで事前に実行されます。
第2ステップ、検索段階。ユーザーがクエリを発行すると、クエリも同様にベクトル化され、ベクトルデータベースで類似度検索を行い、最も関連性の高いテキストチャンクをいくつか見つけ出します。
第3ステップ、拡張段階。検索された関連テキストチャンクとユーザーの元の質問を結合して、より完全なプロンプト(Prompt)を構築します。
第4ステップ、生成段階。生成モデルは、この拡張されたコンテキストに基づいて最終回答を生成します。
この4つのステップのうち、オフラインの前処理は第1ステップだけで、後の3つのステップはすべてユーザーが質問したときにリアルタイムで発生します。そのため、検索速度と検索品質がシステム全体の応答速度と回答品質を直接決定します。
コード実装
基本RAG実装
"""
簡易版RAGシステム実装
3つの中核コンポーネントを含む:エンベッダー、ベクトルストア、生成器
"""
def __init__(self, embedder, vector_store, generator):
# エンベッダー:テキストをベクトルに変換する役割
self.embedder = embedder
# ベクトルデータベース:ベクトルを保存し、類似度検索をサポートする役割
self.vector_store = vector_store
# 生成器:コンテキストに基づいて回答を生成する役割
self.generator = generator
def add_documents(self, documents):
"""
ドキュメントを知識ベースに追加
:param documents: 元のドキュメントリスト(各要素は完全なテキスト)
"""
# 第1ステップ:長いドキュメントをより小さなテキストチャンクに分割し、正確な検索を容易にする
chunks = self.chunk_documents(documents)
# 第2ステップ:すべてのテキストチャンクをベクトルに変換する
embeddings = self.embedder.embed(chunks)
# 第3ステップ:ベクトルと対応する原文を一緒にベクトルデータベースに保存する
self.vector_store.add(embeddings, chunks)
def chunk_documents(self, documents, chunk_size=500, overlap=50):
"""
ドキュメント分割:長いドキュメントを固定長で、一定の重複を持つテキストチャンクに分割する
:param documents: 元のドキュメントリスト
:param chunk_size: 各テキストチャンクの文字数
:param overlap: 隣接するテキストチャンク間の重複文字数(セマンティクスが分割箇所で途切れるのを防ぐ)
:return: 分割後のテキストチャンクリスト
"""
chunks = []
for doc in documents:
# (chunk_size - overlap) のステップでスライド分割し、一定の重複領域を保持する
for i in range(0, len(doc), chunk_size - overlap):
chunk = doc[i:i + chunk_size]
chunks.append(chunk)
return chunks
def query(self, question, top_k=5):
"""
ユーザークエリを処理し、生成された回答を返す
:param question: ユーザーが発した質問
:param top_k: 検索時に返される関連テキストチャンクの数
:return: 生成モデルが生成した最終回答
"""
# 1. 質問をベクトルに変換して、知識ベース内のベクトルと比較できるようにする
question_embedding = self.embedder.embed([question])[0]
# 2. ベクトルデータベースで最も類似した top_k 個のテキストチャンクを検索する
results = self.vector_store.search(question_embedding, top_k)
# 3. 検索されたテキストチャンクを連結してコンテキストにする
context = "\n".join([doc for doc, _ in results])
# 4. コンテキストと元の質問を一緒にプロンプトとして構成する
prompt = f"以下の内容に基づいて質問に回答してください:\n{context}\n\n質問:{question}"
# 5. 生成モデルに渡して最終回答を生成させる
return self.generator.generate(prompt)
この基本実装で完全なRAGフローを実行できますが、実際のプロジェクトでは不十分なことがよくあります:分割方法が単純すぎる、検索ランキングが一度のベクトル類似度計算にのみ依存しており、精度に限界があります。以下に、いくつかの一般的な最適化アプローチを紹介します。
Advanced RAG
基本RAGの一般的な問題には、検索されたコンテンツの品質が低い、ランキングが十分に正確でない、コンテキストに無関係な情報が混入するなどがあります。
Advanced RAGは、再ランキングやハイブリッド検索などの技術を導入し、基本フローの上でさらに最適化して、最終回答の正確性を向上させます。
再ランキング(Reranking)
基本RAGのベクトル検索は高速ですが、ランキング精度に限界があり、セマンティクス的にあまり適切でないコンテンツを上位にランク付けしがちです。
再ランキングの手法は:まずベクトル検索で候補テキストをすばやく絞り込み(例:top-20)、次により計算量は多いがより正確なクロスエンコーダー(Cross-Encoder)でこれらの候補を一つずつスコアリングし、再ランキング後に本当に最も関連性の高い少数の項目(例:top-5)を取り出します。
この「粗い絞り込み、その後の精密なランキング」という2段階のアプローチは、検索速度と検索精度の両方を両立させます。
再ランキング付きRAG
"""
高度なRAGシステム
基本フローの上に再ランキングのプロセスを追加
"""
def __init__(self, embedder, vector_store, reranker, generator):
# エンベッダー
self.embedder = embedder
# ベクトルデータベース
self.vector_store = vector_store
# 再ランキングモデル(クロスエンコーダー)。精密なランキングに使用
self.reranker = reranker
# 生成器
self.generator = generator
def query(self, question, initial_k=20, final_k=5):
"""
クエリを処理し、「粗い絞り込み + 精密なランキング」の2段階検索を含む
:param question: ユーザーの質問
:param initial_k: 第1段階のベクトル検索で返される候補の数
:param final_k: 再ランキング後に最終的に保持する数
"""
# ==================== 段階1:初期検索(粗い絞り込み) ====================
# 質問をベクトルに変換する
question_embedding = self.embedder.embed([question])[0]
# ベクトル類似度を使用して候補テキストをすばやくリコールする(高速だが精度は普通)
initial_results = self.vector_store.search(
question_embedding,
initial_k
)
# ==================== 段階2:再ランキング(精密なランキング) ====================
# (質問, 候補ドキュメント) のペアを構築し、クロスエンコーダーに渡して一つずつスコアリングする
doc_pairs = [(question, doc) for doc, _ in initial_results]
# クロスエンコーダーは質問とドキュメントを同時にエンコードし、両者の関連性をより正確に判断できる
reranked = self.reranker.rerank(doc_pairs)
# 再ランキング後に本当に最も関連性の高い final_k 件を取得する
final_context = "\n".join([doc for doc, _ in reranked[:final_k]])
# ==================== フェーズ3:生成 ====================
prompt = f"コンテキスト:{final_context}\n\n質問:{question}"
return self.generator.generate(prompt)
ハイブリッド検索
ハイブリッド検索(Hybrid Retrieval)は、稠密検索とスパース検索のそれぞれの利点を組み合わせたものです。
稠密検索(Dense Retrieval)はベクトル類似度に基づき、意味レベルの関連性を捉えるのが得意で、ユーザーの質問と文書が完全に同じ語句を使っていなくてもマッチングできます。
スパース検索(BM25など)はキーワードの頻度統計に基づき、正確な文字一致に強く、固有名詞・番号・コード断片などのシーンではベクトル検索よりも信頼できることが多いです。
両者にはそれぞれ盲点があります。ベクトル検索は「意味は理解できるが正確なキーワードを見つけられない」可能性があり、キーワード検索は「語句はマッチするが意味を理解できない」可能性があります。ハイブリッド検索は2つの検索経路の結果を融合し、長所を生かして短所を補います。
ハイブリッド検索RAG
"""
ハイブリッド検索RAG
稠密検索(ベクトル)とスパース検索(キーワード)を組み合わせる
"""
def __init__(self, dense_retriever, sparse_retriever, fusion_fn):
# 稠密検索器:ベクトル類似度に基づき、意味マッチングに強い
self.dense_retriever = dense_retriever
# スパース検索器:キーワード統計(BM25など)に基づき、正確一致に強い
self.sparse_retriever = sparse_retriever
# 融合関数:2つの検索結果を統合するために使用(例:RRF、下記の実装参照)
self.fusion_fn = fusion_fn
def query(self, question, top_k):
"""
ハイブリッド検索を実行し、融合後にランキング付けされた結果を返す
:param question: ユーザーの質問
:param top_k: 最終的に返す結果の件数
"""
# ==================== 稠密検索 ====================
# 候補を多めに取得し(top_k * 2)、融合時に絞り込む余地を残す
dense_results = self.dense_retriever.search(question, top_k * 2)
# ==================== スパース検索 ====================
# キーワードベースの検索。こちらも候補を多めに取得する
sparse_results = self.sparse_retriever.search(question, top_k * 2)
# ==================== 結果の融合 ====================
# Reciprocal Rank Fusion(RRF)などの戦略で2つのランキング結果を融合する
fused = self.fusion_fn(dense_results, sparse_results)
# 融合後のランキングが上位の top_k 件を取得する
return fused[:top_k]
def reciprocal_rank_fusion(results_a, results_b, k=60):
"""
Reciprocal Rank Fusion(RRF)アルゴリズム
2系統(または複数系統)の検索のランキング結果を1つの統合ランキングに融合するために使う
核心となる考え方:各文書の各系統の結果における「順位」だけを見て、具体的なスコアは見ない
ベクトル類似度スコアとBM25スコアの次元が異なり、直接比較できない問題を回避できる
:param results_a: 検索結果A、形式は [(doc, score), ...]
:param results_b: 検索結果B、形式は [(doc, score), ...]
:param k: 平滑化定数。順位が下位の文書の重みの差を小さくするために使う。通常は60
:return: 融合スコアの高い順に並べた [(doc, score), ...] のリスト
"""
scores = {}
# 結果Aを処理:順位が上であるほど、寄与するスコアが高い
for rank, (doc, _) in enumerate(results_a):
scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)
# 結果Bも処理し、同じく順位に応じてスコアを加算する
for rank, (doc, _) in enumerate(results_b):
scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)
# 加算後の融合スコアが高い順にソートする
sorted_docs = sorted(scores.items(), key=lambda x: -x[1])
return sorted_docs
その他の最適化戦略
クエリ拡張:同義語や関連語を補うことで元のクエリを拡張し、検索のリコール範囲を広げ、再現率を向上させる。
クエリ書き換え:モデルを使ってユーザーの本当の意図を理解し、口語的で曖昧な質問を検索に適した表現に書き換える。
コンテキスト圧縮:検索結果を生成モデルに渡す前に、冗長で無関係な部分を除去し、重要な情報だけを残す。コスト削減になるだけでなく、生成モデルが無関係な内容に干渉されるリスクも減らせる。
ベクトルデータベース
ベクトルデータベースはRAGシステムの基盤となるインフラであり、大量の高次元ベクトルを格納し、大規模・低遅延の類似度検索をサポートする。
中核となる概念
埋め込み(Embedding):テキスト(または画像・音声など)を一連の稠密な数値ベクトルに変換するプロセス。意味的に近い内容はベクトル空間上でも距離が近くなる。
ベクトル次元:埋め込みベクトルの長さ。次元が高いほど理論的には表現できる意味情報が豊富になるが、保存・計算コストも増加する。
類似度の尺度:2つのベクトル間の「類似の程度」または「距離」を測る具体的な計算方法。異なる尺度は異なるシーンに適している。
一般的な類似度指標
| 尺度方法 | 公式 | 特徴 | 適用シーン |
|---|---|---|---|
| コサイン類似度 | cos(A,B) = A·B/(|A||B|) | 方向の類似性のみを測り、ベクトルの長さとは無関係。値域は [-1,1] | 文書・テキスト類似度 |
| ユークリッド距離 | d(A,B) = sqrt(sum((Ai-Bi)^2)) | ベクトル間の絶対的な空間距離を測る。値域は [0,+∞) | 画像特徴マッチング |
| ドット積 | A·B = sum(Ai*Bi) | 方向と長さの両方の影響を受ける。計算効率が高い | レコメンドシステム |
主流ベクトルデータベースの比較
| データベース | タイプ | 特徴 | 適用シーン |
|---|---|---|---|
| Pinecone | クラウドサービス | フルマネージドサービス。導入が簡単で自動スケールし、自前運用が不要 | 迅速に本番稼働させたい環境 |
| Weaviate | オープンソース | ネイティブでハイブリッド検索(ベクトル+キーワード)をサポート。モジュール化の度合いが高い | 検索ロジックを柔軟にカスタマイズしたいシーン |
| Milvus | オープンソース | 高可用・水平スケールが容易で、10億件以上のベクトル規模を支えられる | 超大規模ベクトル検索 |
| Chroma | オープンソース | 軽量でAPIがシンプル。数行のコードで実行できる | プロトタイプ開発・教育・ローカルテスト |
| Qdrant | オープンソース | Rustで実装され、性能が優秀で、豊富なフィルタ条件をサポート | 高スループット・複雑なフィルタリングが必要なシーン |
コード例
Chromaベクトルデータベースを使用
from chroma_client import ChromaClient
# クライアントを作成(ローカルインスタンスにもリモートサービスにも接続可能)
client = ChromaClient()
# コレクション(Collection)を作成または取得する。リレーショナルデータベースの「テーブル」に相当
collection = client.get_or_create_collection("knowledge_base")
# ドキュメントを追加:ID、ベクトル、原文、メタデータはそれぞれ対応させる必要がある
collection.add(
ids=["doc1", "doc2", "doc3"], # 各ドキュメントの一意な識別子
embeddings=[
[0.1, 0.2, 0.3], # ドキュメント1に対応するベクトル(実際の次元は通常3次元よりはるかに高い)
[0.4, 0.5, 0.6], # ドキュメント2に対応するベクトル
[0.7, 0.8, 0.9], # ドキュメント3に対応するベクトル
],
documents=[
"人工知能は計算機科学の一分野である",
"機械学習は人工知能のサブ分野である",
"深層学習は機械学習のサブ分野である"
],
metadatas=[
{"source": "教科書"}, # ドキュメント1の出所情報。後続のフィルタリングに使用できる
{"source": "論文"},
{"source": "論文"}
]
)
# 検索:クエリベクトルを渡し、最も類似した n_results 件の結果を返す
results = collection.query(
query_embeddings=[[0.15, 0.25, 0.35]], # クエリに対応するベクトル
n_results=2 # top-2 の最も類似した結果を返す
)
# 検索結果を出力
print(results["documents"]) # ヒットした原文内容
print(results["distances"]) # 対応する距離・類似度スコア。値が小さいほど通常は類似している
ベクトルデータベースを選ぶ際は、データ規模、クエリ遅延要件、デプロイ方式(クラウド管理 or 自社構築)、チームの運用能力を総合的に考慮する必要がある。小規模プロトタイプ開発ではChromaを優先し、すぐ使える本番環境を求めるならPinecone、超大規模で自主制御が必要な場合はMilvusやQdrantがより適している。
GraphRAG
GraphRAGは知識グラフ(Knowledge Graph)とRAGを組み合わせた先進的な手法です。
エンティティと関係からなるグラフ構造を構築することで、システムの検索・推論能力を強化します。
なぜGraphRAGが必要か
従来のRAGはテキスト断片の類似度検索に基づくため、「多段推論」が必要な問題(答えを導くために複数の情報をつなぎ合わせる必要がある問題)を処理するのが難しい。
従来のRAGは、「誰が誰の上司か」「どの会社がどの会社を買収したか」といった構造化情報のような、エンティティ間の意味的関係を明示的に捉えることも容易ではない。
GraphRAGはこれらのエンティティと関係を明示的にグラフ構造に整理し、システムが「類似テキストを探す」だけでなく、関係パスに沿って推論できるようにする。
中核となる利点
エンティティ間の意味的関係を捉え、断片的なテキストではなく構造化された知識ネットワークを形成できる。
パスベースの推論をサポートし、関係型の質問により適切に答えられる。
マルチホップ(multi-hop)推論が必要な複雑な問題を処理できる。例えば「ある人の指導教官の指導教官は誰か」といった、複数の関係ノードを連続的にまたいで答えを導く必要がある問題。
コード実装
GraphRAG実装
"""
GraphRAG システム
知識グラフ検索とベクトル検索を組み合わせ、関係推論と意味的類似性の両方を考慮する
"""
def __init__(self, kg_builder, embedder, vector_store, generator):
# 知識グラフ構築器:エンティティ/関係抽出とグラフ検索を担当
self.kg_builder = kg_builder
# 埋め込み器:ドキュメントと質問をベクトルに変換するために使用
self.embedder = embedder
# ベクトルデータベース:知識グラフ検索の補完チャネルとして機能
self.vector_store = vector_store
# 生成器:融合されたコンテキストに基づいて最終回答を生成
self.generator = generator
def index_documents(self, documents):
"""
ドキュメントを知識グラフとベクトルデータベースの両方にインデックス化する
:param documents: ドキュメントリスト
"""
for doc in documents:
# ドキュメントからエンティティを抽出
# 例:「OpenAIは2015年に設立された」から抽出
# エンティティ:OpenAI(組織)、2015年(時間)
entities = self.kg_builder.extract_entities(doc)
# ドキュメントからエンティティ間の関係を抽出
# 例:関係トリプルを抽出:(OpenAI, 設立された, 2015年)
relations = self.kg_builder.extract_relations(doc)
# エンティティと関係を知識グラフに書き込む
self.kg_builder.add_entities(entities)
self.kg_builder.add_relations(relations)
# 同時に原文をベクトル化してベクトルデータベースに格納し、意味検索を補完する
embedding = self.embedder.embed([doc])
self.vector_store.add(embedding, [doc])
def query(self, question):
"""
クエリ処理:知識グラフ推論とベクトル検索を組み合わせる
"""
# ==================== 知識グラフ検索 ====================
# まず質問に関連するエンティティを見つける
# 例:「誰がOpenAIを設立したか?」-> エンティティ「OpenAI」を識別
relevant_entities = self.kg_builder.query_entities(question)
# これらのエンティティに基づいて、グラフ内の直接関連するサブグラフを取り出す
# サブグラフには関連エンティティとその隣接ノード、接続エッジが含まれる
subgraph = self.kg_builder.get_subgraph(relevant_entities)
# ==================== ベクトル検索 ====================
# 補完チャネルとして、意味的に類似したテキスト内容を検索
vector_results = self.vector_store.search(question, top_k=5)
# ==================== 結果融合 ====================
# グラフ推論結果とベクトル検索結果を統一コンテキストに融合
combined_context = self.fuse_results(subgraph, vector_results)
# ==================== 生成 ====================
return self.generator.generate(combined_context, question)
def fuse_results(self, subgraph, vector_results):
"""
知識グラフのサブグラフとベクトル検索結果を融合し、生成モデルが読み取り可能なコンテキストテキストに整理する
"""
# サブグラフを自然言語の記述に変換(例:「OpenAI -設立された-> 2015年」)
kg_text = subgraph.to_text()
# ベクトル検索で取得した原文の断片を連結
vector_text = "\n".join([doc for doc, _ in vector_results])
return f"{kg_text}\n{vector_text}"
class KnowledgeGraphBuilder:
"""知識グラフ構築器(参考実装。実際のプロジェクトでは通常、専用のNER/関係抽出モデルを呼び出す)"""
def extract_entities(self, text):
"""
テキストから固有表現を抽出する。通常はNER(固有表現認識)モデルを使用して行う
エンティティタイプには一般的にPERSON(人物)、ORG(組織)、TIME(時間)などがある
"""
entities = []
return entities
def extract_relations(self, text):
"""
テキストからエンティティ間の関係を抽出する。通常は関係抽出(Relation Extraction)モデルを使用して行う
関係は通常トリプルで表現される:(頭部エンティティ, 関係タイプ, 尾部エンティティ)
"""
relations = []
return relations
def query_entities(self, question):
"""ユーザーの質問に基づいて、質問に関連するエンティティを識別し、後続のサブグラフ検索に使用する"""
return []
応用シナリオ
GraphRAGは特に以下のシナリオに適している:
エンティティ関係の理解が必要なQA。例:「この会社のCEOは誰ですか」。
複数ホップの推論が必要な質問。例:「張三の指導教員の指導教員は誰ですか」。
知識ベースに暗黙的に存在し、明示的に記述されていない関係を発掘する必要があるシナリオ。
注意すべき点として、知識グラフの構築と維持には一定の工数コストがかかる(エンティティ/関係抽出の精度、グラフの更新・保守など)。そのため、関係推論に明確なニーズがあるシナリオに適しており、すべてのRAGプロジェクトのデフォルトの出発点としては推奨されない。
手法の比較と選択のアドバイス
| 方式 | 中核となる考え方 | 利点 | 制限 | 適用シナリオ |
|---|---|---|---|---|
| 基本RAG | ベクトル検索 + 直接生成 | 実装が簡単、コストが低い、習得が早い | 検索精度に限界があり、関連性の低い内容を取得しやすい | 簡単なQA、迅速なプロトタイプ検証 |
| Advanced RAG(リランキング) | ベクトル粗選別 + 交差エンコーダーによる精密ランキング | 検索ランキングの精度を大幅に向上 | モデル推論が1回増えるため、レイテンシとコストが増加 | 回答の正確性に対する要求が高いシナリオ |
| ハイブリッド検索 | ベクトル検索 + キーワード検索の融合 | 意味的マッチングと正確なキーワードマッチングの両方を考慮 | 2セットの検索インデックスを同時に保守する必要がある | 固有名詞、コード、番号が多い知識ベース |
| GraphRAG | 知識グラフ + ベクトル検索の融合 | 関係推論とマルチホップQAをサポート | グラフ構築と保守のコストが高い | 関係型・マルチホップ推論のニーズが明確なシナリオ |
章のまとめ
この章では、RAGと知識検索の中核技術を紹介した。
RAGの基本原理:検索と生成を組み合わせることで、Agentがモデルパラメータ内の記憶に完全に依存するのではなく、外部知識を利用して質問に回答できるようにする。
Advanced RAG:リランキング、ハイブリッド検索などの技術を通じて、基本フローの上に検索品質と回答精度をさらに向上させる。
ベクトルデータベース:RAGシステムの基盤インフラであり、ドキュメントのベクトル表現の効率的な保存と検索を担当する。選定はデータ規模とデプロイ方式を考慮して総合的に判断する必要がある。
GraphRAG:知識グラフと組み合わせることで、関係推論とマルチホップQA能力が強化されるが、追加の構築・保守コストも発生する。
適切なRAG方式を選択するには、まずビジネスシナリオの実際のニーズを明確にすることが核心だ。単純なQAシナリオでは、基本RAGで十分なことが多い。検索精度への要求が高い場合は、リランキングを導入できる。知識ベースに固有名詞/キーワードが多い場合は、ハイブリッド検索の追加が適している。大量の関係推論やマルチホップQAが関わる場合は、GraphRAGの導入を検討する。技術が複雑になるほど、開発・保守コストも高くなる。そのため、「より高度な方式」を盲目的に追求するよりも、ニーズに応じて選択することの方が重要だ。
その他の拡張