「RAGを作りたいが、社内文書を外部APIに送るのは許可が下りない」。機密情報を扱う現場では、これが最大の壁になります。答えは、データを一切外に出さないローカル完結の構成です。ここでは Ollama と ChromaDB を使った実装の勘所を紹介します。
なぜローカル構成を選ぶのか
クラウドのLLM APIは精度・手軽さで有利ですが、機密文書の送信が許容されないケースでは選べません。ローカルLLM(Ollama)とローカルのベクトルDB(ChromaDB)なら、データはネットワークの外に出ません。「精度」と「機密保持」を天秤にかけ、後者が要件なら迷わずローカルです。
構成要素
- Ollama:ローカルでLLMを動かすランタイム。
ollama runでモデルを手元で実行できる - ChromaDB:埋め込みベクトルを保存・検索する軽量なベクトルDB
- 埋め込みモデル:チャンクをベクトル化するモデル(Ollama経由でも利用可)
取り込み:ドキュメントをベクトル化する
ドキュメントを意味のまとまりで分割し、ベクトル化してChromaDBに格納します。
import chromadb
from chromadb.utils import embedding_functions
client = chromadb.PersistentClient(path="./store")
collection = client.get_or_create_collection("docs")
# チャンク(事前に分割したテキスト)を格納
collection.add(
documents=chunks,
ids=[f"doc-{i}" for i in range(len(chunks))],
)
検索 → 生成:根拠付きで答える
質問が来たら、関連チャンクを検索し、それを根拠としてOllamaのLLMに渡します。
import ollama
hits = collection.query(query_texts=[question], n_results=4)
context = "\n\n".join(hits["documents"][0])
prompt = f"""次の資料だけを根拠に、日本語で答えてください。
資料にない場合は「分かりません」と答えること。
# 資料
{context}
# 質問
{question}
"""
res = ollama.chat(model="llama3", messages=[{"role": "user", "content": prompt}])
print(res["message"]["content"])
ポイントは、プロンプトで**「資料にないことは答えない」**と明示すること。これだけでハルシネーションがかなり抑えられます。さらに、検索でヒットしたチャンクの出典を回答に添えれば、利用者が根拠を確認できます。
つまずきやすいところ
- チャンク分割が粗いと検索が外す:意味のまとまりを意識して分割する
- 検索件数が多すぎると文脈が薄まる:まずは3〜5件から調整する
- モデル選定は速度とのバランス:ローカルはマシン性能に依存するため、用途に合うサイズを選ぶ
まとめ
- 機密要件があるなら、Ollama + ChromaDB のローカル完結が現実解
- プロンプトで「資料にないことは答えない」と縛る
- 検索の作り込みが回答品質を決める
自社データでのローカルRAG構築、設計から対応できます。