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>
5.4 KiB
5.4 KiB
ADR-018: LLM 測試策略
| 屬性 | 值 |
|---|---|
| 狀態 | 延緩 (Deferred) - 方案 A 先行 |
| 建立日期 | 2026-03-26 |
| 決策者 | 首席架構師 + 統帥 |
| 關聯 | Phase 12.3, #68, #69 |
背景
現有 LLM 測試 (test_model_regression.py, test_prompt_validation.py) 存在以下問題:
- 字串精確匹配 - 檢查 "AWOOOI" 或 "kubectl" 是否存在
- LLM 非確定性 - 相同 prompt 產生不同輸出
- CI 依賴 Live LLM - Ollama 不穩定導致 CI 失敗
- 無語意理解 - 無法判斷「意思相近」
決策
採用 三層測試策略:
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 天)
- 修改 OpenClaw Prompt 強制 JSON 輸出
- 建立
LLMProposalOutputPydantic Model - 所有 LLM 回應經過 Schema 驗證
Phase 2: Golden Response 錄製 (1 天)
- 執行現有測試,錄製成功回應
- 儲存為
tests/fixtures/golden_responses.json - CI 改用 Golden Response Mock
Phase 3: 屬性測試重構 (2 天)
- 將 lambda validators 改為屬性測試
- 新增 kubectl 解析驗證
- 新增風險等級 Enum 驗證
Phase 4: 語意測試 (2 天)
- 整合 sentence-transformers
- 建立語意相似度基準線
- 設定 Weekly 排程
替代方案
| 方案 | 優點 | 缺點 | 決定 |
|---|---|---|---|
| Skip 測試 | 簡單 | 無品質保證 | ❌ |
| 增加 Retry | 簡單 | 治標不治本 | ❌ |
| 降低閾值 | 簡單 | 漏檢問題 | ❌ |
| 三層策略 | 完整 | 實作成本 | ✅ |
風險
- Embedding 模型載入時間 - 使用輕量模型 (MiniLM)
- Golden Response 過時 - 定期更新機制
- 語意閾值調校 - 初期從寬,逐步收緊
成功指標
- 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 強制繁中 |
原因:
- 三層策略實作成本高 (~1000 行代碼)
- 方案 A 已解決 CI 不穩定問題
- 優先確保 CI 穩定,後續再評估是否需要完整框架
評估文件: docs/evaluations/2026-03-26_llm_testing_evaluation.md
後續: 若方案 A 效果良好,此 ADR 維持 Deferred;若仍有問題,再實施三層策略