# ADR-032: RAG 自主學習迴圈 — Distiller + PromotionGate + 反饋環 - **Status**: Accepted - **Date**: 2026-05-03 - **Decision Maker**: 統帥 - **Author**: Operation Ollama-First v5.0(Phase 11 落地後追認) - **Supersedes**: 無 - **Related**: ADR-002(pgvector 唯一向量庫)、ADR-007(持久化雙寫)、ADR-028(LLM 路由統一準則)、ADR-029(Hermes-First 雙塔分工)、ADR-033(RAG 三護欄) --- ## Context 戰役 v5.0 Wave 1 完成後,momo-pro 已具備 ai_calls / mcp_calls / ai_call_budgets 觀測層,但仍是「無狀態 LLM 用戶」 — 每次 Hermes/OpenClaw 提問都重新燒 token,重複問題沒被攔截。 Phase 0 audit 發現: - 統帥 Telegram 答題 30%+ 是同一類問題(「PChome 補貼」「家電促銷檔期」「SKU 競爭力分析」) - ai_insights 已累積 14k+ 筆(pgvector + bge-m3)但**沒有 RAG 攔截層**,全部走 LLM - 預估:30% 流量可被 RAG cache 攔截 = 月省 ~9M Hermes/OpenClaw tokens **Owen 提出三大風險**(v5.0 強化護欄): 1. **學習污染**:LLM 幻覺自動進 RAG → 正反饋錯誤循環(ADR-033 護欄 #1) 2. **資源消耗**:自建 Firecrawl Playwright 池吃 188 主機記憶體(ADR-033 護欄 #2) 3. **Embedding 一致性**:bge-m3 floating tag → RAG 召回率悄悄退化(ADR-033 護欄 #3) 本 ADR 鎖定 **「LLM 結果 → 蒸餾 → Promotion Gate → ai_insights → RAG」自主學習迴圈** 的設計與護欄。 --- ## Decision ### 1. 雙表分離設計 | 表 | 用途 | 保留期 | PII 等級 | |---|---|---|---| | `rag_query_log` (migration 027) | 每次 RAG 查詢的 audit log | 90 天 | 中(query_text 可能含用戶問題)| | `learning_episodes` (migration 028) | LLM/MCP 結果蒸餾池 | 永久(蒸餾溯源)| 低(distilled_text 已過 PII redact)| | `ai_insights` (既有) | 已驗收的知識庫 | 永久 | 經 PromotionGate 過濾 | ### 2. 自主學習迴圈 ``` ┌─────────────────────────────────────────────────────────────┐ │ LLM 呼叫(Hermes/OpenClaw) │ │ ↓ │ │ RAG-first 攔截(cosine >= 0.85 命中) │ │ ↓ 命中 ↓ miss │ │ return synthesize LLM 跑 │ │ (rag_hit=true) ↓ │ │ Distiller 蒸餾 │ │ ↓ │ │ learning_episodes (pending) │ │ ↓ │ │ PromotionGate 4 階段 │ │ ↓ │ │ ai_insights (approved) │ │ ↓ │ │ 下次 RAG 查得到 │ └─────────────────────────────────────────────────────────────┘ ``` ### 3. Distiller 規則引擎(純 Hermes,零 LLM 成本) ```python QUALITY_RULES = { 'mcp_result': lambda c: 0.8 if len(c) > 200 and len(keywords(c)) >= 2 else 0.4, 'llm_json_ok': lambda c: 0.9, # 結構化 JSON + status='ok' 'llm_text': lambda c: 0.6 if has_zh_numbers(c) else 0.3, 'thumb_up': lambda _: 1.0, # 用戶 👍 反饋 'thumb_down': lambda _: 0.0, # 負樣本不晉升 } ``` **為何不用 LLM 蒸餾?** 避免循環燒錢(Distiller 跑頻率高,用 LLM 等於每次 RAG miss 都燒兩次)。 ### 4. PromotionGate 4 階段晉升閘(v5.0 護欄 #1) ```python class PromotionGate: STAGE_1_AUTO_QUALITY = 0.7 # 蒸餾品質分 STAGE_2_HALLUCINATION_RULES = [ '可能/也許/我猜測 + 缺具體數字', '自相矛盾(同段含 A=X 又 A=Y)', '引用不存在 SKU/品牌(查 DB)', ] STAGE_3_DEDUP_THRESHOLD = 0.95 # cosine 相似度 STAGE_4_HUMAN_REVIEW_WEIGHT = 0.8 # 高權重必經 👍/👎 ``` **鐵律**:weight ≥ 0.8 的 episode **不能跳 Stage 4**,必須推 Telegram 等 24h 反饋。 | 結果 | promotion_status | |---|---| | 4 階段全過 | `approved` → 寫入 ai_insights | | Stage 1 失敗 | `rejected_quality` | | Stage 2 失敗 | `rejected_hallucination` | | Stage 3 失敗 | `rejected_duplicate` | | Stage 4 推送等待 | `awaiting_review` | | 24h 無反饋 | `expired`(weight 降為 0.5,不晉升但保留)| | 用戶 👎 | `rejected_human` | ### 5. Telegram 反饋環(強制晉升門檻) ```python # services/telegram_templates.py def rag_feedback_keyboard(rag_query_log_id: int) -> dict: return {'inline_keyboard': [[ {'text': '👍 有用', 'callback_data': f'rag_fb:{id}:5'}, {'text': '👎 沒用', 'callback_data': f'rag_fb:{id}:1'}, ]]} def promotion_review_keyboard(episode_id: int) -> dict: return {'inline_keyboard': [[ {'text': '✅ 通過晉升', 'callback_data': f'pg_ok:{id}'}, {'text': '❌ 拒絕', 'callback_data': f'pg_no:{id}'}, ]]} ``` `routes/openclaw_bot_routes.py` 三組 callback handler 已就位(Phase 11 落地)。 ### 6. Feature Flag 灰度 ```python RAG_ENABLED = os.getenv('RAG_ENABLED', 'false').lower() == 'true' RAG_DEFAULT_THRESHOLD = float(os.getenv('RAG_DEFAULT_THRESHOLD', '0.85')) RAG_DEFAULT_TOP_K = int(os.getenv('RAG_DEFAULT_TOP_K', '5')) ``` **預設 OFF**,戰前部署後行為與 v4.x 完全相同。灰度開啟條件: 1. ANTHROPIC_API_KEY 已設(Phase 7 已備) 2. learning_episodes 累積 100+ 筆 3. RAG_ENABLED=true + threshold=0.90(保守起步) 4. 1 週後 feedback_score ≥ 4 比率 > 70% → threshold 降至 0.85 ### 7. 失敗安全(fire-and-forget 哲學) | 失敗模式 | 行為 | |---|---| | DB 寫入 rag_query_log 失敗 | 主流程不爆,logger.warning | | embedding 失敗 | 不查 DB 直接 fallback LLM | | signature 不一致 | log warning + 不採該筆 hit | | Distiller 寫 learning_episodes 失敗 | LLM 結果照樣回 caller | | PromotionGate Stage 1-3 失敗 | episode 留 learning_episodes(不晉升即可,無 DB 副作用)| --- ## Alternatives Considered | 方案 | 否決理由 | |---|---| | **A. 直接 ai_insights 寫入(無蒸餾池)** | LLM 幻覺直接污染知識庫,無 PromotionGate 阻擋(核心風險)| | **B. LLM 蒸餾(用 Gemini 寫 distill prompt)** | 循環燒錢:每次 RAG miss → LLM call → 又燒 LLM 蒸餾 = 2× 成本 | | **C. 純 push 不 pull(無反饋按鈕)** | 統帥無法糾正幻覺,正反饋錯誤循環(Owen 強調的痛點)| | **D. 跳過 dedup(Stage 3)** | ai_insights 將累積大量重複,RAG 查詢無謂耗時 | | **E. 用 ChromaDB / Qdrant 替代 pgvector** | 違反 ADR-002(pgvector 唯一)+ 增加運維面 | --- ## Consequences ### 正面(5) 1. **重複問題攔截**:預估月省 ~9M Hermes/OpenClaw tokens(Hermes 流量 -30%) 2. **自主學習**:每次 LLM 結果都進蒸餾池,知識庫持續成長 3. **錯誤可糾正**:👎 反饋直接降權,避免幻覺循環污染 4. **零 LLM 蒸餾成本**:Distiller 純規則引擎 5. **PII 安全**:query_text 截 4KB + human_approver SHA1[:8] ### 負面(3) 1. **複雜度↑**:兩表 + 4 階段閘 + 反饋環,新人理解曲線陡 2. **Stage 4 人工驗收延遲**:高權重 episode 必須等 24h 才能晉升 3. **embedding 寫入路徑暫缺**(已知 limitation):Stage 3 dedup 待 Phase 12+ 補 ### 風險(4) 1. **Distiller 規則漂移**:規則引擎可能漏判幻覺 → mitigate by Stage 4 人工驗收 2. **Stage 4 人工疲勞**:統帥不可能 24h 看 Telegram → mitigate by `expired` 自動降級 3. **ai_insights 膨脹**:學習迴圈累積快 → mitigate by Stage 3 dedup(Phase 12+ 啟用) 4. **PromotionGate worker cron 未掛**(已知):需 Phase 12+ 排程任務 --- ## Verification ### V1:RAG 攔截率(部署 1 週後) ```sql SELECT COUNT(*) FILTER (WHERE saved_call) AS hit_count, COUNT(*) AS total, ROUND(100.0 * COUNT(*) FILTER (WHERE saved_call) / COUNT(*), 1) AS hit_pct FROM rag_query_log WHERE queried_at > NOW() - INTERVAL '7 days'; -- 期望 hit_pct >= 25% ``` ### V2:晉升通過率 ```sql SELECT promotion_status, COUNT(*) FROM learning_episodes WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY promotion_status; -- 期望 approved + awaiting_review 占 >50%;rejected_hallucination < 10% ``` ### V3:反饋分布 ```sql SELECT feedback_score, COUNT(*) FROM rag_query_log WHERE feedback_score IS NOT NULL GROUP BY feedback_score; -- 期望 score=5 比率 > 60% ``` ### V4:Embedding 一致性(v5.0 護欄 #3) ```sql SELECT embedding_signature, COUNT(*) FROM ai_insights WHERE embedding IS NOT NULL GROUP BY embedding_signature; -- 期望單一簽名(多個 = 模型版本漂移,需處理) ``` --- ## Migration Plan | Phase | 工作 | 狀態 | |---|---|---| | 11.1 | rag_query_log + learning_episodes schema | ✅ migration 027/028 commit 2f20d8d | | 11.2 | rag_service.py + learning_pipeline.py | ✅ commit c7d6db3 | | 11.3 | Hermes/OpenClaw RAG-first 整合 | ✅ commit c7d6db3 | | 11.4 | Telegram 反饋按鈕 + callback | ✅ commit c7d6db3 | | 11.5 | learning_episodes.embedding 寫入 | ⏳ Phase 12+ | | 11.6 | PromotionGate worker cron 掛排程 | ⏳ Phase 12+ | | 11.7 | awaiting_review Telegram 推播 | ⏳ Phase 12+(callback 已就位)| | 11.8 | RAG_ENABLED=true 灰度啟用 | ⏳ 1 週觀察期後 | --- ## References - `migrations/027_create_rag_query_log.sql` - `migrations/028_create_learning_episodes.sql` - `services/rag_service.py`(532 行) - `services/learning_pipeline.py`(750 行) - `tests/test_rag_service.py` + `test_learning_pipeline.py` + `test_promotion_gate.py`(70 unit tests) - `docs/phase11_db_design_20260503.md` - ADR-002(pgvector 唯一向量庫) - ADR-007(雙寫保證) - ADR-029(Hermes-First 雙塔分工) - ADR-033(RAG 三護欄)— 即將補