本文說明為什麼傳統 CI/CD 關卡不足以因應生產環境的 AI 系統。我分享了一種實用的 LLM 管線發行關卡方法,包含基線評估、漂移偵測、影子驗證,以及成本/延遲防護機制。重點在於預防:在 AI 回歸影響使用者之前就攔截它們,這些方法來自生產基礎架構的實際平台工程經驗。
我在週五下午部署了一個更新的 RAG 管線。所有評估都通過了,相似度分數看起來很不錯,但到了週一早上,系統卻自信地推薦過時的定價資料,因為嵌入模型的漂移程度剛好足以偏好過時的區塊而非新鮮的內容。沒有警示觸發,沒有測試失敗,儀表板顯示綠燈,但輸出卻是一團亂。
那一刻我不再相信綠燈就是可以發行的訊號。我們遇到的不是部署問題,而是發行關卡的問題。傳統 CI/CD 是為確定性軟體而設計的。LLM 則是機率的。我們的關卡也必須是機率的。
為什麼傳統 CI/CD 無法應對 LLM 管線
在一般的軟體交付中,關卡很簡單:單元測試通過、整合測試通過、安全檢查通過,然後部署。這是二元的。建置結果要麼是綠燈,要麼是紅燈。當你交付的東西是「行為」時,這個模式就失效了。
「傳統 CI/CD 是為確定性軟體而設計的。LLM 則是機率的。我們的關卡也必須是機率的。」
一個生產環境的 AI 系統,今天的相關性得分可能是 0.82,明天變成 0.79,下週又掉到 0.74。因為沒有單一次執行超過失敗門檻,所以沒有人收到通知,儘管使用者已經在使用更差的答案。
我有超過 20 年管理生產基礎架構的經驗,包括 OpenStack 私有雲、資料庫遷移、CI/CD 管線,以及任務關鍵系統。在傳統 DevOps 中,我們學到一台伺服器回報 99.9% 可用性,卻遺漏 0.1% 的金融交易,這不能算健康。它是在隱藏錯誤。LLM 評估也是如此。總分會掩蓋局部失效。
第一種失效模式是評估漂移:分數逐漸下降,但從未低到觸發硬性門檻。第二種是分布偏移:真實使用者會問簡短、混亂、奇怪的問題,而這些問題不在你乾淨的評估資料集中。第三種是上下文污染:你檢索的文件已經改變,但你的測試仍在問昨天的問題。
傳統 CI/CD 關卡會問:「測試通過了嗎?」LLM 發行關卡會問:「行為是否維持在可接受的範圍內?」一般的部署關卡會比較預期輸出與實際輸出。AI CI/CD 關卡則會將候選行為與歷史及生產環境行為、成本、延遲與風險進行比較。傳統關卡會在例外發生時快速失敗。LLM 發行關卡則會在漂移發生時謹慎失敗。
「傳統關卡會在例外發生時快速失敗。LLM 發行關卡則會在漂移發生時謹慎失敗。」
這個差異很重要,因為生產環境的 AI 系統是慢慢腐壞,而不是突然爆炸。
我建立了 llm-eval-drift-release-gates-AGENT,因為我希望有一個把 AI 發行當成基礎架構發行來對待的關卡:可衡量、可重複,而且盡可能無聊。無聊是值得的。它讓工程師能好好睡覺。
LLM 發行關卡的組成
第一個關卡是基線評估套件。它會針對候選管線執行固定資料集,並評分相關性、忠實度、安全性、 groundedness,以及任何你關心的領域特定檢查。重點不是證明模型完美,而是要在回歸變成客戶故事之前就攔截它們。
以下是我使用的模式中的簡化 Python 結構:
from enum import StrEnum
from pydantic import BaseModel, Field
class GateStatus(StrEnum):
PASS = "pass"
WARN = "warn"
BLOCK = "block"
class EvalResult(BaseModel):
run_id: str
status: GateStatus
scores: dict[str, float] = Field(default_factory=dict)
drift_detected: bool = False
reason: str | None = None
使用 StrEnum 讓 enum 保持原生字串型別,而不需要依賴多重繼承。這在發行工具中很重要,因為狀態值經常會被記錄、序列化、在 CI 中比較,或傳入儀表板。
這可以捕捉明顯的失敗,例如相關性崩壞、忠實度下降、不安全的輸出,或格式錯誤的評估器結果。如果硬性失敗,管線會立即阻擋;如果觸發警告,則需要人工核准。我不喜歡無聲的警告;它們只是穿著漂亮衣服的未來事件。
第二個關卡是評估漂移偵測。固定的門檻是不夠的。如果相關性從 0.91 掉到 0.86,你的 0.80 門檻會說一切正常,但使用者可能不同意。
因此我會將目前分數與近期部署的滾動基線進行比較:
def detect_score_drift(
current: dict[str, float],
baseline: dict[str, float],
max_drop_pct: float = 5.0,
) -> tuple[bool, list[str]]:
failures: list[str] = []
for metric, base_value in baseline.items():
current_value = current.get(metric)
if current_value is None:
failures.append(f"Missing metric in current run: {metric}")
continue
drop_pct = ((base_value - current_value) / base_value) * 100
if drop_pct > max_drop_pct:
failures.append(f"{metric} dropped {drop_pct:.2f}%")
return bool(failures), failures
評估套件在週四晚上 11 點偵測到相關性下降 6%。如果沒有這個機制,那個下降會在週一影響 200 位使用者。這個關卡不知道業務情境。它也不需要知道。它只知道候選版本比上一個已知良好的版本更差。
第三個關卡是影子流量驗證。在全面上線之前,我會將一小部分真實流量導向候選管線。使用者仍然會收到生產環境的答案,但系統會記錄候選輸出的比較結果。
這就是應用在 AI 上的金絲雀部署。我曾在基礎架構上線中使用過相同的模式,包括我的 eks-canary-deployment-pipeline 儲存庫背後的想法。差別在於,對於 LLM,你不只比較 HTTP 200。你還會比較答案品質、檢索到的上下文、延遲,以及候選版本是否出於可疑理由而與生產環境意見相左。
評審模型可能有用,但我不會讓它成為唯一的權威。它只產生一個訊號。發行政策才做決定。
第四個關卡是成本與延遲。一個得分完美但成本高出三倍的模型不是有效的發行版本。一個增加兩秒延遲的 RAG 管線也還沒準備好。這個想法也影響了我的 enterprise-rag-guardrails-costops 工作。
def enforce_runtime_budget(
total_tokens: int,
latency_ms: int,
max_tokens: int = 8000,
max_latency_ms: int = 2500,
) -> None:
if total_tokens > max_tokens:
raise RuntimeError(f"token budget exceeded: {total_tokens}")
if latency_ms > max_latency_ms:
raise RuntimeError(f"latency budget exceeded: {latency_ms}ms")
當這個關卡失敗時,系統會阻擋發行或導向人工核准。我學到不要在部署時跟延遲談判。它總是會在之後獲勝。
這四個關卡無法讓 LLM 部署變得完美,但它們會讓你更難用綠色徽章發送垃圾。
將其整合到你現有的 CI/CD
發行關卡不應該是一個獨立的科學專案。如果你的平台團隊已經在使用 GitHub Actions 或 GitLab CI,LLM 部署管線應該融入該工作流程。
這個模式刻意保持無聊:
YAML pipeline: - build - unit-tests - eval-baseline - drift-check - shadow-validate - cost-guard - deploy - post-deploy-monitor
這是我從 devsecops-pipeline-github-actions 工作延伸出來的形狀。建置應用程式。執行確定性測試。執行基線評估套件。與滾動基線比較。對影子流量進行驗證。檢查成本與延遲預算。只有當所有關卡都同意時才進行部署。
這就是 MLOps 防護機制對平台工程師有用的地方。你不需要一種獨立的部署宗教。你只需要在團隊已經信任的管線中加入一組額外的檢查。
以下是一個可以從 CI 呼叫的小型 Python 入口點。重要的細節是,當所需的報告遺失或格式錯誤時,它會優雅地失敗,因為原始堆疊追蹤不是一種發行策略。
import json
import sys
from pathlib import Path
def load_scores(path: str) -> dict[str, float]:
payload = json.loads(Path(path).read_text(encoding="utf-8"))
return {key: float(value) for key, value in payload.items()}
current = load_scores("reports/current_eval.json")
baseline = load_scores("reports/baseline_eval.json")
drifted, reasons = detect_score_drift(current, baseline, max_drop_pct=5.0)
if drifted:
print("LLM release gate blocked:", "; ".join(reasons))
sys.exit(1)
print("LLM release gate passed")
最好的發行關卡是你的團隊實際會使用的關卡。如果它需要機器學習博士才能設定,那它就不是關卡,而是牆。
我正在攻讀雲端運算安全的博士學位,我甚至不想要一個每週五都需要論文的部署流程。關卡應該簡單到能在 CI 中執行,嚴格到能阻擋不良發行,並且靈活到不會變成辦公室裝飾。
最後這一點,我花了比我願意承認的更長時間才學會。
我做錯了什麼,以及我會怎麼做不同
我的第一個版本過於嚴格。每一次部署都被阻擋。評估套件會抱怨小小的用詞改變、無害的格式差異,以及邊緣的分數下降。團隊開始完全繞過這些關卡。那是我的錯。
阻擋一切的關卡比沒有關卡更糟。至少在沒有關卡的情況下,人們知道他們正在承擔風險。但在一個糟糕的關卡下,他們學會忽略這個系統。
「阻擋一切的關卡比沒有關卡更糟。在一個糟糕的關卡下,人們學會忽略這個系統。」
我的第二個錯誤是只在合成查詢上進行評估。它們乾淨、完整且有禮貌。真實使用者不是這樣。真實使用者會輸入三個字、拼錯產品名稱、貼上片段,並期望系統理解他們從未提供的上下文。
評估看起來很棒。生產環境卻不是。
我的第三個錯誤是沒有對評估資料集進行版本控制。當我加入新的邊緣案例時,我就失去了將舊發行版本與新基線進行比較的能力。現在我會像版本控制 Terraform 狀態一樣版本控制評估集:小心翼翼、有備份,並帶著一點偏執。
在玻利維亞科恰班巴建立這個專案也影響了設計。我沒有無限的雲端額度可以燒在大量評估執行上。這迫使我優化取樣、快取評審呼叫,並將快速的 PR 檢查與較重的夜間檢查分開。
限制催生更好的工程。雖然煩人,但這是真的。
交付信心,而不只是程式碼
在傳統軟體中,我們交付程式碼並驗證行為。在 AI 中,我們交付行為並驗證對齊。發行關卡就是我們彌補這個差距的方式。
LLM 發行關卡不會消除生產環境 AI 系統的不確定性。沒有任何東西能做到。但它們為平台團隊提供了一種實用的方法,在使用者發現之前就攔截評估漂移、分布偏移、上下文污染、成本激增和延遲回歸。
這對代理工作流程的可靠性很重要。它對 AI CI/CD 很重要。它很重要,因為「所有測試都通過了」已經不再足夠。
我建立 llm-eval-drift-release-gates-AGENT,是因為我厭倦了綠色管線對我說謊。這個儲存庫是這個發行關卡模式的開源、離線優先參考實作。它刻意保持足夠小,讓你可以在自己的 CI/CD 環境中檢查、執行、破壞和強化。
儲存庫:https://github.com/fdaniel-alvarez-dev/llm-eval-drift-release-gates-AGENT,
分叉它、破壞它、改進它。最糟糕的發行關卡是你從未建立的那一個。
科技發展迅速,別錯過任何一集。訂閱我們的 YouTube 頻道,串流所有播客、訪談、示範,以及更多內容。
Group Created with Sketch.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.