# 自分のドキュメントに答えるAIをローカルで作る：プライベートRAG入門（データは外に出ない）

> 手元のPDF・メモ・社内資料に質問できるAIを、データを外部に一切出さずにローカルで作る方法（プライベートRAG）を、実際にRAGを本番運用するエンジニアが入門解説。RAGの仕組み、Ollamaの埋め込みAPIとコサイン類似度による最小実装、精度を上げるコツ、本番化への道筋までを、型安全なコードとともに紹介します。

- 公開日: 2026-06-25
- 著者: 友田 陽大
- タグ: 生成AI, RAG, Ollama, ローカルLLM, セルフホスト, 型安全
- URL: https://tomodahinata.com/blog/private-rag-local-llm-chat-with-your-own-documents

## 要点

- RAG（検索拡張生成）は『質問に関連する文書を検索し、それを根拠としてLLMに渡して答えさせる』技術。ローカルで完結すればデータは外に出ない
- 仕組みは3段——文書を埋め込み（ベクトル化）→質問に近い文書を検索→根拠と一緒にローカルLLMへ。すべて自分のPC内で完結できる
- 最小実装はOllamaの埋め込みAPI＋コサイン類似度だけで作れる。少数の文書なら追加のDBも不要
- 精度の鍵は『検索の質』。固有名詞に弱いベクトル検索は、キーワード検索との併用やチャンク化の工夫で補う
- 文書数が増えたらベクトルDB（pgvector等）へ、業務利用ならアクセス制御と評価へ——本番化の道筋がある

---

最初に結論を述べます。**手元のPDF・メモ・社内資料に「質問したら答えてくれるAI」を、データを一切外部に出さずに、自分のPCの中だけで作れます。** この仕組みを **RAG（検索拡張生成）** と呼びます。ChatGPTに資料を貼り付けて聞く代わりに、**ローカルLLMと組み合わせて完全にプライベートに**実現する——それがプライベートRAGです。本記事は、RAGの仕組みを最短で理解し、Ollama だけで動く最小実装から、精度を上げるコツ、本番化への道筋までを、実際にRAGを本番運用しているエンジニアの視点で解説します。

> 本記事は[ローカルLLMの始め方ガイド](/blog/local-llm-getting-started-ollama-lm-studio-vram-model-selection-guide)の応用編です。Ollamaの基本はそちらを参照してください。

---

## RAGとは？「カンペを渡して答えさせる」技術

LLMは賢いですが、**あなたの手元の資料（社内規程・議事録・個人のメモ）は知りません**。これを解決するのがRAGです。発想はシンプルで——**質問に関連する文書を検索して見つけ、それを「根拠（カンペ）」としてLLMに一緒に渡し、その根拠に基づいて答えさせる**。

ファインチューニング（モデルを追加学習させる）と違い、RAGは**知識をその場で注入**します。資料が変わってもデータを差し替えるだけでよく、学習し直す必要がありません（この使い分けは[RAG vs ファインチューニング](/blog/rag-vs-fine-tuning-cost-effectiveness-decision-guide)で詳説）。

RAGの処理は、3段階です。

```text
1. 準備（一度だけ）: 手元の文書を「埋め込み（ベクトル）」に変換して保存する
   ↓
2. 検索: 質問を同じくベクトル化し、意味が近い文書を探す
   ↓
3. 生成: 見つけた文書を「根拠」として、質問と一緒にローカルLLMへ渡して答えさせる
```

**この3段すべてを自分のPC内で動かせば、データは外部に一切出ません**。これがプライベートRAGの最大の価値です。

---

## 最小実装：Ollama の埋め込みAPI ＋ コサイン類似度

「ベクトルDBが必要そう」と身構えるかもしれませんが、**少数の文書なら、Ollamaの埋め込みAPIとコサイン類似度だけで作れます**。追加のデータベースも不要です。

### ステップ1：文書を埋め込み（ベクトル化）する

「埋め込み」とは、文章を**意味を表す数値の配列（ベクトル）**に変換することです。意味が近い文章どうしは、ベクトルも近くなります。Ollama は埋め込み用のモデルを提供しており、ローカルで変換できます。

```ts
/** Ollamaのローカル埋め込みAPIで、テキストをベクトルに変換する。通信はlocalhostのみ＝データは外に出ない。 */
async function embed(
  text: string,
  model = "nomic-embed-text",
  endpoint = "http://localhost:11434",
): Promise<readonly number[]> {
  const res = await fetch(`${endpoint}/api/embeddings`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ model, prompt: text }),
    signal: AbortSignal.timeout(30_000), // タイムアウトで止まらない（回復性）
  });
  if (!res.ok) throw new Error(`embed failed: ${res.status} ${res.statusText}`);
  const data = (await res.json()) as { embedding: readonly number[] };
  return data.embedding;
}
```

### ステップ2：質問に近い文書を検索する（コサイン類似度）

ベクトルどうしの「近さ」は、**コサイン類似度**で測ります。これは外部ライブラリなしで書ける、副作用のない純粋関数です。

```ts
/** 2つのベクトルの近さ（-1〜1、1に近いほど類似）。純粋関数なので単体テストが容易。 */
function cosineSimilarity(a: readonly number[], b: readonly number[]): number {
  if (a.length !== b.length) throw new Error("vector length mismatch");
  let dot = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
  return denom === 0 ? 0 : dot / denom; // ゼロ除算を防ぐ（堅牢性）
}

interface Chunk {
  readonly text: string;
  readonly embedding: readonly number[];
}

/** 質問ベクトルに近い順に上位k件の文書を返す。検索ロジックは生成と分離（SRP）。 */
function retrieveTopK(
  queryEmbedding: readonly number[],
  chunks: readonly Chunk[],
  k: number,
): readonly Chunk[] {
  return chunks
    .map((c) => ({ chunk: c, score: cosineSimilarity(queryEmbedding, c.embedding) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, k)
    .map((r) => r.chunk);
}
```

### ステップ3：根拠と一緒にローカルLLMへ渡す

検索した文書を「根拠」としてプロンプトに埋め込み、ローカルLLM（[始め方ガイドのストリーミングクライアント](/blog/local-llm-getting-started-ollama-lm-studio-vram-model-selection-guide)）に渡します。

```ts
function buildPrompt(question: string, context: readonly Chunk[]): string {
  const grounding = context.map((c, i) => `[${i + 1}] ${c.text}`).join("\n\n");
  // 「根拠にないことは答えない」と明示し、幻覚（でっち上げ）を抑える
  return [
    "以下の根拠だけに基づいて、質問に日本語で答えてください。",
    "根拠に答えがない場合は「資料からは分かりません」と答えてください。",
    "",
    `# 根拠\n${grounding}`,
    "",
    `# 質問\n${question}`,
  ].join("\n");
}
```

これで、**手元の文書に基づいて答えるAIが、完全にローカルで完成**します。文書の埋め込み・検索・生成のすべてが `localhost` で完結し、データは外に出ません。

---

## 精度を上げる3つのコツ

最小実装は動きますが、「思ったより的外れな答えが返る」こともあります。RAGの精度は、**LLMの賢さではなく『検索の質』でほぼ決まります**。すぐ効くコツを3つ。

1. **チャンク化（文書の分割）を整える**——長い文書は、意味のまとまり（見出し・段落）で分割します。粗すぎると根拠がぼやけ、細かすぎると文脈が失われます。隣接チャンクを少し重ねる（オーバーラップ）と安定します。
2. **固有名詞はキーワード検索で補う**——ベクトル検索は「意味の近さ」は得意ですが、**型番・固有名詞・略語の完全一致が苦手**です。キーワード検索（全文検索）と併用する「ハイブリッド検索」にすると、精度が大きく上がります。
3. **根拠を提示させる**——回答に「どの文書を根拠にしたか」（上の例の`[1][2]`）を含めさせると、誤りに気づけ、信頼性が上がります。

この「検索の質」を本番品質まで突き詰めると、ハイブリッド検索・リランク・評価といった設計が効いてきます。詳しくは[本番RAGの落とし穴と精度改善](/blog/production-rag-pitfalls-accuracy-improvement-guide)を参照してください。私が手がけた音声接客AIでは、まさにこの検索の質を作り込むことで「専門商材の誤答を構造的に排除」しました。

---

## 本番化への道筋：文書が増えたら、業務で使うなら

最小実装は「少数の文書を、自分が使う」段階に最適です。そこから先は、規模と用途で進化させます。

| 段階 | やること |
|---|---|
| **文書が数百〜数千を超える** | メモリ上の総当たりから、ベクトルDB（pgvector等）へ。検索が高速・スケーラブルに |
| **複数人・部門で使う** | アクセス制御を検索層に組み込む（他人の文書が混ざらないよう構造的に分離） |
| **業務の意思決定に使う** | 精度を測る評価の仕組みを用意し、改善を回す |

特に**業務利用では、アクセス制御（他人・他部門の文書が検索結果に混ざらない）が必須**です。これは「あとから」では危険で、最初から検索条件に組み込む設計が要ります（[本番RAGの落とし穴](/blog/production-rag-pitfalls-accuracy-improvement-guide)参照）。個人で試す最小実装から、社内全体で使う本番RAGまでは地続きですが、本番には本番の作り込みが必要です。

---

## よくある質問（FAQ）

### Q. プライベートRAGとは何ですか？

手元の文書（PDF・メモ・社内資料）に質問できるAIを、データを外部に出さずに作る仕組みです。質問に関連する文書を検索し、それを根拠としてローカルLLMに渡して答えさせるRAG（検索拡張生成）を、すべて自分のPC内で完結させます。機密情報を外部のAIに入力したくない場合に最適です。

### Q. データは本当に外部に出ませんか？

文書の埋め込み・検索・生成のすべてをローカル（Ollama等）で動かせば、データは外部に送信されません。通信は`localhost`で完結します。これがプライベートRAGの最大の価値で、機密情報・個人情報・未公開資料を安心して扱えます。

### Q. ベクトルデータベースは必要ですか？

少数の文書なら不要です。Ollamaの埋め込みAPIとコサイン類似度（外部ライブラリなしで書ける純粋関数）だけで作れます。文書が数百〜数千を超えて検索が遅くなってきたら、pgvectorのようなベクトルDBへ移行すると、高速でスケーラブルになります。

### Q. RAGとファインチューニング、どちらを使うべきですか？

手元の資料に基づいて答えてほしいだけなら、まずRAGです。RAGは知識をその場で注入するため、資料が変わってもデータを差し替えるだけで済みます。ファインチューニング（追加学習）は文体や振る舞いを変えたいときの選択肢で、知識の更新には不向きです。詳しくは「RAG vs ファインチューニング」の記事を参照してください。

### Q. 答えが的外れになります。どうすれば精度が上がりますか？

RAGの精度は「検索の質」でほぼ決まります。LLMを賢いモデルに変える前に、(1)文書のチャンク化を整える、(2)固有名詞に弱いベクトル検索をキーワード検索と併用する（ハイブリッド検索）、(3)根拠を提示させる、を試してください。それでも不十分なら、リランクや評価の仕組みといった本番RAGの設計が必要です。

---

## まとめ：ローカルで完結する「自分専用AI」

手元の資料に答えるAIをプライベートに作るために、押さえるべきは次の通りです。

1. **RAGは『関連文書を検索し、根拠としてLLMに渡して答えさせる』技術**——ローカルで完結すればデータは外に出ない。
2. **仕組みは3段**——埋め込み→検索→生成。すべて自分のPC内で動かせる。
3. **最小実装はOllamaの埋め込みAPI＋コサイン類似度だけ**——少数の文書ならDBも不要。
4. **精度の鍵は『検索の質』**——チャンク化・ハイブリッド検索・根拠提示で上げる。
5. **本番化の道筋がある**——ベクトルDB・アクセス制御・評価へと段階的に発展できる。

「社内の資料に答えるAIを、データを外に出さずに業務へ導入したい」——個人で試す最小実装から、アクセス制御と精度評価を備えた本番の社内RAGまで、私は実務で構築しています。お気軽にご相談ください。
