RecomNext

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

StageWhat it doesTunable in Admin?
1 ResolveLoad scenario by slug; pick algorithm (or default for logic)Scenario editor
2 CandidatesCH / Qdrant / factors / category fallbackAlgorithm + Qdrant/hybrid settings
3 AvailabilityDrop available: falseCatalog / feeds
4 applyGeoLiftMultiply scores by country/cluster/global lift when country is setRequest country (not a scenario field). Skipped for weighted-history (geo fill only) and recently-viewed
5 FiltersRemove non-matching itemsfilter, rules type=filter
6 BoostersMultiply already-lifted scoresboosters, rules type=booster
7 ConstraintsDiversity, freshness ×1.5, long-tail injectconstraints, rules type=constraint
8 SegmentsBalance across segmentssegmentationId, itemsPerSegment
9 Purchase capCap already purchased (90d)purchasedItemsMaxPercent (default 10); needs userId
10 Min/maxEnsure enough resultsminItems, 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

FieldPurpose
slugPlacement key (pdp-similar-products)
logicTypeuser-to-item / item-to-item / items-to-items / similar-products / recently-viewed
algorithmOverride default algo for that logic
filternextQL remove predicate
boosters[]nextQL boost expressions
constraintsmaxPerCategory, freshnessBoostDays, injectLongTail, longTailPercentage, purchasedItemsMaxPercent
qdrantSettingshnsw_ef, score_threshold, filter_categories, quantization_* (vector logics)
hybridSettingsmulti-seed-hybrid: strategy, rrfK, coTopK, vectorTopK, categoryBoost, …
rulesSlugs[]Attach shared filter/booster/constraint rules
minItems / maxItemsFloor / ceiling on result count
autoRelaxConstraintsIf under-filled, loosen constraints and retry
autoRelaxFiltersIf filter matches nothing, drop clauses right→left (opt-in, default off)
relaxEntireFilterOnEmptyIf filter auto-relax still empty, drop whole filter (opt-in, default off)
segmentationId / itemsPerSegmentDiversity across segments

Request-time post-process overrides

Recommendation APIs can replace selected post-process fields on a single request without editing the saved scenario:

FieldMeaning
filterReplace scenario nextQL filter (null / "" clears)
boostersReplace booster list (null / [] clears)
rulesSlugsReplace linked rules (missing slugs ignored)
segmentationIdReplace segmentation for balancing
itemsPerSegmentCompanion 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 list
  • type: booster → expression added to booster list
  • type: constraint → merges into constraints config

Scenario-local filter/boosters are applied in addition to rules.


Constraints (post-rank)

Applied in order:

  1. Diversity (maxPerCategory) - keep at most N items per category.
  2. Freshness (freshnessBoostDays) - items fresh within N days get ×1.5 score (date from tenant freshness config or createdAt).
  3. Long-tail (injectLongTail + longTailPercentage) - reserve ~P% of slots for low-interaction items.
  4. 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 autoRelaxFilters on 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 relaxEntireFilterOnEmpty on 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 (null only if the entire filter was dropped)
  • droppedEntireFilter — true when the whole filter was cleared
  • originalFilter — the full joined filter that was evaluated

UX idea: “Showing results without price filter.”

Contrast with constraint auto-relax

KnobTrigger
autoRelaxFiltersFilter matched zero candidates
autoRelaxConstraintsResults below minItems after ranking (diversity / freshness / long-tail)

Checklist before save

  1. Left = durable; right = soft
  2. Rule priorities intentional
  3. Policy B on only if unfiltered fallback is OK
  4. 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

  1. Confirm logic matches the placement (PDP → similar-products; cart → items-to-items).
  2. Confirm algorithm is intentional (default vs ML).
  3. Keep filters selective but not empty-set (pair with auto-relax / minItems).
  4. Use boosters for soft preference; filters for hard policy (geo, stock, brand exclusion).
  5. Set purchase cap thoughtfully for logged-in personalization.
  6. After changes, validate with Admin funnel and spot-check a few seed items in UI - metrics lag ingestion slightly.