Context & Intent Orchestration Guardrails
Quality enforcement in the context orchestration layer prevents invalid searches, context pollution, and state inconsistencies.
Purpose
Context guardrails ensure:
- Valid intents before triggering searches
- Proper exclusion to avoid duplicates
- Context preservation for followup queries
- Graceful degradation when data is missing
Intent Gating Before Search
Purpose: Prevent pointless searches
Nonsense Detection
function isObviousNonsense(message: string): boolean {
// Too short
if (message.trim().length < 2) return true;
// Only punctuation/numbers
if (!/[a-zA-ZäöüõÄÖÜÕ]/.test(message)) return true;
// Repetitive characters
if (/(.)\1{5,}/.test(message)) return true;
return false;
}
if (isObviousNonsense(userMessage)) {
return clarifyingQuestionResponse(); // Don't search
}
Clarification Routing
function shouldAskClarifyingQuestion(
giftContext: GiftContext
): boolean {
// Low confidence
if (giftContext.confidence < 0.5) return true;
// No product signals
if (!giftContext.productType &&
!giftContext.category &&
(!giftContext.categoryHints || giftContext.categoryHints.length === 0)) {
return true;
}
// Author clarification needed
if (giftContext.meta?.authorClarificationNeeded) return true;
return false;
}
Benefit: Saves search costs, improves UX with targeted questions
Exclude List Discipline
Merge Strategy
Location: Context orchestrator
// Merge from multiple sources
const mergedExcludeIds = Array.from(new Set([
...clientExcludeIds, // From frontend (current session)
...storedContext.shownProductIds // From database (all sessions)
]));
Deduplication: Set ensures no duplicate IDs
Pruning Strategy
const MAX_EXCLUDE_LIST_SIZE = 30;
if (finalExcludeIds.length > MAX_EXCLUDE_LIST_SIZE) {
// Keep most recent 30 (FIFO)
finalExcludeIds = finalExcludeIds.slice(-MAX_EXCLUDE_LIST_SIZE);
console.log('✂️ EXCLUDE LIST PRUNED:', {
before: finalExcludeIds.length,
after: 30,
strategy: 'FIFO'
});
}
Why 30: Balance between variety and pool exhaustion
Reset Logic
When to reset exclude list:
function shouldResetExcludeList(
previousProductType: string,
currentProductType: string,
userMessage: string
): boolean {
// Never reset for "show more"
if (isShowMoreMessage(userMessage)) return false;
// Reset if product type truly changed
if (isBothSpecific(prev, curr) && prev !== curr) {
console.log(' RESETTING EXCLUDES: Product type changed', {
from: prev,
to: curr
});
return true;
}
return false;
}
Example:
"raamatud" → "kinkekaardid" → RESET (different types)
"raamatud" → "näita rohkem" → KEEP (same context)
Context Preservation
Preservation Intents
const PRESERVATION_INTENTS = new Set([
'show_more_products',
'cheaper_alternatives',
'budget_alternatives'
]);
if (PRESERVATION_INTENTS.has(giftContext.intent)) {
// Preserve taxonomy
preserveContext(storedContext, giftContext);
}
Preservation Logic
Field-Specific Priorities:
// categoryHints & isPopular: Frontend first
const categoryHints =
request.lastSearchParams?.categoryHints ?? // ← Priority 1
storedContext?.giftContext?.categoryHints ??
storedContext?.categoryHints;
// productType & category: Database only
const productType =
storedContext?.giftContext?.productType ?? // ← Only source
storedContext?.productType;
Why Different: Frontend has most recent search state; database has stable taxonomy
Graceful Degradation
Fallback Hierarchy
Example Fallback:
// Ideal: lastSearchParams exists
context = {
categoryHints: ['Ilukirjandus', 'Fantaasia'],
productType: 'Raamat',
isPopular: true
};
// Fallback 1: Only storedContext
context = {
categoryHints: ['Ilukirjandus'], // May be stale
productType: 'Raamat'
};
// Fallback 2: Derive from category
context = {
categoryHints: [giftContext.category], // Weak
productType: undefined
};
// Fallback 3: Generic
context = {
categoryHints: [],
productType: 'Kingitus' // Very broad
};
Quality Checks
Validation Before Storage
function validateContextBeforeStorage(
context: GiftContext
): ValidationResult {
const errors = [];
// Budget sanity
if (context.budget?.min > context.budget?.max) {
errors.push('Budget min > max');
}
// Product type consistency
if (context.productType && context.category) {
if (!isCompatible(context.productType, context.category)) {
errors.push('Incompatible productType and category');
}
}
// Exclude list size
if (context.excludeProductIds?.length > 100) {
errors.push('Exclude list too large');
}
return {
isValid: errors.length === 0,
errors
};
}
Context Sanitization
// Remove invalid characters
context.recipient = context.recipient?.trim().replace(/[^\w\s]/g, '');
// Normalize product type
context.productType = normalizeProductType(context.productType);
// Deduplicate hints
context.categoryHints = Array.from(new Set(context.categoryHints));
Monitoring
{
// Preservation metrics
preservationRate: number, // % of followups preserved
resetCount: number, // Context switches
// Exclude list metrics
avgExcludeSize: number,
pruningTriggered: number, // How often we hit 30
// Degradation metrics
fallbackTier1: number, // Used lastSearchParams
fallbackTier2: number, // Used storedContext
fallbackTier3: number, // Used defaults
// Quality metrics
intentConfidence: number, // Avg confidence score
clarificationRate: number // % needing clarification
}
Testing
describe('Context Guardrails', () => {
it('preserves context for show more', async () => {
const initial = { productType: 'Raamat', category: 'Ilukirjandus' };
const preserved = await orchestrateContext({
intent: 'show_more_products',
storedContext: initial
});
expect(preserved.productType).toBe('Raamat');
expect(preserved.category).toBe('Ilukirjandus');
});
it('resets excludes on context switch', () => {
const shouldReset = shouldResetExcludeList(
'Raamat', // previous
'Kinkekaart', // current
'kinkekaarte' // message
);
expect(shouldReset).toBe(true);
});
it('prunes large exclude lists', () => {
const large = Array(50).fill(0).map((_, i) => `id-${i}`);
const pruned = pruneExcludeList(large);
expect(pruned.length).toBe(30);
expect(pruned[0]).toBe('id-20'); // FIFO: kept last 30
});
});
Related Documentation
- Frontend Guardrails - Previous phase
- Search Guardrails - Next phase
- GiftContext System - Detailed context logic