<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>RAG on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/rag/</link>
    <description>Recent content in RAG on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Sat, 28 Feb 2026 17:00:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/rag/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>RAG評価基盤の作り方：精度・再現率・運用コストを同時に最適化する実践手順</title>
      <link>https://www.ai2core.com/posts/2026-02-28-rag-evaluation-pipeline-practical/</link>
      <pubDate>Sat, 28 Feb 2026 17:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-28-rag-evaluation-pipeline-practical/</guid>
      <description>RAGシステムの品質評価を自動化し、検索・生成・運用コストをバランスさせる評価パイプラインの実装方法を解説。</description>
      <content:encoded><![CDATA[<h1 id="rag評価基盤の作り方精度再現率運用コストを同時に最適化する実践手順">RAG評価基盤の作り方：精度・再現率・運用コストを同時に最適化する実践手順</h1>
<p>RAG（Retrieval Augmented Generation）は導入が進んでいますが、運用で最も難しいのは「改善したつもり」が頻発する点です。embedding モデルを変えた、chunk サイズを変えた、reranker を追加した。どれも良さそうに見えるのに、ユーザー満足は上がらない。このギャップを埋めるのが評価基盤です。</p>
<p>本記事では、RAG を継続改善するための評価パイプラインを、データセット設計から CI 統合まで具体的に解説します。</p>
<h2 id="rag評価で見るべき3層">RAG評価で見るべき3層</h2>
<p>RAG の品質は 1 指標では測れません。最低でも次の3層を分けて評価します。</p>
<ol>
<li><strong>Retrieval層</strong>: 正しい文書を取れているか</li>
<li><strong>Generation層</strong>: 回答が正確で有用か</li>
<li><strong>System層</strong>: レイテンシ・コスト・安定性</li>
</ol>
<p>この分離がないと、生成品質低下の原因が retrieval なのか prompt なのか判別できません。</p>
<h2 id="ステップ1評価データセットを設計する">ステップ1：評価データセットを設計する</h2>
<h3 id="1-1-問い合わせカテゴリを分割">1-1. 問い合わせカテゴリを分割</h3>
<p>例として次の5カテゴリに分けます。</p>
<ul>
<li>定義確認（用語説明）</li>
<li>手順質問（How-to）</li>
<li>例外対応（エラー解決）</li>
<li>比較検討（A vs B）</li>
<li>根拠提示（出典必須）</li>
</ul>
<p>カテゴリごとに難易度と重要度を持たせ、偏りを防ぎます。</p>
<h3 id="1-2-正解の持ち方">1-2. 正解の持ち方</h3>
<p>正解は「理想回答1つ」では不十分です。RAGでは表現揺れが自然なので、次を保存します。</p>
<ul>
<li>期待要素（必須ポイント）</li>
<li>禁止要素（誤情報、過剰断定）</li>
<li>参照すべき文書ID</li>
</ul>
<p>この形式にすると、自動評価と人手レビューを両立できます。</p>
<h2 id="ステップ2retrieval評価を自動化">ステップ2：Retrieval評価を自動化</h2>
<p>代表指標:</p>
<ul>
<li>Recall@k</li>
<li>MRR</li>
<li>nDCG</li>
</ul>
<p>例えば、正解文書IDを持つ場合は次のように計算します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recall_at_k</span>(retrieved_ids, gold_ids, k<span style="color:#f92672">=</span><span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>    topk <span style="color:#f92672">=</span> set(retrieved_ids[:k])
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1.0</span> <span style="color:#66d9ef">if</span> len(topk<span style="color:#f92672">.</span>intersection(gold_ids)) <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">else</span> <span style="color:#ae81ff">0.0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>運用では平均値だけでなく、カテゴリ別分布を見ることが重要です。手順質問だけ recall が低い場合、chunk 戦略や見出し抽出に問題がある可能性が高いです。</p>
<h2 id="ステップ3generation評価の設計">ステップ3：Generation評価の設計</h2>
<p>自動評価では次を推奨します。</p>
<ul>
<li>Faithfulness（出典との整合）</li>
<li>Answer Relevance（質問への適合）</li>
<li>Completeness（必要要素網羅）</li>
<li>Safety（禁止事項違反）</li>
</ul>
<p>LLM-as-a-judge を使う場合、判定プロンプトを固定し、temperature=0 で再現性を確保します。さらに、週次で人手サンプル監査を入れて判定ドリフトを検出します。</p>
<h2 id="ステップ4system評価遅延コスト">ステップ4：System評価（遅延・コスト）</h2>
<p>品質改善がコスト爆増を招くと継続できません。次を同時に計測します。</p>
<ul>
<li>P50/P95 latency</li>
<li>平均 input/output token</li>
<li>1回答あたり推定コスト</li>
<li>timeout率、fallback率</li>
</ul>
<p>この4指標を CI レポートに含めると、精度改善の副作用を早期に発見できます。</p>
<h2 id="ステップ5ciへの組み込み">ステップ5：CIへの組み込み</h2>
<p>PR ごとに評価ジョブを実行し、閾値を満たさない変更をブロックします。</p>
<p>判定例:</p>
<ul>
<li>Recall@5: 0.82 以上</li>
<li>Faithfulness: 0.90 以上</li>
<li>P95 latency: 2500ms 以下</li>
<li>Cost/answer: $0.005 以下</li>
</ul>
<p>疑似フロー:</p>
<ol>
<li>変更ブランチでインデックス再構築</li>
<li>評価データセット100件で推論</li>
<li>指標を計算して前回基準と比較</li>
<li>差分レポートをPRコメントに投稿</li>
</ol>
<p>これで「なんとなく改善」を排除できます。</p>
<h2 id="ステップ6オンライン評価との接続">ステップ6：オンライン評価との接続</h2>
<p>オフライン評価だけでは実利用の多様性を拾えません。オンライン指標を接続します。</p>
<ul>
<li>ユーザー評価（👍/👎）</li>
<li>再質問率（同一セッションで再問い合わせ）</li>
<li>人間オペレータ転送率</li>
</ul>
<p>重要なのは trace_id でオフライン指標と紐づけることです。これにより「オフラインは良いのに本番満足が低い」差分を原因追跡できます。</p>
<h2 id="改善ループの実例">改善ループの実例</h2>
<p>ある社内ヘルプデスクRAGでの改善例:</p>
<ul>
<li>問題: 手順質問で誤回答が多い</li>
<li>原因: chunk が短すぎ、手順文脈が分断</li>
<li>対策: section-aware chunking + reranker導入</li>
</ul>
<p>結果:</p>
<ul>
<li>Recall@5: 0.74 → 0.88</li>
<li>Faithfulness: 0.81 → 0.93</li>
<li>P95 latency: +180ms（許容内）</li>
</ul>
<p>このように、どの変更がどの指標に効いたかを記録すると、次回改善の再現性が高まります。</p>
<h2 id="よくある失敗">よくある失敗</h2>
<ol>
<li><strong>評価データが少なすぎる</strong>
<ul>
<li>20件程度では統計的に不安定。最低100件、理想300件。</li>
</ul>
</li>
<li><strong>単一スコアで判定する</strong>
<ul>
<li>精度だけでコストを見ないと運用破綻。</li>
</ul>
</li>
<li><strong>判定プロンプトを頻繁に変える</strong>
<ul>
<li>指標比較の連続性が失われる。</li>
</ul>
</li>
<li><strong>失敗事例をデータセットへ反映しない</strong>
<ul>
<li>同じ不具合を繰り返す。</li>
</ul>
</li>
</ol>
<h2 id="90日ロードマップ">90日ロードマップ</h2>
<ul>
<li><strong>0-30日</strong>: 評価データセット整備、retrieval指標導入</li>
<li><strong>31-60日</strong>: generation指標 + CIゲート導入</li>
<li><strong>61-90日</strong>: オンライン評価統合、週次改善会の定着</li>
</ul>
<p>この順序なら、運用負荷を抑えつつ確実に品質を上げられます。</p>
<h2 id="まとめ">まとめ</h2>
<p>RAG の実力は、モデル選定より評価基盤で決まります。retrieval、generation、system の3層を分離し、CI に組み込むことで、改善の再現性が生まれます。</p>
<p>まずは小さく始めて、失敗ケースを評価データセットに反映し続けてください。評価が回り始めると、RAG は「当たるかどうかの賭け」から「制御可能なプロダクト」へ変わります。</p>
<h2 id="実装例評価結果をprコメントに自動投稿する">実装例：評価結果をPRコメントに自動投稿する</h2>
<p>運用で効くのは、評価結果を開発者が日常的に見る導線を作ることです。GitHub Actions で評価スクリプトを実行し、結果を PR コメントへ投稿します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">rag-eval</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>: [<span style="color:#ae81ff">pull_request]</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">evaluate</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">uv sync</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">uv run python scripts/run_rag_eval.py --dataset evalset_v3.json --out report.json</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">uv run python scripts/post_pr_comment.py report.json</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>この仕組みがあると、レビュー段階で「この変更は Faithfulness を 0.04 落とすが latency は改善」という会話ができ、意思決定が定量化されます。</p>
<h2 id="評価データセットの更新運用">評価データセットの更新運用</h2>
<p>評価セットを固定しすぎると、現実の問い合わせ変化に追従できません。次のルールを推奨します。</p>
<ul>
<li>月1回、実ユーザー失敗ケースを20件追加</li>
<li>四半期ごとに古いケースを棚卸し</li>
<li>重要カテゴリ比率を維持（例: 手順質問30%以上）</li>
</ul>
<p>この更新を怠ると、指標が良くても体感品質が落ちる「評価腐敗」が起きます。</p>
<h2 id="abテストとの接続">A/Bテストとの接続</h2>
<p>大きな変更（embedding刷新、reranker導入）は、オフライン評価だけでなくオンライン A/B を併用します。</p>
<ul>
<li>A群: 現行パイプライン</li>
<li>B群: 新パイプライン</li>
<li>比較指標: 👍率、再質問率、回答時間、コスト</li>
</ul>
<p>2週間程度の観測で統計差が出るケースが多く、主観ベースの議論を減らせます。</p>
<h2 id="まとめ定着のポイント">まとめ（定着のポイント）</h2>
<p>RAG 改善を継続する鍵は、評価を「一回の検証」ではなく「開発フローの標準」にすることです。CI コメント、データセット更新、A/B テストを回すことで、品質向上が偶然ではなく再現可能な活動になります。</p>
<h3 id="補足">補足</h3>
<p>評価結果は経営指標とも接続できます。問い合わせ解決率やサポート工数削減と紐づけることで、RAG 改善が事業価値にどう効いたかまで説明可能になります。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>RAG</category>
      <category>LLM</category>
      <category>Evaluation</category>
      <category>MLOps</category>
    </item>
    <item>
      <title>AIエージェント開発の必須知識：RAGとVector DBの基礎</title>
      <link>https://www.ai2core.com/posts/2026-02-13-rag-basics/</link>
      <pubDate>Fri, 13 Feb 2026 19:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-13-rag-basics/</guid>
      <description>自社データをAIに学習させずに活用する技術、RAG（検索拡張生成）の仕組みを解説。</description>
      <content:encoded><![CDATA[<h1 id="aiエージェント開発の必須知識ragとvector-dbの基礎">AIエージェント開発の必須知識：RAGとVector DBの基礎</h1>
<h2 id="はじめに">はじめに</h2>
<p>「自社の膨大なマニュアルやナレッジベースの内容を、ChatGPTのように対話形式で手軽に引き出したい」
「開発中のAIチャットボットに、社内規定や顧客との過去のやり取りを正確に回答させたい」
「でも、機密情報を含む自社データを、外部のAIサービスに学習データとして渡すのはセキュリティ的に絶対に避けたい」</p>
<p>AI、特に大規模言語モデル（LLM）の活用を検討する多くのエンジニアや開発担当者が、このような課題に直面しているのではないでしょうか。LLMは非常に強力ですが、その知識は特定の時点までのものであり、自社の独自データについては何も知りません。</p>
<p>この課題を解決するために「ファインチューニング」を検討するかもしれません。しかし、ファインチューニングには大量の教師データと高い計算コストが必要な上、情報の更新があるたびにモデルを再学習させるのは現実的ではありません。さらに、AIがもっともらしい嘘をつく「ハルシネーション」という問題も依然として残ります。</p>
<p>本記事では、これらの課題をエレガントに解決する技術として、今、AIエージェント開発の現場でデファクトスタンダードとなりつつある**RAG（Retrieval-Augmented Generation：検索拡張生成）**というアプローチを徹底的に解説します。</p>
<p>RAGは、LLMに自社データを「学習」させるのではなく、必要な情報を「検索」して外部から与えることで、LLMの能力を最大限に引き出す画期的な手法です。そして、その中核を担うのが**Vector DB（ベクトルデータベース）**です。</p>
<p>この記事を読み終える頃には、あなたは以下のことを理解し、自社のAIエージェント開発に活かすための一歩を踏み出せるようになっているはずです。</p>
<ul>
<li>LLMが抱える根本的な課題（知識のカットオフ、ハルシネーション）</li>
<li>なぜファインチューニングだけでは不十分なのか</li>
<li>RAGがどのようにしてこれらの課題を解決するのか、その具体的な仕組み</li>
<li>RAGの心臓部であるEmbeddingとVector DBの役割</li>
<li>PythonとLangChainを使ったRAGの基本的な実装方法</li>
</ul>
<p>それでは、AIエージェント開発の新たな扉を開く、RAGとVector DBの世界へご案内します。</p>
<h2 id="なぜragとvector-dbが重要なのか-llmの限界と従来の課題">なぜRAGとVector DBが重要なのか？ LLMの限界と従来の課題</h2>
<p>RAGの重要性を理解するためには、まずLLMが単体で抱える限界を知る必要があります。</p>
<h3 id="llmが抱える3つの大きな壁">LLMが抱える3つの大きな壁</h3>
<ol>
<li>
<p><strong>知識のカットオフ（Knowledge Cut-off）</strong>
GPT-4のような最先端のLLMでさえ、その知識は学習データが収集された特定の日時で止まっています。例えば、GPT-4の初期モデルは2021年9月までの情報しか持っていません。そのため、それ以降の出来事や、新製品の情報、最新の社内規定について質問しても、答えることができません。ビジネスの世界では情報の鮮度が命であり、この「知識の壁」は致命的な欠点となります。</p>
</li>
<li>
<p><strong>ハルシネーション（Hallucination：幻覚）</strong>
LLMは、事実に基づかない情報を、あたかも真実であるかのように生成することがあります。これをハルシネーションと呼びます。特に、学習データに含まれていない専門的な内容や、社内情報のようなクローズドなドメインについて質問された場合に、この現象は顕著になります。顧客サポート用のAIが誤った製品情報を伝えたり、社内アシスタントが架空の規定を案内したりする事態は、企業の信頼を著しく損なうリスクをはらんでいます。</p>
</li>
<li>
<p><strong>情報セキュリティとプライバシー</strong>
自社の機密情報や顧客の個人情報を扱う場合、それらを外部のLLM提供企業のサーバーに学習データとしてアップロードすることには、非常に大きなセキュリティリスクが伴います。一度学習データとして取り込まれてしまうと、他のユーザーへの回答に利用されてしまう可能性もゼロではなく、データのコントロールを失うことになります。</p>
</li>
</ol>
<h3 id="従来の解決策ファインチューニングとその限界">従来の解決策「ファインチューニング」とその限界</h3>
<p>これらの課題を解決するアプローチとして、以前は「ファインチューニング」が主流でした。これは、既存の学習済みモデルに対して、自社データを含む追加の教師データセットを与えて再学習させる手法です。</p>
<p>ファインチューニングは、LLMに特定の文体や口調を真似させたり、特定のタスク（例えば、要約や感情分析）への性能を特化させたりするのには有効です。しかし、「知識を注入する」という目的においては、いくつかの大きな課題があります。</p>
<ul>
<li><strong>高いコスト</strong>: ファインチューニングには、大量の高品質な教師データ（質問と回答のペアなど）の準備と、モデルの学習を実行するための高価な計算リソース（GPU）が必要です。</li>
<li><strong>知識の更新が困難</strong>: 新しい情報（例えば、週次レポートや新しいマニュアル）を追加したい場合、その都度ファインチューニングをやり直す必要があります。これは時間的にも金銭的にも非効率です。</li>
<li><strong>透明性の欠如</strong>: ファインチューニングされたモデルが、なぜその回答を生成したのか、どの情報を根拠にしているのかを追跡することは非常に困難です。ハルシネーションが起きた場合の原因究明も難しくなります。</li>
</ul>
<p>そこで登場したのが、**RAG（検索拡張生成）**です。RAGは「学習」ではなく「検索」というアプローチで、これらの問題を根本から解決します。</p>
<h2 id="具体的な解決策ragの仕組みとvector-dbの役割">具体的な解決策：RAGの仕組みとVector DBの役割</h2>
<p>RAGは、その名の通り「検索（Retrieval）」でLLMの知識を「拡張（Augmented）」し、回答を「生成（Generation）」するアーキテクチャです。LLMを「非常に優秀だが記憶喪失のコンサルタント」、Vector DBを「完璧な記憶力を持つ外部の専門図書館」に例えると分かりやすいでしょう。</p>
<p>コンサルタント（LLM）は、質問を受けるたびに、まず図書館（Vector DB）へ行って関連資料を調べ（検索）、その資料を読み込みながら（コンテキストとしてプロンプトに含める）、質問に対する的確な回答を生成します。</p>
<p>この仕組みにより、LLMは常に最新かつ正確な情報に基づいて回答できるようになり、ハルシネーションを劇的に抑制できます。</p>
<h3 id="ragの全体像と処理フロー">RAGの全体像と処理フロー</h3>
<p>RAGのシステムは、大きく2つのフェーズに分かれています。</p>
<ol>
<li><strong>データ準備フェーズ（Indexing）</strong>: 事前に自社ドキュメントを検索可能な状態にして、Vector DBに保存しておくフェーズ。</li>
<li><strong>実行フェーズ（Retrieval &amp; Generation）</strong>: ユーザーからの質問を受け取り、Vector DBから関連情報を検索して、LLMが回答を生成するフェーズ。</li>
</ol>
<p>この流れを図で示すと以下のようになります。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    subgraph &#34;データ準備フェーズ（Indexing）&#34;
        A[ドキュメント群&lt;br&gt;PDF, TXT, Markdown, etc.] --&gt; B(Load&lt;br&gt;ドキュメント読み込み);
        B --&gt; C(Split&lt;br&gt;テキストを適切なサイズに分割);
        C --&gt; D(Embed&lt;br&gt;分割したテキストをベクトル化);
        D --&gt; E[Vector DB&lt;br&gt;ベクトルと元のテキストを保存];
    end

    subgraph &#34;実行フェーズ（Retrieval &amp; Generation）&#34;
        F[ユーザーからの質問] --&gt; G(Embed&lt;br&gt;質問をベクトル化);
        G --&gt; H{Vector DB&lt;br&gt;類似ベクトル検索};
        E --&gt; H;
        H --&gt; I[関連性の高い&lt;br&gt;テキストチャンク（コンテキスト）];
        F &amp; I --&gt; J(Prompt&lt;br&gt;質問とコンテキストを結合し&lt;br&gt;プロンプトを作成);
        J --&gt; K[LLM (e.g., GPT-4)&lt;br&gt;回答生成];
        K --&gt; L[ユーザーへの回答];
    end
</code></pre><p>それでは、このフローの各要素を詳しく見ていきましょう。</p>
<h3 id="構成要素-embedding---テキストを意味のベクトルに変換する魔法">構成要素①: Embedding - テキストを「意味」のベクトルに変換する魔法</h3>
<p>RAGを理解する上で最も重要な概念が**Embedding（エンベディング、埋め込み）**です。</p>
<p>人間は「犬」と「猫」が似ていて、「犬」と「車」はあまり似ていないことを直感的に理解できます。しかし、コンピュータは単なる文字の羅列としてしか認識できません。Embeddingは、この「意味の近さ」をコンピュータが扱えるように、<strong>テキストを高次元のベクトル（数値の配列）に変換する</strong>技術です。</p>
<p>例えば、以下のように変換されます（次元数は簡略化しています）。</p>
<ul>
<li><code>犬</code>: <code>[0.8, 0.1, 0.3, ...]</code></li>
<li><code>猫</code>: <code>[0.7, 0.2, 0.4, ...]</code></li>
<li><code>車</code>: <code>[-0.5, 0.9, -0.1, ...]</code></li>
</ul>
<p>このベクトル空間上では、意味的に近い単語や文章は、その距離も近くなります。OpenAIの<code>text-embedding-ada-002</code>（1536次元）や、様々なオープンソースのモデルがこの変換を行うために利用されます。</p>
<p>RAGでは、事前にドキュメントの各部分（チャンク）をベクトル化しておき、ユーザーの質問も同じモデルでベクトル化します。そして、<strong>質問のベクトルと最も近いベクトルを持つドキュメントチャンクを探す</strong>ことで、質問に最も関連性の高い情報を特定するのです。</p>
<h3 id="構成要素-vector-db---意味で検索する次世代のデータベース">構成要素②: Vector DB - 意味で検索する次世代のデータベース</h3>
<p>Embeddingによって得られた大量のベクトルデータを効率的に保存し、高速に「意味の近さ（類似度）」で検索するための専用データベースが<strong>Vector DB</strong>です。</p>
<p>従来のRDB（リレーショナルデータベース）が<code>WHERE user_id = 123</code>のように完全一致でデータを検索するのに対し、Vector DBは「このベクトルに最も近いベクトルを探して」という**近似最近傍探索（Approximate Nearest Neighbor, ANN）**を得意とします。</p>
<p>代表的なVector DBには以下のようなものがあります。</p>
<ul>
<li><strong>Chroma</strong>: ローカル環境で手軽に試せるオープンソースのVector DB。プロトタイピングに最適。</li>
<li><strong>FAISS</strong>: Facebook (Meta) AIが開発した、ベクトル類似検索に特化したライブラリ。</li>
<li><strong>Pinecone, Weaviate, Qdrant</strong>: クラウドネイティブなマネージドサービスとして提供されることが多く、スケーラビリティや高度な機能（メタデータフィルタリングなど）が特徴。</li>
</ul>
<p>Vector DBは、ベクトル化されたドキュメントチャンクと、その元となったテキスト本文のペアを保存します。検索時には、質問ベクトルに類似したベクトルを持つチャンクを複数個見つけ出し、その元テキストをLLMへのコンテキストとして渡します。</p>
<h3 id="ragの実装ステップpythonとlangchainによるコード例">RAGの実装ステップ（PythonとLangChainによるコード例）</h3>
<p>それでは、実際にPythonのフレームワーク<code>LangChain</code>を使って、簡単なRAGシステムを構築してみましょう。<code>LangChain</code>は、LLMアプリケーション開発における一連の流れを抽象化し、簡単に実装できるようにしてくれる便利なツールです。</p>
<p>ここでは、架空の「社内副業規定.pdf」というドキュメントの内容について回答するAIエージェントを作成します。</p>
<p><strong>前提</strong>: PDFファイル<code>internal_rules.pdf</code>が手元にあるとします。内容は以下のようなものです。</p>
<blockquote>
<p><strong>第5条（副業・兼業）</strong></p>
<ol>
<li>社員は、会社の許可を得た上で、副業または兼業を行うことができる。</li>
<li>副業を希望する社員は、所定の申請書を人事部に提出し、事前の承認を得なければならない。</li>
<li>会社の競合他社での業務や、会社の信用を損なう可能性のある業務は許可されない。</li>
</ol></blockquote>
<h4 id="ステップ1-環境構築と準備">ステップ1: 環境構築と準備</h4>
<p>まず、必要なライブラリをインストールし、OpenAIのAPIキーを設定します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install langchain openai chromadb pypdf tiktoken
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 環境変数にOPENAI_API_KEYを設定</span>
</span></span><span style="display:flex;"><span>os<span style="color:#f92672">.</span>environ[<span style="color:#e6db74">&#34;OPENAI_API_KEY&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;YOUR_OPENAI_API_KEY&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="ステップ2-ドキュメントの読み込み-load">ステップ2: ドキュメントの読み込み (Load)</h4>
<p><code>LangChain</code>の<code>PyPDFLoader</code>を使って、PDFファイルを読み込みます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.document_loaders <span style="color:#f92672">import</span> PyPDFLoader
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>loader <span style="color:#f92672">=</span> PyPDFLoader(<span style="color:#e6db74">&#34;internal_rules.pdf&#34;</span>)
</span></span><span style="display:flex;"><span>documents <span style="color:#f92672">=</span> loader<span style="color:#f92672">.</span>load()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;ドキュメントを </span><span style="color:#e6db74">{</span>len(documents)<span style="color:#e6db74">}</span><span style="color:#e6db74"> ページ読み込みました。&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 出力例: ドキュメントを 1 ページ読み込みました。</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="ステップ3-テキストの分割-splitchunking">ステップ3: テキストの分割 (Split/Chunking)</h4>
<p>LLMが一度に処理できるテキスト量（コンテキストウィンドウ）には限りがあるため、また、検索精度を向上させるために、読み込んだドキュメントを小さなチャンクに分割します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.text_splitter <span style="color:#f92672">import</span> RecursiveCharacterTextSplitter
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># テキスト分割の設定</span>
</span></span><span style="display:flex;"><span>text_splitter <span style="color:#f92672">=</span> RecursiveCharacterTextSplitter(
</span></span><span style="display:flex;"><span>    chunk_size<span style="color:#f92672">=</span><span style="color:#ae81ff">500</span>,  <span style="color:#75715e"># チャンクの最大文字数</span>
</span></span><span style="display:flex;"><span>    chunk_overlap<span style="color:#f92672">=</span><span style="color:#ae81ff">50</span>   <span style="color:#75715e"># チャンク間のオーバーラップ文字数</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 分割の実行</span>
</span></span><span style="display:flex;"><span>split_docs <span style="color:#f92672">=</span> text_splitter<span style="color:#f92672">.</span>split_documents(documents)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;ドキュメントを </span><span style="color:#e6db74">{</span>len(split_docs)<span style="color:#e6db74">}</span><span style="color:#e6db74"> 個のチャンクに分割しました。&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 出力例: ドキュメントを 3 個のチャンクに分割しました。</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>chunk_size</code>と<code>chunk_overlap</code>はRAGの性能を左右する重要なパラメータです。<code>chunk_overlap</code>を設けることで、文の途中でチャンクが分断され、文脈が失われるのを防ぎます。</p>
<h4 id="ステップ4-embeddingとvector-dbへの保存-embed--store">ステップ4: EmbeddingとVector DBへの保存 (Embed &amp; Store)</h4>
<p>分割したチャンクをEmbeddingモデルでベクトル化し、Chroma DBに保存します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.embeddings.openai <span style="color:#f92672">import</span> OpenAIEmbeddings
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.vectorstores <span style="color:#f92672">import</span> Chroma
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Embeddingモデルのインスタンス化</span>
</span></span><span style="display:flex;"><span>embeddings <span style="color:#f92672">=</span> OpenAIEmbeddings()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Chroma DBにドキュメントを読み込み、ベクトル化して保存</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># persist_directoryを指定すると、DBがディスクに永続化される</span>
</span></span><span style="display:flex;"><span>vectordb <span style="color:#f92672">=</span> Chroma<span style="color:#f92672">.</span>from_documents(
</span></span><span style="display:flex;"><span>    documents<span style="color:#f92672">=</span>split_docs,
</span></span><span style="display:flex;"><span>    embedding<span style="color:#f92672">=</span>embeddings,
</span></span><span style="display:flex;"><span>    persist_directory<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;./chroma_db&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Vector DBの準備が完了しました。&#34;</span>)
</span></span></code></pre></td></tr></table>
</div>
</div><p>このコードを実行すると、<code>./chroma_db</code>というディレクトリが作成され、ベクトルデータが保存されます。</p>
<h4 id="ステップ5-検索と生成-retrieve--generate">ステップ5: 検索と生成 (Retrieve &amp; Generate)</h4>
<p>いよいよ、作成したVector DBを使って質問応答チェーンを構築し、実際に質問をしてみます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.chat_models <span style="color:#f92672">import</span> ChatOpenAI
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> langchain.chains <span style="color:#f92672">import</span> RetrievalQA
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># LLMモデルのインスタンス化</span>
</span></span><span style="display:flex;"><span>llm <span style="color:#f92672">=</span> ChatOpenAI(model_name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;gpt-4&#34;</span>, temperature<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Vector DBをRetriever（検索機）として使用するQAチェーンを作成</span>
</span></span><span style="display:flex;"><span>qa_chain <span style="color:#f92672">=</span> RetrievalQA<span style="color:#f92672">.</span>from_chain_type(
</span></span><span style="display:flex;"><span>    llm<span style="color:#f92672">=</span>llm,
</span></span><span style="display:flex;"><span>    chain_type<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;stuff&#34;</span>,  <span style="color:#75715e"># 最もシンプルなチェーンタイプ</span>
</span></span><span style="display:flex;"><span>    retriever<span style="color:#f92672">=</span>vectordb<span style="color:#f92672">.</span>as_retriever()
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 質問を実行</span>
</span></span><span style="display:flex;"><span>question <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;社員が副業を始めるには、どのような手続きが必要ですか？&#34;</span>
</span></span><span style="display:flex;"><span>response <span style="color:#f92672">=</span> qa_chain<span style="color:#f92672">.</span>run(question)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;質問: </span><span style="color:#e6db74">{</span>question<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;回答: </span><span style="color:#e6db74">{</span>response<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>実行結果の例:</strong></p>
<pre tabindex="0"><code>質問: 社員が副業を始めるには、どのような手続きが必要ですか？
回答: 社員が副業を始めるには、所定の申請書を人事部に提出し、事前の承認を得る必要があります。
</code></pre><p>見事に、PDFの内容に基づいた正確な回答が生成されました。これはLLMが元々持っていた知識ではなく、私たちがVector DB経由で提供した情報に基づいています。これがRAGの力です。</p>
<h2 id="ragのメリットとデメリット">RAGのメリットとデメリット</h2>
<p>RAGは非常に強力な技術ですが、万能ではありません。そのメリットとデメリットを正しく理解し、ファインチューニングとの使い分けを考えることが重要です。</p>
<h3 id="ragのメリット">RAGのメリット</h3>
<ol>
<li><strong>ハルシネーションの劇的な抑制</strong>: LLMは与えられたコンテキスト（検索結果）に基づいて回答を生成するため、事実に基づかない情報を捏造する可能性が大幅に低下します。</li>
<li><strong>知識の更新が容易かつ低コスト</strong>: 新しい情報やドキュメントの更新があった場合、モデル全体を再学習する必要はありません。Vector DB内の該当データを追加・更新するだけで、即座に知識を最新の状態に保てます。</li>
<li><strong>透明性と解釈可能性</strong>: RAGシステムでは、LLMがどのドキュメントチャンクを参考にして回答を生成したのかを追跡できます。これにより、ユーザーに出典を提示することが可能となり、回答の信頼性が向上します。</li>
<li><strong>高いセキュリティ</strong>: 自社の機密情報を外部のLLMに学習させる必要がありません。データは自社で管理するVector DB内に保持し、実行時に必要な情報だけをプロンプトの一部としてLLMに渡すため、データ漏洩のリスクを最小限に抑えられます。</li>
</ol>
<h3 id="ragのデメリットと課題">RAGのデメリットと課題</h3>
<ol>
<li><strong>検索精度への依存</strong>: RAGの性能は、検索コンポーネントの精度に大きく依存します。ユーザーの質問に対して関連性の低いドキュメントしか検索できなかった場合、当然ながら回答の質も低くなります（Garbage In, Garbage Out）。</li>
<li><strong>Chunking戦略の難しさ</strong>: テキストをどのように分割するか（<code>chunk_size</code>、<code>chunk_overlap</code>、分割単位など）は、試行錯誤が必要な職人芸的な側面があります。ドキュメントの構造を無視した不適切なChunkingは、検索精度を著しく低下させます。</li>
<li><strong>システム構成の複雑化</strong>: LLM単体で完結せず、データローダー、テキストスプリッター、Embeddingモデル、Vector DBなど、複数のコンポーネントを組み合わせたパイプラインを構築・運用する必要があります。</li>
<li><strong>レイテンシの増加</strong>: ユーザーからのリクエストごとに「検索」というステップが挟まるため、LLM APIを直接呼び出す場合に比べて、応答に時間がかかる可能性があります。</li>
</ol>
<h3 id="rag-vs-ファインチューニングどちらを選ぶべきか">RAG vs ファインチューニング：どちらを選ぶべきか？</h3>
<p>RAGとファインチューニングは対立するものではなく、補完関係にあります。目的によって使い分けるのが賢明です。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">観点</th>
          <th style="text-align: left">RAG（検索拡張生成）</th>
          <th style="text-align: left">ファインチューニング</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>主な目的</strong></td>
          <td style="text-align: left"><strong>外部知識の参照</strong>、事実ベースの回答、ハルシネーション抑制</td>
          <td style="text-align: left"><strong>特定のスタイル・口調の模倣</strong>、特定タスクへの性能特化</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>知識の更新</strong></td>
          <td style="text-align: left"><strong>容易</strong>（Vector DBのデータを更新するだけ）</td>
          <td style="text-align: left"><strong>困難</strong>（モデルの再学習が必要で高コスト）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>ハルシネーション</strong></td>
          <td style="text-align: left"><strong>抑制しやすい</strong>（根拠となる情報が与えられるため）</td>
          <td style="text-align: left">抑制しにくい（モデル内部の知識に依存するため）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>出典の明示</strong></td>
          <td style="text-align: left"><strong>可能</strong></td>
          <td style="text-align: left">不可能</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>適した用途</strong></td>
          <td style="text-align: left">社内ナレッジQA、マニュアル検索、最新情報に基づく回答生成</td>
          <td style="text-align: left">特定のキャラクター模倣、メール自動作成、要約タスクの精度向上</td>
      </tr>
  </tbody>
</table>
<p><strong>使い分けの指針</strong>:</p>
<ul>
<li><strong>事実に基づいた正確な知識</strong>を扱いたい場合は、まず<strong>RAG</strong>を検討します。</li>
<li>LLMの**振る舞い（口調、文体、思考プロセス）**を特定の形に変えたい場合は、<strong>ファインチューニング</strong>が有効です。</li>
<li>両方を組み合わせる、つまりファインチューニングしたモデルをRAGの生成器（Generator）として使用することで、特定のスタイルで、かつ正確な情報に基づいた回答を生成する、という高度なアプローチも可能です。</li>
</ul>
<h2 id="現場で使える実践的なtips">現場で使える実践的なTips</h2>
<p>基本的なRAGの実装は比較的簡単ですが、実運用で高い性能を出すためにはいくつかの工夫が必要です。</p>
<ol>
<li>
<p><strong>高度なChunking戦略</strong>:
単純な固定長分割ではなく、ドキュメントの構造を活かしましょう。Markdownであれば<code>MarkdownHeaderTextSplitter</code>、ソースコードであれば<code>CodeSplitter</code>など、<code>LangChain</code>には様々なスプリッターが用意されています。これにより、意味のあるまとまりでテキストを分割でき、検索精度が向上します。</p>
</li>
<li>
<p><strong>Embeddingモデルの選定</strong>:
OpenAIのモデルは高性能ですが、コストがかかります。Hugging Faceで公開されているオープンソースのモデル（例: <code>intfloat/multilingual-e5-large</code>など日本語性能が高いもの）をセルフホストすることで、コストを抑えつつ、特定のドメインに特化した性能を得られる場合があります。</p>
</li>
<li>
<p><strong>ハイブリッド検索（Hybrid Search）</strong>:
Vector Search（意味検索）は万能ではありません。特定の製品名や型番、人名といった固有名詞を含むクエリには、従来のキーワード検索（BM25アルゴリズムなど）の方が強い場合があります。この2つを組み合わせたハイブリッド検索を実装することで、検索の網羅性と精度を両立させることができます。多くのマネージドVector DBサービスがこの機能を提供しています。</p>
</li>
<li>
<p><strong>Retrieverのチューニング</strong>:</p>
<ul>
<li><strong>検索ドキュメント数（k）の調整</strong>: <code>retriever=vectordb.as_retriever(search_kwargs={&quot;k&quot;: 5})</code> のように、一度に検索するチャンク数を調整します。多すぎるとノイズが増え、少なすぎると必要な情報が欠落します。</li>
<li><strong>Re-ranking</strong>: 最初に多めにチャンクを検索（例: k=20）し、その後、より軽量で高速なCross-Encoderモデルなどを使って、質問との関連性を再計算し、上位のチャンク（例: top 5）だけをLLMに渡す手法です。ノイズを減らし、コンテキストの質を高めるのに非常に有効です。</li>
</ul>
</li>
<li>
<p><strong>メタデータフィルタリング</strong>:
ドキュメントをVector DBに保存する際に、作成日、カテゴリ、著者などのメタデータを一緒に格納します。これにより、「2024年以降に作成された、&ldquo;技術部&quot;カテゴリのドキュメントの中から検索する」といった、より高度な絞り込み検索が可能になります。これは実用的なアプリケーションを構築する上で必須の機能です。</p>
</li>
</ol>
<h2 id="まとめ">まとめ</h2>
<p>本記事では、AIエージェント開発における必須知識として、RAG（検索拡張生成）と、その中核をなすVector DBの基礎について、仕組みから具体的な実装例、実践的なTipsまでを網羅的に解説しました。</p>
<p><strong>RAGは、LLMに自社のデータを「学習」させるのではなく、必要な情報をリアルタイムに「検索」して与えることで、LLMの持つハルシネーションや知識の陳腐化といった課題を解決し、ビジネスの現場で安全かつ効果的に活用するための強力なパラダイムです。</strong></p>
<p>その心臓部であるEmbeddingとVector DBは、非構造化データであるテキストを「意味」で扱えるようにする革新的な技術であり、これからのAIアプリケーション開発においてますます重要性を増していくでしょう。</p>
<p>今日学んだことをまとめると、以下のようになります。</p>
<ul>
<li>LLMの限界を克服するため、RAGは「検索」と「生成」を組み合わせる。</li>
<li>Embeddingがテキストを意味的ベクトルに変換し、Vector DBがそれを高速に検索する。</li>
<li><code>LangChain</code>のようなフレームワークを使えば、RAGパイプラインを効率的に構築できる。</li>
<li>RAGは知識の注入に、ファインチューニングは振る舞いの調整に適している。</li>
<li>実用的な性能を出すには、Chunking、Hybrid Search、Re-rankingなどの高度なテクニックが鍵となる。</li>
</ul>
<p>RAGはまだ発展途上の技術であり、日々新しい手法が提案されています。しかし、本記事で解説した基礎をしっかりと理解していれば、その進化にキャッチアップしていくことは十分に可能です。</p>
<p>まずは、あなた自身のPCで、身近なドキュメントを使って小さなRAGシステムを構築してみてください。自分のデータに基づいてAIが的確な回答を生成する体験は、きっと新たなインスピレーションを与えてくれるはずです。この記事が、あなたのAIエージェント開発の第一歩となることを願っています。</p>
]]></content:encoded>
      <category>AI Technology</category>
      <category>RAG</category>
      <category>Vector DB</category>
      <category>LLM</category>
    </item>
  </channel>
</rss>
