【備忘録】Windows11で日本語PDFを使ったRAGを構築（LangChain + PGVector + Ollama）

## はじめに

日本語の文章を扱うRAG（Retrieval-Augmented Generation）システムを、Windows 11環境で構築したときの備忘録です。  
主な構成は以下の通り：

- **LLM**: Ollama（TokyoTech Swallow 8B）
- **Embeddingモデル**: mxbai-embed-large
- **ベクトルデータベース**: PostgreSQL + PGVector
- **ドキュメント**: 日本語のpdf(検証用の創作物語)

---

## 1. 環境構築（Ollama, PostgreSQL + PGVector）

Ollama や PostgreSQL + PGVector の導入手順は以下の外部記事を参考にしました。

**Ollamaのインストール方法**

https://zenn.dev/hellorusk/books/e56548029b391f/viewer/ollama




**PostgreSQL + PGVector のインストール方法**

https://masa-engineer-lab.com/postgresql_pgvector/102/


### 使用モデル：

- LLM：`hf.co/mmnga/tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.3-gguf:latest`

- 埋め込みモデル：`mxbai-embed-large`


### データベース設定（例）：
データベース名：`postgres`
ユーザー名：`postgres`
パスワード：`pass`


---

## 2. 必要モジュールのインストール

以下のコマンドで必要なパッケージをインストールします：

```bash
pip install langchain langchain-community langchain-postgres langchain-ollama 
pip install "psycopg[binary]"  unstructured[all-docs]

```

---

## 3. コード①：PDFの読み込みとベクトルデータベースへの登録

日本語PDFを読み込み、テキストを分割し、Ollamaでベクトル化した上で、PGVectorに保存します。

``` python
from langchain.document_loaders import UnstructuredFileLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_postgres import PGVector
from langchain_ollama import OllamaEmbeddings, ChatOllama

# LLMの初期化（Ollamaに接続）
llm = ChatOllama(
    model="hf.co/mmnga/tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.3-gguf:latest",
    base_url="http://localhost:11434",
    temperature=0,
    seed=0,
)

# PDFファイルの読み込み（日本語対応・ページ単位）
loader = UnstructuredFileLoader("./test.pdf", mode='paged', languages=['ja'])
documents = loader.load()

# テキストをチャンク（分割）に分ける
text_splitter = CharacterTextSplitter(
    chunk_size=600,       # 1チャンクあたりの文字数
    chunk_overlap=20,     # チャンク同士の重複（文脈保持のため）
    separator="\n"        # 改行で分割
)
docs = text_splitter.split_documents(documents)

# Embeddingモデルの設定
embeddings = OllamaEmbeddings(model='mxbai-embed-large')

# PostgreSQL + PGVector に接続
connection = "postgresql+psycopg://postgres:pass@localhost:5432/postgres"
collection_name = "test"

# インデックスを新規作成
vectorstore = PGVector(
    embeddings=embeddings,
    collection_name=collection_name,
    connection=connection,
    use_jsonb=True,
    pre_delete_collection=True,
)

# ドキュメントをベクトルDBに追加
vectorstore.add_documents(docs)
```

### 📌補足：
PyPDFLoader ではpdfファイルによっては文字化けが発生したため、UnstructuredFileLoader を使用しました。

``` python
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader(file_path='test.pdf')
```

ちなみにpdfの中身は架空の人物"Kタロウ"の物語を適当に制作したもの。

### 📄 使用したPDFの中身（ばｓ）

``` text
知恵者Kタロウの物語。
昔々、京の都の西にある小さな山村に、「Kタロウ」という名の若者が住んでおった。
年は二十歳を少し超えたばかり。村の者たちは皆、...
```


### 🗄 データベースの中身
テーブル：langchain_pg_embedding に保存されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717747/bbe0004b-963e-461b-a450-1662b67ffbbd.png)


## 4. コード②：質問応答チェーンの構築と実行
保存したベクトル情報を使い、LLMを通じて質問に答える処理を実装します。

``` python
from langchain_postgres import PGVector
from langchain.chains.retrieval import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain import hub

# LLMの設定（前と同様）
llm = ChatOllama(
    model="hf.co/mmnga/tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.3-gguf:latest",
    base_url="http://localhost:11434",
    temperature=0,
    seed=0,
)


# Embeddingの再設定
embeddings = OllamaEmbeddings(model='mxbai-embed-large')

# PGVectorに接続
connection = "postgresql+psycopg://postgres:pass@localhost:5432/postgres"
collection_name = "test"
vectorstore = PGVector.from_existing_index(
    embedding=embeddings,
    collection_name=collection_name,
    connection=connection,
    use_jsonb=True
)

# LangChain Hubから質問応答用のプロンプトテンプレートを取得
retrieval_qa_chat_prompt = hub.pull("langchain-ai/retrieval-qa-chat")

# ドキュメントを統合するチェーンを作成
combine_docs_chain = create_stuff_documents_chain(llm, retrieval_qa_chat_prompt)

# RAGの質問応答チェーンを構築
retrieval_chain = create_retrieval_chain(vectorstore.as_retriever(), combine_docs_chain)

# 質問を投げる
res = retrieval_chain.invoke({"input": "Kタロウに関して教えてください。"})

# 回答を出力
print(res["answer"])
``` 

得られた回答は以下の通り。pdfの内容を参照して回答してくれていることを確認しました。

### ✅ 出力結果
``` text
Kタロウについて、以下の情報が分かっています。

* **知恵者:** 村人から「知恵者K」と呼ばれるほど、頭が良く、問題解決能力に優れている。
* **謎の事件を解決:** 村の田畑に現れた謎の黒い影（実は夜猿）の正体を見破り、その原因と対策を提示した。
* **村のために尽力:**  村の発展のために、様々な難問を解き続け、「東山の賢者」として広く知られるようになった。... 
``` 



## 6. おわりに
RAGの構築に際して、Ollama + LangChain + PGVectorの構成は思ったよりシンプルでした。ローカルで完結する点も安心材料です。GUIも試してみたいと思います。