【RAG】Ollama + LangChain + ChromaDBでローカルRAGシステム構築

ローカル環境で構築するRAGシステム:Ollama、LangChain、ChromaDBの連携

大規模言語モデル(LLM)は汎用的な知識に優れていますが、特定の組織や個人のドキュメントに含まれる最新・非公開の情報を扱うことは苦手です。Retrieval-Augmented Generation (RAG) はこの課題を解決する技術で、外部の知識源から関連情報を検索・取得し、LLMの応答生成に活用します。本ガイドでは、完全にローカル環境で動作するRAGシステムを、軽量LLMエンジンのOllama、LLMアプリケーションフレームワークのLangChain、そして埋め込みベクトルデータベースのChromaDBを用いて構築する方法を解説します。インターネット接続や高額なAPIキーが不要な、プライバシーとコストを考慮した実用的なソリューションです。

構築における主要な課題と原因

ローカルRAGシステムの構築を成功させるには、いくつかの技術的ハードルを理解する必要があります。第一に、ドキュメントの適切な分割(チャンキング)が挙げられます。大きな文書をそのまま扱うと、関連部分が埋もれて検索精度が落ちたり、LLMのコンテキストウィンドウを浪費したりします。第二に、高品質なベクトル埋め込みの生成です。検索精度は埋め込みモデルの性能に直結します。第三は、各コンポーネント間の適切な連携です。OllamaはLLMの実行と埋め込み生成の両方を行いますが、LangChainを通じて適切に設定し、ChromaDBと連携させる必要があります。これらのステップのいずれかが不適切だと、システムは「知識を持っている」にもかかわらず、正確な情報を答えに反映できないという事態が発生します。

ステップバイステップ構築ガイド

以下では、Python環境を用いて、Ollama、LangChain、ChromaDBを統合した基本的なRAGパイプラインを構築します。事前にPythonがインストールされていること、およびターミナルでollama pull llama3.2などのコマンドを実行して使用したいモデル(例:llama3.2, mistral, nomic-embed-text)をOllamaにダウンロードしておいてください。

1. 環境構築とライブラリのインストール

まず、必要なPythonパッケージをインストールします。仮想環境の使用を推奨します。

# 必要なパッケージのインストール
pip install langchain langchain-community langchain-chroma chromadb pypdf

2. ドキュメントの読み込みと分割(チャンキング)

ここでは例としてPDFファイルを処理します。LangChainのドキュメントローダーとテキスト分割器を使用します。

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. PDFドキュメントの読み込み
loader = PyPDFLoader("./your_document.pdf") # ご自身のPDFファイルパスに置き換えてください
documents = loader.load()

# 2. ドキュメントの分割(チャンキング)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,  # 各チャンクの文字数(目安)
    chunk_overlap=50, # チャンク間のオーバーラップ文字数(文脈を保つため)
    separators=["\n\n", "\n", "。", "、", " ", ""] # 分割するセパレータ
)
chunks = text_splitter.split_documents(documents)
print(f"元のドキュメント数: {len(documents)}")
print(f"分割後のチャンク数: {len(chunks)}")

3. ベクトルストア(ChromaDB)の作成と埋め込み

分割したテキストチャンクを、Ollamaで動作する埋め込みモデルを用いてベクトル化し、ChromaDBに保存します。

from langchain_chroma import Chroma
from langchain_community.embeddings import OllamaEmbeddings

# Ollamaの埋め込みモデルを指定(事前に`ollama pull nomic-embed-text`などでダウンロード必要)
embeddings = OllamaEmbeddings(model="nomic-embed-text")

# ドキュメントチャンクと埋め込みモデルを使ってChromaDBを永続化ディレクトリに作成・保存
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"  # ベクトルDBの保存先
)
vectorstore.persist() # ディスクに永続化
print("ベクトルストアの作成と保存が完了しました。")

4. 検索連鎖(Retrieval Chain)の構築

保存したベクトルストアから類似する文書を検索(Retrieval)し、その結果をLLMに渡して回答を生成(Generation)する一連の流れ(Chain)を定義します。

from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

# 1. Ollama LLMの初期化(事前に`ollama pull llama3.2`などでダウンロード必要)
llm = Ollama(model="llama3.2", temperature=0) # temperature=0で再現性向上

# 2. 永続化したベクトルストアの読み込み
persisted_vectorstore = Chroma(
    persist_directory="./chroma_db",
    embedding_function=embeddings
)

# 3. カスタムプロンプトテンプレートの定義(オプションだが推奨)
prompt_template = """以下の情報を参考に、ユーザーの質問に答えてください。
参考情報が質問に関連しない場合は、「参考情報からはわかりません」と答えてください。

参考情報:
{context}

質問: {question}
回答:"""

PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 4. RetrievalQAチェーンの構築
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff", # 検索した文書をそのままプロンプトに挿入するシンプルな方法
    retriever=persisted_vectorstore.as_retriever(search_kwargs={"k": 3}), # 類似度上位3件を取得
    chain_type_kwargs={"prompt": PROMPT}, # カスタムプロンプトを使用
    return_source_documents=True # 回答の元となったソース文書も返す
)

5. システムの実行とテスト

構築したRAGシステムに対して質問を投げてみましょう。

# 質問の実行
query = "このドキュメントで言及されている主要なプロジェクトは何ですか?"
result = qa_chain.invoke({"query": query})

print(f"質問: {query}")
print(f"\n回答: {result['result']}")
print(f"\n参照されたソース:")
for i, doc in enumerate(result['source_documents']):
    print(f"[{i+1}] {doc.page_content[:200]}...") # 最初の200文字を表示

トラブルシューティングとチューニングのポイント

  • 検索精度が低い場合:
    • 埋め込みモデルの変更: OllamaEmbeddings(model="...") のモデルを変えてみる(例: mxbai-embed-large)。
    • チャンキングの調整: chunk_sizechunk_overlap の値を変更する。一般的に、事実検索には小さめ(250-500)、要約には大きめ(1000-2000)のチャンクが適します。
    • 検索数kの調整: as_retriever(search_kwargs={"k": 3})k の値を増やす(例: 4, 5)。
  • Ollama接続エラーが発生する場合:
    • Ollamaサービスが起動しているか確認 (ollama serve)。
    • LangChainのOllamaインテグレーションはデフォルトで http://localhost:11434 を参照します。環境が異なる場合は、Ollama(base_url='...', model='...') で指定します。
  • 回答が参考情報を無視する場合:
    • プロンプトテンプレートを強化し、「参考情報に基づいて答えよ」 という指示を明確に含める。
    • chain_type"refine" などに変更して、複数の文書を逐次的に処理させる方法も検討します。

まとめ

Ollama、LangChain、ChromaDBを組み合わせることで、外部APIに依存しない、強力でプライベートなローカルRAGシステムを構築できます。本ガイドで紹介したパイプラインは基本的なものですが、ドキュメントの前処理(チャンキング)適切な埋め込みモデルの選択、そして検索と生成を結ぶプロンプトの設計が、システムの成否を分ける三大要素です。実際の運用では、複数ファイルのバッチ処理、メタデータの活用によるフィルタリング検索、あるいはより高度な再ランキング手法の導入など、段階的に機能を拡張していくことが可能です。このローカルRAGシステムは、社内ナレッジベースのQ&A、個人文書の分析、オフライン環境でのAI活用など、多様なシナリオの中核エンジンとして活用できるでしょう。

🔧 快適な開発環境のために

本記事の手順をスムーズに進めるために、以下のスペックを推奨します。

この記事は役に立ちましたか?