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

263 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 幻覺檢測規則
```python
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 無回應 → `expired`weight 降 0.5,不晉升)
- 用戶 👎 → `rejected_human`(永不晉升)
- 用戶 👍 → `approved` 寫 ai_insights
**無條件規則**:高權重 episode 不能跳 Stage 4即使 Stage 1-3 都過。
### 護欄 #2Firecrawl 資源護欄188 主機保護)
#### Docker 限制
```yaml
# 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
```yaml
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模型版本可能不一致
#### 簽名鎖定機制
```python
# 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
```sql
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 護欄)
```python
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 查詢時保護
```python
# 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 週後)
```sql
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 反饋率
```sql
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 資源(部署後)
```bash
ssh ollama@192.168.0.188 'docker stats firecrawl-self --no-stream --format "{{.MemUsage}}"'
# 期望 < 1.8GBmem_limit 2GB 的 90%
```
### V4Embedding 一致性
```sql
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.sql`8 狀態機 CHECK
- `services/rag_service.py:get_embedding_signature()`
- `services/learning_pipeline.py`PromotionGate 4 階段)
- `tests/test_promotion_gate.py`23 unit tests
- ADR-002pgvector 唯一)
- ADR-027三主機架構
- ADR-032RAG 自主學習迴圈)
- ADR-031MCP 自建 — Phase 10 將補)