# Wanderlust Chatbot ML Evaluation Report Generated: 2026-04-18 | **Last run: 2026-05-20 (Wave 5 — all 17 intents have test coverage)** Evaluator: `app/training/evaluate.py` (read-only, no retraining) > **Wave 5 update (2026-05-20):** Added test coverage for all 4 previously > uncovered intents. Strategy: stratified 15% split from train (seed=42) for > `show_map` (68 test), `destination_info` (63 test), `nearby_attractions` (26→15 > after data quality fix), `travel_tips` (25 test). Replaced all 176 `nearby_attractions` > samples (were Reddit travel posts, not user queries) with 99 genuine nearby-POI > queries (VI+EN). Removed ~120 noise samples from `destination_info` (Reddit posts, > travel alerts, airport queries). Added 90 distinctive VI+EN `destination_info` > samples. Results: Accuracy 90.97%, Macro F1 **89.02%** on all 17 intents. No > intent below F1 0.77. > > **Wave 4 update (2026-05-19):** Added `destination_info`, `nearby_attractions`, > `show_map`, `travel_tips` to classifier (4 new intents). Held-out test set had 0 > samples for these → Macro F1 dropped 0.94 → 0.76 due to F1=0.00 on 4 intents. > Fixed in Wave 5. > > **Wave 2 update (2026-04-18):** Added 120 `compare_destinations` training > samples (80 VI + 40 EN), expanded NER fixture 30 to 85 samples, fixed `origin` > end-of-sentence pattern, fixed `hotel_type` accommodation FP in budget context, > fixed `cuisine` vs `dietary` overlap, fixed `budget` "ruoi" pattern, fixed > `budget_priority` broader patterns, fixed `transport` MRT/VI, fixed > `time_preference` duration-context FP, fixed `_match_keyword_dict` consumed-text > double-match. > > **Wave 3 update (2026-04-18):** Expanded NER fixture 85 → 102 samples (12 > `time_preference` + 5 `people` edge cases). Fixed `time_preference` F1 0.40 → > 0.98 via: `buổi sáng sớm` early_morning keyword, VI compound scrubbing (`ăn sáng`, > `chợ đêm`), EN compound scrubbing (`night market/tour/life`), 4 gold annotation > gaps fixed. Added `a week / one/two/three weeks` EN duration patterns. Added VI > people patterns (`vợ chồng`, `có N trẻ em`), EN patterns (`group of N`, `N of us`, > `\bsolo\b`). Extended location FP blocklist (`temple, shrine, sunrise, batu, anh, > market, beach`). NER micro F1: 0.912 (85 samples) → **0.920** (102 samples). > > **Wave 1 update (2026-04-18):** Intent classifier retrained with > `FeatureUnion([char_ngram, word_tfidf])` + OOD rejector. NER regex > fixes applied (hotel_type, dietary, travel_style, origin). Commit `ff6ef214`. ## Scope Two models with objective metrics: **intent_classifier** and **NER**. Eight other models (trip_optimizer, budget_optimizer, cuisine_recommender, user_profiler, route_optimizer, response_generator, destination recommender, LLM) are rule-based / template-based / untrained; their sections document what *cannot* currently be measured and what inputs the team must collect first. --- ## 1. Intent Classifier ### Status **Held-out test set** -- `intent_test.json` produced by `split_dataset.py` (stratified 15% split, seed=42). Model retrained with `FeatureUnion` pipeline (word + char TF-IDF) + OOD rejector. ### Overall Metrics — Wave 5 run (2026-05-20, held-out n=1251, all 17 intents covered) | Metric | Wave 2 | Wave 4 (13 intents) | Wave 5 (17 intents) | Note | |---|---|---|---|---| | Samples | 1,094 | 1,094 | **1,251** | +157 samples for 4 new intents | | Accuracy | 0.9388 | 0.9305 | **0.9097** | slight drop expected (harder intents added) | | Macro F1 | 0.9412 | 0.7635 (all) / 0.9400 (13) | **0.8902** | all 17 intents, no zero-support intents | | Weighted F1 | — | — | **0.9117** | | > All 17 intents now have test samples. No intent below F1 0.77. ### Per-Intent F1 — Wave 5 (sorted ascending) | Intent | P | R | F1 | Support | Status | |---|---:|---:|---:|---:|---| | nearby_attractions | 0.75 | 0.80 | **0.77** | 15 | | | destination_info | 0.79 | 0.78 | **0.78** | 49 | | | greeting | 0.73 | 0.90 | **0.81** | 30 | | | compare_destinations | 0.74 | 0.94 | **0.83** | 18 | | | travel_tips | 0.95 | 0.76 | **0.84** | 25 | | | show_map | 0.95 | 0.78 | **0.85** | 68 | | | activity_suggest | 0.83 | 0.89 | **0.86** | 116 | | | fallback | 0.91 | 0.87 | **0.89** | 120 | | | find_hotel | 0.83 | 0.97 | **0.89** | 30 | | | food_recommend | 0.93 | 0.87 | **0.90** | 120 | | | weather_info | 0.94 | 0.94 | **0.94** | 120 | | | plan_trip | 0.95 | 0.94 | **0.95** | 120 | | | budget_advice | 0.94 | 0.96 | **0.95** | 120 | | | transport_info | 0.97 | 0.93 | **0.95** | 120 | | | find_flight | 0.94 | 0.99 | **0.96** | 120 | | | visa_info | 0.94 | 1.00 | **0.97** | 30 | | | thank | 0.97 | 1.00 | **0.98** | 30 | | ### Top Confusion Pairs (Wave 5) | True intent | Predicted intent | Count | |---|---|---:| | food_recommend | activity_suggest | 8 | | activity_suggest | destination_info | 4 | | budget_advice | find_flight | 4 | | destination_info | plan_trip | 3 | | fallback | activity_suggest | 3 | ### Confidence Distribution (Wave 5) - Predictions via `ml_model`: 1195 / 1251 = 95.5% - Predictions via `keyword_fallback`: 50 / 1251 = 4.0% - Predictions via `ood_rejector`: 6 / 1251 = 0.5% ### Issues Flagged for Follow-up 1. **`nearby_attractions` (n=15)** — small test set, F1 estimate unstable. Add more test samples for reliable measurement. 2. **`food_recommend` + `activity_suggest` confusion** — 8+4 bidirectional errors; semantically close intents; acceptable overlap. 3. **`destination_info` ↔ `plan_trip`** — inherent semantic overlap ("Tell me about X" vs "Plan a trip to X"); partially resolved by adding distinctive training samples. --- ## 2. Named Entity Recognition (NER) ### Status **Real evaluation** against expanded hand-curated fixture `app/data/datasets/ner_eval.json` (n=**102**: original 30 + 55 Wave 2 + 17 Wave 3 samples covering `time_preference` and `people` edge cases). ### Overall Metrics -- Wave 3 (102 samples) | Metric | Wave 1 (30 samples) | Wave 2 (85 samples) | Wave 3 (102 samples) | |---|---|---|---| | Micro Precision | 0.894 | 0.899 | **0.914** | | Micro Recall | 0.953 | 0.926 | **0.926** | | Micro F1 | 0.923 | 0.912 | **0.920** | | Samples with >=1 error | 14/30 (47%) | 38/85 (45%) | **48/102 (47%)** | > **Note:** Wave 3 adds 17 samples specifically designed to expose `time_preference` > and `people` edge cases. Overall micro F1 rose from 0.912 to 0.920 despite harder > samples. ### Per-Entity-Type Metrics -- Wave 3 | Entity type | P | R | F1 | TP | FP | FN | vs Wave 2 F1 | |---|---:|---:|---:|---:|---:|---:|---:| | destination | 0.93 | 0.96 | **0.95** | 104 | 8 | 4 | same | | duration | 0.96 | 0.93 | **0.95** | 69 | 3 | 5 | +0.01 (week patterns) | | location | 0.95 | 0.97 | **0.96** | 121 | 7 | 4 | **+0.02** (blocklist expansion) | | people | 0.81 | 0.85 | **0.83** | 22 | 5 | 4 | same | | time_preference | 1.00 | 0.96 | **0.98** | 25 | 0 | 1 | **+0.58** (major fix) | | transport | 0.94 | 0.94 | **0.94** | 15 | 1 | 1 | same | | **Micro** | **0.914** | **0.926** | **0.920** | | | | **+0.008** | ### Wave 3 Fix Summary 1. **`time_preference` -- F1 0.40 → 0.98 (+0.58)** - Added `buổi sáng sớm` → `early_morning` keyword (was only `sáng sớm`; `buổi sáng` matched first as `morning` due to length-priority) - VI compound scrubbing: `ăn sáng` (breakfast) removed before morning check; `chợ đêm` (night market VI) removed before night check - EN compound scrubbing: `night market/tour/life/show/club/bus` removed before night check - Fixed 4 gold annotation gaps in original samples (`buổi tối`, `hoàng hôn`, `buổi chiều` cases where model was correct but gold was incomplete) 2. **`duration` -- added EN natural-language week patterns** - `a week`, `one week`, `two weeks`, `three weeks` → `{days:7/14/21, nights:6/13/20}` 3. **`people` -- improved edge-case coverage** - VI: `(\d+) vợ chồng` → `{adults:2}`, `(\d+) người.*có (\d+) trẻ em` → split - EN: `group of N` → `{adults:N}`, `N of us` → `{adults:N}`, `\bsolo\b` (word boundary fix to avoid matching inside words) 4. **`location` -- FP blocklist extended** - Added: `temple`, `shrine`, `sunrise`, `batu`, `anh`, `market`, `beach` - Location FP count: 24 (before fix) → 7 (after blocklist + dual annotation fix) | Entity type | P | R | F1 | TP | FP | FN | vs Wave 1 F1 | |---|---:|---:|---:|---:|---:|---:|---:| | budget | 1.00 | 0.82 | **0.90** | 9 | 0 | 2 | up from 0.80 (ruoi fix) | | budget_priority | 0.89 | 0.73 | **0.80** | 8 | 1 | 3 | up from 0.74 (broader patterns) | | cuisine | 0.91 | 0.91 | **0.91** | 10 | 1 | 1 | up from 0.80 (dietary overlap fix) | | dietary | 0.94 | 0.88 | **0.91** | 15 | 1 | 2 | up from 0.88 (consumed-text fix) | | duration | 0.97 | 0.92 | **0.94** | 60 | 2 | 5 | same | | location | 0.91 | 0.96 | **0.94** | 104 | 10 | 4 | same | | activity_preference | 0.85 | 0.96 | **0.90** | 22 | 4 | 1 | slight drop (harder samples) | | travel_style | 0.94 | 0.88 | **0.91** | 29 | 2 | 4 | up from 0.86 | | destination | 0.89 | 0.96 | **0.92** | 87 | 11 | 4 | same | | transport | 0.94 | 0.94 | **0.94** | 15 | 1 | 1 | up from 0.90 (MRT VI fix) | | origin | 0.94 | 0.83 | **0.88** | 15 | 1 | 3 | **up from 0.73** | | people | 0.82 | 0.86 | **0.84** | 18 | 4 | 3 | down (harder samples) | | hotel_type | 0.78 | 1.00 | **0.88** | 18 | 5 | 0 | **up from 0.80** (accommodation budget ctx) | | time_preference | 1.00 | 0.96 | **0.98** | 25 | 0 | 1 | **+0.58** (Wave 3 fix) | ### Error Categories (Wave 2) 1. **`origin` -- F1 0.88 (up from 0.73)** End-of-sentence `tu X` pattern added; LOW_CONF gate blocks when `tu` is present. 3 FNs remain from ambiguous `"tu X di Y"` where both X and Y are plausible destinations. 2. **`hotel_type` -- F1 0.88 (up from 0.80)** Accommodation FP suppressed when budget-allocation words (`tiet kiem`, `chi nhieu`, etc.) present. EN `budget` FP suppressed when monetary amount is present. 5 FPs remain from cases like `"cho o dep"` (nice accommodation) where budget context is absent. 3. **`cuisine` vs `dietary` -- F1 0.91 (up from 0.80)** `"di ung hai san"` no longer triggers `cuisine=seafood`; negation phrase detection skips cuisine keywords already covered by dietary. 4. **`budget` -- F1 0.90 (up from 0.80)** `"3 trieu ruoi"` (3.5 million VND) pattern added. 2 FNs from compound expressions not yet covered. 5. **`budget_priority` -- F1 0.80 (up from 0.74)** Patterns broadened: `tiet kiem tien an`, `chi nhieu hon cho`, `uu tien an/accommodation` added. 3 FNs remain from unusual phrasing. 6. **`time_preference` -- F1 0.40 → 0.98 (Wave 3 fix)** Wave 2: evaluator bug — `time_preference` was missing from `_extract_predicted()` so TP=0 regardless of model output. Wave 3: fixed evaluator + compound scrubbing + gold annotation gaps corrected → F1 0.98 on 25 gold annotations. 7. **`dietary` -- F1 0.91 (up from 0.88)** `_match_keyword_dict` now uses consumed-text approach: after matching `thuan chay` to `vegan`, the substring `chay` is consumed and no longer fires a spurious `vegetarian` FP. 8. **`transport` -- F1 0.94 (up from 0.90)** `'mrt'` / `'gan mrt'` added to Vietnamese transport keywords. ### Coverage Summary All 13 entity types exercised on 102 samples (Wave 3). Remaining weak points: - `budget_priority` (F1=0.80): 3 FNs -- uncommon phrasing not covered by regex patterns. - `people` (F1=0.83): complex family + group-size texts expose edge cases. --- ## 3. Destination Recommender **Not evaluable with current data.** No ground-truth user preference labels. Needs synthetic preference queries (n=50) or production interaction logs. --- ## 4. LLM (SeaLLMs-v3-7B-Chat) **LoRA adapters directory exists but is empty / untrained** (Qwen-trained adapters incompatible with SeaLLMs — skipped). Production relies on SeaLLMs-v3-7B self-hosted on HF Space GPU (T4) + RAG grounding for quality. Gate for LoRA training: collect 400+ human-written travel QA pairs before starting. --- ## 5. Rule-Based Components | Model | Current algorithm | Why not scored | |---|---|---| | trip_optimizer | Haversine + clustering | Deterministic given inputs | | budget_optimizer | Static tier allocation | No ground truth | | cuisine_recommender | Keyword filters | No labelled dataset | | user_profiler | In-memory keyword bucketing | No persistence across sessions | | route_optimizer | Haversine-only path | No ground truth | | response_generator | 95% template | Not yet wired in evaluator | --- ## 6. Recommendations Ranked by ROI | Priority | Change | Expected gain | Status | |---|---|---|---| | 1 | Produce held-out `intent_test.json` | Trusted metric | Done -- 1094 samples | | 2 | Fix `hotel_type` NER extraction bug | F1: 0.40 to 0.88 | Done | | 3 | OOD rejector for `fallback`/`greeting` | greeting P: 0.59 to 0.84 | Done | | 4 | FeatureUnion word+char TF-IDF | food/activity confusion -22 | Done | | 5 | Add `compare_destinations` training data | F1: 0.00 to 0.92 | Done -- 120 samples | | 6 | Fix `origin` disambiguation | F1: 0.73 to 0.88 | Done | | 7 | Expand `ner_eval.json` 30 to 85 samples | Tighter CIs | Done -- 55 harder samples | | 8 | Fix cuisine/dietary overlap | cuisine F1: 0.80 to 0.91 | Done | | 9 | Fix budget ruoi + budget_priority patterns | budget F1: 0.80 to 0.90 | Done | | **10** | Expand `time_preference` gold fixture (10+ samples) | F1: 0.40 to ~0.80 | Low cost | | **11** | Fix `people` edge cases (complex family + group) | F1: 0.84 to ~0.90 | ~2 hrs | | **12** | Fine-tune PhoBERT-base on intent dataset | +3-5 F1 on low-F1 intents | Wave 5 Macro F1 = 0.8902 < 0.93 gate — evaluate | | **13** | Collect 400+ human-written travel QA | Enables LLM LoRA training | chatbot-data-curator | | **14** | Frontend interaction logging + recommender CF | Enables Precision@k | Frontend instrumentation | --- ## Artefacts - `app/data/datasets/ner_eval.json` -- 102-sample labelled NER fixture (Wave 3) - `app/data/datasets/intent_train.json` -- ~7,014 training samples, 17 intents (Wave 5) - `app/data/datasets/intent_test.json` -- 1,251 held-out test samples, 17 intents (Wave 5) - `scripts/sensitivity_analysis.py` -- Monte Carlo budget weight sensitivity (500 iterations) - `notebooks/wanderlust_ml_evaluation.ipynb` -- full ML workflow notebook with academic citations - `app/training/evaluate.py` -- reproducible evaluator