Skip to main content

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');
});
});