RAG
cagentでRAGソースを設定すると、エージェントはそのナレッジベース専用の検索ツールを自動的に取得します。エージェントはいつ検索すべきかを自ら判断し、関連する情報のみを抽出して、質問への回答やタスクの完了に利用します。これらすべてにおいて、ユーザーが手動でプロンプトに含める内容を管理する必要はありません。
このガイドでは、cagentのRAGシステムの仕組み、使用すべき場面、およびコンテンツに合わせて効果的に設定する方法について説明します。
RAGは、設定とチューニングを必要とする高度な機能です。デフォルト設定でも使い始めるには十分ですが、特定のコンテンツやユースケースに合わせて設定を調整することで、結果を大幅に改善できます。
課題:過剰なコンテキスト
エージェントはコードベース全体を対象に作業できますが、すべてをコンテキストウィンドウに収めることはできません。200Kトークンの制限があっても、中規模のプロジェクトでは大きすぎます。何百ものファイルに埋もれた関連コードを探し出すことは、コンテキストの無駄遣いです。
ファイルシステムツールはエージェントがファイルを読み取るのを助けますが、エージェントはどのファイルを読み取るべきか推測しなければなりません。ファイル名による検索はできても、意味(セマンティック)による検索はできないからです。「リトライ処理を探して」と頼んでも、エージェントは正しいコードに偶然行き当たることを期待してファイルを読み進めるしかありません。
Grepは正確なテキストの一致を見つけますが、関連する概念を見逃します。「authentication(認証)」で検索しても、「auth」や「login」を使用しているコードは見つかりません。結果として、何百もの一致が得られるか、あるいはゼロになるかのどちらかであり、grepはコードの構造を理解していません。単に出現した文字列を一致させているだけです。
RAGはコンテンツを事前にインデックス化し、セマンティック検索を可能にします。エージェントは、正確なワードではなく、意味によって事前にインデックス化されたコンテンツを検索します。コードの構造を尊重した関連性の高いチャンク(断片)のみを取得します。探索にコンテキストを浪費することはありません。
cagentにおけるRAGの仕組み
cagentの構成でRAGソースを設定します:
rag:
codebase:
docs: [./src, ./pkg]
strategies:
- type: chunked-embeddings
embedding_model: openai/text-embedding-3-small
vector_dimensions: 1536
database: ./code.db
agents:
root:
model: openai/gpt-5
instruction: あなたはコーディングアシスタントです。必要に応じてコードベースを検索してください。
rag: [codebase]rag: [codebase] を参照すると、cagentは以下の処理を行います:
-
起動時 - ドキュメントをインデックス化します(初回のみ、完了までブロックされます)。
-
会話中 - エージェントに検索ツールを提供します。
-
エージェントが検索したとき - 関連するチャンクを取得してコンテキストに追加します。
-
ファイル変更時 - 変更されたファイルを自動的に再インデックス化します。
エージェントは会話に基づいて、いつ検索するかを決定します。ユーザーがコンテキストに何を入れるかを管理するのではなく、エージェントが管理します。
インデックス作成プロセス
初回実行時、cagentは以下を行います:
-
設定されたパスからファイルを読み取る
-
.gitignoreパターンを尊重する(無効化可能) -
ドキュメントをチャンクに分割する
-
選択した戦略を使用して、検索可能な表現を作成する
-
すべてをローカルデータベースに保存する
以降の実行では、既存のインデックスを再利用します。ファイルが変更されると、cagentはそれを検知し、変更された部分のみを再インデックス化して、手動の介入なしにナレッジベースを最新の状態に保ちます。
リトリーバル(検索・取得)戦略
コンテンツの種類によって、最適なリトリーバル手法は異なります。cagentは3つの戦略をサポートしており、それぞれ異なるユースケースに最適化されています。デフォルト設定でも十分に機能しますが、トレードオフを理解することで、最適なアプローチを選択できます。
セマンティック検索(chunked-embeddings)
テキストを意味を表すベクトルに変換し、正確な単語ではなく概念による検索を可能にします:
strategies:
- type: chunked-embeddings
embedding_model: openai/text-embedding-3-small
vector_dimensions: 1536
database: ./docs.db
chunking:
size: 1000
overlap: 100インデックス作成中、ドキュメントはチャンクに分割され、各チャンクは埋め込み(embedding)モデルによって1536次元のベクトルに変換されます。これらのベクトルは、本質的には高次元空間内の座標であり、似た概念は近くに配置されます。
「ユーザーを認証するにはどうすればいいですか?」と検索すると、クエリがベクトルになり、データベースはコサイン類似度(ベクトルの成す角の測定)を使用して、近くにあるベクトルを持つチャンクを見つけ出します。埋め込みモデルは「authentication(認証)」「auth」「login」が関連する概念であることを学習しているため、そのうちの一つを検索すれば他も見つけることができます。
例:クエリ「ユーザーを認証するにはどうすればいいですか?」は、言い回しが異なっていても「User authentication requires a valid API token(ユーザー認証には有効なAPIトークンが必要です)」と「Token-based auth validates requests(トークンベースの認証がリクエストを検証します)」の両方を見つけ出します。一方で「The authentication tests are failing(認証テストが失敗しています)」は、単語が含まれていても意味が異なるため、見つかりません。
これは、ユーザーがドキュメントとは異なる用語で質問をする可能性があるドキュメント検索によく機能します。欠点は、正確な技術用語を見逃す可能性があること、そして時としてセマンティックな一致ではなくリテラル(文字通り)な一致が求められる場合があることです。また、インデックス作成中に埋め込みAPIの呼び出しが必要になります。
キーワード検索(BM25)
単語の出現頻度と希少性によって一致させ、ランク付けを行う統計的アルゴリズムです:
strategies:
- type: bm25
database: ./bm25.db
k1: 1.5
b: 0.75
chunking:
size: 1000
overlap: 100インデックス作成中、ドキュメントはトークン化され、アルゴリズムは各用語がどれくらい頻繁に出現するか(用語頻度:TF)と、すべてのドキュメントの中でどれくらい希少か(逆ドキュメント頻度:IDF)を計算します。スコアリングインデックスはローカルのSQLiteデータベースに保存されます。
「HandleRequest function」を検索すると、アルゴリズムはこれらの正確な用語を含むチャンクを見つけ、用語の頻度、希少性、およびドキュメントの長さに基づいてスコアリングします。「HandleRequest」という単語を見つけることは、「function」のような一般的な単語を見つけるよりも重要であるとスコアリングされます。統計的なランキング機能を備えたgrepのようなものだと考えてください。
例:「HandleRequest function」を検索すると、func HandleRequest(w http.ResponseWriter, r *http.Request) や「The HandleRequest function processes incoming requests」は見つかりますが、意味が似ていても「process HTTP requests(HTTPリクエストを処理する)」というフレーズは、単語が一致しないため見つかりません。
k1 パラメータ(デフォルト 1.5)は、繰り返される用語がどれくらい重要かを制御します。値が高いほど繰り返しが強調されます。b パラメータ(デフォルト 0.75)は長さの正規化を制御します。値が高いほど、長いドキュメントに強いペナルティが課されます。
これは高速でローカル(APIコストなし)であり、関数名、クラス名、APIエンドポイント、およびそのままの形で出現する識別子を見つけるのに適しています。トレードオフとして、意味の理解は全くありません。「RetryHandler」と「retry logic」は関連があっても一致しません。セマンティック検索を補完するために不可欠な手法です。
LLM強化型セマンティック検索(semantic-embeddings)
埋め込みの前にLLMでセマンティックな要約を生成し、コードの名前ではなく「何をするか」で検索できるようにします:
strategies:
- type: semantic-embeddings
embedding_model: openai/text-embedding-3-small
chat_model: openai/gpt-5-mini
vector_dimensions: 1536
database: ./code.db
ast_context: true
chunking:
size: 1000
code_aware: trueインデックス作成中、コードはAST(抽象構文木)構造を使用して分割され(関数がそのまま保持されます)、次に chat_model が各チャンクのセマンティックな要約を生成します。生のコードではなく、この要約が埋め込まれます。検索時には、クエリはこの要約に対して照合されますが、返されるのは元のコードです。
これは通常の埋め込みにおける課題を解決します。生のコードの埋め込みは、変数名や実装の詳細に支配されがちです。リトライロジックを実装している processData という関数は、「retry」という言葉とセマンティックに一致しない可能性があります。しかし、LLMが最初に要約すると、要約には明示的に「リトライロジック」と記載されるため、検索で見つけられるようになります。
例:以下のコードを考えます。
func (c *Client) Do(req *Request) (*Response, error) {
for i := 0; i < 3; i++ {
resp, err := c.attempt(req)
if err == nil { return resp, nil }
time.Sleep(time.Duration(1<<i) * time.Second)
}
return nil, errors.New("max retries exceeded")
}LLMによる要約:「HTTPリクエストに対して、失敗する前に1秒、2秒、4秒の遅延を伴う最大3回までの指数バックオフ・リトライロジックを実装しています。」
これで、「retry logic exponential backoff(リトライロジック 指数バックオフ)」と検索すれば、コード内でそれらの単語が使われていなくても、このコードを見つけることができます。ast_context: true オプションは、より良い理解のためにプロンプトにASTメタデータを含めます。code_aware: true チャンキングは、関数の実装途中で分割されるのを防ぎます。
このアプローチは、命名規則が一貫していない大規模なコードベースにおいて、動作によってコードを探すのに非常に優れています。トレードオフは、インデックス作成が大幅に遅くなること(チャンクごとにLLMの呼び出しが発生)と、APIコストが高くなること(チャットモデルと埋め込みモデルの両方)です。十分にドキュメント化されたコードや単純なプロジェクトでは、多くの場合オーバースペックです。
ハイブリッド検索による戦略の組み合わせ
各戦略には一長一短があります。これらを組み合わせることで、意味の理解と正確な用語の一致の両方を捉えることができます:
rag:
knowledge:
docs: [./documentation, ./src]
strategies:
- type: chunked-embeddings
embedding_model: openai/text-embedding-3-small
vector_dimensions: 1536
database: ./vector.db
limit: 20
- type: bm25
database: ./bm25.db
limit: 15
results:
fusion:
strategy: rrf
k: 60
deduplicate: true
limit: 5フュージョン(融合)の仕組み
両方の戦略が並列で実行され、それぞれが上位の候補(この例では20個と15個)を返します。フュージョンはランクベースのスコアリングを使用して結果を統合し、重複を削除して、最終的な上位5件の結果を返します。エージェントは、セマンティックなクエリ(「〜するにはどうすれば…」)と正確な用語検索(「configure_auth 関数を探して」)の両方で機能する結果を得ることができます。
フュージョン戦略
RRF(Reciprocal Rank Fusion:相互順位融合)が推奨されます。絶対スコアではなくランクに基づいて結果を組み合わせるため、戦略ごとにスコアリングの尺度が異なる場合でも信頼性が高く機能します。チューニングは不要です。
重み付けフュージョン(weighted fusion)では、特定の戦略をより重視します:
fusion:
strategy: weighted
weights:
chunked-embeddings: 0.7
bm25: 0.3これにはコンテンツに合わせた調整が必要です。ある手法が自分のユースケースにおいてより優れているとわかっている場合に使用してください。
最大スコアフュージョン(max score fusion)は、戦略間で最も高いスコアを採用します:
fusion:
strategy: maxこれは、各戦略が比較可能なスコアリング尺度を使用している場合にのみ機能します。シンプルですが、RRFほど洗練されてはいません。
検索品質の向上
結果のリランキング(再順位付け)
最初の検索(リトリーバル)は速度を重視して最適化されています。リランキングは、より精緻なモデルを使用して結果を再スコアリングし、関連性を高めます:
results:
reranking:
model: openai/gpt-5-mini
threshold: 0.3
criteria: |
関連性をスコアリングする際は、以下を優先してください:
- コミュニティのコンテンツよりも公式ドキュメント
- 古い資料よりも最新の情報
- 理論的な説明よりも実践的な例
- 設計上の議論よりもコードの実装
limit: 5criteria(基準)フィールドは非常に強力です。特定のユースケースにおいて何が結果を関連性の高いものにするか、ドメイン知識を記述してください。基準が具体的なほど、リランキングの精度は上がります。
トレードオフ:結果は大幅に改善されますが、レイテンシ(遅延)とAPIコスト(各結果のスコアリングのためのLLM呼び出し)が増加します。
チャンキングの設定
ドキュメントの分割方法は、検索品質に劇的な影響を与えます。コンテンツの種類に合わせてチャンキングを調整してください。チャンクサイズはトークンではなく、文字数(Unicodeコードポイント)で測定されます。
ドキュメントや散文(Prose)の場合は、適度なチャンクサイズとオーバーラップ(重複)を使用します:
chunking:
size: 1000
overlap: 100
respect_word_boundaries: trueオーバーラップはチャンクの境界部分のコンテキストを維持します。respect_word_boundaries は、単語が途中で切断されるのを防ぎます。
コードの場合は、より大きなチャンクサイズとASTベースの分割を使用します:
chunking:
size: 2000
code_aware: true現在、Go言語のみがサポートされています。他の言語へのサポートも計画されています。
APIリファレンスのような短く焦点の絞られたコンテンツの場合:
chunking:
size: 500
overlap: 50短いセクションは自然に自己完結しているため、必要なオーバーラップは少なくなります。
これらの値を試行錯誤してください。検索結果にコンテキストが不足している場合は、チャンクサイズまたはオーバーラップを増やします。結果が広すぎる場合は、チャンクサイズを小さくします。
RAGに関する意思決定
RAGを使用すべきとき
以下の場合にRAGを使用してください:
-
コンテンツがコンテキストウィンドウに収まらないほど大きい場合
-
すべてを一度に読み込むのではなく、ピンポイントで情報を取得したい場合
-
コンテンツが頻繁に変更され、常に最新の状態を保つ必要がある場合
-
エージェントが多数のファイルをまたいで検索する必要がある場合
RAGを使用すべきでないとき:
-
コンテンツが十分に小さく、エージェントの指示に直接含められる場合
-
情報が滅多に変更されない場合(プロンプトエンジニアリングを検討してください)
-
リアルタイムデータが必要な場合(RAGは事前にインデックス化されたスナップショットを使用します)
-
コンテンツがすでにエージェントが直接照会できる検索可能な形式(SQLなど)である場合
検索戦略の選択
セマンティック検索(chunked-embeddings) は、ユーザー向けドキュメント、多様な用語が使われるコンテンツ、およびユーザーがドキュメントとは異なる言い回しで質問をする概念的な検索に適しています。
キーワード検索(BM25) は、コード内の識別子、関数名、APIエンドポイント、エラーメッセージ、および正確な用語の一致が重要なあらゆるコンテンツに適しています。技術用語や固有名詞には不可欠です。
LLM強化型セマンティック(semantic-embeddings) は、機能によるコード検索、名前ではなく動作による実装の特定、または深い理解を必要とする複雑な技術コンテンツに適しています。インデックス作成速度よりも精度が重要な場合に選択してください。
ハイブリッド(複数の戦略) は、混合されたコンテンツに対する汎用的な検索、どのアプローチが最適か不明な場合、または品質が最も重要視される本番システムに適しています。複雑さと引き換えに最大のカバレッジを提供します。
プロジェクトに合わせたチューニング
まずはデフォルトから始め、結果に基づいて調整してください。
関連するコンテンツが見つからない場合:
-
戦略の
limitを増やして、より多くの候補を取得する -
thresholdを調整して、判定を緩くする -
チャンクの
sizeを増やして、より多くのコンテキストを取り込む -
検索戦略を追加する
無関係なコンテンツが返される場合:
-
limitを減らして、候補を絞り込む -
thresholdを高くして、判定を厳しくする -
具体的な基準(criteria)を指定したリランキングを追加する
-
チャンクの
sizeを小さくして、より焦点を絞った結果にする
インデックス作成が遅すぎる場合:
-
batch_sizeを増やして、API呼び出し回数を減らす -
max_embedding_concurrencyを増やして、並列性を高める -
埋め込みの代わりにBM25を検討する(ローカル、API不要)
-
より小さな埋め込みモデルを使用する
結果にコンテキストが不足している場合:
-
チャンクの
overlapを増やす -
チャンクの
sizeを増やす -
return_full_content: trueを使用してドキュメント全体を返す -
近隣のチャンク(neighboring chunks)を結果に追加する