Pipeline & scenarios
Request stages, nextQL, constraints, auto-relax.
04 - Request pipeline & scenarios
Related: Logic & algorithms · Playbook
Stages of a recommendation request
Every recommendation request runs roughly this pipeline. Order matters when explaining “why isn’t item X showing?”
Stage notes
| Stage | What it does | Tunable in Admin? |
|---|---|---|
| 1 Resolve | Load scenario by slug; pick algorithm (or default for logic) | Scenario editor |
| 2 Candidates | CH / Qdrant / factors / category fallback | Algorithm + Qdrant/hybrid settings |
| 3 Availability | Drop available: false | Catalog / feeds |
| 4 Filters | Remove non-matching items | filter, rules type=filter |
| 5 Boosters | Multiply scores | boosters, rules type=booster |
| 6 Constraints | Diversity, freshness ×1.5, long-tail inject | constraints, rules type=constraint |
| 7 Segments | Balance across segments | segmentationId, itemsPerSegment |
| 8 Purchase cap | Cap already purchased (90d) | purchasedItemsMaxPercent (default 10); needs userId |
| 9 Min/max | Ensure enough results | minItems, maxItems, autoRelax* |
Implementation detail: filters/boosters run inside applyScenario; constraints + segments + purchase cap run in postRankPipeline / ensureMinItems.
Scenario fields that matter
| Field | Purpose |
|---|---|
slug | Placement key (pdp-similar-products) |
logicType | user-to-item / item-to-item / items-to-items / similar-products / recently-viewed |
algorithm | Override default algo for that logic |
filter | nextQL remove predicate |
boosters[] | nextQL boost expressions |
constraints | maxPerCategory, freshnessBoostDays, injectLongTail, longTailPercentage, purchasedItemsMaxPercent |
qdrantSettings | hnsw_ef, score_threshold, filter_categories, quantization_* (vector logics) |
hybridSettings | multi-seed-hybrid: strategy, rrfK, coTopK, vectorTopK, categoryBoost, … |
rulesSlugs[] | Attach shared filter/booster/constraint rules |
minItems / maxItems | Floor / ceiling on result count |
autoRelaxConstraints | If under-filled, loosen constraints and retry |
segmentationId / itemsPerSegment | Diversity across segments |
Request-time post-process overrides
Recommendation APIs can replace selected post-process fields on a single request without editing the saved scenario:
| Field | Meaning |
|---|---|
filter | Replace scenario nextQL filter (null / "" clears) |
boosters | Replace booster list (null / [] clears) |
rulesSlugs | Replace linked rules (missing slugs ignored) |
segmentationId | Replace segmentation for balancing |
itemsPerSegment | Companion to segmentation (null → 1) |
Semantics: omit a field → keep scenario (or none); send a value → replace that field only. Constraints stay scenario-only (no request override in v1).
Supported on:
POST /recommendations(JSON body)- Legacy GETs (
user-to-item,item-to-item,recently-viewed,similar-products) — lists as comma-separated query params POST /recommendations/items-to-items(JSON body)
Works with or without scenario. Browser / Node SDKs expose the same fields on recommend options. Admin Try it / Scenario Preview can exercise overrides without mutating the saved scenario.
When any override field is present, the Redis recommendation cache key includes a hash of the effective post-process config.
nextQL - filters & boosters
Filter examples
'price' < 50 and 'stock' > 0
'category' in ['games', 'software']
'platform' = 'Steam'
Matching items stay; others are removed from the scored list.
Booster examples
boost by 1.5 where 'featured' = true
boost by 1.2 where 'category' = 'premium'
If an item matches, score := score × factor. Multiple boosters can stack (sequential multiply).
Shared rules
Rules are reusable:
type: filter→ expression added to filter listtype: booster→ expression added to booster listtype: constraint→ merges into constraints config
Scenario-local filter/boosters are applied in addition to rules.
Constraints (post-rank)
Applied in order:
- Diversity (
maxPerCategory) - keep at most N items percategory. - Freshness (
freshnessBoostDays) - items fresh within N days get ×1.5 score (date from tenant freshness config orcreatedAt). - Long-tail (
injectLongTail+longTailPercentage) - reserve ~P% of slots for low-interaction items. - Re-sort by score and truncate.
Purchase cap (separate)
After ranking, if userId is provided:
- Load distinct purchased
item_ids for that user in 90 days. - Allow at most
purchasedItemsMaxPercent(default 10%) of the result list to be already purchased (minimum 1 slot if percent>0 and list non-empty).
Without userId, purchase cap is skipped.
Auto-relax & min fill
If after the pipeline results.length < minItems and autoRelaxConstraints is on:
- Round 1: double
maxPerCategory, increase long-tail %, double freshness window, force long-tail on; re-run constraints. - Round 2: remove diversity cap, push long-tail higher, widen freshness further.
Also may expand itemsPerSegment if segment balancing is enabled.
Results are always capped by maxItems (and the requested count).
Sequence: scenario-shaped request
PM checklist when editing a scenario
- Confirm logic matches the placement (PDP → similar-products; cart → items-to-items).
- Confirm algorithm is intentional (default vs ML).
- Keep filters selective but not empty-set (pair with auto-relax / minItems).
- Use boosters for soft preference; filters for hard policy (geo, stock, brand exclusion).
- Set purchase cap thoughtfully for logged-in personalization.
- After changes, validate with Admin funnel and spot-check a few seed items in UI - metrics lag ingestion slightly.
