Query Classification
How classifyQuery categorizes search input by word count and the planned evolution toward weighted routing.
The classifyQuery() function in packages/search/src/fusion.ts is a lightweight heuristic that categorizes a search query into one of three types based on word count. It is used by the hybridSearch.search and hybridSearch.debugSearch procedures.
Current Implementation
export function classifyQuery(query: string): "keyword" | "semantic" | "balanced" {
const words = query.trim().split(/\s+/).length;
if (words <= 3) return "keyword";
if (words >= 7) return "semantic";
return "balanced";
}
Classification Rules
| Word Count | Type | Rationale | Examples |
|---|---|---|---|
| 1-3 | keyword | Short queries are typically exact term lookups or entity names | "notion pricing", "AI", "competitor analysis" |
| 4-6 | balanced | Medium queries may be either keyword-oriented or natural language | "AI research workflow comparison", "document management market size" |
| 7+ | semantic | Longer queries are typically natural language questions | "what are the key differences between notion and coda for research workflows" |
Current Behavior
Today, the classification result is informational only. The hybridSearch.search procedure:
- Calls
classifyQuery()to get the type - Runs both keyword and semantic searches in parallel regardless of the type
- Fuses them with equal-weight RRF
- Returns the
queryTypein the response metadata
The classification appears in the Search Debugger as a badge above the results, helping developers understand how a query was categorized. But it does not alter the search execution or scoring.
Why Word Count?
The heuristic makes a pragmatic assumption: short queries tend to be exact lookups ("SWOT analysis", "pricing table") while long queries tend to be natural language ("how do competitors handle cost attribution in their pricing models"). This holds well for Trovella's research-oriented content where users alternate between looking up specific terms and asking open-ended questions.
The approach has known limitations:
- A 3-word query like "machine learning fundamentals" is classified as
keywordbut is arguably semantic in intent - A 7-word query with specific entity names like "Notion Coda Roam Research comparison pricing table" is classified as
semanticbut has strong keyword characteristics - Language and domain context are ignored entirely
These limitations are acceptable at the current scale because the classification does not affect search behavior. As it evolves to influence scoring (see below), more sophisticated approaches will be needed.
Planned Evolution: Weighted Routing
The classification system is designed to evolve into a weight router that biases RRF scores toward the more appropriate search engine for each query type.
Target Behavior
| Classification | Keyword Weight | Semantic Weight | Rationale |
|---|---|---|---|
keyword | 1.2-1.5x | 0.8-0.7x | Short exact queries benefit more from BM25 term matching |
balanced | 1.0x | 1.0x | Equal weighting (current default behavior) |
semantic | 0.8-0.7x | 1.2-1.5x | Natural language questions benefit more from vector similarity |
Implementation Approach
Weighted RRF modifies the score formula per source:
RRF(d) = w_keyword * 1/(k + rank_keyword) + w_semantic * 1/(k + rank_semantic)
This would require adding keywordWeight and semanticWeight parameters to reciprocalRankFusion(). The function signature would become:
function reciprocalRankFusion(
keywordResults: RankedResult[],
semanticResults: RankedResult[],
limit?: number,
k?: number,
weights?: { keyword: number; semantic: number },
): FusedResult[];
Prerequisites
Before implementing weighted routing:
- Baseline evaluation data. Collect a set of test queries with known-good results to measure whether weighting improves relevance. See Evaluation.
- Sufficient indexed content. The system needs enough content for BM25 and vector search to produce meaningfully different rankings.
- Admin tuning UI. A way to adjust weights experimentally and compare results. The existing Search Debugger is the natural home for this.
Test Coverage
The classifyQuery tests in packages/search/src/__tests__/fusion.test.ts verify all three classification buckets:
- Short queries (1-3 words) classify as
keyword - Long queries (7+ words) classify as
semantic - Medium queries (4-6 words) classify as
balanced
Related Pages
- Relevance Overview -- how classification fits into the scoring system
- RRF Algorithm -- the fusion algorithm that classification will eventually influence
- Tuning Guide -- practical tuning guidance including query routing