メインコンテンツへスキップ
友田 陽大
ローカルLLM・自分のPCでAI
生成AI
RAG
Ollama
ローカルLLM
セルフホスト
型安全

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

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

公開日
読了時間
9分
著者
友田 陽大
シェア

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

本記事はローカルLLMの始め方ガイドの応用編です。Ollamaの基本はそちらを参照してください。


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

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

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

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

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

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


最小実装:Ollama の埋め込みAPI + コサイン類似度

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

ステップ1:文書を埋め込み(ベクトル化)する

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

/** 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:質問に近い文書を検索する(コサイン類似度)

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

/** 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(始め方ガイドのストリーミングクライアント)に渡します。

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の落とし穴と精度改善を参照してください。私が手がけた音声接客AIでは、まさにこの検索の質を作り込むことで「専門商材の誤答を構造的に排除」しました。


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

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

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

特に業務利用では、アクセス制御(他人・他部門の文書が検索結果に混ざらない)が必須です。これは「あとから」では危険で、最初から検索条件に組み込む設計が要ります(本番RAGの落とし穴参照)。個人で試す最小実装から、社内全体で使う本番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まで、私は実務で構築しています。お気軽にご相談ください。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

生成AI音声チャットボット(RAGで専門商材の誤答を構造的に排除)

ケーススタディを見る