🍪

LangChain × LangGraph × pgvector でシンプルなRAG システムを構築してみた

に公開

はじめに

はじめまして、大学 4 年生のとうふです。
今回で Zenn への投稿は 10 本目になりました!

この記事では、LangChain と LangGraph を用いた RAG(Retrieval-Augmented Generation)システム構築の記録をまとめています。
12 月から RAG を扱うインターンに参加する予定があり、その予習も兼ねて実際に手を動かしてみました。

今回は社内ドキュメント検索を模した RAG システムを構築しながら、LangChain や LangGraph の理解を深めた過程をまとめました。

この記事は以下の GitHub リポジトリの実装を題材にしています:
https://github.com/dem3860/langchain_rag_system

なぜ LangChain を触ろうと思ったのか

「はじめに」でも述べた通り、12 月から RAG を扱うインターンに参加する予定があり、その予習として LangChain を触ってみようと思いました。
また、インターン先の社員さんにLangChain と LangGraph による RAG・AI エージェント[実践]入門を薦められたことも大きな動機となりました。

特に、

  • 「LangChain で RAG を作るってどうやるの?」
  • 「LangGraph のノードや State って結局どう動くの?」

こういった疑問を整理しながら、最小限だけど本格的な RAG を作りたい——という動機で始めました。

また、pgvector を使ったベクトル検索や、uv を使ったパッケージ管理など、自分の興味とも相性が良かったためこの題材を選んでいます。

uv については以下の記事で紹介しています:

Python のパッケージ管理 uv について

実装の概要

今回作成した RAG システムの構成は以下のようなものです。

User -> Router -> Retrieve -> Generate -> Evaluate -> Output

LangGraph で実装したノード構成は次の通り:

  • receive_question:ユーザの入力を State に取り込む
  • should_search:検索が必要か LLM に判断させる
  • retrieve:pgvector から top-k の類似文書を取得
  • generate_answer:コンテキストあり/なしに応じて返答を生成

画像として表すと以下のようになります。
graph

この AI Router → Retriever → Generator の流れは
AI エージェントデザインパターンの基本形でもあります。

RAG とはなにか(ざっくり整理)

RAG (Retrieval-Augmented Generation) とは、LLM によるテキスト生成を行う際、LLM が知らない情報(例えば最新のニュースや企業の内部ドキュメントなど)を参照し、出力の精度を向上させる技術のことです。
埋め込みや Vector Search などについてはここでは詳しく触れませんが、以下のサイトが参考になると思います。

https://zenn.dev/umi_mori/books/llm-rag-langchain-python/viewer/rag-flow

また、以下の書籍の 2 章も参考になります。具体的な実装例を交えながら、詳しく解説されていて非常に勉強になりました。

今回の構成は非常にスタンダードな RAG の形で、ユーザの質問に回答するために社内ドキュメントが必要であれば検索を行い、適切な情報を元に回答を生成するようになっています。

AI エージェントデザインパターンについて

AI エージェントの デザインパターンは色々紹介されています。今回は Router アーキテクチャパターン を採用しています。
Router アーキテクチャパターン では、最初にルーターがユーザの質問を受け取り、質問内容に応じて適切な処理フローに振り分けます。
例えば、質問が社内ドキュメントに関するものであれば RAG フローに進み、そうでなければ通常の LLM 応答フローに進む、といった具合です。
このパターンの利点は、質問の性質に応じて最適な処理を選択できるため、効率的かつ柔軟な応答が可能になることです。

他のデザインパターンが気になる方は、以下のドキュメント、書籍が参考になります。

実装のポイント

LangGraph のノード設計

LangGraph を使う上で重要なのはノード設計です。
各ノードがどのような役割を持ち、どのように State を操作するかを明確にする必要があります。
今回の実装では、各ノードが単一の責任を持つように設計し、State の変更は最小限に抑えました。
例えば、should_search ノードは質問が検索を必要とするかどうかの判断のみを行い、State の他の部分には影響を与えません。

State 管理

LangGraph の State 管理は、ノード間のデータの流れを制御する上で重要です。
State は各ノードが必要とするデータを保持し、ノード間でのデータの受け渡しを容易にします。
今回の実装では、State のキーを明確に定義し、各ノードが必要なデータのみを操作するようにしました。
これにより、ノード間の依存関係を最小限に抑え、コードの可読性と保守性を向上させました。

実際の出力例

以下は、実際にシステムを動かした際の出力例です。

User: 手動でマイグレーションを行う方法

--- Agent Processing ---

Agent: 手動でマイグレーションを適用する場合、踏み台サーバーより以下のコマンドを実行します。

```bash
export DATABASE_URL="postgresql://..."
alembic upgrade head
```

また、ローカル環境でのマイグレーション実行手順は以下の通りです。

```bash
# 環境変数の準備
cp .env.example .env
# 1Password から開発用シークレットを取得して .env を埋める
op read "op://Engineering/Nexus-Dev/env" > .env

# コンテナ起動
docker-compose up -d

# マイグレーション実行
uv run alembic upgrade head

# サーバー起動
uv run uvicorn app.main:app --reload --port 8000
```

Agent の出力は、質問に対して適切な情報をドキュメントをもとに提供しており、RAG システムが正しく機能していることが確認できます。

実装してみて感じたこと

良かった点

  • LangGraph の State を通して値が流れる仕組みは理解するととても直感的
  • RAG 構築のステップが明確になり応用しやすい
  • pgvector が Docker だけで手軽に構築できる
  • Gemini API の無料枠で軽い処理は問題なく動く

気づいた点

  • LangChain の抽象度が高く、どこで何が起きるか最初は把握しづらい
  • LangGraph の State 管理を間違えると “role がないエラー” などで落ちがち
  • multi-turn の管理は checkpointer を使うとより確実になりそう
  • 今回のようなシンプルなシステムなら問題ないが、大規模になると設計をしっかり行わないとすぐにコードベースが肥大化、複雑化しそう

まとめ

ここまで読んでいただき、ありがとうございました!
今回のプロジェクトで、

  • LangChain の基本(Prompt → LLM → Retriever → Chain)
  • LangGraph のノード / State / Edge の動き
  • RAG の実装の流れ

を一通り実装しながら理解できました。

LangGraph のおかげで「ただの RAG」も「ルーター付き RAG」も、
ノードをつなぎ変えるだけで簡単に拡張できるのがとても魅力です。

今後、実際のプロジェクトで LangChain / LangGraph を使う機会が増えそうなので、今回の経験を活かしていきたいと思います。
そして、ドキュメントや書籍、記事等を参考にしながら、さらに深く理解を進めていきたいです。

また、今回ご紹介した実装はあくまで基本的な形であり、より自律的に動作するエージェントや、複雑なワークフローを扱うための拡張も可能です。
今後も LangChain や LangGraph の新機能やベストプラクティスを追いかけながら、より高度な RAG システムの構築に挑戦していきたいと思います。

この記事が、LangChain や RAG に挑戦してみたい方の参考になれば幸いです!

参考資料

Discussion