大規模な本番環境でのRAG:1日1万件以上の求人情報を処理して得た教訓
求人サイト向けのRAGパイプラインを構築しました。ステージング環境では動作していましたが、実際の負荷がかかると苦戦しました。毎日数千件の求人情報を処理するには、単に優れたベクトルストアを用意するだけでは不十分です。システムがどこで破綻するのかを理解する必要があります。
ここでは、チャンキング、埋め込み(embeddings)、コスト、そしてオブザーバビリティ(可観測性)に関する教訓を紹介します。
1. チャンキング戦略を勘に頼らない
ほとんどのチュートリアルでは、チャンキングを単なる設定項目として扱っています。しかし、本番環境においては、その戦略が精度とコストを左右します。
求人情報に対して3つの手法をテストしました:
- 固定サイズ・チャンク(Fixed-size chunks): これは失敗でした。「要件」や「福利厚生」といったセクションがランダムな箇所で分割されてしまい、検索結果にノイズが混じる原因となりました。
- セマンティック・チャンキング(Semantic chunking): これ better でしたが、一貫性に欠けました。チャンクが長すぎたり、逆に短すぎたりすることがありました。
- オーバーラップを用いた再帰的文字分割(Recursive character splitting with overlap): これが最も効果的でした。改行と文章単位で分割し、サイズは400トークン、オーバーラップは50トークンに設定しました。これにより、2つのチャンクにまたがる文章が適切に接続されるようになります。
プロのヒント: チャンキングの前にデータを正規化してください。GreenhouseやLeverといった異なるソースからは、異なるフォーマットでデータが返ってきます。チャンカーが一貫した構造を認識できるように、まずはテキストをクリーニングしましょう。
2. 埋め込み(Embeddings):コスト vs 精度
Ollama経由のLlama 3.1と、OpenAIのtext-embedding-3-smallを比較テストしました。
ローカルモデルは無料ですが、「equity compensation(株式報酬)」のようなドメイン固有の用語に弱く、ノイズの多い結果を生成しました。
OpenAIはコストがかかりますが、正確なマッチングを実現しました。検索精度が低いと、その後のLLM呼び出しのコストが増大するため、私はOpenAIを選択しました。
時間を節約するために、リクエストをバッチ処理しています。1回の呼び出しで最大100個のチャンクを送信することで、レイテンシを抑え、パイプラインの速度を維持しています。
3. ベクトルストアのトレードオフ
プロトタイプ作成には、セットアップが容易なPineconeを使用しました。しかし、規模が拡大するにつれてコストが膨大になりました。
そこで、PostgreSQL内のpgvectorに切り替えました。
- セットアップの手間は増えました。
- しかし、大幅なコスト削減につながりました。
- また、トランザクションの整合性も確保できました。
埋め込みデータが求人データと同じデータベース内に存在するため、単一の「信頼できる情報源(source of truth)」を持つことができます。2つの異なるシステムを同期させる必要はありません。
4. LLMコストの制御
すべての求人情報をGPT-4oでスコアリングするのは高額です。コストを下げるために、以下の3つの戦術を用いました:
- OpenAI Batch API: スコアリングのジョブを夜間にまとめて処理します。これにより大幅な割引が受けられます。
- キャッシング: 繰り返し現れる候補者プロファイルの結果をキャッシュします。
- モデルの階層化(Model tiering): 「Sales Representative(営業担当)」のような一般的な職種にはGPT-4o-miniを使用します。精度が極めて重要なニッチな職種にのみ、GPT-4oを使用します。
5. まずオブザーバビリティを構築する
私のパイプラインは、かつてエラーを出さずに静かに失敗したことがありました。不正な形式のデータによって空のチャンクが生成され、システムがエラーを出さずにそれをスキップしてしまったのです。
これを解決するために、相関ID(correlation ID)を用いた構造化ロギングを追加しました。これにより、データの取り込みからスコアリングまで、特定の求人情報を追跡できるようになりました。その結果、どのデータソースが失敗の原因となっているのかを特定できるようになりました。
最大の教訓: ほとんどの問題はAIではなく、乱雑なデータから発生します。まずはデータの配管(data plumbing)を整えましょう。
Optional learning community: https://t.me/GyaanSetuAi
