RecSys Challenge 2026 · Team npatta01
How one conversation becomes a ranked list of 20 tracks and a natural-language response.
1. High-Level Architecture → 2. State Extraction → 3. Retrieval → 4. Bi-Encoder → 5. Ranking → 6. Response Generation
Examples · Label Audit · References · Repository · Paper
Move horizontally between sections. Scroll down inside a section for progressively deeper detail.
Section 1 · deployed path
Whole-system input
Conversation, played tracks, user profile
Retrieve → rank → respond
Typed state → 11 catalog queries → candidate union → LambdaMART → final ordering → response LLM
Output
20 track IDs + one short recommendation
↓ Detail: deployed pipeline · one-turn contract
Dark adaptation of the architecture figure. The main learned path and the separate RRF baseline/fallback are visually distinguished.
Listener
“Find the specific subdued Neko Case song…”
State
Hidden target · Neko Case · mood/lyric/sonic clues
Ranking
Candidate union → learned scores → final ordering → top 20
Experience
Top track + grounded response
What crosses each system boundary
Sources: paper/main.tex Figure 1; final Blind-B configuration.
Section 2 · language → retrieval contract
Input: session
“Subdued, female singer… a sense of place… not it either.”
Earlier: Neko Case; rejected “Calling Cards” and “Man”
State Extraction
Read the whole dialog → preserve facts and feedback → resolve named entities
Output: usable state
Hidden target · Neko Case anchor · two rejected tracks · mood, lyric, and sonic facets
↓ Detail: session example · extracted facts · retrieval view · resolution · contract
Conversation excerpt
Listener “It’s a Neko Case track.”
Previous recommendation Neko Case — “Calling Cards”
Listener: “not quite it”
Previous recommendation Neko Case — “Man”
Listener: “Much more subdued… sense of place… stark, almost a cappella… not it either.”
Source: retained Blind-B trace, session 024a2738-a96c-4e11-adf3-b2cb8311a493, turn 3; paper/main.tex Figure 2.
The extractor preserves six kinds of information. This turn produced:
Code contract: mcrs/conversation_state/schema.py. Architecture: docs/architectures/session_state.md.
Source: docs/architectures/session_state.md `1–2.
Normalizecase, punctuation, whitespace
Match by entity typeartist / track / album against catalog names
Fuzzy fallbackartist top-20 · track top-5 · cutoff 80
✓ Artist foundPat Metheny → catalog artist ID
× Track not foundWatercolors remains unresolved
Unresolved names stay visible instead of silently becoming a different catalog item.
Source: retained Blind-B trace; resolver architecture in docs/architectures/session_state.md.
Intent
What the listener wants now.
Anchors
Resolved artists/tracks allowed to seed candidates.
Avoid
Played items, explicit rejections, abandoned artists.
Query facets
Tags, mood, lyrics, sonic and temporal cues.
Routing
Which query shapes fit this request.
Known gaps
Facts preserved in text but absent from catalog fields.
The resolved state is the only state object consumed by retrieval and feature construction.
Section 3 · one catalog, many query views
Input
Resolved intent, anchors, rejections, tags, history, user vector
11 active branches
BM25 + 3 dense text + CLAP audio + 3 anchor centroids + user CF + 2 lookups
Output
Union of branch pools, each truncated to 500, ready for learned ranking
Lexical BM25/tag resolver · Semantic Qwen text views · Multimodal CLAP/SigLIP · Behavioral CF centroids · Exact catalog lookups
↓ Detail: catalog · branch map · lexical · dense · anchors · candidate union
47,071
tracks
1 table
shared ID namespace
11
active retrieval branches
metadata + vectors
FTS, text, audio, image, CF, b1
Every branch queries the same catalog row and returns the same track_id, which makes union, feature joins, and replay deterministic.
Goal not fully materializedWe wanted one catalog-grounded query plan for the listener’s complete request. In practice, retrieval split the request across branches and the ranker had to recombine their evidence.
Source: paper/main.tex `2; mcrs/qu_modules/catalog_lance.py.
Resolved state
BM25 + tag resolver
Dense text ×3
CLAP text → audio
Anchor centroids ×3
User-CF centroid
Discography + era lookups
Candidate union
Track ID + branch rank + branch score + presence
The final learned path does not use the RRF order as its ranking input; it uses the union and branch evidence.
Source: paper/main.tex Figure 1 and Table 1.
Free-form phrase → catalog tag
SIMPLIFIED BM25 CLAUSES · NEKO CASE TURN
artist_name“Neko Case”× 3.0
track_name“subdued… sense of place…”× 3.0
tag_listresolved descriptive tags× 1.5
Each clause is a separate SHOULD match. Rejected tracks are removed after retrieval.
If no tag resolves, the raw phrase remains in text search so the signal is not dropped.
Source: docs/architectures/v0plus_retrieval.md; final Blind-B config.
| Query view | Catalog field searched | What it can surface |
|---|---|---|
| current request | metadata_qwen3_embedding_8b |
artist, title, album, and broad semantic matches |
| musical attributes | attributes_qwen3_embedding_8b |
genre, mood, style, and descriptive matches |
| lyrical theme | lyrics_qwen3_embedding_0_6b |
lyric and narrative similarity |
| sonic description | audio_laion_clap |
text-to-audio similarity |
The 11 modeled branches preserve their own score scales. LambdaMART receives branch-specific rank, score, margin, hit, z-score, and percentile evidence.
Source: paper/main.tex `2 and model feature names.
Liked-track audio
CLAP centroid of positive anchors.
Liked-track cover art
SigLIP centroid.
Liked-track behavior
CF-BPR centroid.
User behavior
User-CF centroid when available.
Discography
Resolved artist → catalog tracks.
Era
Popularity lookup within the requested period.
Source: paper/main.tex Table 1; final Blind-B config.
Route
Request flags shape query text and enable gated branches.
Union
Deduplicate branch top-500 lists while retaining provenance.
Handoff
Build one candidate row with catalog, state, branch, and embedding evidence.
Weighted RRF over the same branches remains available as an explicit no-training baseline and fallback; the deployed LambdaMART path replaces its final order.
Source: paper/main.tex Figure 1 and Results Table 3.
Section 4 · conversation ↔︎ track similarity
Input
Compact conversation rendering + a candidate track card
Shared encoder
Fine-tuned Qwen3-Embedding-4B → two 2560-d unit vectors → cosine
Output
b1_cos, one candidate feature used by LambdaMART
The bi-encoder was feature-only in the submitted path, not a candidate-producing branch.
↓ Detail: architecture · actual example · training · serving · evidence
Source: paper/main.tex Figure 3; docs/architectures/biencoder.md.
Conversation rendering
[prev] “Calling Cards is not quite it”
[now] “More subdued… sense of place… stark almost a cappella”
[prev_track] Neko Case — “Man”
same
encoder cos
Neko Case
album The Worse Things Get, The Harder I Fight, The Harder I Fight, The More I Love You
year unavailable; catalog tag: 2013
alt-countryfemale vocalistsindie rock
Artist context: Neko Case is best known for her alt-country and indie rock style, characterized by her powerful voice and poetic lyrics.
Conversation/track formats: docs/architectures/biencoder.md. Track selected in retained Blind-B Neko Case trace.
53,885
positive conversation–track pairs
4 hard negatives
per positive
2560-d
normalized vectors
MNRL
contrastive ranking loss
Source: paper/main.tex `2.4; docs/architectures/biencoder.md.
Offline
Precompute all 47,071 track vectors into LanceDB.
Per turn
Encode and cache one conversation vector.
Per candidate
Dot product with cached track vector → b1_cos.
Source: docs/architectures/biencoder.md `Serving.
b1_cos0.2032OOF nDCG@20
+0.0062
The deployed ranker kept b1_cos as one candidate-level similarity feature.
Source: paper/main.tex Tables 2–3. Development evidence only.
Section 5 · candidate evidence → ordered top 20
Input
Candidate union: up to 500 per branch + 146 features per turn–track pair
Learned scoring
LambdaMART learns one score per candidate within the turn
b1 cosine 59.0% · branch 11.5% · affinity 10.7% · other cosine 8.9%
Final ordering
Exact-track pin + final artist check → ordered top 20
↓ Detail: inference contract · feature gain · sample features · training labels
Candidates are compared only against other candidates from the same conversation turn.
Source: models/reranker_v12_goalfree/model.txt; scripts/rerank/features.py.
Bi-encoder cosine59.0%
Per-branch rank/score11.5%
Session/artist affinity10.7%
Other similarity cosines8.9%
Popularity5.0%
Remaining families: state/intent 2.2%, tag/lexical overlap 2.0%, constraints/rejections 0.7%.
Source: paper/main.tex Table 2; gain recomputed from the deployed model.
| Family | Concrete checked-in features |
|---|---|
| branch evidence | rank__bm25, margin__dense…metadata, hit__lookup…discography |
| semantic match | b1_cos, msg_meta_cos, q06_lyric_cos, clap_centroid |
| session/artist | same_artist_last, same_album_any, artist_played_count |
| catalog | pop_pct, within_artist_pop, release_year, tag_count |
| request/state | request_type, intent_mode, wants_new_artist, year_in_constraint |
| constraints | rejected_track_exact, rejected_artist_exact, violates_new_artist |
Source: models/reranker_v12_goalfree/model.txt feature_names.
Binary relevance
The challenge next track is the positive row.
Targeted downweights
×0.3 if next turn rejects the track or says it did not help; ×0.6 for artist-only rejection.
LambdaRank
User-grouped CV; final model fit on all 8,000 development turns.
We also trained a model on fully rejudged labels. It performed worse on development data, so we did not ship it. The shipped model used the targeted downweights above.
Source: paper/main.tex `2.3; scripts/rerank/build_label_weights.py and train_lgbm.py.
Section 6 · selected track → listener-facing text
Inputs
Latest extracted state + selected top track + catalog metadata
Single pass
XML item card → Qwen3-30B-A3B-Instruct-2507, temperature 0
Output
1–2 concise sentences about only the selected track
The generator presents the ranking decision; it cannot replace the selected track.
Response-only experiment: changing the text generation setup improved the organizer judge score from 4.20 to 4.70 while recommendations stayed fixed.
↓ Detail: actual example · input contract · prompt
STATE
current_request.request_typehidden_target
facts[].artistNeko Case · must use
facts[].moodsubdued
facts[].lyrical_themesense of place
facts[].sonicstark · almost a cappella
TOP 1
Neko Case — “Bracing For Sunday”
SUBMITTED RESPONSE
“You’re looking for something subdued with a strong sense of place and a stark, almost a cappella delivery—and Bracing For Sunday fits that mood perfectly. Neko Case’s haunting, intimate vocals carry the weight of a quiet, specific moment, like a solitary figure in a weathered room, making the song feel both deeply personal and grounded in a distinct place.”
Source: retained Blind-B output; paper/main.tex Figure 2.
Source: final Blind-B config; docs/architectures/explanation_generation.md.
You are an expert music recommendation assistant. Your task is to understand user preferences and provide personalized music recommendations.
You are the conversational voice of a music recommender — a "track explainer." A separate recommendation system has ALREADY selected one track to play next. Your only job is to write the short listener-facing message that presents that track: acknowledge what the listener just asked, then naturally explain why this track fits their request and taste.
Guidelines:
- Output ONLY the listener-facing message — no labels, headers, quotes, or YAML.
- Brief and conversational: 1-2 concise sentences. Match the listener's tone, energy, and LANGUAGE (reply in the same language they wrote in).
- Ground the "why it fits" in their request/stated taste. You MAY name the title/artist, but do NOT recite a metadata or tag dump (genre/mood/style lists) unless they asked for those details.
- If the track clearly doesn't match, briefly and honestly acknowledge the mismatch — don't oversell.
- Vary your wording across turns. Use only facts supported by the provided track; invent nothing.
- The track may be provided as structured <recommended_track> data. NEVER output that data, the tag list, or any XML verbatim — always write a natural conversational sentence.
Prioritize the latest user request and extracted state over older conversation history.
If the track is reasonably aligned, explain the fit with one specific supported reason.
If it clearly conflicts with an explicit avoid/new-artist constraint, do not oversell it or blame the system; briefly frame the limitation and the closest supported reason.
Source: deployed response template quoted in paper/main.tex `2.5.
Section 7 · traces across the full system
Input evidence
Four retained Blind-B listener requests and their extracted states
Trace view
Session → state → resolved constraints → top predictions → response
Output insight
One constraint-preserving trace; three distinct architectural failure boundaries
↓ Cases: Neko Case · Kamelot · missing metadata · Watercolors
Session
“Subdued… female singer… sense of place… stark almost a cappella… not it either.”
Compiled state
current_request.request_typehidden_target
facts[].artistNeko Case · must use
exclusions[].trackCalling Cards · Man
facts[].lyrical_themesense of place
routing.lyric_searchtrue
Top 5
Selected Top 1 Blind-B relevance labels · unavailable
All five preserve the required Neko Case artist constraint, and the response carries forward the request’s concrete clues. Because Blind-B relevance labels are unavailable, this trace demonstrates constraint preservation—not target correctness.
Source: retained Blind-B trace/output, session 024a… turn 3.
LISTENER REQUEST
“The Kamelot track from Silverthorn…”
facts[].artist Kamelotfacts[].album Silverthorn
resolved.artist Kamelotalbum_filter missing
TOP 5 · ALL VIOLATE THE ALBUM CONSTRAINT
The state understood the album. Retrieval and ranking did not enforce it.
Source: retained Blind-B trace/output, session 5c066e… turn 2.
“An aggressive metal/hardcore track that exactly matches 126.70 BPM and G minor.”
facts[].facet=sonic
✓ 126.70 BPM · G minor
× No BPM or key fields
No executable filter could be created.
Top 1: Kreator — “Extreme Aggression”
Response asserted the exact BPM/key without evidence.
“The 80s synth-pop track used in a Breaking Bad montage…”
current_request.summary
✓ soundtrack · montagefacts[].genre 80s synth-pop
× No film/TV soundtrack field
No authoritative target could be resolved.
Top 1: Soft Cell — “Tainted Love”
Response asserted soundtrack fit without evidence.
Source: retained Blind-B traces bf27… turn 8 and b267… turn 5; catalog schema.
EXACT REQUEST
“I wanted ‘Watercolors’ by Pat Metheny specifically.”
Artist exact matchPat Metheny exists in the catalog.
Track not foundNo catalog track matched “Watercolors.”
TOP 1 + SUBMITTED RESPONSE
Pat Metheny — “Alfie”
“Got it — here’s "Alfie" by Pat Metheny, a smooth, introspective piece from his What’s It All About album…”
The artist match produced substitutes, while the response sounded as if the exact request had been fulfilled.
Source: retained Blind-B trace/output, session 954de6… turn 2.
Section 8 · request fit of the training target
Problem observed
Our model kept returning the same artist after listeners asked to move on.
Question
Was the model learning that behavior from the challenge’s next-track labels?
Audit
Rejudge request fit and artist anchoring; test a cleaner-label model.
Why we did this: anchoring appeared in our recommendations, and the training target sometimes rewarded the same behavior.
↓ Detail: Bonobo example · judge flow · counts · outcome
Previously played
Bonobo — “Antenna”
Previously played
Bonobo — “Cirrus”
Listener
“Similar chill, electronic vibe… but from a different artist?”
Ground truth
Bonobo — “Jets” MOVES toward goal
Listener
“‘Jets’ is cool, but… a different artist this time…”
Ground truth
Bonobo — “Pieces” MOVES toward goal
The ground truth repeats the artist the listener explicitly asked to leave.
Listener: “Could you suggest some other artists… with a similar acoustic folk sound?”
Played label: Kings of Convenience — “Singing Softly To Me” same artist · originally liked
Audit: artist anchoring → NEGATIVE
Asked: slow, smooth 90s R&B
Played label: Post Malone — “Broken Whiskey Glass” 2016 · wrong genre/era
Audit: request mismatch → NEGATIVE
Source: paper/main.tex Figure 4.
Turn + candidate label
Request, context, played track
G Gemma-4-26B
request fit + anchoring
D DeepSeek-V4-Flash
request fit + anchoring
Conflict gate
Agree → keep
Disagree → send to Opus
OClaude Opus
Final label, blind to synthetic reaction
Source: data/anchor_labels_v1 reports/flow.html and reproducible release scripts.
113,393
train + dev turns judged
106,393
training turns
61,063
negative labels (57.4%)
18,222
artist-anchoring negatives
After fixing the conflict gate, 3,486 additional disagreements were sent to Opus. The correction reduced the anchoring count from 19,813 to 18,222.
Source: data/anchor_labels_v1/README.md and DATASET_CARD.md.
It performed worse on development data, so we did not ship it.The audit remains useful evidence, but it did not improve our model.
Source: paper/main.tex `4.2; scripts/rerank/build_label_weights.py.
Section 9 · source index
Paper
Architecture details: State · Retrieval · Bi-Encoder · Ranking reproduction · Response Generation
Blind-A and Blind-B appear here as audit references only; the deck documents one final submission architecture.