高基数状況におけるクエリ分析のベンチマーク評価
LangChain は、LLM が構造化データ抽出時に高カルディナリティの値を正しく扱えない課題に対し、コンテキスト制限や推論コストの問題を解決する分析手法とドキュメントを更新した。
キーポイント
高カルディナリティ定義と課題
LLM は特定の値のセット(列挙型)から選ぶ際、可能な値の範囲が膨大だと正しく認識できず、幻覚を起こすかコンテキスト枠を超えてしまう。
単純なプロンプト注入の限界
許容値をすべてプロンプトに含める手法は、値が増えるとコスト増、速度低下、推論能力の分散といった深刻な問題を引き起こす。
Query Analysis の再構築
LangChain は構造化データ抽出とクエリ分析のドキュメントを刷新し、高カルディナリティ値への対応策を明示的に追加した。
影響分析・編集コメントを表示
影響分析
本記事は、LLM を用いた構造化データ抽出の実務において頻出する「値の範囲が膨大すぎる」問題への解決策を提示しており、開発者がプロダクション環境で安定したクエリ分析を実装するための重要な指針となる。特にコンテキスト制限と推論コストのバランスを取る手法は、大規模な列挙型データを扱うシステム設計において即座に適用可能な知見である。
編集コメント
実務レベルの LLM アプリケーション開発において、単に「LLM に任せる」だけでは解決できない境界ケース(高カルディナリティ値)への対処法を体系的に解説しており、エンジニアにとって非常に有益な情報です。
LLM の主要なユースケースのいくつかには、構造化形式でデータを返すことが含まれます。抽出はそのようなユースケースの一つであり、私たちは最近 更新されたドキュメント と 専用リポジトリ でこれを強調しました。クエリ分析もその一例です - 私たちは最近これに関する ドキュメントの更新 も行っています。構造化形式で情報を返す場合、フィールドは文字列、ブール値、整数など多様な型になり得ます。正しく処理するのが最も難しい型の一つが、高カルディナリティのカテゴリカル値(または列挙型)です。
「高カルディナリティのカテゴリカル値」とは何を意味するのでしょうか?「カテゴリカル値」とは、いくつかの可能な値のうちの一つでなければならない値を指します - 例えば、任意の数や文字列ではなく、許可されたセットに含まれている必要があります。「高カルディナリティ」というのは、有効な値が多数存在することを意味します。
なぜこれが難しいのでしょうか?これは、LLM がそのフィールドが実際に取りうる値を本能的に知らないからです。したがって、LLM に対して可能な値の範囲に関する情報を提供する必要があります。これを怠ると、LLM はでたらめな値を作り出してしまいます。カテゴリカル値の可能性が少数であればこれは簡単です——これらの値をプロンプトに記述し、LLM に丁寧に「これらの値のみを使用してください」と指示するだけで済みます。しかし、値の数が膨大になると話は複雑になります。
可能な値の数が増えるにつれて、LLM が正しい値を埋め込むことは次第に難しくなります。第一に、可能な値の数が十分に多くなると、コンテキスト(文脈)に収まらなくなる可能性があります。第二に、たとえすべての可能な値がコンテキストに収まる場合でも、それらをすべて詰め込むと、速度やコストの問題が生じるだけでなく、LLM がそのすべてのコンテキストを推論する能力にも支障をきたします。
私たちは最近、クエリ分析について多くの時間を費やしてきました。そして、このユースケース向けのドキュメントを刷新する際、高カルディナリティのカテゴリカル値への対処法に関するページを明示的に追加しました。今回のブログでは、私たちが試行したいくつかのアプローチについて深く掘り下げ、それらのパフォーマンスに関する具体的なベンチマークを提供したいと思います。
以下は結果のプレビューです。LangSmith でも確認できます。詳細については以下をご覧ください:

データセットについて
こちら でデータセットを確認できます。
この問題をシミュレーションするために、著者名で本を検索したい状況を想定します。著者フィールドは高カルディナリティのカテゴリ変数です。取りうる値は多数存在しますが、それらはすべて特定の有効な著者名であるべきです。これをテストするため、著者名とその一般的な別称からなるデータセットを作成しました。例えば、ハリー・チェイス(Harry Chase)はハリソン・チェース(Harrison Chase)の別称となり得ます。賢明なシステムであれば、こうした別称を処理できることを期待します。この名前と別称のリストが整った後、さらに 10,000 のランダムな名前を追加生成しました。なお、10,000 という数は決して高カルディナリティとは言えません。エンタープライズシステムでは、これは数百万に達することさえあります。
このデータセットを作成した後、私たちは「ハリー・チェイスによる宇宙人についての書籍は何か?」といった質問を投げかけます。クエリ分析システムはこれを構造化された形式に解析し、2 つのフィールド(トピックと著者)を持つべきです。この場合、期待される結果は {"topic": "aliens", "author": "Harrison Chase"} となります。システムは「ハリー・チェイス」という名前の著者は存在しないが、「ハリソン・チェイス」こそがユーザーの意図した名前である可能性を認識すべきだと考えられます。
この設定のもと、作成したエイリアス(別名)のデータセットに対してこの演習を実行し、それらが真の名前に正しくマッピングされたかを確認します。また、レイテンシとコストも追跡します。このようなクエリ分析システムは検索によく使用されるため、これらの両方の要素に合理的な程度まで関心を持ちます。そのため、すべてのアプローチを1 回のみの LLM(大規模言語モデル)呼び出しに制限しました。複数の LLM 呼び出しを行うアプローチについては、今後の投稿でベンチマークする可能性があります。
以下に、いくつか異なるアプローチとそのパフォーマンス結果を示します。
LangSmith で完全な結果はこちらで確認でき、再現用のコードはこちらにあります。
ベースライン
まず、LLM に有効な名前に関する知識を与えずに単にクエリ分析を行わせるというベースラインをベンチマークしました。予想通り、この方法では正解する回答は一つも得られませんでした。これは意図的な設計によるものです。私たちは、著者をその別名で特定することを明示的に求めるデータセットをベンチマークしているのです。
コンテキストの詰め込み
このアプローチでは、10,000 件の正当な著者名すべてをプロンプトに詰め込み、LLM にそれらが正当な著者名であることを念頭に置いてクエリ分析を行わせました。一部のモデル(GPT-3.5 など)は、コンテキストウィンドウの制限によりこの処理自体を実行できませんでした。より長いコンテキストウィンドウを持つ他のモデルでも、正しい名前を正確に選択することに苦戦しました。GPT-4 は例の26%でしか正しい名前を選択しませんでした。最も頻繁に起こったミスは、名前を修正せずに抽出してしまうことでした。この方法はまた、非常に遅く高コストであり、平均して実行に5 秒かかり、合計で8.44 ドルのコストがかかりました。
LLM 前フィルタリング
次にベンチマークしたアプローチは、LLM に渡す可能性のある値のリストをフィルタリングするものです。これにより、LLM に可能な名前のサブセットのみを渡すことができ、考慮すべき名前の数が大幅に減るため、クエリ分析をより迅速かつ安価、そして正確に行えるようになるはずです。ただし、これには別の潜在的な失敗モードが追加されます。もし初期のフィルタリングが間違っていたらどうなるでしょうか?
埋め込み類似性によるフィルタリング
初期のフィルタリングでは、埋め込みに基づくアプローチを使用し、クエリに対して最も類似度の高い 10 件の名前を選択しました。ここで注意すべきは、クエリ全体と名前を比較している点です—これは必ずしも最適な比較方法ではありません!
このアプローチを用いた場合、GPT-3.5 は例の57%を正しく処理できることがわかりました。また、この方法は以前の手法よりもはるかに高速かつ低コストで、平均実行時間は0.76 秒、総費用は$0.002でした。
NGram 類似性によるフィルタリング
2 つ目のフィルタリングアプローチでは、すべての有効な名前の 3-gram 文字列シーケンスに TF-IDF ベクトライザー(TF-IDF vectorizer)を適合させ、ベクトル化された有効な名前とベクトル化されたユーザー入力の間のコサイン類似度(cosine similarity)を用いて、モデルのプロンプトに追加する最も関連性の高い上位 10 件の有効な名前を選択しました。ここで注意すべき点は、クエリ全体と名前を比較している点です—これも必ずしも最適な比較方法ではありません!
このアプローチを用いた場合、GPT-3.5 は例の65%を正しく処理できることがわかりました。また、この方法は以前の手法よりもはるかに高速かつ低コストで、平均実行時間は0.57 秒、総費用は$0.002でした。
Post-LLM Selection
最終的にベンチマークした手法は、LLM を用いてクエリ分析を開始し、その後で発生したミスを修正するというものでした。まず、ユーザー入力に対してクエリ分析を実行しましたが、プロンプトには有効な著者名に関する情報は一切含めませんでした。これは当初実行したベースラインと同じ条件です。その後、著者フィールドに含まれる名前を取得し、最も類似する有効な名前を見つけるステップを追加しました。
Select by Embedding Similarity
まずこの類似性チェックは、埋め込み(embeddings)を用いて行いました。
このアプローチでは GPT-3.5 が83%の例を正しく処理できることが分かりました。また、この方法は以前の手法よりもはるかに高速で安価であり、平均実行時間は0.66 秒、総コストは$0.001でした。
Select by NGram Similarity
最後に、3-gram ベクトライザー(vectorizer)を用いて同様の類似性チェックを試みました。
このアプローチでは GPT-3.5 が74%の例を正しく処理できることが分かりました。これもまた以前の手法よりもはるかに高速で安価であり、平均実行時間は0.48 秒、総コストは$0.001でした。
Conclusion
高カルディナリティ(high cardinality)の分類項目に対するクエリ分析のために、さまざまな手法をベンチマークしました。単一の LLM 呼び出しに制限したため、チェーン処理やエージェント技術の使用は避けました。これは現実世界のレイテンシ制約を模倣するためです。その結果、埋め込み類似性を用いたポスト-LLM 選択(post-LLM selection)が最も優れたパフォーマンスを示しました。
ベンチマークには他の方法もあります。特に、LLM の呼び出しの前または後に最も類似したカテゴリ値を見つけるための多くの方法を検討する必要があります。このデータセットで使用されているカテゴリは、多くのエンタープライズシステムが対処しなければならないものほど高カルディナリティではありません。このデータセットには約 10,000 個の値が含まれていましたが、多くの実世界のデータセットでは数百万に達します。より高カルディナリティのデータ、およびより多くのデータでベンチマークを行う価値があります。
原文を表示
Several key use cases for LLMs involve returning data in a structured format. Extraction is one such use case - we recently highlighted this with updated documentation and a dedicated repo. Query analysis is another - we’ve also updated our documentation around this recently. When returning information in a structured format the fields can be a myriad of types: string, boolean, integers. One of the hardest types to correctly handle is high-cardinality categorical values (or enums).
What does “high cardinality categorical values” mean? “Categorical values” refers to values that need to be one of several possible values - e.g. they cannot be an arbitrary number or string, they have to be in an allowed set. By “High cardinality” we mean that there are many valid values.
Why is this hard? This is hard because the LLM does not intrinsically know what the possible values the field could be actually are. Therefore, you need to provide information to the LLM about the range of possible values. If you do not do this, then it will make up values. For a small number of possible categorical values, this is easy - you can just put those values in the prompt and ask the LLM nicely to only use those values. However, for a large amount of values it gets tricker.
As the number of possible values increases, it becomes harder and harder for the LLM to fill in the correct value. First, if the number of possible values gets high enough then they may not fit in context. Second, even if all possible values could fit in context, stuffing them all in would cause issues with speed, cost, and the ability of the LLM to reason over all of that context.
We’ve spent a lot of time thinking about query analysis recently, and when we revamped the documentation for this use case we explicitly added in a page on how to deal with high cardinality categorical values. In this blog, we want to deep dive into a few of the approaches we experimented with and provide concrete benchmarks for how they perform.
Here’s a preview of the results, which you can also view in LangSmith. Read on for more details:

The Dataset
You can view the dataset here.
In order to simulate this problem we will imagine a situation where we want to look up books by an author. The author field is a high cardinality categorical variable - there are many different values it could take on, but they should be specific valid author names. In order to test this, we created a dataset of author names and common aliases. For example, Harry Chase could be an alias for Harrison Chase. We would hope that an intelligent system would be able to handle these types of aliases. Once we had this list of names and aliases, we then generated 10,000 other random names. Note that 10,000 is not even really that high of cardinality - for enterprise systems, this could perhaps be in the millions.
With this dataset created, we then would ask questions like “what are books by Harry Chase about aliens?”. Our query analysis system would be expected to parse this into a structured format, with two fields: topic and author. In this case, the expected result would be {“topic”:’ “aliens”, “author”: “Harrison Chase”}. We would expect our system to recognize that there was no author named Harry Chase, but that Harrison Chase was probably what the user meant.
With this setup, we could run this exercise for the dataset of aliases we created, seeing if they were correctly mapped to the true name. We would also track latency and cost. This type of query analysis system would often be used for search, which means that we would care a reasonable amount about both those factors. For this reason we also restricted all approaches to only make a single LLM call. We may benchmark approaches with multiple LLM calls in a future post.
Below, we present a few different approaches and how they performed.
You can see the full results in LangSmith here, and code to reproduce them here.
Baseline
First, we benchmarked the baseline of just asking the LLM to do query analysis without giving it knowledge of what valid names could be. As expected, this did not get a single response correct. This is by construction - we are benchmarking a dataset which explicitly asks for authors by their aliases.
Context Stuffing
In this approach we stuffed all the 10,000 legitimate author names into the prompt and asked the LLM to do the query analysis keeping in mind that those are the legimate author names. Some models (like GPT-3.5) were not even able to run this at all (due to context window limitations). For other models that had longer context windows, they struggled to accurately choose the right name. GPT-4 only chose the right name on 26% of examples. The biggest mistake that it would make was extract the name without correcting it. This method was also fairly slow and costly, taking on average 5 seconds to run and costing $8.44 in total.
Pre-LLM Filtering
The next approach we benchmarked was filtering the list of possible values that we passed to the LLM. This would have the benefit of passing a subset of the possible names to the LLM, which would mean that there would be far less names for it to have to consider, hopefully allowing it to do the query analysis more quickly, cheaply, and accurately. This does add in a separate potential failure mode - what if the initial filtering is wrong?
Filter by Embedding Similarity
For the initial filtering we used an embedding based approach and selected the 10 most similar names to the query. Note that here we are comparing the whole query to the name - this isn’t a great comparison!
We found that with this approach GPT-3.5 managed to get 57% of examples correct. This was also much faster and cheaper than the previous method, taking 0.76 seconds to run on average and costing $.002 total.
Filter by NGram Similarity
The second filtering approach we used was to fit a TF-IDF vectorizer to 3-gram character sequences of all the valid names, and to use cosine similarity between the vectorized valid names and the vectorized user input to select the top 10 most relevant valid names to add to the model prompt. Note that here we are comparing the whole query to the name - this isn’t a great comparison!
We found that with this approach GPT-3.5 managed to get 65% of examples correct. This was also much faster and cheaper than the previous method, taking 0.57 seconds to run on average and costing $.002 total.
Post-LLM Selection
The final method we benchmarked involved doing query analysis with the LLM to start and then trying to correct any mistakes after the fact. We first ran a query analysis on the user input and did not put ANY information about what valid author names could be into the prompt. This was the same as the baseline we ran initially. We then ran a step after that which took the name that was in the author field and found the most similar valid name.
Select by Embedding Similarity
First we did this similarity check by using embeddings.
We found that with this approach GPT-3.5 managed to get 83% of examples correct. This was also much faster and cheaper than the previous method, taking 0.66 seconds to run on average and costing $.001 total.
Select by NGram Similarity
Lastly we tried doing this similarity check using our 3-gram vectorizer.
We found that with this approach GPT-3.5 managed to get 74% of examples correct. This was also much faster and cheaper than the previous method, taking 0.48 seconds to run on average and costing $.001 total.
Conclusion
We benchmarked a variety of methods for doing query analysis with high cardinality categoricals. We constrained ourselves to only being allowed to make a single LLM call, which prevented us from using chaining or agent techniques. This was done to mimic real-world latency constraints. We found that using post-LLM selection via embedding similarity performed best.
There are other methods to benchmark. In particular, there are many methods to consider for finding the most similar categorical value (before or after the LLM call). The category used in this dataset is also not as high cardinality as the ones many enterprise systems have to deal with. This dataset had ~10k values, many real world ones have millions. It would be worthwhile to benchmark on higher cardinality data, and more of it.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み