Ollama + LangChainでローカルRAGを構築する

目次
注意事項
- 本記事の内容は試験的な実装であり、アイデアベースの検証です
- 実務での利用を保証するものではありません
- 実装についての責任は負いかねます。自己責任でご利用ください
- AIの出力結果は常に検証が必要です
Ollamaで動作するローカルLLMとLangChainを組み合わせて、外部APIを使わずにRAG(Retrieval-Augmented Generation)環境を構築する手順をまとめます。
RAGとは
RAG(検索拡張生成)は、LLMに外部知識を検索して与えることで、学習データにない情報にも回答できるようにする手法です。
ユーザーの質問 → ベクトル検索(類似文書を取得) → LLMに文書+質問を渡す → 回答
ローカルRAGのメリット
- データが外部に出ない — 社内文書・機密情報を安全に検索
- APIコストゼロ — Ollamaは完全無料
- オフライン動作 — ネットワーク接続不要
環境構成
| 項目 | 使用技術 |
|---|---|
| LLM | Ollama (gemma3:12b) |
| Embedding | Ollama (nomic-embed-text) |
| フレームワーク | LangChain |
| ベクトルDB | Chroma |
| 言語 | Python 3.11+ |
セットアップ
1. Ollamaのインストールとモデル取得
# Ollamaインストール(macOS)
brew install ollama
# モデルのダウンロード
ollama pull gemma3:12b
ollama pull nomic-embed-text
Macの性能別おすすめモデルはMac M5 vs M2 Ollamaベンチマークを参照してください。
2. Pythonパッケージのインストール
pip install langchain langchain-community langchain-ollama chromadb
実装
ドキュメントの読み込みとチャンク分割
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# ドキュメントの読み込み
loader = DirectoryLoader(
"./docs",
glob="**/*.md",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"}
)
documents = loader.load()
# チャンク分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "、", " "]
)
chunks = text_splitter.split_documents(documents)
print(f"ドキュメント数: {len(documents)}, チャンク数: {len(chunks)}")
ベクトルDBの構築
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
# Ollamaのembeddingモデルを使用
embeddings = OllamaEmbeddings(model="nomic-embed-text")
# Chromaにベクトルを保存
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
RAGチェーンの構築
from langchain_ollama import ChatOllama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# ローカルLLM
llm = ChatOllama(model="gemma3:12b", temperature=0)
# プロンプトテンプレート
prompt_template = PromptTemplate(
input_variables=["context", "question"],
template="""以下のコンテキストを参考にして質問に回答してください。
コンテキストに情報がない場合は「該当する情報が見つかりませんでした」と回答してください。
コンテキスト:
{context}
質問: {question}
回答:"""
)
# RAGチェーン
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": prompt_template}
)
質問の実行
result = qa_chain.invoke({"query": "SQLで移動平均を計算する方法は?"})
print(result["result"])
完成版スクリプト
上記をまとめた完成版は以下の通りです。
import os
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
DOCS_DIR = os.environ.get("DOCS_DIR", "./docs")
CHROMA_DIR = os.environ.get("CHROMA_DIR", "./chroma_db")
LLM_MODEL = os.environ.get("LLM_MODEL", "gemma3:12b")
def build_vectorstore():
loader = DirectoryLoader(DOCS_DIR, glob="**/*.md", loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"})
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(documents)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
return Chroma.from_documents(chunks, embeddings, persist_directory=CHROMA_DIR)
def create_qa_chain(vectorstore):
llm = ChatOllama(model=LLM_MODEL, temperature=0)
prompt = PromptTemplate(
input_variables=["context", "question"],
template="コンテキスト:\n{context}\n\n質問: {question}\n回答:"
)
return RetrievalQA.from_chain_type(
llm=llm, chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": prompt}
)
if __name__ == "__main__":
vs = build_vectorstore()
chain = create_qa_chain(vs)
while True:
q = input("\n質問 (qで終了): ")
if q.lower() == "q":
break
result = chain.invoke({"query": q})
print(f"\n{result['result']}")
パフォーマンスの目安
| Mac | モデル | 初回応答 | チャンク検索 |
|---|---|---|---|
| M2 Air (8GB) | gemma3:4b | 3-5秒 | <0.5秒 |
| M2 Air (16GB) | gemma3:12b | 5-8秒 | <0.5秒 |
| M5 Pro (36GB) | gemma3:27b | 3-5秒 | <0.3秒 |
より詳細なベンチマークはMac M5 vs M2 Ollamaベンチマークを参照。
関連記事
- Apple MLXでOllama最適化 — Macに最適化されたLLM推論
- Mac M5 vs M2 Ollamaベンチマーク — ハードウェア別の推論性能比較
- AIシェルプロンプトテンプレート — CLI環境でのAI活用