去年まで作っていたRAGシステムの話 —— AIチャットを独自にカスタマイズ
ちょうど去年の5月頃まで、私はAIを使った社内向けチャットシステムを作っていた。
最近はAIの進化が早すぎて、少し離れるともう昔の話のように感じる。せっかくなので、忘れてしまう前に当時やっていた構成を記録として残しておこうと思う。
注:業務内容をそのまま書けないので、社内システムということにしている。実際は違う:)
今回は、いわゆる RAG(Retrieval-Augmented Generation) の一般的な構成について、実際に作っていたシステムをベースに書いてみる。
そもそもRAGとは何か?
RAGというのは簡単に言えば、
「AIに、事前に会社の資料を読ませてから回答させる仕組み」
である。
通常のLLM(ChatGPTのような大規模言語モデル)は、インターネット上の一般知識は持っているが、当然ながら会社固有の情報は知らない。
例えば:
- 社内ルール
- 商品仕様
- 障害対応手順
- 顧客向けFAQ
- 独自業務フロー
こういったものは、学習されていない。
そこで必要になるのがRAGだ。
実際にやっていた構成
当時の構成は、かなりオーソドックスだった。
流れとしてはこんな感じ。
- 社内データを登録
- ドキュメントを分割(チャンク化)
- ベクトル化(Embedding)
- pgvectorへ保存
- ユーザー質問をEmbedding化
- 類似チャンク検索
- LLMへ渡して回答生成
今見ると「教科書通り」なのだが、実際この構成はかなり強力だった。
1. まずは社内データを集める
元データは主にPostgreSQLに入っていた。
例えば:
- FAQ
- 商品説明
- 社内ナレッジ
- 問い合わせ履歴
- マニュアル
など。
ただ、ここで重要なのは、
「DBに入っている = AIが理解できる」
ではないということ。
人間向けのデータは、そのままだとAIには扱いづらい。
2. ドキュメントをチャンク化する
ここで登場するのがLangChain。
当時はかなり使われていた。
例えば長いマニュアルを、そのままLLMへ送ると:
- トークンが大きすぎる
- ノイズが増える
- 必要情報が埋もれる
という問題が起きる。
なので、文章を小さく分割する。
例えば:
製品Aについて
========
機能説明...
障害時対応...
注意事項...
これを:
チャンク1: 機能説明
チャンク2: 障害対応
チャンク3: 注意事項
みたいに切る。
この「どこで切るか」が意外と重要だった。
細かすぎると文脈が消えるし、大きすぎると検索精度が落ちる。
RAGは、実はこの辺の地味な調整がかなり効く。
3. Embedding(ベクトル化)
次に、文章をEmbeddingする。
Embeddingとは簡単に言えば、
「文章を数値ベクトルに変換すること」
である。
例えば:
"パスワードを変更したい"
みたいな文章が、数百〜数千次元の数値配列になる。
重要なのは、
意味が近い文章ほど、近いベクトルになる
という点。
つまり:
- 「パスワード変更」
- 「ログイン情報更新」
みたいな文は、数学的に近くなる。
これがRAGの核心部分。
4. pgvectorへ保存
Embeddingしたベクトルは、PostgreSQL + pgvectorへ保存していた。
これは非常に実務的だった。
理由は単純で、
- 既存DBがPostgres
- 運用しやすい
- SQL資産がそのまま使える
- バックアップも既存運用に乗る
から。
ただし、ここで一つ注意点がある。
チャンク化するとデータ量が一気に膨らむので、ベクトル用のDB自体は業務DBと分けた方がいい。
同じPostgresでも、インスタンスは別にする、というイメージ。
バックアップやモニタリングなど、運用の流儀はそのまま流用できる。
でも、本番DBとI/Oを取り合う構成だけは避けたかった。
世の中的には:
- Pinecone
- Weaviate
- Milvus
みたいな専用Vector DBもある。
ただ、業務システムでは「新しいDBを増やしたくない」という事情も強い。
なので、pgvectorはかなり現実解だった。
5. ユーザー質問をEmbedding化
ユーザーが質問する。
例えば:
経費精算の締め日は?
これを同じEmbeddingモデルでベクトル化する。
ここが重要で、
登録時と検索時で、同じEmbeddingモデルを使う
必要がある。
でないと空間の意味がズレる。
6. 類似チャンク検索
次にpgvectorで類似検索する。
つまり:
SELECT *
FROM documents
ORDER BY embedding <-> :question_vector
LIMIT 5;
みたいなことをやる。
すると:
- 経費精算ルール
- 月末締め
- 承認期限
など、意味的に近いチャンクが取得される。
ここが普通の全文検索と違う部分。
キーワード一致ではなく、
「意味が近い」
で引っ張れる。
なので:
- 表記ゆれ
- 言い換え
- 曖昧表現
に比較的強い。
7. 最後にLLMへ渡す
取得したチャンクを、LLMへ渡す。
イメージとしては:
以下の情報を参考に回答してください。
[関連資料]
- 経費精算は毎月25日締め
- 承認期限は月末
[質問]
経費精算の締め日は?
するとLLMが、
経費精算の締め日は毎月25日です。
のように自然文で返す。
これがRAG。
当時感じた「RAGの現実」
やってみて感じたのは、
RAGは「AI魔法」ではなく、かなり泥臭い検索システム
ということだった。
実際、精度を左右するのは:
- チャンク分割
- 前処理
- 不要データ除去
- Metadata設計
- 検索条件
- Prompt設計
みたいな地味な部分。
LLMそのものより、
「どう検索させるか」
の方が重要だったりする。
そして今
もう1年近く、AI自体を作る業務はやってないが、今では:
- Hybrid Search
- Rerank
- GraphRAG
- Agentic RAG
などのような手法があるようだ。
ただ、根本思想は当時から変わっていない。
「AI単体ではなく、外部知識を検索して補強する」
これがRAGの本質だと思う。
おわりに
AIチャットというと、つい「LLMが全部賢い」と思われがちだ。
でも実際の業務システムでは、
- 検索
- データ整備
- 文書管理
- ベクトルDB
- 前処理
みたいな、かなり従来型の技術が土台になっている。
むしろ、
「検索エンジンの進化版」
として見る方が、実態に近いかもしれない。
そして、その泥臭い部分こそ、実は一番面白かったりする。
——なお、経験則として「AIを入れれば全部解決する」と言う人ほど、だいたい社内のドキュメントは10年前から更新されていない。
AIに賢くなってもらう前に、まずは自分たちの資料を整理する方が先だ。
魔法を待つより、棚卸しの方が早い。