システム提案作業を行っていると、「あの業界向けに作った提案書どこだっけ」となることがあります。ファイル名やフォルダ構成を覚えていればまだいいですが、たしかIoT関連の提案で、体制図が独特だった、くらいの記憶しかないと、キーワード検索ではまず出てきません。
これを解決するために、Embedding(埋め込み表現)を使った提案書検索システムを個人の学習用PoCとして作りました。
Embeddingとは何か
文章を、意味の近さを表す数値の並びに変換する技術です。
「IoTで工場の設備故障を予知する」という文章と「センサーデータから機械の不調を事前に検知する」という文章は、単語としてはほとんど重なりません。それでも意味はほぼ同じです。Embeddingで数値化すると、この2つの文章はベクトル空間上の近い位置に置かれます。逆に「花見の日程調整をする」のような無関係な文章は、遠い位置に置かれます。
これが効くのは、キーワードが一致しなくても意味が近い文書を引っ張ってこられる点にあります。従来の全文検索は「IoT」という単語が入っていないと引っかかりませんが、Embeddingベースの検索なら「工場の機械が壊れる前に気づきたい」という聞き方でも、意味の近い提案書がちゃんと返ってきます。
考え方からシステム構成までを1枚にまとめた図が以下になります。
検索だけの技術ではない
今回は検索に使いましたが、Embeddingはもっと根源的なところで、生成AIそのものの動作を支えています。
ChatGPTやClaudeのようなLLMも、内部では入力された単語や文章を一度Embeddingに変換してから処理しています。言葉の意味を理解しているように見える動きの裏側には、意味を数値空間上の位置関係として扱うという発想があります。今回作った検索システムは、LLMの中で起きていることを外側に取り出し、提案書という自前のデータに応用しただけとも言えます。
応用範囲も検索にとどまりません。
- レコメンド — 商品説明や購買履歴をEmbeddingで近さ判定し、似た商品を薦める
- 異常検知 — 普段のログやセンサー値のベクトルから離れたデータを異常として検出する
- クラスタリング — 大量の文書やレビューを、似た内容ごとに自動でグループ分けする
- 画像検索・マルチモーダル検索 — 文章と画像を同じベクトル空間に埋め込み、写真に近い商品を探す
こうして並べると、Embeddingは検索の一部品というより、AI活用全般に通底する共通言語に近いと感じます。今回の提案書検索は、その技術を一番わかりやすい形、社内文書検索で試したものです。
どんなRAGを作ったか
今回のPoCの学習目標のひとつが、RAGを一から自分の手で組んでみることでした。LangChainのようなフレームワークには頼らず、チャンク分割からベクトル検索、引用付き回答までを自前で実装しています。
やりたかったのは、質問すると根拠となる提案書のスライドを示しながらAIが答えてくれるものです。
- キーワード検索ではなく、自然文の質問で聞ける。「体制やスケジュールってどんな感じ?」のように
- 回答は生成AI任せの創作ではなく、実在する提案書スライドを根拠として引用付きで返す
- どのスライドから来た回答なのか、すぐ確認できる
構成はいたってシンプルです。埋め込みはAmazon Bedrock経由のTitan Embeddings V2(1024次元)、ベクトルDBはAmazon RDS上のpgvector、生成はBedrock経由のClaude、UIはStreamlitの簡易チャット画面という組み合わせにしました。
個人学習用なので実在の顧客提案書は使わず、生成AIで作った架空の業種・案件のダミー提案書、PowerPointファイルで10数件を検索対象にしています。
技術的な仕組み
全体の流れとポイントは以下の通りです。
ポイントは3つあります。
チャンクの単位はスライド1枚。 文書全体を1つのベクトルにすると粒度が粗すぎますし、逆に文単位まで細かくすると文脈が失われます。スライド1枚を1チャンクにすることで、検索結果を「どの提案書の何枚目か」までピンポイントで示せるようにしました。
引用はメタデータ設計で後付けできる。 ベクトルと一緒に、どの提案書ファイルか、何枚目のスライドかというメタデータを保存しておくだけで、生成AIの回答に番号を振り、対応する根拠スライドを一覧表示する引用機能が実現できます。生成モデル側に特別な工夫は要りません。検索結果の順序とメタデータさえ保持していれば成立します。
検索と生成は役割が違う。 検索、つまりEmbeddingとベクトルDBは関連しそうな候補を持ってくる役割で、生成、つまりClaudeは持ってきた候補をもとに人間向けの回答文にまとめる役割です。生成AIに丸投げで検索させると根拠のない回答が混ざるリスクがありますが、この構成なら回答の根拠は必ず実在するスライドに紐づきます。
使ってみるとこんな感じ
Streamlitで簡易UIを作り、チャット形式で質問すると、回答と一緒に根拠スライドがサムネイルタイルで表示されます。「AI-OCRを使った窓口業務の提案はある?」のような質問でも、該当する提案書が引用付きで返ってきます。
回答の下には、根拠になったスライドがサムネイルで一覧表示されます。提案書名・スライド番号・類似度も一緒に出るので、どの文書のどこから来た答えなのかがすぐ分かります。
該当する提案書がない質問、存在しないサービス名などには、答えを捏造せず見当たらないと正直に返してきます。
おわりに
個人の学習用PoCではありますが、Embeddingからベクトル検索、生成AIによる引用付き回答まで、RAGの基本構成を一通り自分の手で組んでみたことで、仕組みの理解はだいぶ深まりました。特にEmbeddingは検索の道具というより、生成AIそのものの中で起きていることだという点はよく理解できた気がします。



