簡単なRAG

Simple RAG: LangChain + Ollama + FAISS を使った簡易RAG構成

Ollama・LangChain・FAISSを使い、ローカル環境でRAG(検索拡張生成)を構築する手順をまとめた実験です。


1. pyenvのインストール

Python環境のバージョン管理のため pyenv を使用します。

sudo yum install gcc zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel git libffi-devel

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

if grep "pyenv init -" ~/.bash_profile ; then
  echo "skip."
else
  echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bash_profile
  echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
fi
source ~/.bash_profile

pyenv install --list
pyver="3.13.5"
pyenv install ${pyver}
pyenv global  ${pyver}
pyenv rehash

2. sqlite3のビルド(ChromaDB対応)

ChromaDBやFAISS利用時の依存として最新版の sqlite3 を使用します。

curl -O https://www.sqlite.org/2025/sqlite-src-3500200.zip
unzip sqlite-src-3500200.zip
cd sqlite-src-3500200/
./configure && make
sudo make install

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

3. プロジェクト構成

RAGプロジェクトを作成します。

mkdir rag
cd rag

# 必要なPythonライブラリ
cat <<'EOF' > requirements.txt
langchain
ollama
faiss-cpu
langchain-community
langchain-ollama
EOF

pip3.13 install -r requirements.txt

4. ドキュメント作成

検索対象となるサンプルドキュメント(Markdown形式)を用意します。

mkdir docs

cat <<'EOF' > docs/text.md
* 猫はニャー
* 犬ならワン
* 象はパオーン
* ライオンガオーッ
* 人間だもんあかさたなはやまらわおん
EOF

# モデルのダウンロード(埋め込み用と生成用)
ollama pull gemma3n
ollama pull nomic-embed-text

5. スクリプト:build_index3.py

RAGの構築と問い合わせ処理を行うPythonスクリプトです。

import glob
from langchain_community.vectorstores import FAISS
from langchain_ollama import OllamaEmbeddings, OllamaLLM
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain.chains import RetrievalQA

# ---------- 設定 ----------
EMBED_MODEL = "nomic-embed-text"
LLM_MODEL = "gemma3n"
DOC_PATTERN = "docs/*.md"
QUERY = "犬は?"

# ---------- ドキュメント読み込み ----------
file_paths = glob.glob(DOC_PATTERN)
all_docs = []

for path in file_paths:
    loader = TextLoader(path, encoding="utf-8")
    docs = loader.load()
    for doc in docs:
        doc.metadata["source"] = path
    all_docs.extend(docs)

print(f"✅ 読み込んだファイル数: {len(file_paths)}")
print(f"✅ ドキュメント数: {len(all_docs)}")

# ---------- 分割処理 ----------
splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_docs = splitter.split_documents(all_docs)
print(f"✅ チャンク数: {len(split_docs)}")

# ---------- ベクトルストア作成 ----------
embedder = OllamaEmbeddings(model=EMBED_MODEL)
vectorstore = FAISS.from_documents(split_docs, embedder)

# ---------- RetrievalQA ----------
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
llm = OllamaLLM(model=LLM_MODEL)

rag_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=retriever,
    chain_type="stuff",
    return_source_documents=True
)

# ---------- 問い合わせ ----------
print(f"\n❓ 質問: {QUERY}")
result = rag_chain.invoke({"query": QUERY})

# ---------- 出力 ----------
print("\n🧠 回答:\n", result["result"])

print("\n📚 参照ソース:")
for doc in result["source_documents"]:
    print(f"- {doc.metadata.get('source', '不明')}")
    print(doc.page_content[:100].replace("\n", " "), "\n---")

6. 実行

python3.13 build_index3.py

7. 実行結果(例)

✅ 読み込んだファイル数: 1
✅ ドキュメント数: 1
✅ チャンク数: 1

❓ 質問: 犬は?

🧠 回答:
 犬ならワン

📚 参照ソース:
- docs/text.md
猫はニャー 犬ならワン 象はパオーン ライオンガオーッ 人間だもんあかさたなはやまらわおん 
---
|