Operation Ollama-First v5.0 / Phase 12 Wave 2 收尾 ADR-032 — RAG 自主學習迴圈 - 雙表分離:rag_query_log (audit) / learning_episodes (蒸餾池) / ai_insights (知識庫) - Distiller 規則引擎(純 Hermes 零 LLM 成本) - PromotionGate 4 階段晉升閘 - Telegram 反饋環(rag_feedback / promotion_review keyboard) - feature flag RAG_ENABLED 預設 OFF - V1-V4 驗收 SQL(命中率 / 晉升通過率 / 反饋分布 / embedding 一致性) ADR-033 — RAG 三護欄(Owen v5.0 鐵律) - 護欄 #1 Promotion Gate:強制反饋門檻,weight>=0.8 必經人工驗收 - 護欄 #2 Firecrawl 資源:Docker mem_limit:2g + chrome-reaper sidecar + 1.8GB 告警 - 護欄 #3 BGE-M3 一致性:embedding_signature SHA1[:12] + 啟動跨主機驗證 - 五案否決理由完整(包含「不要反饋按鈕」「不限資源」「:latest 接受漂移」) Migration Plan 對照: ✅ migration 026/028 schema + service 已落地 ⏳ Phase 12+ 補:embedding 寫入 / worker cron / Telegram 推播 / Firecrawl 部署 / signature 回填 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 KiB
10 KiB
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 強化護欄):
- 學習污染:LLM 幻覺自動進 RAG → 正反饋錯誤循環(ADR-033 護欄 #1)
- 資源消耗:自建 Firecrawl Playwright 池吃 188 主機記憶體(ADR-033 護欄 #2)
- 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 成本)
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)
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 反饋環(強制晉升門檻)
# 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 灰度
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 完全相同。灰度開啟條件:
- ANTHROPIC_API_KEY 已設(Phase 7 已備)
- learning_episodes 累積 100+ 筆
- RAG_ENABLED=true + threshold=0.90(保守起步)
- 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)
- 重複問題攔截:預估月省 ~9M Hermes/OpenClaw tokens(Hermes 流量 -30%)
- 自主學習:每次 LLM 結果都進蒸餾池,知識庫持續成長
- 錯誤可糾正:👎 反饋直接降權,避免幻覺循環污染
- 零 LLM 蒸餾成本:Distiller 純規則引擎
- PII 安全:query_text 截 4KB + human_approver SHA1[:8]
負面(3)
- 複雜度↑:兩表 + 4 階段閘 + 反饋環,新人理解曲線陡
- Stage 4 人工驗收延遲:高權重 episode 必須等 24h 才能晉升
- embedding 寫入路徑暫缺(已知 limitation):Stage 3 dedup 待 Phase 12+ 補
風險(4)
- Distiller 規則漂移:規則引擎可能漏判幻覺 → mitigate by Stage 4 人工驗收
- Stage 4 人工疲勞:統帥不可能 24h 看 Telegram → mitigate by
expired自動降級 - ai_insights 膨脹:學習迴圈累積快 → mitigate by Stage 3 dedup(Phase 12+ 啟用)
- PromotionGate worker cron 未掛(已知):需 Phase 12+ 排程任務
Verification
V1:RAG 攔截率(部署 1 週後)
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:晉升通過率
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:反饋分布
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)
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.sqlmigrations/028_create_learning_episodes.sqlservices/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 三護欄)— 即將補