三ヶ月前、僕らはRAGパイプラインを構築した。
正しいやり方で。ドキュメントをチャンクに分割し、埋め込みベクトルを生成し、ベクトルデータベースに格納し、類似度検索で関連するチャンクを取得する。教科書通りだ。カンファレンスのスライドに載っている通り。AIスタートアップが$24Mの資金調達を発表するときに説明する通り。
二週間後、やめた。
パイプラインの重さ
RAGが解決する問題は明快だ。データが多すぎてAIのコンテキストウィンドウに収まらない。だから検索する。関連する断片だけを取り出して、プロンプトに注入する。
問題は、この「だから検索する」の部分が一つのシステムではなく、五つのシステムだということだ。
チャンキング——ドキュメントをどこで切るか。段落?見出し?トークン数?意味の境界?切り方が違えば結果が違う。正解はドキュメントの種類ごとに違う。会議の議事録と技術仕様書では最適なチャンクサイズが違う。これだけで一つのエンジニアリング問題だ。
埋め込み——テキストをベクトルに変換する。どのモデルを使うか。OpenAIのada?Cohereのembed?ローカルのsentence-transformers?モデルが変われば、全てのベクトルを再計算する必要がある。数千のドキュメントなら数時間。数万なら一日。
格納——ベクトルデータベースが必要だ。Pinecone?Weaviate?Chroma?pgvector?それぞれに運用コストがある。バックアップ、スケーリング、モニタリング。データベースが一つ増えるということは、障害点が一つ増えるということだ。
検索——クエリをベクトル化して、最も近いチャンクを取得する。でも「最も近い」は「最も関連する」ではない。ベクトル類似度は意味的な近さの近似であって、正確な一致ではない。「デプロイ手順」を検索して「デプロイの失敗事例」が返ってくることがある。言葉は近い。意図は違う。
同期——ドキュメントが更新されたら?チャンクを再分割し、再埋め込みし、古いベクトルを削除し、新しいベクトルを格納する。これを自動化するパイプラインが必要だ。パイプラインにはモニタリングが必要だ。モニタリングにはアラートが必要だ。
五つのシステム。それぞれにエッジケースがある。それぞれにメンテナンスが必要だ。そしてこれら全てが解決する問題は——ファイルを読むことだ。
僕らがやめた理由
ourstack.devのドキュメントは多くない。数百ファイルだ。数万ではない。
RAGパイプラインを動かしていた二週間で、三つのことに気づいた。
第一に、検索結果が不安定だった。同じ質問を少し違う言い方で聞くと、違うチャンクが返ってくる。「パーミッションの設定方法」と「アクセス制御のやり方」は同じことを聞いているのに、別のドキュメントが返ってくる。ユーザーがクエリの言い回しに敏感なシステムは、検索エンジンではなくスロットマシンだ。
第二に、チャンクの境界が文脈を壊していた。ドキュメントの前半に定義があり、後半に使用例がある。チャンクが後半だけを返すと、定義なしの使用例になる。意味が半分欠落したテキストをAIに渡して「これに基づいて答えて」と言うのは、ページの下半分だけコピーして「これ読んで」と言うのと同じだ。
第三に、修正のループが終わらなかった。チャンクサイズを調整する。埋め込みモデルを変える。検索のリランキングを追加する。メタデータフィルターを足す。一つ直すと別の問題が出る。問題はパラメータではなかった。抽象化そのものだった。
ファイルはそのまま読める
RAGを捨てた後にやったことは、驚くほど単純だった。
ドキュメントを整理した。
必要な情報をmarkdownファイルに構造化した。ファイル名を検索しやすくした。ディレクトリ構造を論理的にした。そしてAIに「このディレクトリを読んで」と言った。
それだけだ。
埋め込みなし。ベクトルデータベースなし。チャンキングなし。同期パイプラインなし。ファイルが更新されたら、次に読むときに更新された内容が読まれる。同期の問題は存在しない——ファイルシステムが同期だから。
「でも、データが多すぎたら?」
多くの場合、多すぎない。
僕らのプロジェクトのドキュメントは全部合わせても数MBだ。コンテキストウィンドウに全部入れる必要はない。必要なファイルだけ読めばいい。ファイルのパスが分かっていれば——そしてディレクトリ構造が論理的なら——検索する必要がない。ナビゲーションで十分だ。
RAGが必要になるのは、データが本当に膨大で、どこに何があるか分からないときだ。数百万のドキュメント。構造化されていないデータ。ログの山。そういう規模なら、検索は必要だ。
でも僕の経験では、AIと仕事をしているほとんどのチームはその規模にいない。ドキュメントは数十から数百。コードベースは大きくても、AIが一度に必要とする情報は限られている。その限られた情報のために、五つのシステムを運用するのか。
修正の問題
RAGにはもう一つ、あまり語られない問題がある。フィードバックループだ。
AIが間違った回答をしたとする。ユーザーが訂正する。「いや、パーミッションの設定はこうだ」と。
構造化ファイルなら、そのファイルを開いて修正する。次にAIがそのファイルを読むとき、正しい情報が反映される。フィードバックから修正まで、一つの編集だ。
RAGパイプラインの場合はどうか。まず元のドキュメントを修正する。次にそのドキュメントを再チャンクする。再埋め込みする。古いベクトルを削除する。新しいベクトルを格納する。そして次の検索で正しいチャンクが返ってくることを祈る——なぜなら、修正したチャンクが検索結果のトップに来る保証はないからだ。
一つの訂正に五つのステップ。しかも最後のステップは確率的だ。
これがRAGの本質的な問題だ。検索が確率的であるため、修正も確率的になる。「直した」と「直ったことが反映される」の間にギャップがある。そのギャップは、パイプラインが複雑になるほど広がる。
正しい抽象化
RAGが間違っていると言いたいわけではない。RAGは特定の問題に対する正しい解決策だ。その問題とは——本当に膨大な非構造化データの中から情報を見つけること。
問題は、ほとんどのチームがその問題を持っていないのに、RAGを導入していることだ。
会計事務所が顧客の書類をAIに読ませたい。RAGが必要か?書類は数百件だ。フォルダに入れて読ませればいい。法律事務所が判例を検索したい。数万件ある。これはRAGの出番だ。
区別は単純だ。ファイルシステムのナビゲーションで十分なら、RAGは過剰だ。ナビゲーションでは追いつかないなら、RAGが必要だ。
僕らは前者だった。ほとんどのチームも前者だ。
インフラの複雑さには慣性がある。一度構築すると、捨てにくい。「せっかく作ったから」という理由で維持する。でもその維持コストは、ファイルを整理するコストの何倍にもなる。
四つのシェルスクリプトとmarkdownファイルが、ベクトルデータベースに勝った。勝因はシンプルさだ。シンプルなシステムは壊れにくく、直しやすく、理解しやすい。そしてAIにとって——読みやすい。
最高のインフラは、存在しないインフラだ。
— Max