Files
awoooi/docs/adr/ADR-018-llm-testing-strategy.md
OG T 2f5986df5c docs: ADR 整理與新增 (021-029)
ADR 編號修正:
- ADR-023 failure-auto-repair → ADR-028
- ADR-025 cicd-ai-integration → ADR-029

新增 ADR:
- ADR-021: Playbook 更新驗證
- ADR-022: Sentry 整合架構
- ADR-027: Incident-Approval 同步
- ADR-028: 失敗自動修復閉環
- ADR-029: CI/CD AI 整合 (原 ADR-025)

更新:
- ADR-018: LLM 測試策略狀態更新

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-26 19:09:08 +08:00

5.4 KiB
Raw Blame History

ADR-018: LLM 測試策略

屬性
狀態 延緩 (Deferred) - 方案 A 先行
建立日期 2026-03-26
決策者 首席架構師 + 統帥
關聯 Phase 12.3, #68, #69

背景

現有 LLM 測試 (test_model_regression.py, test_prompt_validation.py) 存在以下問題:

  1. 字串精確匹配 - 檢查 "AWOOOI" 或 "kubectl" 是否存在
  2. LLM 非確定性 - 相同 prompt 產生不同輸出
  3. CI 依賴 Live LLM - Ollama 不穩定導致 CI 失敗
  4. 無語意理解 - 無法判斷「意思相近」

決策

採用 三層測試策略

Tier 1: 結構驗證 (CI 必跑)

測試目標: 輸出符合 Schema
執行時機: 每次 CI
Mock 策略: 使用錄製的 Golden Responses

原則:

  • 驗證 JSON Schema不驗證內容
  • 驗證 kubectl 語法有效性,不驗證具體命令
  • 驗證風險等級為有效 Enum不驗證「應該是 HIGH」

實作:

# ❌ 錯誤: 字串匹配
lambda r: "CRITICAL" in r.upper()

# ✅ 正確: Schema 驗證
class LLMProposalOutput(BaseModel):
    risk_level: Literal["LOW", "MEDIUM", "HIGH", "CRITICAL"]
    kubectl_command: str
    reasoning: str

def validate_output(response: str) -> bool:
    try:
        parsed = LLMProposalOutput.model_validate_json(response)
        return True
    except ValidationError:
        return False

Tier 2: 屬性測試 (Nightly)

測試目標: 輸出符合不變量
執行時機: 每晚 / PR Merge
Mock 策略: Live LLM + 寬鬆閾值

不變量範例:

  • kubectl 命令必須可解析 (用 shlex.split)
  • 風險等級必須為有效值
  • 回應長度 < 500 字
  • 繁體中文比例 > 30% (用 regex 檢測)

實作:

def test_kubectl_parseable(llm_response):
    """kubectl 命令必須可被 shell 解析"""
    import shlex
    cmd = extract_kubectl(llm_response)
    if cmd:
        tokens = shlex.split(cmd)  # 若無法解析會拋 ValueError
        assert tokens[0] == "kubectl"

def test_risk_level_valid(llm_response):
    """風險等級必須是有效值"""
    risk = extract_risk_level(llm_response)
    assert risk in {"LOW", "MEDIUM", "HIGH", "CRITICAL"}

Tier 3: 語意品質 (Weekly/Manual)

測試目標: 輸出語意品質
執行時機: 每週 / 手動觸發
Mock 策略: Live LLM + Embedding 比對

原則:

  • 使用 Embedding 計算語意相似度
  • 與 Golden Response 比對,相似度 > 0.7 為通過
  • 統計通過率8/10 通過 = 整體通過

實作:

async def test_semantic_similarity(llm_response, golden_response):
    """語意相似度測試"""
    from sentence_transformers import SentenceTransformer

    model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
    emb1 = model.encode(llm_response)
    emb2 = model.encode(golden_response)

    similarity = cosine_similarity(emb1, emb2)
    assert similarity > 0.7, f"相似度 {similarity:.2f} 低於閾值 0.7"

測試分層總覽

Tier 執行時機 Mock 驗證內容 失敗處理
1 每次 CI Golden Response Schema + 語法 阻擋合併
2 Nightly Live LLM 不變量屬性 告警 + Issue
3 Weekly Live LLM + Embedding 語意品質 報告 + 趨勢追蹤

實作計畫

Phase 1: Schema 強制 (1 天)

  1. 修改 OpenClaw Prompt 強制 JSON 輸出
  2. 建立 LLMProposalOutput Pydantic Model
  3. 所有 LLM 回應經過 Schema 驗證

Phase 2: Golden Response 錄製 (1 天)

  1. 執行現有測試,錄製成功回應
  2. 儲存為 tests/fixtures/golden_responses.json
  3. CI 改用 Golden Response Mock

Phase 3: 屬性測試重構 (2 天)

  1. 將 lambda validators 改為屬性測試
  2. 新增 kubectl 解析驗證
  3. 新增風險等級 Enum 驗證

Phase 4: 語意測試 (2 天)

  1. 整合 sentence-transformers
  2. 建立語意相似度基準線
  3. 設定 Weekly 排程

替代方案

方案 優點 缺點 決定
Skip 測試 簡單 無品質保證
增加 Retry 簡單 治標不治本
降低閾值 簡單 漏檢問題
三層策略 完整 實作成本

風險

  1. Embedding 模型載入時間 - 使用輕量模型 (MiniLM)
  2. Golden Response 過時 - 定期更新機制
  3. 語意閾值調校 - 初期從寬,逐步收緊

成功指標

  • Tier 1 測試 100% 通過率 (CI 不再因 LLM 波動失敗)
  • Tier 2 測試 > 90% 通過率 (Nightly)
  • Tier 3 測試 > 80% 通過率 (Weekly)
  • 無 LLM 相關 CI 失敗導致的開發阻塞

參考


2026-03-26 決策更新

狀態: 延緩 (Deferred)

實際採用方案: 方案 A - 最小改動優先

項目 實作內容
確定性參數 temperature: 0.0, seed: 42
CI 分層 LLM 測試移至 Nightly
超時調整 300 秒 (CPU 推理)
繁體中文 System Prompt 強制繁中

原因:

  1. 三層策略實作成本高 (~1000 行代碼)
  2. 方案 A 已解決 CI 不穩定問題
  3. 優先確保 CI 穩定,後續再評估是否需要完整框架

評估文件: docs/evaluations/2026-03-26_llm_testing_evaluation.md

後續: 若方案 A 效果良好,此 ADR 維持 Deferred若仍有問題再實施三層策略