AIアプリケーションを作るとき、最初に直面する壁があります。
それは「どうやって自社のデータをAIに理解させるか」という問題です。
最近、Reddit上で興味深い投稿を見つけました。
あるエンジニアが1200時間以上かけてエンタープライズ向けRAGシステムを開発したのです。
そして、その過程で得た教訓を共有していました。
学術論文の理想と本番環境の現実のギャップ。
実際に機能する技術の組み合わせ。
これらについて、詳細に語られていたのです。
今回は、その投稿とコミュニティの反応から、実践的なRAGシステム構築の要点を整理してみます。
RAGの本質:カスタムモデルは不要
多くの人が誤解していることがあります。
AIアプリケーションの開発には、独自のモデル訓練が必要だと思い込んでいるのです。
しかし現実は違います。
ほとんどの商用AIアプリケーションは、既存モデルをそのまま使っています。
OpenAI、Google、Anthropic、xAIなどの大手プロバイダー。
あるいはLlamaやMistralのようなオープンソースモデル。
これらを活用しているのです。
なぜか?
モデルの訓練には膨大なリソースが必要だからです。
しかも、最先端モデルの性能差は急速に縮まっています。
では、開発者は何をするのか。
答えはRAG(Retrieval Augmented Generation)です。
既存のモデルに適切なデータを与える。
そして、望む答えを引き出す。
この技術を磨くことが本質なのです。
データの前処理:すべてはMarkdownへ
企業のデータは混沌としています。
SharePoint、Notion、Confluence。
PDF、Office文書。
形式はバラバラです。
投稿者が辿り着いた解決策はシンプルでした。
すべてをGitHub Flavored Markdown(GFM)に変換するのです。
なぜMarkdownなのか?
平文でありながら、構造を持っているからです。
見出し、リスト、階層的な特徴。
LLMはこの形式を非常によく理解します。
変換パイプラインの構築には、以下のような工夫が必要でした:
- PDFからMarkdownへの変換:Gemini 2.5 Flashを使用
- Office文書の処理:Gotenbergというツールを活用
- 不要な要素の除去:改行、フォーマット情報、ヘッダー、フッター
重要なのは、単なる変換ではありません。
「クリーンアップ」が必要なのです。
検索の邪魔になる要素を丁寧に除去していきます。
チャンキング:文脈を失わない分割術
LLMにはコンテキストウィンドウの制限があります。
かといって、すべてのデータを詰め込むわけにもいきません。
コストが跳ね上がります。
性能も劣化します。
チャンキング手法には様々なアプローチがあります。
投稿者が最も効果的だと判断したのは「Document-Based Chunking」でした。
Markdownの構造に基づいて分割する方法です。
見出し、段落、コードブロック。
これらを単位として切り分けます。
しかし、ここで大きな問題が発生します。
「ベルリンの人口は385万人を超え…」
この文章があったとします。
この部分だけを切り出したら?
「ベルリン」という主語が失われてしまうのです。
文脈パスの追加という解決策
この問題に対する解決策が秀逸でした。
MarkdownのAST(抽象構文木)から階層構造を抽出します。
そして、各チャンクの先頭にパンくずリストとして追加するのです。
# 例:段落が "Berlin > History > Prehistory" の階層にある場合 context_path = "Berlin > History > Prehistory" chunk_with_context = f"{context_path}\n\n{chunk_content}"
さらに工夫があります。
文書がフォルダ構造の中にある場合は、フォルダパスも含めます。
これにより、どんな小さなチャンクでも元の文脈を保持できるようになりました。
エンベディング:Late Chunkingという革新
テキストを数値ベクトルに変換するエンベディング。
これはRAGの心臓部です。
しかし、ここにも落とし穴があります。
従来のアプローチを見てみましょう。
まず文書をチャンクに分割します。
それぞれを個別にエンベディングします。
でも、これでは文書全体の文脈が失われてしまいます。
Late Chunkingは、この順序を逆転させます:
# 従来のアプローチ chunks = chunk_document(full_document) embeddings = [embed(chunk) for chunk in chunks] # 個別にエンベディング # Late Chunkingアプローチ chunks = chunk_document(full_document) contextual_embeddings = embed(chunks) # 全チャンクを一緒にエンベディング
全体を考慮しながら各部分のエンベディングを作成する。
これにより、より文脈を反映したベクトル表現が得られるのです。
階層的検索:二段階で絞り込む
投稿者が特に強調していたのが、階層的な検索戦略です。
第一段階では、文書レベルでの候補を絞り込みます。
文書の要約エンベディングを使います。
全文検索も組み合わせます。
こうして関連する可能性のある文書を特定するのです。
第二段階はどうでしょうか。
選ばれた文書内のチャンクに対して詳細な検索を実行します。
セマンティック検索とキーワード検索。
この二つをハイブリッドでスコアリングします。
この方法のメリットは明確です。
数百万の文書から効率的に関連情報を見つけ出せるようになりました。
HyDEとクエリ拡張
ユーザーのクエリは多くの場合、短く曖昧です。
「四半期結果」とだけ入力される。
何の四半期結果なのか分かりません。
HyDE(Hypothetical Document Embeddings)は、この問題に対する巧妙な解決策です。
仕組みはこうです。
ユーザーのクエリに対する仮想的な回答を生成します。
その回答のエンベディングを使って検索するのです。
# 通常のアプローチ query_embedding = embed("四半期結果") # HyDEアプローチ hyde_answer = llm.generate("四半期結果にはどんな内容が含まれる?") # → "四半期結果には売上高23億円、利益率15%..." hyde_embedding = embed(hyde_answer)
短いクエリと詳細な文書のギャップ。
これを埋める、エレガントな手法です。
自己反省型RAG
検索結果を取得したら、それで終わりではありません。
システムは自身の検索結果を評価します。
情報のギャップを特定します。
重要度が高いギャップが見つかったら?
最大2回まで追加検索を実行します。
ただし、制限があります。
3回以上の検索は避けるべきです。
遅延が増える割に効果が薄いことが分かっています。
リランキングの落とし穴
多くの論文がリランキングの有効性を主張しています。
しかし、投稿者の経験では異なる結論に至りました。
適切に設計された階層的検索システムを考えてみましょう。
リランキングによる品質向上はわずか5-10%程度です。
それに対して、レスポンス時間は倍増してしまいます。
パレート最適の観点から見るとどうでしょう。
現在の実装では、リランキングを無効化しているそうです。
コミュニティからのフィードバック
この投稿に対して、多くの実務者から共感と追加の知見が寄せられていました。
特に印象的だったコメントがあります。
メタデータフィルタリングの重要性を指摘するものです。
時間フィルタだけではありません。
部門、プロジェクト、ドキュメントタイプ。
様々なメタデータを活用します。
これにより、検索空間を大幅に削減できるとのことでした。
また、可観測性ツールの導入を勧める声もありました。
LangFuseやOpikのようなツールです。
どのクエリがうまく機能しているか。
どれが失敗しているか。
これらを継続的にモニタリングすることの重要性が強調されていました。
パフォーマンス最適化のポイント
投稿者が実装したシステムは高速に動作します。
その秘訣は何でしょうか。
まず、HNSWベクトルインデックスの活用です。
IVFインデックスと比較して、約95%高速な類似検索を実現しています。
次に、PostgreSQLの機能を最大限に活用しています。
マテリアライズドCTEを使用。
候補セットの再計算を避けています。
並列処理も重要です。
自己反省型検索で追加クエリを実行する際、並列に処理します。
そして、賢いキャッシング戦略。
エンベディングとクエリ拡張の結果をキャッシュ。
無駄な再計算を避けています。
結果として、数百万の文書を持つ企業データセットでも、2秒以内に結果を返せるようになりました。
まとめ
RAGシステムの構築は、単一の技術や手法に頼るものではありません。
データのクリーニングから始まります。
適切なチャンキング。
文脈を保持したエンベディング。
階層的な検索。
クエリの理解と拡張。
これらを組み合わせることで、初めて実用的なシステムが完成します。
学術論文の理想的な環境とは異なります。
実際の企業データは混沌としています。
ユーザーのクエリも曖昧で不完全です。
この現実を受け入れましょう。
そして、実践的な解決策を積み重ねる。
これが成功するRAGシステムの鍵となるでしょう。
投稿者が1200時間かけて学んだ最も重要な教訓。
それは「魔法の解決策は存在しない」ということです。
地道な改善の積み重ね。
これこそが、本当に機能するシステムを生み出すのです。