# Thuật toán Tìm kiếm Cửa hàng

Chức năng tìm kiếm cửa hàng (Store Search) được thiết kế qua kiến trúc **2-Agent LangGraph (Planner → Tools → Responder)** kết hợp thuật toán **Phrase Sliding Window** trong SQL.

## Luồng hoạt động (Sequence Diagram)

```mermaid
sequenceDiagram
    actor User
    participant Planner as Planner AI
    participant Tool as Store Search Tool
    participant Responder as Responder AI
    participant DB as StarRocks

    User->>Planner: "Cửa hàng ở Nội Bài Đông Anh"
    Planner->>Tool: Gọi canifa_store_search(location="Nội Bài Đông Anh")

    rect rgb(240, 248, 255)
        Note right of Tool: Bước 1: Normalize & Alias
        Tool->>Tool: Mở rộng Alias (VD: "hn" -> "Hà Nội")<br>Loại bỏ tiền tố ("quận", "tp")
    end

    rect rgb(255, 240, 245)
        Note right of Tool: Bước 2: Phrase Sliding Window Search
        Tool->>Tool: Sinh cụm từ: "nội bài đông anh", "nội bài đông", ...
        Tool->>DB: Truy vấn với CASE WHEN tính score<br>LIKE nhiều phrase
        DB-->>Tool: Trả về kết quả (Score cao xếp trước)
    end

    rect rgb(255, 250, 205)
        alt Không có kết quả từ Phrase Search
            Note right of Tool: Bước 3: Reverse LIKE Fallback
            Tool->>DB: LOCATE(city_trong_db, location_user) > 0
            DB-->>Tool: Kết quả fallback
        end
    end

    rect rgb(240, 255, 240)
        Note right of Tool: Bước 4: Enriched Location Context
        Tool->>Tool: _format_results() - Xác định cụm từ bị miss (unmatched_phrases)
        Tool-->>Responder: Chuỗi kết quả + ⚠️ Chỉ dẫn (prompt) phần không khớp
    end

    Responder-->>User: Trả lời tự nhiên, nhắc nhở phần địa điểm không có cửa hàng
```

## Các kỹ thuật cốt lõi

### 1. Kiến trúc 2-Agent
Sử dụng kiến trúc `Planner → Tools → Responder`:
- **Planner AI** chỉ lo quyết định xem có gọi Tool không.
- **Responder AI** đóng vai trò chắt lọc và format lại kết quả do Tool ném ra. Sự chia tách này giúp giảm hallucination (ảo giác) và duy trì sự ổn định.

### 2. Phrase Sliding Window (Cửa sổ trượt cụm từ)
Thay vì thực hiện `LIKE '%nội%' AND LIKE '%bài%'` (dễ ra sai kết quả do các từ tách rời), thuật toán sinh tất cả cụm từ liên tiếp $\geq 2$ từ từ input.
- Input: `nội bài đông anh`
- Cụm từ sinh ra: `"nội bài đông anh"`, `"nội bài đông"`, `"bài đông anh"`, `"nội bài"`, `"bài đông"`, `"đông anh"`

SQL sử dụng `CASE WHEN` để gán `match_score` cao nhất cho cụm từ dài nhất khớp với CSDL. Kết quả trả về ưu tiên cửa hàng có `match_score` giảm dần.

### 3. Reverse-LIKE Fallback
Nếu người dùng gõ chuỗi quá dài khiến sliding window không bắt được (hoặc bị lọt), hệ thống dùng chiến thuật "Reverse LIKE":
Kiểm tra xem `city` hoặc `state` trong Database có xuất hiện như một **chuỗi con** bên trong chuỗi đầu vào của người dùng không.
- SQL syntax: `LOCATE(db_city, 'cửa hàng ở thành phố hà nội nhé') > 0`

### 4. Enriched Context & Inline Prompting trong Tool
Sau khi lấy kết quả, Tool đối chiếu ngược lại chuỗi người dùng nhập để tìm ra **unmatched_phrases** (các cụm địa danh bị miss và không có cửa hàng).
Nếu phát hiện, Tool trực tiếp chèn chỉ dẫn hướng dẫn hệ thống cho Responder AI vào thẳng text trả về:
> `⚠️ CANIFA hiện KHÔNG có cửa hàng tại khu vực 'Nội Bài'. Hãy thông báo rõ cho khách hàng rằng 'Nội Bài' không có cửa hàng CANIFA, và gợi ý các cửa hàng gần nhất bên dưới.`

Cách này điều hướng Responder AI cư xử khéo léo thay vì báo lỗi toàn bộ query.
