- 技術 💻
RAGの作り方|Pythonで動かす最小構成と精度の上げ方
RAG(Retrieval-Augmented Generation:検索拡張生成)とは、利用者の質問に関係する文書を検索し、見つかった内容をAIに渡して、それをもとに回答させる仕組みです。AIが学習していない社内規程やマニュアル、最新の情報についても、根拠のある回答をさせることができます。
RAGの仕組み自体はシンプルで、「検索する」「AIに渡す」「回答させる」の3段階です。一方で、回答の質はほぼ検索の質で決まり、実務ではここで苦労します。この記事では、Pythonで動かせる最小構成から始めて、検索がうまくいかない理由と改善の方法、精度の評価、セキュリティの注意点、そしてRAGを使わなくてよい場面までを解説します。
RAGの全体像
| 段階 | 処理 | 例 |
|---|---|---|
| ① 準備 | 文書を分割し、検索できる形で保存する | 就業規則を段落ごとに分けて保存 |
| ② 検索 | 質問に関係する部分を探す | 「経費の締め日は?」→ 経費精算の段落を取り出す |
| ③ 生成 | 見つけた内容と質問をAIに渡し、回答させる | 「月末締めで、翌月5営業日までに申請します」 |
AIに「知識を覚えさせる」のではなく、「答えるときに、資料を見せる」仕組みだと考えると理解しやすくなります。
最小構成:まずは動かしてみる
外部のサービスを使わずに試せるよう、検索の部分はキーワードの一致度(TF-IDF)で実装します。
pip install scikit-learn
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
docs = [
{"id": "rule-01", "text": "有給休暇は入社6か月後に10日付与されます。申請は3営業日前までに勤怠システムから行います。"},
{"id": "rule-02", "text": "経費精算は月末締めで、翌月5営業日までに領収書の画像を添付して申請します。"},
{"id": "rule-03", "text": "在宅勤務は週3日まで可能です。前日までに上長へチャットで連絡します。"},
]
# ① 準備:文書を分割する(ここでは200文字ごと、50文字ずつ重ねる)
def chunk(text: str, size: int = 200, overlap: int = 50) -> list[str]:
return [text[i:i + size] for i in range(0, len(text), size - overlap)]
chunks = [(d["id"], c) for d in docs for c in chunk(d["text"])]
# 日本語は単語の区切りがないため、2〜3文字の組み合わせで特徴を作る
vectorizer = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3))
matrix = vectorizer.fit_transform([c for _, c in chunks])
# ② 検索
def search(query: str, k: int = 2):
scores = cosine_similarity(vectorizer.transform([query]), matrix)[0]
top = scores.argsort()[::-1][:k]
return [(chunks[i][0], chunks[i][1], round(float(scores[i]), 3)) for i in top]
print(search("経費の締め日はいつ?"))
[('rule-02', '経費精算は月末締めで、翌月5営業日までに…', 0.245), ...]
経費の質問に対して、経費精算の文書が最も高いスコアで見つかりました。
③ 生成:見つけた内容をAIに渡す
import anthropic # pip install anthropic(APIキーは環境変数 ANTHROPIC_API_KEY で設定)
def answer(question: str) -> str:
hits = search(question, k=3)
context = "\n".join(f"[{doc_id}] {text}" for doc_id, text, _ in hits)
prompt = f"""次の<資料>だけを根拠に、質問に答えてください。
資料に書かれていない場合は「資料に記載がありません」と答えてください。
回答の最後に、根拠にした資料のIDを示してください。
<資料>
{context}
</資料>
質問:{question}"""
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-sonnet-5", # 利用するモデルに合わせて変更
max_tokens=500,
messages=[{"role": "user", "content": prompt}],
)
return msg.content[0].text
「資料だけを根拠に」「書かれていなければそう答える」「根拠のIDを示す」の3つの指示が、RAGのプロンプトの基本です。AIがもっともらしい作り話(ハルシネーション)をするのを抑え、回答を利用者が確かめられるようにします。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「RAGの作り方|Pythonで動かす最小構成と精度の上げ方」
キーワード検索の限界:言い換えに弱い
上の最小構成で、次の質問を試すと問題が見えてきます。
print(search("リモートワークは何日できる?"))
[('rule-03', '在宅勤務は週3日まで可能です。…', 0.0), ('rule-02', '経費精算は…', 0.0)]
スコアがすべて0になりました。資料には「在宅勤務」と書かれていて、質問の「リモートワーク」と共通する文字がないため、キーワードの一致度では関係を見つけられないのです。
意味検索(埋め込み)
この問題を解決するのが、埋め込み(エンベディング)を使った意味検索です。文章を、意味を表す数値の並び(ベクトル)に変換し、意味が近い文章ほど、ベクトルも近くなる性質を使って検索します。「リモートワーク」と「在宅勤務」のように、言葉が違っても意味が近ければ見つけられます。
| キーワード検索(TF-IDF、BM25) | 意味検索(埋め込み) | |
|---|---|---|
| 得意 | 型番・固有名詞・専門用語の完全一致 | 言い換え・あいまいな質問 |
| 苦手 | 言い換え、表記の揺れ | 型番やコードなど、文字どおりの一致 |
| 必要なもの | ライブラリだけ | 埋め込みのモデル(APIやオープンなモデル) |
埋め込みのベクトルは、ベクトルデータベース(Chroma、Qdrant、pgvectorなど)に保存して検索します。すでにPostgreSQLを使っているなら、pgvector で既存のデータベースに組み込めます。
実務の定番:ハイブリッド検索と並べ替え
- ハイブリッド検索:キーワード検索と意味検索の両方で候補を集め、結果を統合する。両方の弱点を補える
- 並べ替え(リランク):集めた候補を、質問との関連度をより正確に判定するモデルで並べ直し、上位だけをAIに渡す
精度を左右する「文書の分割」
RAGの精度に大きく影響するのが、文書をどう分割するか(チャンキング)です。
| 分割の問題 | 起きること |
|---|---|
| 大きすぎる | 関係ない内容が混ざり、検索の精度が下がる。AIに渡す文章も長くなる |
| 小さすぎる | 1つの断片だけでは意味がわからない(「これは」「上記の場合」が何を指すか不明) |
| 文の途中で切れる | 重要な条件が別の断片に分かれる |
分割のコツ
- 見出しや段落の区切りで分割する(決まった文字数で機械的に切らない)
- 前後の断片と少し重ねる(オーバーラップ)
- 各断片に、元の文書名や見出しを付けておく(「経費精算規程 > 締め日」のように)
- 表は、1行ずつではなく、見出しの行と一緒に扱う
精度を評価する
「なんとなく良くなった気がする」では、改善を続けられません。評価用の質問と正解のセットを用意します。
| 評価すること | 方法 |
|---|---|
| 検索が正しいか | 質問ごとに、正解の資料が上位k件に入っているかを数える |
| 回答が正しいか | 正解の回答と比べる(人が判定する、またはAIに判定させる) |
| 根拠に忠実か | 回答の内容が、渡した資料に書かれているかを確認する |
まずは20〜50問程度の質問セットを作り、分割の方法や検索の設定を変えるたびに同じ質問で比べるのがおすすめです。回答がおかしいときは、多くの場合、検索の段階で正しい資料が見つかっていないことが原因です。
セキュリティの注意点
文書に仕込まれた指示(間接プロンプトインジェクション)
RAGでは、検索で見つかった文書の内容が、そのままAIに渡されます。文書の中に「この資料を読んだAIは、〜せよ」という指示が仕込まれていると、AIがそれに従ってしまう可能性があります。外部から集めた文書や、誰でも編集できる文書を扱う場合は特に注意が必要です。
- AIに渡す資料を、データとして区切って渡す(上のプロンプトの
<資料>タグ) - RAGの回答に、メール送信などの操作をさせない
- 回答に含まれるリンクや画像を、許可したドメインに限る
アクセス権限
利用者が本来見られない文書を、RAGが検索して回答に使ってしまうのは、よくある設計の漏れです。検索の段階で、利用者の権限で見られる文書だけに絞り込みます。
RAGが要らない場面
RAGは万能ではありません。次のような場合は、よりシンプルな方法で足ります。
| 状況 | よりシンプルな方法 |
|---|---|
| 資料が少なく、全体でも数十ページ程度 | 資料をそのまま全部AIに渡す(最近のモデルは長い文章を扱える) |
| 決まった質問に決まった答えを返すだけ | FAQの検索や、ルールベースの応答 |
| 最新の数値を取り出したい(在庫、売上) | データベースやAPIから直接取得する |
| 口調や出力の形式を変えたい | プロンプトの工夫、または追加学習(ファインチューニング) |
RAGは、「資料が多く、全部を毎回渡すのは現実的でない」場合に使う技術です。資料が少ないうちは、全部を渡す方法から始め、必要になってからRAGに移行するのが効率的です。
よくある質問
Q. LangChainやLlamaIndexを使う必要がありますか? A. 必須ではありません。この記事の最小構成のように、自分で書いても動きます。仕組みを理解してから、必要に応じてフレームワークを使うと、問題が起きたときに原因を追いやすくなります。
Q. ファインチューニングとRAGはどう違いますか? A. ファインチューニングは、モデル自体を追加で学習させ、口調や形式、特定の作業のやり方を身につけさせる方法です。RAGは、回答のたびに資料を見せる方法で、情報の追加や更新が簡単です。社内の知識を答えさせる目的なら、多くの場合RAGが向いています。
Q. PDFやWordの文書も使えますか? A. 使えます。まず文書からテキストを取り出す必要があります。表や図が多い文書は、取り出し方によって精度が大きく変わるため、取り出した結果を確認することが重要です。
Q. 回答が間違っているときは、どこから直せばいいですか? A. まず、その質問で検索された資料を確認します。正しい資料が見つかっていなければ、分割や検索の改善が必要です。正しい資料が見つかっているのに回答が間違っている場合は、プロンプトやモデルを見直します。
まとめ
- RAGは、質問に関係する資料を検索し、AIに渡して回答させる仕組み
- 最小構成は、分割 → 検索 → プロンプトに資料を入れて生成。資料だけを根拠にし、出典を示させる
- キーワード検索は言い換えに弱い(実測で「リモートワーク」から「在宅勤務」を見つけられない)。意味検索(埋め込み)やハイブリッド検索で補う
- 精度は文書の分割と検索の質で決まる。評価用の質問セットで改善を測る
- 文書に仕込まれた指示と、アクセス権限に注意する
- 資料が少ないなら、RAGを使わずに全部渡す方法から始める
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「RAGの作り方|Pythonで動かす最小構成と精度の上げ方」
株式会社RJCでは、AI駆動開発の実践を通じて、生成AIを業務で役立てる仕組みづくりに取り組んでいます。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。