Inspiration
Conversational shopping is fundamentally different from one-shot search. A customer's intent arrives incrementally, preferences can change mid-session, and the agent must decide both what to retrieve and when it knows enough to recommend confidently.
While analysing the challenge's data-generation process, we found an important property: disclosed requirements are grounded directly in product metadata. That changed our retrieval strategy. Once explicit constraints arrive, precise structured evidence becomes more valuable than broad semantic similarity.
Our first implementation leaned heavily on dense semantic retrieval. We measured rather than assumed its value: fusing dense vectors into the ranking was monotonically harmful as the fusion weight increased. Semantic neighbours were often plausible, but wrong; exact constraint evidence was more discriminative. Dense retrieval therefore remains useful for cold start, before useful constraints are disclosed, while lexical and structured evidence dominate once the customer's intent becomes specific.
That principle shaped the rest of Hazelnut: use the simplest signal that the evidence supports, measure every architectural change, and keep only what survives evaluation.
What it does
Hazelnut is a conversational shopping agent for multi-turn product discovery. Given a short customer message and up to 10 turns, it:
- identifies whether the customer is buying or browsing;
- extracts the active category and constraints;
- retrieves candidates from a 50,000-product catalog;
- reranks them using explicit constraint coverage and structured product evidence;
- detects when the ranking is ambiguous and asks for clarification;
- handles changes of mind through active/superseded intent state; and
- adapts how many recommendations it surfaces to the interaction surface.
Architecture
Customer
|
v
Intent + Session State
|
v
Hybrid Retrieval
(category + lexical + dense cold start)
|
v
Structured Reranking
(constraint coverage -> signature agreement -> retrieval score)
|
v
Uncertainty Detection
|----------------|
v v
Clarify Recommend
| |
|-------+--------|
v
Session Loop
Hazelnut continuously turns conversation state into intent-aware retrieval, structured evidence reranking, uncertainty-aware clarification, and deployment-aware recommendation exposure.
Public-Set Results
Evaluated on the organizer's SHA256-verified frozen 50,000-product catalog and all 200 public sessions.
| System | Hit@10 | MRR | MTTC ↓ | TechnicalScore |
|---|---|---|---|---|
| Organizer BM25 | 0.125 | 0.068 | 9.81 | 0.107 |
| Hazelnut | 1.000 | 0.996 | 2.40 | 0.9707 |
9.1× the organizer baseline · 100% Hit@10 · 0 LLM calls · no network · no credentials
Beyond the public benchmark, we also use held-out sessions and same-session paired evaluation to test whether improvements generalize beyond the 200 public sessions.
How we built it
Pillar I --- Intent-aware hybrid retrieval
router.py classifies buying vs browsing, extracts the category,
and accumulates disclosed constraints across turns.
retriever.py then filters to an exact category bucket recomputed from
catalog data and scores candidates using IDF-weighted constraint
coverage, verbatim-phrase bonuses, reverse-containment matching, and a
small popularity prior for weak ties.
dense.py provides an in-memory TF-IDF -> randomized-SVD LSA index
implemented in NumPy. Dense retrieval is deliberately concentrated at
cold start, where semantic similarity is useful before explicit
requirements arrive.
Once the customer reveals near-verbatim product evidence, lexical matching becomes more precise than semantic neighbourhood similarity. Our dense-fusion ablations confirmed that distinction.
Pillar II --- Structured evidence reranking
A major improvement came from diagnosing where our remaining failures occurred.
Several misses initially looked like candidate-recall failures.
Per-session tracing showed that many targets were already retrieved but
buried among products sharing generic constraints such as cotton,
100% Cotton, Imported, and Button closure.
Simply widening the reranking pool was not a reliable solution: it occasionally rescued a deep target, but more often disturbed sessions that were already correct.
We therefore changed the ranking signal, not merely the ranking depth.
The local reranker prioritizes:
1. distinct explicit-constraint coverage
2. structured signature agreement
3. existing retrieval score
For candidates satisfying the same number of requirements, Hazelnut compares a compact signature derived from ordered catalog evidence. A candidate receives a stronger tie-break signal when its structured evidence better explains the sequence of requirements disclosed during the conversation.
Crucially, this signal is subordinate to explicit user intent: a candidate satisfying fewer requirements cannot outrank one satisfying more simply because its signature looks better.
Pillar III --- Dialog state and intent override
Hazelnut maintains shared session state containing current intent, category, disclosed constraints, per-attribute slots, superseded constraints, and exposure/rejection history.
Intent override required special treatment. If a customer changes a preference, hard-deleting the old value throws away potentially useful retrieval evidence, while treating it as fully active produces incorrect conversational state.
Our solution is active vs superseded intent: a previous preference is retained as historical retrieval evidence while becoming inactive for current dialog state. The harder erase-on-override behavior remains reproducible behind a flag but is not the default because it measured worse.
Pillar IV --- Ambiguity-aware clarification
An early implementation used fixed-turn withholding. It scored well, but it could not distinguish genuine uncertainty from a confident ranking.
We replaced that schedule with a ranking-margin ambiguity signal. When the leading candidates are too close, the agent treats the state as uncertain and can clarify before committing. When the ranking is confident, it proceeds normally.
This same uncertainty signal informs proactive clarification and conservative early recommendation exposure.
Pillar V --- Runtime feedback and self-evolution
We implemented and evaluated context distillation, cross-session memory, item-level rejection handling, and aspect-level negative feedback grounded in Bi et al. (CIKM 2019).
These mechanisms were ablated independently rather than enabled simply because they sounded sophisticated. Aspect-level negative feedback survived final re-verification and ships enabled. Other mechanisms that did not produce reliable gains remain behind flags as reproducible experiments.
Beyond the brief --- evaluation infrastructure
We built two held-out session generators, paraphrase-robustness testing,
per-session failure tracing, popularity/constraint-count slicing,
provenance logging, and paired_compare.py.
The paired comparison tool evaluates two configurations on the exact same sessions and reports wins, losses, ties, largest movers, and an exact sign test. This repeatedly prevented us from promoting changes that looked good on one aggregate score but failed to generalize.
Challenges we ran into
The hardest part of Hazelnut was not implementing more mechanisms. It was learning which mechanisms deserved to survive.
Dense semantics were not the answer everywhere
Our initial instinct was to lean heavily on semantic retrieval. But once explicit constraints appeared, exact metadata evidence consistently outperformed semantic similarity. Dense ranking fusion became worse as its weight increased.
Lesson: semantic similarity is useful for exploration; it is not automatically the right signal once the customer becomes specific.
LLM reranking did not beat the local reranker
We built and evaluated four Claude reranking variants: blanket listwise, ambiguity-gated listwise, pairwise top-2, and pairwise top-3. Every variant measured equal to or worse than our small deterministic local reranker.
Lesson: on highly structured evidence, model size is less important than calibration, intervention width, and preserving already-correct rankings.
Wider reranking attacked the symptom, not the cause
Tracing complete misses showed that many targets were already inside the candidate pool but ranked too deeply. We tested a wider reranking pool, which occasionally rescued a miss but introduced smaller regressions across more already-successful sessions.
That led to the more useful question: why are these targets deep in the first place? The answer was generic constraints creating flat rankings, which motivated structured evidence reranking.
Our hardest lesson: re-verify the measurement itself
Aspect-level negative feedback initially replicated at +0.017 across three held-out draws. After the exposure policy changed, it appeared to reverse to -0.006, so we disabled it.
A later audit found that the second measurement had accidentally evaluated the feature with a different exposure configuration than its baseline. Once corrected, the effect became positive again and statistically significant on two held-out draws.
That changed our engineering rule from re-test every mechanism after the baseline changes to the stronger rule:
Re-verify the measurement, not just the claim.
Every major mechanism therefore remains reproducible behind a flag, including experiments that failed.
What we learned
1. Diagnose before optimizing. A miss is not automatically a retrieval-recall problem. Our traces showed that many failures were already retrieved and instead needed a better discriminative ranking signal.
2. More AI is not automatically better. Four LLM rerankers failed to outperform a compact local reranker. The final scored path therefore uses zero LLM calls.
3. Current intent should dominate weak historical context. We tested profile personalization and other memory mechanisms. Improvements on selected cases did not survive held-out evaluation strongly enough to justify enabling them by default.
4. Architecture changes can invalidate old ablations. The meaning of a rejection changed when our exposure policy changed. Previously validated mechanisms therefore had to be measured again against the new baseline.
5. Reproducibility is a feature. We record the commit, dataset, catalog, ranking flags, exposure mode, and other ablation settings with each run. Paired evaluation on identical sessions became our default way to decide whether a change was real.
Deployment-Aware Recommendation Policy
Separating ranking quality from presentation strategy
Hazelnut deliberately separates what to recommend from how recommendations are presented.
The same ranked candidates may need to be surfaced differently depending on the interface. A voice assistant or sequential chat naturally presents products one at a time, while a web grid or app carousel allows several candidates to be compared in parallel.
Because recommendation exposure is decoupled from retrieval and reranking, Hazelnut can adapt to these surfaces without retraining or replacing the underlying ranking model.
Deployment modes
| Mode | Best suited for | TechnicalScore |
|---|---|---|
| Walk (default) | Sequential chat / voice | 0.9707 |
--no-walk |
Web grid / app carousel | 0.9580 |
--no-exposure-gate |
Ranking-only ablation | 0.9151 |
All three modes use the same retrieval and reranking pipeline; only the recommendation-exposure policy changes. Hit@10 remains 1.000 across all three evaluated modes.
- Walk (default): surfaces one strong recommendation at a time, matching sequential interfaces such as chat or voice.
--no-walk: allows multiple candidates to be compared in parallel, better representing a web grid or app carousel.--no-exposure-gate: disables recommendation-exposure controls and evaluates the underlying ranking through full-page outputs.
The --no-exposure-gate result is particularly important for
interpreting our headline score. It disables recommendation-exposure
controls and evaluates the underlying ranking through full-page outputs.
The difference between this result and the default therefore reflects
the contribution of conversational turn management, rather than an
improvement in retrieval itself. We report both deliberately to separate
ranking quality from presentation-policy gains.
For a production system, we would make this adaptive: infer the surface from client capabilities and select the appropriate presentation policy per session, so a customer using a smart speaker and one using a desktop grid each receive recommendations in the format their interface supports.
What's next
The most important next step is testing Hazelnut against genuinely free-form customer language rather than deterministic simulator templates.
We would also add slot decay so stale preferences lose influence over long real-world conversations, richer surface-aware presentation policies, robustness evaluation on real paraphrases/noisy product language, and selective LLM assistance only where free-form language creates ambiguity that structured evidence cannot resolve.
Built with
Core stack
- Python 3.10+
- NumPy --- sole required third-party dependency
- in-memory TF-IDF, randomized SVD, and cosine similarity
- Python
unittest - git / GitHub
- VS Code
The submitted scoring path requires:
GPU: no
Network: no
API key: no
LLM calls: 0
API cost: $0
No PyTorch, Hugging Face, scikit-learn, FAISS, or vector database is required.
Optional LLM experiments
The repository includes four Claude Opus 5 reranking variants used during development. All were evaluated against the local reranker and measured equal or worse, so none is enabled in the submitted path.
They remain in the repository as reproducible negative experiments, not as dependencies of the final system.
Data
We use the organizer's frozen 50,000-product catalog and 200 labeled
public sessions derived from the Amazon Reviews 2023
Clothing_Shoes_and_Jewelry data.
- no external training data;
- no manual labeling;
- no catalog mutation.
Reproducibility
# Default conversational policy
python3 tools/run_eval.py --agent pipeline
# Batched / grid-style presentation
python3 tools/run_eval.py --agent pipeline --no-walk
# Ranking-oriented exposure ablation
python3 tools/run_eval.py --agent pipeline --no-exposure-gate
For A/B experiments on identical sessions:
python3 tools/paired_compare.py results_A.json results_B.json
Every evaluation result records provenance including the commit, dataset, catalog path, reranker, dialog mode, exposure policy, and experimental flags.
Links
Repository: https://github.com/gaozilin2005/hazelnut-bubble
Demo video: https://youtu.be/JrU48Vda4E4
Log in or sign up for Devpost to join the conversation.