RAGとは?検索して渡す仕組みと、実装に必要な5つの部品
RAG(Retrieval-Augmented Generation/検索拡張生成)とは、質問に答える直前に関連する資料を検索して、それをプロンプトに入れてからモデルに答えさせる仕組みです。
ポイントは、モデルに何かを覚えさせるわけではないという点です。モデルは毎回まっさらな状態で、渡された資料を読んで答えます。だから資料を差し替えれば答えも変わり、モデルを再学習する必要はありません。
なぜ必要か
LLMは学習した範囲のことしか知りません。具体的には次の3つが問題になります。
- 社内のデータを知らない — 自社の仕様書、議事録、問い合わせ履歴は学習データに入っていません
- 最新の情報を知らない — 学習が終わった後の出来事は答えられません
- 知らないことを、知らないと言わない — ハルシネーションが起きます
RAGはこの3つを同時に解きます。手元の資料を渡すので社内データに答えられ、資料を更新すれば最新になり、「資料に書いてあることだけ答えて」と指示できるので作り話が減ります。
ファインチューニングとの違い
よく比較されますが、目的が違います。
| RAG | ファインチューニング | |
|---|---|---|
| 何を変えるか | 渡す資料 | モデルの重み |
| 向くもの | 知識・事実を与える | 口調・形式・振る舞いを教える |
| 更新 | 資料を差し替えるだけ | 再学習が必要 |
| 出典の提示 | できる | できない |
「うちのデータに答えさせたい」の答えはほぼRAGです。 ファインチューニングは「常に敬語のJSONで返させたい」のような、振る舞いの型を教えたいときのものです。
5つの部品
RAGは1つの技術ではなく、5つの部品の組み合わせです。どれが弱くても全体が効きません。
1. チャンク分割
長い文書をそのまま扱うことはできないので、検索できる単位に切ります。
ここが品質をいちばん左右します。 細かく切りすぎると文脈が失われ(「これ」「同社」が何を指すか分からなくなる)、大きく切りすぎると無関係な部分まで一緒に渡すことになります。実務では数百〜千数百字を目安に、見出しや段落の切れ目で切り、前後を少し重ねる(オーバーラップ)のが定石です。
2. 埋め込み(Embedding)
各チャンクを数値のベクトルに変換します。意味が近い文章ほどベクトルが近くなるので、「言葉は違うが意味は同じ」文書を拾えます。キーワード一致では拾えないものが拾えるのが、この部品の価値です。
詳しくはEmbeddingとはにまとめました。
3. ベクトルの保存と検索
ベクトルを保存し、質問のベクトルに近いものを取り出します。PostgreSQLの拡張(pgvector)でも、専用のベクトルデータベースでも構いません。最初はpgvectorで十分なことがほとんどです。すでにPostgreSQLを運用しているなら、増える運用対象がゼロで済みます。
4. 再ランク(リランキング)
検索で拾った20〜50件を、質問との関連度で並べ替えて上位数件に絞ります。省略されがちですが、入れると効果が大きい部品です。ベクトル検索は「だいたい近い」ものを返すので、その中の本当に関連する数件を選び直す工程が効きます。
5. 生成
絞り込んだ資料をプロンプトに入れて、モデルに答えさせます。ここがLLM APIの出番です。
生成の部分を実装する
5番目だけならすぐ書けます。検索結果が手元にある前提のコードです。
from openai import OpenAI
client = OpenAI(api_key="<FASTMETAL_API_KEY>", base_url="https://api.fastmetal.ai/v1")
def answer(question: str, chunks: list[str]) -> str:
context = "\n\n---\n\n".join(f"[資料{i+1}]\n{c}" for i, c in enumerate(chunks))
resp = client.chat.completions.create(
model="anthropic-claude-haiku-4-5",
max_tokens=800,
messages=[
{
"role": "system",
"content": (
"以下の資料だけを根拠に日本語で答えてください。"
"資料に書かれていないことは『資料からは判断できません』と答えること。"
"回答の末尾に、使った資料の番号を必ず挙げること。"
),
},
{"role": "user", "content": f"{context}\n\n質問: {question}"},
],
)
return resp.choices[0].message.content
システムプロンプトの3つの指示が、それぞれ実務上の意味を持っています。
- 「資料だけを根拠に」 — モデルが学習知識で補完するのを抑えます
- 「判断できませんと答えること」 — 逃げ道を明示的に与えないと、モデルは無理にでも答えを作ります
- 「資料の番号を挙げること」 — 出典が出ることで、人が検証できるようになります
書き方の考え方はシステムプロンプトとはを参照してください。
FastMetalが担当する範囲
正直に書いておくと、FastMetalが提供するのは5番目の生成だけです。埋め込みモデルは提供していません。
理由はEmbeddingとはに書きましたが、要するにゲートウェイが埋め込みを扱う経済的な理由が薄いためです(世界最大のゲートウェイであるOpenRouterも、396のモデルを扱いながら埋め込みモデルは1つも提供していません)。
現実的な構成はこうなります。
- 埋め込み — ローカルでOllama、または埋め込み専業のサービス
- 保存と検索 — pgvector
- 生成 — FastMetal(円建て・プリペイド)
埋め込みは大量に回すぶん、ローカルで動かすのが合理的な場面が多い部品です。一方で生成は品質が直接効くので、良いモデルを使う価値があります。役割で分けるのが素直です。
つまずきやすい点
チャンクが悪い 検索精度が出ないとき、原因の多くは埋め込みモデルではなくチャンク分割です。まず切り方を疑ってください。
検索結果を無条件に信じている ベクトル検索は必ず何かを返します。「関連する資料がない」という状態も返ってこないので、関連度のしきい値を設けて、下回ったら「該当なし」と答えさせてください。
渡しすぎている 拾った20件を全部渡すと、コンテキストが埋まり、コストも増え、かえって精度が落ちます。再ランクで数件に絞ってください。
評価していない 「なんとなく良くなった気がする」で運用すると、変更が改善なのか改悪なのか分かりません。質問と期待される答えの組を数十件用意して測ってください(Evals入門)。
よくある質問
Q. RAGとファインチューニングはどちらを使うべきですか? 知識や事実を与えたいならRAG、口調や出力形式を教えたいならファインチューニングです。「自社のデータに答えさせたい」の答えはほぼRAGで、資料を差し替えるだけで更新でき、出典も提示できます。
Q. ベクトルデータベースは専用のものが必要ですか? 最初は不要なことがほとんどです。PostgreSQLを使っているならpgvectorで始められ、運用対象が増えません。規模が問題になってから専用のものを検討してください。
Q. RAGを使えばハルシネーションはなくなりますか? 減りますが、なくなりません。資料に書かれていないことを推測して答えることはあります。「資料だけを根拠に」「判断できない場合はそう答える」「出典を挙げる」の3つを指示してください。
Q. ロングコンテキストのモデルがあれば、RAGは不要ですか? 資料が少なければ全部渡す方が簡単です。ただし量が増えるとコストと精度の両面で成立しなくなります。判断基準はRAG vs ロングコンテキストにまとめました。
まとめ
RAGは「答える直前に資料を探して渡す」仕組みで、チャンク分割・埋め込み・保存と検索・再ランク・生成の5部品でできています。品質を落とすのはたいていチャンク分割と再ランクの省略で、埋め込みモデルの選定ではありません。
生成の部分はAPIキーを取れば今日から書けます。埋め込みはローカルや専業サービス、生成はFastMetal——という分担が現実的です。対応モデルはモデルカタログから選べます。