Search Pipeline Guardrails
Quality enforcement in the search pipeline ensures relevant results through progressive filtering and intelligent fallbacks.
Purpose
Search guardrails ensure:
- Pre-flight validation stops bad queries early
- Deterministic retrieval keeps results reproducible
- Hard filters enforce constraints before ranking
- Quality gating removes weak candidates
- Fallback ladders prevent empty results
Pre-flight Guardrails
Location: Product search system
Nonsense Detection
// Block obviously invalid searches
if (isNonsenseQuery(query)) {
return {
products: [],
reason: 'nonsense-query',
suggestion: 'Please provide more details'
};
}
Implicit Budgets
// Add reasonable budget if missing
if (!giftContext.budget && giftContext.recipient) {
giftContext.budget = {
max: inferBudget(giftContext.recipient, giftContext.occasion)
};
// Child gift: €30, Adult gift: €50, etc.
}
Benefit: Better initial results, fewer followup refinements
Coverage Without Drift
Deterministic Query Rewriting
Stability features:
// Same context → same queries
const queries = generateQueryVariations(giftContext);
// Stable seeds (when testing)
if (process.env.NODE_ENV === 'test') {
queries.forEach(q => q.seed = 42);
}
// Reproducible results
const results = await executeParallelSearches(queries);
Why: A/B testing requires consistent baselines
Retrieval Stability
// Vector search with stable parameters
await convex.query(api.search.vectorSearch, {
query: queryText,
limit: 100,
seed: isTest ? 42 : undefined // Deterministic in tests
});
Hard Filters Before Ranking
Three-Stage Funnel
Stage A: Basic Filters
// Product type matching
if (productType && product.productType !== productType) {
return false;
}
// Exclude IDs
if (excludeIds.includes(product.id)) {
return false;
}
// Stock availability
if (product.stock <= 0) {
return false;
}
Stage B: Budget & Constraints
// Budget enforcement
if (budget?.max && product.price > budget.max) {
return false;
}
if (budget?.min && product.price < budget.min) {
return false;
}
// Safety constraints
if (constraints?.includes('EXCLUDE_BOOKS') && isBook(product)) {
return false;
}
// Allergy constraints
if (constraints?.includes('FOOD_ALLERGY') && isFoodRelated(product)) {
return false;
}
// Age appropriateness
if (ageGroup && !isAgeAppropriate(product, ageGroup)) {
return false;
}
// Author matching (for author searches)
if (authorName && !matchesAuthor(product, authorName)) {
return false;
}
Stage C: Category Distribution
// Limit per category for diversity
const MAX_PER_CATEGORY = 5;
const categoryCount = new Map<string, number>();
candidates.forEach(product => {
const count = categoryCount.get(product.category) || 0;
if (count >= MAX_PER_CATEGORY) {
exclude(product); // Too many from this category
} else {
categoryCount.set(product.category, count + 1);
}
});
Quality Gating
LLM Semantic Rerank
// Score finalists for relevance
const scored = await rerankProducts(finalists, giftContext);
// Quality thresholds
const PREFERRED_THRESHOLD = 0.5;
const MINIMUM_THRESHOLD = 0.3;
// Preferred: High quality
const highQuality = scored.filter(p => p.score >= 0.5);
if (highQuality.length >= 3) {
return highQuality.slice(0, 20);
}
// Fallback: Medium quality
const mediumQuality = scored.filter(p => p.score >= 0.3);
return mediumQuality.slice(0, 20);
Safety net: Always have minimum threshold
Diversity Selection
// Balance categories and prices
const diverse = selectDiverseProducts(scoredFinalists, 3);
// Enforce rules:
// - Max 1 gift card
// - Different categories preferred
// - Price range distribution
// - Top scores within diversity constraints
Fallback Ladder
When searches fail, progressive fallbacks prevent empty results:
Fallback 1: Language Relaxation (Books)
// Initial: Estonian books only
results = await search({ bookLanguage: 'et' });
if (results.length < 3) {
// Fallback: Any language
results = await search({ bookLanguage: undefined });
metadata.fallback = {
type: 'language-relaxation',
original: 'et',
broadened: 'any'
};
}
Fallback 2: Category Broadening
// Initial: Specific category
results = await search({ category: 'Eesti Luule' });
if (results.length < 3) {
// Fallback: Gift category
results = await search({ productType: 'Kingitus' });
metadata.fallback = {
type: 'category-broadening',
original: 'Eesti Luule',
broadened: 'Kingitus'
};
}
Fallback 3: Remove Excludes
// Initial: With excludes
results = await search({ excludeIds: [1,2,3,4,5...30] });
if (results.length < 3) {
// Fallback: Clear excludes (pool exhausted)
results = await search({ excludeIds: [] });
metadata.fallback = {
type: 'pool-exhaustion',
excludeCount: 30,
action: 'cleared'
};
}
Emergency Bypass
// Last resort: Generic gifts
if (results.length === 0) {
results = await search({
productType: 'Kingitus',
limit: 10,
sortBy: 'popular'
});
metadata.fallback = {
type: 'emergency-generic',
reason: 'all-strategies-failed'
};
}
Transparency Flags
All fallbacks set metadata flags for user transparency:
// Frontend can display:
if (metadata.fallback?.type === 'pool-exhaustion') {
showMessage('Oleme näidanud kõik sellised tooted. Siin on alternatiivid:');
}
if (metadata.fallback?.type === 'category-broadening') {
showMessage('Laiendasi otsingut sarnastele kategooriatele:');
}
Testing
describe('Search Guardrails', () => {
it('enforces budget hard filter', async () => {
const results = await search({
budget: { max: 20 },
productType: 'Raamat'
});
results.forEach(p => {
expect(p.price).toBeLessThanOrEqual(20);
});
});
it('triggers language fallback for books', async () => {
mockEmptyResults({ bookLanguage: 'et' });
mockResults({ bookLanguage: undefined }, 5);
const { results, metadata } = await search({
productType: 'Raamat',
bookLanguage: 'et'
});
expect(results.length).toBeGreaterThan(0);
expect(metadata.fallback?.type).toBe('language-relaxation');
});
});
Related Documentation
- Context Guardrails - Previous phase
- Response Guardrails - Next phase
- Orchestration System - Search architecture