Files
ewoooc/docs/adr/ADR-033-rag-three-guardrails.md
OoO c29ce83653 docs(adr): ADR-032 RAG 自主學習迴圈 + ADR-033 三護欄
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>
2026-05-04 00:01:19 +08:00

9.8 KiB
Raw Blame History

ADR-033: RAG 治理三護欄 — Promotion Gate / Firecrawl 資源 / BGE-M3 一致性

  • Status: Accepted
  • Date: 2026-05-03
  • Decision Maker: 統帥
  • Author: Operation Ollama-First v5.0Owen 三點專業洞察 → v5.0 強化)
  • Related: ADR-032RAG 自主學習迴圈、ADR-031MCP 自建、ADR-002pgvector、ADR-027Primary Ollama on GCP

Context

戰役 v4.0 階段 Owen 提出三點專業洞察,被升級為 v5.0 護欄級鐵律:

  1. 學習污染風險LLM 幻覺自動進 RAG → 正反饋錯誤循環
  2. Firecrawl 資源消耗:自建 Playwright 池吃 188 主機記憶體
  3. BGE-M3 Embedding 一致性floating tag → RAG 召回率悄悄退化

這三點不是普通建議,而是 RAG 系統能否安全長期運轉的命脈。本 ADR 鎖定三護欄的設計決策與驗收條件。


Decision — 三護欄架構

護欄 #1Promotion Gate學習污染防護

核心原則反饋按鈕從「選配」升級為「強制晉升門檻」。learning_episodes → ai_insights 必經 4 階段嚴格門檻。

4 階段晉升閘

learning_episodes (pending)
    ↓ Stage 1: quality_score >= 0.7(蒸餾器自動評分)
    ↓ Stage 2: 無幻覺檢測(規則引擎,零 LLM
    ↓ Stage 3: 與既有 insight 相似度 < 0.95(去重)
    ↓ Stage 4: weight >= 0.8 必經 Telegram 👍/👎 人工驗收
ai_insights (approved)

Stage 2 幻覺檢測規則

HALLUCINATION_PATTERNS = [
    # 規則 1含「可能 / 也許 / 我猜測」+ 缺具體數字
    lambda txt: any(p in txt for p in ['可能', '也許', '我猜', '推測'])
                and not any(c.isdigit() for c in txt),
    
    # 規則 2自相矛盾同段含 'A=X' 又含 'A=Y'
    detect_contradiction,
    
    # 規則 3引用不存在 SKU/品牌(查 DB
    lambda txt: not _verify_skus_exist(extract_skus(txt)),
]

Stage 4 強制門檻Owen 鐵律)

  • weight >= 0.8 → 推 Telegram + 等 24h 👍/👎
  • 24h 無回應 → expiredweight 降 0.5,不晉升)
  • 用戶 👎rejected_human(永不晉升)
  • 用戶 👍approved 寫 ai_insights

無條件規則:高權重 episode 不能跳 Stage 4即使 Stage 1-3 都過。

護欄 #2Firecrawl 資源護欄188 主機保護)

Docker 限制

# docker-compose.mcp.ymlPhase 10 將部署)
services:
  firecrawl-self:
    image: firecrawl/firecrawl:latest
    deploy:
      resources:
        limits:
          memory: 2g          # ⭐ Owen 要求硬上限
          cpus: '1.5'
    environment:
      - PLAYWRIGHT_BROWSER_POOL_MAX=3   # 瀏覽器池上限
      - SCRAPE_TIMEOUT_MS=30000
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
      interval: 30s

Chrome 殘留清理 sidecar

chrome-reaper:
  image: alpine:3
  command: |
    sh -c "while true; do
      docker exec firecrawl-self pkill -f 'chrome.*--type=zygote' 2>/dev/null;
      docker exec firecrawl-self pkill -f 'chrome.*--type=renderer' 2>/dev/null;
      sleep 3600;
    done"

Telegram 告警

  • 每小時檢查 firecrawl 容器 RSS
  • 1.8GB → 🟠 P2 告警(記憶體即將達上限)

護欄 #3BGE-M3 Embedding 一致性RAG 命脈)

風險來源

  • bge-m3:latest floating tag → Ollama upgrade 跳版本
  • normalize / pooling 參數未顯式傳遞 → server-side 預設改變無感知
  • 跨主機GCP / Secondary / 111模型版本可能不一致

簽名鎖定機制

# services/rag_service.py
def get_embedding_signature(
    model: str = 'bge-m3:latest',
    dim: int = 1024,
    normalize: bool = True,
) -> str:
    """SHA1({model}|{normalize}|{dim})[:12]"""
    raw = f"{model}|{str(normalize).lower()}|{dim}"
    return hashlib.sha1(raw.encode()).hexdigest()[:12]

Schema 強制migration 026

ALTER TABLE ai_insights
    ADD COLUMN IF NOT EXISTS embedding_signature VARCHAR(64);

CREATE INDEX CONCURRENTLY idx_ai_insights_embedding_signature
    ON ai_insights (embedding_signature)
    WHERE embedding IS NOT NULL;

啟動時驗證Phase 11.0 護欄)

def verify_embedding_consistency():
    """RAG service 啟動時跑:
    用同一段測試文字呼叫 GCP / Secondary / 111 三主機,
    驗證 cosine 距離 < 1e-4浮點誤差否則拒絕啟動。
    """
    test_text = "momo電商競品分析測試向量一致性檢查"
    embeddings = {
        host: call_ollama(host, 'bge-m3:latest', test_text)
        for host in [GCP_PRIMARY, GCP_SECONDARY, OLLAMA_111]
    }
    diffs = [cosine_distance(embeddings[a], embeddings[b])
             for a, b in itertools.combinations(embeddings, 2)]
    if max(diffs) > 1e-4:
        raise EmbeddingInconsistencyError(...)

RAG 查詢時保護

# rag_service.py:_select_hits
for hit in candidates:
    if hit.embedding_signature != current_signature:
        logger.warning(f"Signature mismatch: hit={hit.id}, "
                      f"expected={current_signature}, got={hit.embedding_signature}")
        continue  # 不採用該筆

Alternatives Considered

方案 否決理由
A. RAG 不要反饋按鈕(純自動晉升) LLM 幻覺進 RAG 後正反饋錯誤循環,是 RAG 系統最危險的失敗模式
B. Firecrawl 不限資源(讓它跑) 188 主機跑 5+ projectreference_188_multi_projectOOM 會拖垮其他容器
C. BGE-M3 用 :latest 接受漂移 模型升級時無告警RAG 召回率悄悄退化,問題暴露時難回溯
D. 三護欄都用 LLM 做(如 LLM 蒸餾、LLM 幻覺檢測) 循環燒錢 + 引入新幻覺風險LLM 檢測 LLM 幻覺)
E. Stage 4 改為非強制(高 weight 直接 approved 違反 Owen 鐵律 — 統帥反饋是 RAG 系統不被污染的最後一道防線

Consequences

正面5

  1. 學習污染防火牆4 階段閘 + 強制人工驗收,幻覺進 RAG 機率 < 5%
  2. 資源預測性Firecrawl mem_limit 2g + chrome-reaper188 主機絕對安全
  3. 模型升級可控embedding_signature 不變才 RAG 採用,模型漂移立即可見
  4. PII 安全human_approver SHA1[:8],反饋紀錄不暴露 Telegram username
  5. 成本可控純規則引擎Stage 1-3+ 24h auto-expireStage 4零 LLM 成本

負面3

  1. Stage 4 統帥疲勞:高權重 episode 都要看 Telegram → mitigate by expired 自動降級
  2. Firecrawl mem 2g 上限可能太小:複雜 SPA 爬蟲可能超 → 監控告警 + 可調 env
  3. Embedding signature 變更需全表回填PG14 ADD COLUMN metadata-only 不鎖表,但回填 14k+ 筆需 worker 跑數小時

風險4

  1. Stage 2 規則漏判:規則引擎可能誤放幻覺進 → mitigate by Stage 4 人工最後關
  2. Firecrawl OOM 連鎖mem_limit 觸發 OOM kill → mitigate by healthcheck + 重啟策略
  3. Embedding 模型升級時 RAG 完全失效:所有 hit signature 不符 → 安全降級為「LLM-only」直到回填完成
  4. 24h expired 太久:用戶可能來不及反饋 → 可調 HUMAN_REVIEW_TIMEOUT_HOURS

Verification

V1Promotion Gate 阻擋率(部署 1 週後)

SELECT promotion_status, COUNT(*)
FROM learning_episodes
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY promotion_status;
-- 期望: rejected_hallucination >= 1證明 Stage 2 真的擋下幻覺)
-- 期望: approved + awaiting_review > 50%

V2Stage 4 反饋率

SELECT
  COUNT(*) FILTER (WHERE promotion_status = 'awaiting_review') AS pending,
  COUNT(*) FILTER (WHERE promotion_status = 'approved' AND human_approver IS NOT NULL) AS human_approved,
  COUNT(*) FILTER (WHERE promotion_status = 'rejected_human') AS human_rejected,
  COUNT(*) FILTER (WHERE promotion_status = 'expired') AS expired
FROM learning_episodes;
-- 期望: human_approved + human_rejected > expired統帥真的有看 Telegram

V3Firecrawl 資源(部署後)

ssh ollama@192.168.0.188 'docker stats firecrawl-self --no-stream --format "{{.MemUsage}}"'
# 期望 < 1.8GBmem_limit 2GB 的 90%

V4Embedding 一致性

SELECT embedding_signature, COUNT(*), MIN(created_at), MAX(created_at)
FROM ai_insights
WHERE embedding IS NOT NULL
GROUP BY embedding_signature
ORDER BY MAX(created_at) DESC;
-- 期望: 單一簽名(多個 = 模型漂移)

Migration Plan

護欄 部分 狀態
#1 PromotionGate Schema learning_episodes 8 狀態機 migration 028 commit 2f20d8d
#1 PromotionGate Service 4 階段邏輯 + reject/promote services/learning_pipeline.py commit c7d6db3
#1 反饋按鈕 rag_feedback + promotion_review telegram_templates + bot routes commit c7d6db3
#1 awaiting_review 推播 Telegram 推 episode 給統帥看 Phase 12+
#2 Firecrawl mem_limit docker-compose.mcp.yml Phase 10 部署
#2 chrome-reaper sidecar 同上 Phase 10
#2 RSS 監控告警 scheduler 加每小時 task Phase 10
#3 embedding_signature 欄位 ai_insights 加欄位 migration 026 commit 4648673
#3 簽名計算 rag_service.get_embedding_signature() commit c7d6db3
#3 啟動驗證 verify_consistency 跨主機 cosine 比對 Phase 11+ 補Phase 11.0 規格)
#3 既有 14k 筆回填 UPDATE ai_insights SET embedding_signature = ... Phase 11+ 補

References

  • migrations/026_add_embedding_signature.sql(含 pgcrypto extension
  • migrations/028_create_learning_episodes.sql8 狀態機 CHECK
  • services/rag_service.py:get_embedding_signature()
  • services/learning_pipeline.pyPromotionGate 4 階段)
  • tests/test_promotion_gate.py23 unit tests
  • ADR-002pgvector 唯一)
  • ADR-027三主機架構
  • ADR-032RAG 自主學習迴圈)
  • ADR-031MCP 自建 — Phase 10 將補)