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 applyGeoLift | Multiply scores by country/cluster/global lift when country is set | Request country (not a scenario field). Skipped for weighted-history (geo fill only) and recently-viewed |
| 5 Filters | Remove non-matching items | filter, rules type=filter |
| 6 Boosters | Multiply already-lifted scores | boosters, rules type=booster |
| 7 Constraints | Diversity, freshness ×1.5, long-tail inject | constraints, rules type=constraint |
| 8 Segments | Balance across segments | segmentationId, itemsPerSegment |
| 9 Purchase cap | Cap already purchased (90d) | purchasedItemsMaxPercent (default 10); needs userId |
| 10 Min/max | Ensure enough results | minItems, maxItems, autoRelax* |
Implementation detail: applyGeoLift runs before applyScenario (except weighted-history and recently-viewed). Filters/boosters run inside applyScenario; constraints + segments + purchase cap run in postRankPipeline / ensureMinItems. Boosters multiply the already-lifted score.
Request country (geo ranking v1)
Optional ISO-2 on POST /recommendations (body) and legacy GETs (query: user-to-item, item-to-item, similar-products, recently-viewed). POST /recommendations/items-to-items: body wins, else query.
Missing, invalid, or OTH → global (same ranking as today). Recommend country does not rewrite ingest tags.
Cache: Redis key includes a country segment (IN or _global_) before the post-process configHash, for u2i / i2i / i2is / sp. recently-viewed ignores country for rank and cache (API still accepts the param).
country is session context, not a request-time post-process override (filter / boosters / …).
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 |
autoRelaxFilters | If filter matches nothing, drop clauses right→left (opt-in, default off) |
relaxEntireFilterOnEmpty | If filter auto-relax still empty, drop whole filter (opt-in, default off) |
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.
Query planning for filter auto-relax
Use this when enabling autoRelaxFilters (and optionally relaxEntireFilterOnEmpty). Both default off — existing scenarios are unchanged until you opt in.
When to enable
- Turn
autoRelaxFilterson for merchandising filters where an empty shelf is worse than dropping soft clauses (price bands, optional tags). - Leave off for hard policy / compliance filters you must never soften.
- Turn
relaxEntireFilterOnEmptyon only when showing unfiltered candidates is acceptable after soft clauses are exhausted.
Core authoring rule
Clauses drop from right to left when the full filter matches nothing. Put must-keep constraints on the left; nice-to-have on the right.
Linked filter rules appear to the left of the scenario filter (highest rule priority first). Soft scenario clauses on the right drop before sticky rules.
Worked examples
AND chain
'allowed_countries' contains 'IN' and 'type' = 'game' and 'price' < 20
If nothing is under 20: drop 'price' < 20 first, keep countries + type. If still empty: drop 'type' = 'game' next.
OR / groups
Put soft bands in the right group. Prefer clear AND + rightmost soft clauses when intent is “durable then soft.”
Response field (filterRelaxation)
When relaxation changes the filter, the API may return:
droppedClauses— what was removed (right→left)effectiveFilter— what still applied (nullonly if the entire filter was dropped)droppedEntireFilter— true when the whole filter was clearedoriginalFilter— the full joined filter that was evaluated
UX idea: “Showing results without price filter.”
Contrast with constraint auto-relax
| Knob | Trigger |
|---|---|
autoRelaxFilters | Filter matched zero candidates |
autoRelaxConstraints | Results below minItems after ranking (diversity / freshness / long-tail) |
Checklist before save
- Left = durable; right = soft
- Rule priorities intentional
- Policy B on only if unfiltered fallback is OK
- Validate with Admin Try-it
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.
