Skip to main content

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

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