To Classify, Try a Trie Tree Quail, an execution engine for AI-SQL, executes the AI.CLASSIFY classification operator through three physical implementations — greedy label generation, scoring all labels at once, and scoring one letter per label — and picks the cheapest based on cost estimates, according to the company's blog post. AI.CLASSIFY accepts an input document, 2 to 255 labels of up to 25 words each, and options including a task_description of up to 50 words, a probabilities flag, and a selectivity estimate. The post argues that letting an LLM generate freely for classification is wasteful and fragile because the response may not contain a valid label. Back to blog https://fsdatalab.github.io/blog/ To Classify, Try a Trie Tree 1. Introduction We recently released Quail https://fsdatalab.github.io/blog/introducing-quail/ , an execution engine for AI-SQL. One of the most common AI-SQL operations is classification: given a document and a list of labels, pick the label that fits best. E.g., a support team might want to sort every incoming message into “billing”, “refund request”, “shipping delay”, and so on. Most systems classify a document the same way they answer any other question. They put the document and the labels into a prompt, let the LLM generate a response, and then parse the response into a label. The documentation of BigQuery’s AI.CLASSIFY https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-classify and Snowflake’s AI CLASSIFY https://docs.snowflake.com/en/sql-reference/functions/ai classify describes a similar interface: each function adds a prompt to the input, sends it to an LLM, and returns the category that best fits. But letting the LLM generate freely is both wasteful and fragile — the LLM can spend many tokens on its response, and the response may not even contain a valid label Since Quail runs the LLM inside its own inference engine, it doesn’t have to let the LLM generate freely. Instead, Quail can restrict the LLM to the labels, and it turns out there are several ways to do so, each with a different cost. In this article, we discuss: For a given LLM, how should Quail execute AI.CLASSIFY ? We’ll go through: - How AI.CLASSIFY works in Quail Section 2 2-aiclassify-in-quail . - Three ways that Quail can execute AI.CLASSIFY : generating the label greedily, scoring all labels at once, and scoring one letter per label Section 3 3-how-quail-executes-aiclassify . - How to estimate the cost of each way, building on our blog post on AI-powered filters https://fsdatalab.github.io/blog/ai-filter-cost-estimates/ , and how Quail picks the cheapest one Section 4 4-pricing-and-picking-a-method . - An aside on decision models, which are LLMs fine-tuned to pick among answers Section 5 5-aside-decision-models . 2. AI.CLASSIFY in Quail AI.CLASSIFY takes a document and a list of labels. It returns one of the labels. Here is the query that we use throughout the post, with Qwen3-4B-fp8 as the LLM. It sorts support messages into 10 topics: SELECT m.id, AI.CLASSIFY m.body, ARRAY 'billing', 'refund request', 'refund status', 'shipping delay', 'shipping damage', 'shipping address change', 'product defect', 'two-factor authentication issue', 'praise', 'other' AS topic FROM messages m; The function has three arguments: - input : the document to classify. Here, the input is the text of a support message. - categories : 2 to 255 labels. Each label can have a short description of up to 25 words. - options not in the example query : an object with optional settings. - task description : extra instructions for the LLM, up to 50 words. - probabilities : when true , Quail also returns the column topic probabilities , with one probability for each label. The probabilities sum to 1. - selectivity : an estimate of the fraction of rows that a condition on the label keeps, e.g., WHERE topic = 'billing' . The optimizer uses the estimate to plan the query. The estimate does not change the labels. Quail always returns one of the labels. AI.CLASSIFY can also appear in a WHERE clause. E.g., the following query keeps only the messages about refunds: SELECT m.id FROM messages m WHERE AI.CLASSIFY m.body, ARRAY ... IN 'refund request', 'refund status' ; A condition on the label uses = or IN . When the same classification appears in SELECT and in WHERE , Quail runs the classification only once. 3. How Quail Executes AI.CLASSIFY In database terms, AI.CLASSIFY is a logical operator. Quail supports three physical implementations of the operator, and the optimizer selects one implementation for each query. All three implementations send the LLM one prompt for each document. The prompt starts with DOCUMENT: and the document. Then it has a question with the list of labels. After the prompt comes the token ANSWER: , where the LLM scores the first token of the answer. The prompts of the three implementations differ slightly in the question and the label list, as shown in Figure 1 figure-1 . The implementations also select the label in different ways. trie decode generates the label token by token, trie tree scores every label in one request, and letters scores one letter for each label. 3.1 trie decode: Greedily Generate the Label trie decode is the implementation that is closest to what prior systems do. It generates the label one token at a time, as an LLM generates text.