# Prompt & Tool Management — Hệ thống quản lý prompt

> **Tóm tắt:** Prompt là "linh hồn" của AI — quyết định bot nói gì, giọng ra sao, gọi tool nào. Tài liệu này giải thích cách Canifa tách prompt ra khỏi code, cho phép hot-reload, và kỹ thuật MD5 caching tránh rebuild chain.

---

## 1. Tại sao phải tách Prompt ra khỏi code?

| Nếu nhúng prompt trong code | Nếu tách ra file .txt |
|---|---|
| Sửa prompt = sửa code = deploy lại | Sửa prompt = edit file = hot-reload |
| Dev mới không biết prompt ở đâu | Tất cả prompt nằm trong `agent/` folder |
| Không A/B test được | Đổi file → test ngay → rollback dễ |
| Product Owner phải nhờ Dev sửa | PO tự sửa qua Admin API |

### Kiến trúc 4 lớp

| Lớp | Component | Vai trò |
|-----|-----------|--------|
| 1. **Storage** | `agent/system_prompt.txt` + `agent/tool_prompts/*.txt` | Single Source of Truth |
| 2. **Cache** | `prompt_utils.py` → `_system_prompt_cache` | In-memory, giảm disk I/O |
| 3. **Hash** | `graph.py` → MD5 chain caching | Chỉ rebuild chain khi prompt thật sự đổi |
| 4. **API** | `POST /api/agent/reload-prompts` | Hot-reload không restart server |

---

## 2. Phân loại Prompt

### 2.1. System Prompt (`system_prompt.txt`)

Định nghĩa **Persona tổng thể** của AI — giọng điệu, quy tắc, giới hạn:

```
Bạn là AI Stylist của Canifa — thân thiện, am hiểu thời trang, tinh tế.
Ngày hiện tại: {date_str}

QUY TẮC:
- KHÔNG bao giờ bịa sản phẩm
- KHÔNG đề cập đối thủ cạnh tranh
- Luôn gọi khách bằng "anh/chị"
- Khi hết hàng → gợi ý thay thế, KHÔNG nói "hết hàng"
```

**Dynamic Parameters:** Hệ thống inject `{date_str}` tự động → AI biết hôm nay ngày mấy để trả lời "chương trình KM đang chạy".

### 2.2. Tool Prompts (`tool_prompts/*.txt`)

Hướng dẫn AI cách dùng từng tool cụ thể:

| File | Nội dung |
|------|---------|
| `lead_search.txt` | Cách format kết quả tìm kiếm sản phẩm |
| `knowledge_search.txt` | Cách trả lời FAQ từ RAG |
| `image_search.txt` | Cách mô tả kết quả tìm ảnh |
| `sql_agent.txt` | Cách format kết quả SQL query |

Mỗi tool prompt được cache riêng trong `_tool_prompt_cache` (dictionary) → update độc lập qua API.

### 2.3. Classifier Prompt vs Stylist Prompt

| | Classifier Prompt | Stylist Prompt |
|---|---|---|
| **Mục đích** | Ép AI xuất JSON chuẩn | Ép AI viết văn đúng giọng |
| **Tone** | Kỹ thuật, lạnh lùng | Thân thiện, sáng tạo |
| **Ràng buộc** | "CHỈ được trả JSON, KHÔNG text" | "Viết tự nhiên, đúng persona Canifa" |
| **Output** | `ClassifierOutput` schema | Free-text + InsightJSON |

---

## 3. Kỹ thuật: LangChain Braces Escaping

**Vấn đề:** LangChain dùng `{}` cho biến. Nhưng prompt chứa JSON (cũng dùng `{}`) → xung đột!

**Giải pháp:** Escape tất cả `{}` → chỉ giữ lại `{date_str}`:

```python
def get_system_prompt_template() -> str:
    raw = _get_system_prompt_raw()
    
    # Escape toàn bộ {} cho LangChain
    escaped = raw.replace("{", "{{").replace("}", "}}")
    
    # Khôi phục chỉ biến {date_str}
    return escaped.replace("{{{{date_str}}}}", "{date_str}")
```

---

## 4. Kỹ thuật: MD5 Cache Invalidation

**Vấn đề:** Mỗi tin nhắn phải build LangChain chain → chậm nếu prompt không đổi.

**Giải pháp:** Hash MD5 nội dung prompt. Chỉ rebuild khi hash thay đổi:

```python
def _build_chain(self, system_prompt_template: str):
    prompt_hash = hashlib.md5(system_prompt_template.encode()).hexdigest()
    
    # Cache hit → dùng chain cũ
    if self._cached_chain and self._cached_prompt_hash == prompt_hash:
        return self._cached_chain
    
    # Cache miss → build mới
    logger.info("Building new chain (prompt hash changed)")
    prompt_template = ChatPromptTemplate.from_messages([
        ("system", system_prompt_template),
        MessagesPlaceholder(variable_name="messages"),
    ])
    
    self._cached_chain = prompt_template | self.llm_with_tools
    self._cached_prompt_hash = prompt_hash
    return self._cached_chain
```

**Hiệu quả:** 99% requests dùng cache → tiết kiệm ~50ms/request.

---

## 5. Hot-Reload API

Admin có thể sửa prompt và reload mà không restart server:

| Endpoint | Method | Mô tả |
|----------|--------|-------|
| `/api/agent/reload-prompts` | POST | Reload tất cả prompt vào cache |
| `/api/agent/system-prompt` | GET | Xem system prompt hiện tại |
| `/api/agent/system-prompt` | PUT | Cập nhật system prompt |
| `/api/agent/tool-prompts/{name}` | GET/PUT | Xem/sửa tool prompt cụ thể |

### Quy trình sửa prompt:

```
PO sửa system_prompt.txt → POST /reload-prompts → MD5 hash đổi → Chain rebuild tự động
                                                                 → Bot dùng prompt mới từ tin nhắn tiếp theo
```

---

## 6. LLM Factory — Quản lý multi-model

`llm_factory.py` là Singleton quản lý tất cả LLM instances:

| Tính năng | Mô tả |
|-----------|-------|
| **Caching** | Tạo 1 lần, dùng lại cho mọi request |
| **Auto JSON mode** | Tự bật JSON mode cho Classifier |
| **Fallback chain** | OpenAI → Gemini → Gemini CLI (nếu thiếu key) |
| **Temperature control** | Classifier: 0.0 (deterministic), Stylist: 0.7 (creative) |

---

## 7. Troubleshooting

| Vấn đề | Nguyên nhân | Fix |
|--------|-------------|-----|
| Bot không đổi giọng sau sửa prompt | Chưa gọi `/reload-prompts` | POST reload API |
| LangChain báo lỗi `KeyError` | Prompt có `{}` chưa escape | Check `get_system_prompt_template()` |
| Bot dùng prompt cũ dù đã reload | MD5 hash trùng (sửa whitespace) | Sửa nội dung thực sự, không chỉ format |
| LLM Factory trả model sai | Cache Singleton giữ model cũ | Restart server hoặc clear factory cache |

---

## References

1. Canifa Engineering — `backend/agent/prompt_utils.py`
2. Canifa Engineering — `backend/agent/llm_factory.py`
3. LangChain Documentation — *"ChatPromptTemplate & Variable Injection"*
