Skip to content

SmartRec: EASE + CoVis + Orchestrator recommenders + serving context - #9

Open
Varfalamei wants to merge 32 commits into
masterfrom
feature/ease-model
Open

SmartRec: EASE + CoVis + Orchestrator recommenders + serving context#9
Varfalamei wants to merge 32 commits into
masterfrom
feature/ease-model

Conversation

@Varfalamei

Copy link
Copy Markdown
Contributor

New recommenders and serving support for the ClickHouse-migration training work
(paired with youtravel-recsys GitLab MR !6, which bumps the submodule pointer to
this branch).

Recommenders

  • RecommenderEASE — EASE^R closed-form item-item model, warm ranker (best warm
    model in offline k-fold, lambda=250 / 30d window).
  • RecommenderCoVis — item-item co-visitation session ranker (scores candidates
    by co-occurrence with the user's real-time history).
  • RecommenderOrchestrator — routing cascade: EASE (warm) -> CoVis (session) ->
    segment popularity (cold, by country/region/type) -> global popularity. Consumes
    an optional item_meta at train time and an optional request context (country)
    at inference.

Serving

  • serving/model.py loads EASE / CoVis / Orchestrator; adds an optional context
    input; config.pbtxt declares it.
  • smartrec-client forwards context (metadata-driven, only sent when the model
    declares it).

Model configs (EASE/CoVis/Orchestrator prod/dev) live in the parent repo
(app/src/settings.py).

Add RecommenderEASE (rectools EASEModel) plus EASESettings. Warm-only ranker:
cold users return empty with a cold strategy marker and are routed by the
orchestrator/cascade layer. Same RecommenderModel interface as ALS/Popular
(train/recommend/save_model_triton/calc_metrics), so Triton serving and the
API contract are unchanged. Offline k-fold benchmarks showed EASE^R beating
the production ALS on click and booking targets with higher coverage.
Add RecommenderCoVis (item-item co-occurrence from user baskets) and CoVisSettings.
Scores candidates by co-occurrence with the user's real-time history (already passed
via the history argument, so no serving-contract change). Returns empty without
session history; cold routing is handled by the orchestrator/cascade layer. Stored
artifact keeps only top-K neighbors per item.
Single serving policy that composes EASE (warm), CoVis (session) and Popular
(global) plus cold segment popularity, with a context-driven cascade:
session -> co-vis, warm -> EASE, cold+context -> segment popularity, else global,
backfilling to top_n. Session-history items are excluded across all tiers.
context (country/region/type) is optional and forwarded from request filters;
absent context keeps backward-compatible session/warm/global behaviour. Puts the
routing logic in smartrec (offline == online) instead of the API service.
Triton backend now loads the ease/covis/orchestrator model types and parses an
optional context tensor (JSON), passing it only to the orchestrator. config.pbtxt
declares an optional context input; the client sends it only when provided, so
existing models and requests are unaffected.
…ep item features

Dataset.construct(interactions_df=...) on an external-id frame dropped item
features, so PolicyModel._build_item_category returned {} and the category
share cap was a silent no-op inside the e2e protocol. Mirror
cross_validate's own fold-dataset construction (dataset.filter_interactions
with keep_external_ids=True) instead, which preserves features and keeps
external ids working for both hot and cold test users. _fold_frames is now
used only for the external test frame and the train-user set.

Also documents the cold/hot fold-count asymmetry and adds an n_folds column
to the aggregated table.
_fit built C(|basket|, 2) pairs per user with no bound, so bot/power users
with large histories could blow up fit time and memory. Add
fit_basket_size (default 100) to CoVisModelConfig/init/config round-trip;
keep only each user's most recent fit_basket_size interactions before
building baskets.

Also strengthens test_recommend_respects_whitelist with a non-emptiness
assertion so the subset check cannot pass vacuously.
…est imports

PolicyModel's session-weight tier is derived from total train interaction
count, a proxy for engagement - online serving uses live session events
instead, so tier semantics differ. State this explicitly in the class
docstring.

Also removes unused PolicyModelConfig/SourceSpec imports from
test_policy_model.py.
…N_COVIS_ENABLED)

One artifact = a model set behind one name. When the flag is ON the two weak
ALS session paths are replaced per the offline research (EXPERIMENTS.md
2026-08-02/03):
- hot user + session: RRF blend of pure ALS and co-visitation (weights from
  the policy grid, +6% map@10 vs pure ALS) instead of the 70/30 item-sim mix
- unknown user + session: co-visitation (4x the item-sim it replaces), with
  fallback to the old path when covis has no answer
Flag OFF (default): behavior is byte-for-byte unchanged; old pickled
artifacts load fine (covis attr guarded in _ensure_lookup_caches).
Tests: 7 new (both flag states, all four serving paths), suite 63 passed.
The mechanism these names describe is session handling (history from the
request), and 'realtime' in identifiers now clashes with the covis session
layer naming. Renamed has_realtime_history -> has_session_history and
_recommend_hot_user_with_realtime -> _recommend_hot_user_with_session.
Strategy VALUES (model_realtime_*) are untouched - they are the wire contract
consumed by the API and logs.
Live dev incident: serving passes history as a numpy array of byte strings;
'if not history' on a multi-element ndarray raises ValueError (inference on
als_covis_youtravel failed with StatusCode.INTERNAL). Explicit None/len check
plus utf-8 decode of bytes entries. Regression test simulates the exact
Triton input shapes (object ndarray of str and of bytes 'id:weight').
… RecommenderALS

The old independent 'if substring' checks let the bare covis branch overwrite
the ALS load for compound names: als_covis_youtravel was served as an
empty-neighbors RecommenderCoVis (seed parsed, 0 results; instant
realtime_warm on empty history). Single elif ladder, most specific first.
…fault)

Seed recency is multiplied by the API event weight from tour_id:weight
history entries (view 1.0 / booking intent 2.0 / paid 3.0). Best click map
of the 90d variant grid, nominal booking lift; see EXPERIMENTS.md 2026-08-04.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant