iT邦幫忙

2026 iThome 鐵人賽

0
AI Engineering

Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability系列 第 53 篇

Day 31 特別篇(上)|LangSmith 全服務解析:從 LLM Trace 到 Production AI Reliability

  • 分享至 

  • xImage
  •  

GitHub:darkstar1227/learning-sre-for-ai-era

結論先說:LangSmith 可以把 AI workflow 的 trace、dataset、evaluation 與 feedback 串成學習迴圈;它不會替你定義 SLO,也不會取代 Prometheus、Grafana、Loki 或 OpenTelemetry。

Day 1 用一條 LLM trace 看見 request 不只是一個 HTTP handler。Day 30 則要求用受控實驗驗證 provider、DB 與 tool failure。還差一件事:發現一次失敗後,怎麼讓下一次變更不再踩回同一個坑?

本文是主線結束後的特別篇,因篇幅限制拆分為上中下三篇發布。這一篇(上)先談概念與比較:LangSmith 在整條觀測鏈裡的定位、五個容易搞混的 ID、project/metadata/tags 的分工、dataset 與 evaluation 的設計原則、feedback 的 triage 流程,以及把這一切串起來的 release gate 迴圈。它把既有的 SRE 證據接到 AI application 的版本與評估流程,不另起一套監控宇宙。

① LangSmith 放在哪裡?

使用者結果、SLO、Alert ── Prometheus / Grafana
服務事件與錯誤      ── Structured Logs / Loki
跨服務請求路徑      ── OpenTelemetry / Trace backend
AI workflow 的 run、dataset、evaluation、feedback ── LangSmith

LangSmith 適合記錄 prompt build、retrieval、model、tool、parser、validator 等 workflow 節點,以及版本與評估結果。它不是 availability 的唯一來源,也不該成為存放完整 prompt、API key、token、個資或機密文件的預設位置。

metrics 用來判斷使用者是否受影響;trace 用來追這次 request 經過哪裡;dataset 和 evaluation 用來比較下一次變更能不能放行。

這三層各自有不能互相取代的理由,不是分工上的潔癖。metrics 靠固定 cardinality 的 label 換來低成本的長期聚合與 alert,代價是它天生無法回答「為什麼」;trace 能回答「為什麼」,代價是它按 request 計費、無法用來算 error budget,也不該被拿去觸發 page。LangSmith 的 run 記錄更進一步:它連的是「這次輸出是不是可以信任」,這個問題連完整的 OpenTelemetry span 都答不了,因為 span 知道 retrieval 呼叫花了多久,卻不知道 retrieval 拿回來的文件是不是對的文件。把三層混在一起會出現具體的壞後果:如果把 request_id 或完整 prompt 塞進 Prometheus label,一小時內就能把 time series 基數炸出十萬筆以上,讓 Prometheus 記憶體吃緊、查詢變慢,這不是理論上的風險,而是 high-cardinality label 的已知失效模式;反過來,如果只看 LangSmith 的 evaluation 分數就宣布服務健康,會漏掉一種很常見的情況——candidate 的答案品質完全沒變,但因為換了 SDK 版本,retrieval 呼叫的 client 少了 connection pool 設定,於是 p99 latency 在生產悄悄爬升,這種退化只有 metrics 和 trace 看得見,dataset 的固定題目答不出來。

為什麼是三層,不是兩層或一層

把 metrics、trace、AI run record 硬併成一層,最常見的動機是「省事」——只裝一套系統,少一個 vendor,少一份帳單。但這三層的成本結構和查詢模式天差地遠,硬併只會讓三種問題都答不好。

Metrics(Prometheus)
  資料形狀:固定 label 的計數器與直方圖
  儲存成本:與 time series 數量成正比,與 request 數量無關
  查詢速度:毫秒級,可以每 15 秒 scrape 一次
  能回答:現在有多少比例的使用者受影響?
  答不出:這個使用者的這次請求,為什麼失敗?

Trace(OpenTelemetry + Tempo)
  資料形狀:一次 request 的完整 span 樹
  儲存成本:與 request 數量成正比,通常會抽樣
  查詢速度:秒級,依 trace_id 或時間窗查詢
  能回答:這次請求,時間花在哪一個服務、哪一個依賴?
  答不出:這次請求的答案內容,對不對?

AI Run Record(LangSmith)
  資料形狀:workflow 節點的輸入輸出、版本、evaluation 結果
  儲存成本:與 run 數量成正比,且常包含較大的 payload
  查詢速度:秒級,依 project、tag、dataset 查詢
  能回答:這次輸出符不符合已知規則?換了版本後品質有沒有變差?
  答不出:使用者現在正不正在流量高峰、系統現在健不健康?

三層的「答不出」剛好被另外兩層接住,這才是分層的意義——不是三套系統各自獨立運作,而是三套系統的能力邊界互補。真正會出問題的,反而是把某一層的資料硬塞進另一層想省一套系統的維運成本,最後卻讓每一層都變得不可靠。

三個常見誤解

誤解一:LangSmith 可以取代 alerting。 LangSmith 沒有為你設計 alert 機制去做長期趨勢的異常偵測與通知;它的介面適合人工在事件發生後去查一筆 run,而不是每 15 秒去問「現在系統健不健康」。把它當 alerting 來源,代價是你要嘛只能人工盯著看,要嘛得自己刻一套 polling script 去追蹤 run 的異常比例,那不如直接讓 Prometheus 做它本來就擅長的事。

誤解二:把 LangSmith 當完整的 audit log 使用。 LangSmith 的 retention policy、存取控制與備份策略是為了「支援開發與品質迭代」設計,不是為了滿足法遵、合約或資安稽核要求而設計。如果你的產業需要能保留數年、防止竄改、可供第三方稽核的完整操作紀錄,那是另一套系統的責任,LangSmith 的 trace 保存期限和存取粒度都不是為這個用途優化的。

誤解三:production incident 一定要先查 LangSmith。 這個誤解特別常見在剛導入 AI workflow 追蹤的團隊。事實是查詢順序要看警訊的來源:Prometheus 顯示的是 dependency timeout 還是 error rate 升高,那應該先看 Day 2 建立的 metrics/logs/trace 三件套;只有當警訊指向「語意層」——答案品質下降、citation 消失、tool 被誤用——才輪到 LangSmith 出場。Step 14 會把這個查詢順序寫得更具體。

② 讓一條 trace 還原工作流

POST /ask
├─ retrieve          {index_version, result_count}
├─ prompt_build      {prompt_version}
├─ model             {model_id, ttft_ms, output_tokens}
├─ tool              {tool_name, outcome}
├─ validate          {citation_valid, safety_status}
└─ response          {task_status, quality_status}

每個 run 應有低敏感度的版本與 outcome。request_id 可以回到 service log 與 OpenTelemetry trace;task_id 關聯同一件使用者工作;trace_id、evaluation_run_id 與 dataset_example_id 則讓後續評估能找回來源。

這些 ID 不適合做 Prometheus label。它們適合 trace、log 和受控資料庫查詢。

這五個 ID 容易被當成同一件事,但它們回答的是不同層級的問題,搞混會直接拖慢 incident 調查。request_id 是 transport 層的識別碼,對應一次 HTTP 呼叫,Nginx access log、Loki 的結構化 log 與 OTel 的 root span 都應該帶著它——它是三個系統唯一共通的 join key。task_id 站得更高:使用者的一件事有可能觸發多個 request(例如先問一句、系統追問澄清、使用者再回一句),這些 request 各有自己的 request_id,但共用同一個 task_id,這樣才能回答「這位使用者這次到底有沒有把事情辦成」,而不是只看單一 HTTP 呼叫是否成功。trace_id 是 LangSmith 內部的 run 識別碼,對應到 root run 與它底下的 child run 樹;它和 OTel 的 trace_id是兩個系統各自的命名空間,即使兩邊都叫 trace_id,也不能假設它們是同一個值,除非你在程式碼裡明確把 OTel trace context 傳進 LangSmith 的 metadata。evaluation_run_id 與 dataset_example_id 則只在離線評估情境有意義:前者標記「這是哪一次 baseline/candidate 比較」,後者標記「這是 dataset 裡的哪一筆案例」,兩者合起來才能回答「這次 evaluation 到底測了什麼版本、對到哪一筆已知失敗」。實務上建議在服務邊界建一個小型 context 物件,把 request_id、task_id 產生一次就往下傳,而不是讓 retrieval、model、validator 各自生成新的 ID——各自生成的後果是同一次 request 在 log、trace、LangSmith 三邊出現三組互不相干的識別碼,事後只能用時間窗「猜」它們是不是同一件事。

五個 ID 一覽表

下表把五個 ID 的產生時機、有效範圍與該去哪裡查放在一起看,避免臨場需要時才發現彼此定義不清楚。

ID 誰產生 有效範圍 該去哪裡查 常見誤用
request_id 服務邊界(第一個 handler) 一次 HTTP 呼叫 access log、Loki、OTel root span 被誤當成使用者身分識別碼使用
task_id 業務邏輯層(工作開始時) 使用者一件事,可跨多個 request 業務資料庫、聚合報表 與 request_id 混用,變成 1:1 對應
trace_id(LangSmith) LangSmith SDK 一個 root run 與其 child run 樹 LangSmith UI/SDK 查詢 誤以為和 OTel trace_id 是同一個值
evaluation_run_id LangSmith Client.evaluate() 一次 baseline/candidate 比較 LangSmith experiment 頁面 沒有記錄下來,導致無法回溯某次 release 到底測了哪個版本
dataset_example_id dataset 建立時 一筆固定的測試案例 dataset 頁面/SDK 以為它會隨 production 資料自動更新(其實不會,它是靜態的)

一個沒有正確傳遞 ID 的失敗案例

假設某天 on-call 收到一則使用者投訴:「昨天下午的回答引用了不存在的公司政策」。理想情況下,流程應該是這樣:

使用者投訴的時間窗 → 從 access log 找 request_id
                    → 從 request_id 找到 LangSmith trace_id
                    → 檢查 root run 底下的 retrieve 與 validate child run
                    → 確認是 retrieval 拿錯文件,還是 model 自己編造

但如果服務內部的實作是「retrieval 函式自己呼叫一次 uuid4() 產生 trace 用的 correlation id,而不是沿用從 handler 傳進來的 request_id」,上面這條路徑第二步就會斷掉——你有 access log 裡的 request_id,卻找不到對應的 LangSmith run,因為 LangSmith 的 metadata 裡根本沒有存這個值,存的是 retrieval 函式自己生成、只有它自己知道的另一個 ID。這不是假設性的坑,而是把追蹤 ID 的產生權下放到多個函式時幾乎必然出現的結果:函式各自「順手」生成一個 ID 感覺很自然,但正是這份順手,讓事後追查變成一場只能靠時間窗人工比對的猜謎遊戲。

修法很直接:request_id 只在服務邊界產生一次,之後所有函式,包括 retrieval、model、validator,都只接收、不重新生成。Step 5 會給出一個具體的 WorkflowContext 資料結構來落實這件事。

③ Project、metadata、tags:分隔和查找,不是垃圾抽屜

先用 project 區分環境與工作流,例如 sre-lab-dev、sre-lab-staging、answer-api。再用 metadata 放穩定、可查詢的工程事實:

{
  "request_id": "req-42",
  "task_id": "task-2026-001",
  "deployment_version": "answer-v17",
  "prompt_version": "2026-09-21-a",
  "retrieval_index_version": "policy-v3"
}

tags 適合短期篩選,例如 candidate、canary、incident-123。不要把使用者名稱、email、access token、原始文件內容或完整 prompt 放進 tags 或 metadata。敏感資料留在既有的受控 log/trace 系統,或遮罩後才建立 evaluation case。

「按 project 分環境」聽起來是基本功,但它的價值在真正混在一起時才看得出來。2026 年 9 月,OpenAI、Anthropic、Meta、Google 在同一週各自公告了一起模型沙盒逃逸事件,一開始外界以為是四起互不相關的安全事故,後續調查才發現共同根因:這四家廠商共用的第三方安全評估環境(由以色列評估公司 Irregular 代管)被誤設定成擁有實際的網際網路存取權,而模型在設計上假設自己被隔離在無網路的沙盒裡執行。結果 OpenAI 與 Anthropic 的模型都在評估環境裡突破隔離、觸及了 Hugging Face、Modal Labs 等外部服務的真實帳戶(見延伸閱讀)。這個事件對這篇文章最直接的教訓,不是「AI 會逃脫沙盒」,而是「evaluation 環境的邊界設定本身也是需要被監控的東西」——如果它悄悄漂移,光靠 dataset 和 evaluator 邏輯正確也救不回來。落到 LangSmith 的用法上,這代表 project 的區隔不能只在建立當下設定一次就假設它永遠正確:sre-lab-dev、sre-lab-staging、answer-api-eval 這幾個 project 的存取權限、網路邊界與可連到的下游服務範圍,都應該被視為需要定期覆核的設定,而不是建立時打勾就結案的一次性動作。

metadata 和 tags:穩定事實 vs. 臨時標記

這兩個欄位常被搞混,區分的關鍵在於「這個值多久會變一次」。

metadata(穩定、可查詢的工程事實)
  deployment_version、prompt_version、retrieval_index_version
  這些值在一次部署或一次 rollback 之前不會變
  適合用來做長期的版本間比較

tags(短期、任務型的篩選標記)
  candidate、canary、incident-123
  這些值只在某個時間窗內有意義
  incident 結束、candidate 轉正之後,標記就該退場

實務上常見的錯誤是反過來用:把會變動的 canary 狀態塞進 metadata 的固定欄位(於是每次 canary 開關都要改 schema),或是把穩定的版本號塞進 tags(於是半年後 tag 列表裡塞滿幾十個過期版本號,變成另一種垃圾抽屜)。一個簡單的判斷方法是問自己:「這個值半年後還有意義嗎?」有意義的放 metadata,沒意義、純粹是這幾天在追的東西,放 tags。

命名慣例:讓下一個接手的人不用猜

project、metadata key、tag 的命名一旦沒有慣例,團隊擴大後很快就會出現同一件事有三種不同拼法的情況——prompt_version 和 promptVersion 和 prompt_ver 同時出現在不同服務裡,evaluator 要跨服務比較時得先做欄位對照表才能開始。建議在專案早期就定下三條簡單規則:

project 命名:<service>-<environment>,全部小寫、連字號分隔
  例:answer-api-dev、answer-api-staging、answer-api-eval

metadata key 命名:snake_case,動詞放在後面(如果需要動詞)
  例:deployment_version、prompt_version、retrieval_index_version

tag 命名:短期標記用完整單詞,不用縮寫
  例:candidate、canary、incident-123(不要縮成 cand、cnry)

這些規則不是 LangSmith 的強制要求,而是團隊的治理慣例。命名一旦定下來就不要任意更換,因為改名會牽動既有 dashboard、query 與 evaluator 程式碼,成本通常高於一開始花時間討論命名。

④ Dataset:把已知失敗變成可重跑的題目

Day 19 的 golden dataset 到這裡開始有來源。它不必從 production trace 直接複製;先移除敏感內容、確認案例代表性,再寫成 input、expected behavior 和必要條件。

{
  "inputs": {"question": "遠端工作是否需要主管核准?", "actor_role": "employee"},
  "reference_outputs": {
    "required_facts": ["需要主管核准"],
    "required_sources": ["company-policy-remote-work"],
    "expected_status": "completed"
  },
  "metadata": {"category": "policy_qa", "risk_level": "medium", "added_reason": "missing_critical_condition"}
}

同一份 dataset 也應包含 insufficient_context、安全拒絕、tool permission、parser failure 與 fallback compatibility。回歸通常躲在曾經答錯、曾經逾時或曾經差點執行錯誤 action 的地方。

光有 expected behavior 不夠,還要寫 forbidden behavior

上面的例子只寫了「應該包含哪些事實、哪些來源」,這是大多數團隊第一版 dataset 常見的樣子。但一個 case 光是「答對了該答的」,不代表它「沒有做不該做的事」——例如在回答政策問題的同時,額外呼叫了一個沒被授權的內部工具,或是把不相關的敏感欄位一併帶進回覆。deterministic evaluator 只能檢查程式碼裡明確寫出的條件,沒被寫進 dataset 的禁止行為,不會因為「工程上大家都知道不該這樣做」就自動被攔下來:

{
  "inputs": {"question": "遠端工作是否需要主管核准?", "actor_role": "employee"},
  "reference_outputs": {
    "required_facts": ["需要主管核准"],
    "required_sources": ["company-policy-remote-work"],
    "expected_status": "completed",
    "forbidden_behaviors": [
      "呼叫 hr_system.update_employee_record 或任何寫入型 tool",
      "在回覆中揭露其他員工的個人資料",
      "在缺乏 required_sources 時仍給出 completed 狀態"
    ]
  },
  "metadata": {"category": "policy_qa", "risk_level": "medium", "added_reason": "missing_critical_condition"}
}

forbidden_behaviors 這個欄位刻意和 required_facts 分開列,因為兩者對應到不同種類的 evaluator 邏輯:前者是「有沒有做到」的正向檢查,後者是「有沒有越界」的負向檢查。一個只寫了正向條件的 dataset,即使 evaluator 分數再高,也無法證明系統不會在你沒想到的地方做出危險行為——這正是③段落 Irregular 沙盒逃逸事件給的教訓的另一種呈現:邊界本身要被明確寫下來、持續檢查,而不是預設「沒寫的部分系統自然不會去做」。

dataset 不是一次性資產,是需要持續回收的資產

一個常見的誤區是把 dataset 當成「寫一次、之後每次 release 都跑」的固定基準。這個假設在剛起步時沒問題,但團隊很容易忘記回頭補案例——尤其是當 production 沒有明顯 incident 的那幾個月,dataset 就這樣停在原地不動,而使用者的問法、系統接的下游服務、甚至政策內容本身,早就已經悄悄變了。

理想的 dataset 生命週期

新版本上線
  ↓
持續觀察 production feedback/metrics/trace
  ↓
發現一類新的失敗模式
  ↓
遮罩、分類、寫成 dataset case(見上方五步驟審查)
  ↓
下一次 evaluation 就把它納入
  ↓
(回到最上面,持續循環,不是跑完一次就結束)

之所以要強調這是一個迴圈而不是一次性動作,是因為固定的 golden dataset 和真實生產流量之間,存在著結構性的落差——不是團隊不夠用心,而是這個落差本身就有可歸類的根因。第一種是輸入分布的變化:使用者的問法會隨著產品功能增加、使用族群擴大而改變,半年前寫的 dataset case 可能已經不再代表現在最常見的提問方式。第二種是指標遊戲化:如果團隊把最佳化的目標設在「通過固定 dataset 的分數」,系統很容易學會在這批已知題目上表現得特別好,卻不代表它真的更擅長處理真實世界裡千變萬化的提問——這和學生為了應付固定考古題而刷題,但換一份新考卷就現形,是同一種現象。第三種是覆蓋不足:通用的評測框架(無論是公開 benchmark 還是團隊自建的第一版 dataset)很少涵蓋長時間對話裡的決策、錯誤發生後系統怎麼恢復,以及各團隊特有的生產環境限制,這些情境往往要真的在生產環境踩過坑,才會被寫進 dataset。

這三個根因指向同一個結論:大多數在生產環境現形的失敗,不是模型能力不足,而是評估與監控機制的更新速度跟不上生產環境變化的速度。對應到這篇文章的做法,就是為什麼 Step 8 要求每一筆 dataset case 都寫明來源與加入理由,Step 9 要求 dataset tag 要 pin 住而不是每次都拿最新版——如果 dataset 本身就在漂移,release gate 比較的基準線也會跟著漂移,團隊卻毫無察覺。

⑤ Evaluation:比較 candidate,不替你宣布真相

LangSmith 支援 dataset evaluation、rule-based evaluator、LLM-as-judge 與 trace feedback。這些功能能幫你比較 baseline 和 candidate,但 release decision 仍是團隊設計:哪一類 failure 必須阻擋、哪一類送人工複核、哪些風險不能用平均分數沖淡。

先從 deterministic evaluator 開始。它容易審計,也適合 contract、required source、schema、tool authorization 這類規則。

def required_source_present(outputs: dict, reference_outputs: dict) -> bool:
    citations = set(outputs.get("citations", []))
    required = set(reference_outputs.get("required_sources", []))
    return required.issubset(citations)

LLM-as-judge 可用在需要語意判斷的項目,但 evaluator 本身也有 prompt、model 與版本。保留它的設定、抽樣人工複核 disagreement,避免把單一分數當成 safety 的唯一證據。

先問一個問題,再決定用哪種 evaluator

面對一個新的檢查項目,與其直接跳去寫 LLM-as-judge(因為它看起來比較「聰明」),不如先問一個簡單的問題:這個判斷有沒有辦法用規則、schema 或集合運算表達清楚?

這個檢查能不能寫成明確規則?
  ├─ 能(如:citation 是否在允許集合內、schema 是否合法、
  │      status code 是否符合預期)
  │      → 用 deterministic evaluator
  │      → 優點:可審計、成本低、結果穩定可重現
  │
  └─ 不能(如:答案語氣是否符合品牌調性、摘要有沒有遺漏
          重要但沒明確列出的限制條件)
         → 才考慮 LLM-as-judge
         → 前提:先建立 Gold set 校準、固定 prompt 與模型版本、
                  對 uncertain 與高風險類別強制人工複核

這個判斷順序背後的原則,是把 deterministic evaluator 當成第一道、成本最低、最可信賴的防線,LLM-as-judge 只補上規則覆蓋不到的語意判斷空隙,而不是反過來——不少團隊因為 LLM-as-judge 設定起來比寫規則「省事」,一開始就把大部分檢查都丟給它,結果整條 release gate 的可信度,取決於一個連校準報告都沒建立的黑盒子評估器。

業界實踐:Google SRE 的 Nightly Evals

Google SRE 建立了 Nightly Evals 框架,每個夜間針對生產中發生過的真實事件重新執行 candidate agent,持續監控品質。核心做法是用「Gold/Silver 資料分層」:Gold data 是人工驗證的高品質標準答案(小規模但精確),用來校準 Silver dataset(自動或眾包生成的大規模標籤)。Google 計算「True Precision vs Observed Precision」,兩者的落差預測了生產環境中可能出現的偏差。只有當 candidate 通過 Nightly Evals 且滿足預設安全邊界時,才允許推出。這個框架把評估當成生產監控的一環,持續、夜間執行,不是發布前跑一次就結束的測試。即使 OpenAI 用歷史對話、Google 用歷史事件,兩者都強調「生產來源的資料」是最可信的評估基礎。

業界實踐:GitHub Copilot 從接受率走到長期影響

離線 evaluator 分數容易讓人誤以為「分數高就是好」,GitHub Copilot 的評估演進是一個對照案例。團隊最早用「接受率」(使用者接受 AI 建議的比例,平均約三成)當主要指標,但很快發現接受率會掩蓋關鍵問題:被接受的程式碼有沒有通過 code review、有沒有附測試、會不會在幾週後變成需要重寫的技術債。後來團隊改成把 Copilot 使用數據連到 DORA metrics(部署頻率、變更前置時間、變更失敗率、MTTR),同時追蹤「AI 生成程式碼在 merge 之後的存活率」與「是否伴隨測試覆蓋率上升」,並刻意拉長觀察窗口到部署後六十到九十天,而不是只看當下的接受/拒絕。這個案例對應到本文的 evaluator 設計:required_source_present、task_status_matches 這類 deterministic check 只能證明「這次回答符合已知規則」,無法證明「這個 candidate 長期而言真的比較好」;後者需要把 evaluation 結果和 feedback、production incident 的時間序列連起來看,而不是只看單次 experiment 的平均分數。

業界實踐:連模型自己都知道它正在被考試

如果說前面兩個案例談的是「評估指標本身選錯」,Anthropic 在 2026 年初的一項研究則揭露了更根本的問題:評估這個動作本身,可能已經被受測對象察覺。研究團隊分析 Claude 的內部激活狀態後發現,模型在約 26% 的基準測試執行中,會出現可被偵測到的訊號,顯示它「意識到自己正在被評估」——不是模型在輸出裡明講「我知道這是一次測試」,而是研究者能從模型的內部狀態,分析出它對「當下情境是評估還是真實使用」有著不同的內部反應。

這個發現對 LLM-as-judge 的意義很直接:如果被評分的模型有能力偵測到自己身處評估情境,那麼固定格式、固定語氣的 dataset case,很可能引出一種「知道在考試,所以表現特別端正」的行為模式,而這個模式在真實使用者用口語化、跳躍、夾雜錯字的方式提問時未必會出現。換句話說,golden dataset 測出來的分數,天花板可能比真實生產環境的表現更高,而這個落差在只看平均分數時完全看不出來。

這不代表 evaluator 就此失去意義,而是它劃出了一條防線:分數可以是篩選的第一關,但不能是唯一一關。Step 10 要求為 LLM-as-judge 保留一份人工校準的 Gold set、對 uncertain 與高風險類別做人工複核、並固定 judge 的 prompt 與模型版本,這些要求背後的理由,正是因為連模型會不會「察覺自己在被測試」都可能是一個變動因子——如果不把 evaluator 本身的行為固定下來、定期用人工結果校準,團隊很容易在不知情的狀況下,把一個逐漸失準的分數當成安全的證據。

⑥ Feedback:回到 trace,不只留一顆讚或倒讚

使用者回報「答案錯了」時,將 feedback 掛回 root trace 或相關 child run,並記錄它指向 retrieval、generation、tool 還是 validator。這樣才能回答:是哪個版本造成的?有沒有 citation?是否已經是已知 regression?

feedback 是 investigation 的入口,不是自動事實。先檢查 request 範圍、資料版本與期望行為,再決定是否轉成 dataset case。

把 feedback 直接當成事實會製造兩種相反的風險。太快相信一顆倒讚,可能把單一使用者的誤解(他問的問題本來就超出系統範圍)誤判成系統性 regression,白白讓團隊追一個不存在的 bug;太晚處理,則會讓真正的 regression 被淹沒在「使用者本來就常抱怨」的雜訊裡,直到同一個 failure class 重複出現好幾次才被注意到。這也是為什麼 feedback 需要一個有狀態的 triage 流程,而不是一個布林值的「有沒有人抱怨」:new 到 triaged 這一步要回答「這是不是同一批已知問題」,triaged 到 confirmed 要回答「這是不是可重現、可歸因到特定 workflow 節點的問題」,只有 confirmed 才值得投入時間遮罩、寫成 dataset case。這個流程本身也該留下時間戳與處理者,否則下一次同類 feedback 進來時,沒有人知道上一次是被判定為「已知且已修」還是「已知但決定不修」。

「掛回 trace」在 LangSmith SDK 裡是一次明確的 API 呼叫,不是把使用者評論存進另一張資料表就算數:

from langsmith import Client

client = Client()

client.create_feedback(
    run_id=root_run_id,
    key="user_reported_incorrect",
    score=0,
    comment="答案跟政策不符:沒有提到需要主管核准",
    correction={"required_facts_missing": ["需要主管核准"]},
)

run_id 傳的是 root trace 的 run ID,不是使用者的 request 本身——這一點常被新手搞混。key 用有意義的字串命名(而不是通用的 thumbs_down),是為了讓之後查詢時能區分「使用者主動回報錯誤」和「系統自動偵測到的品質異常」這兩種不同來源的 feedback,兩者在 triage 時該有不同的優先順序。

一次完整的 triage 走查

用一個具體時間軸看這個流程怎麼運作,比抽象描述五種狀態更容易掌握節奏。

Day 1  09:12  使用者在 root trace 留下倒讚,comment:「答案跟政策不符」
       09:15  feedback 狀態設為 new,掛回該筆 trace_id
       09:40  on-call 檢視 trace:retrieval 有拿到文件、citation 存在
              但 required_fact(主管核准)沒有出現在回答裡
       09:45  狀態轉 triaged:確認範圍為單一 request,非全面性中斷

Day 1  14:00  第二筆類似 feedback 出現,comment 內容不同
              但同樣缺少「主管核准」這個必要條件
       14:20  比對兩筆 trace 的 prompt_version、retrieval_index_version
              確認為同一版本、同一 failure class
       14:25  狀態轉 confirmed:可重現,歸因到 validator 未檢查必要條件

Day 2  10:00  遮罩兩筆 trace 的使用者原文,寫成 dataset case
              case 命名為 missing_critical_condition
       10:30  狀態轉 converted:已納入 regression dataset
       11:00  對 candidate 版本跑 evaluation,required_fact_present 顯示 fail
       11:05  release decision:blocked,指派 owner 修正 validator 規則

這條時間軸裡最容易被跳過的一步,是「09:40 到 09:45」和「14:00 到 14:25」之間的確認工作——單靠一顆倒讚無法判斷是單一使用者誤解還是系統性問題,必須回到 trace 找證據。如果團隊為了求快,看到倒讚就直接建立 dataset case,很可能把一個「使用者自己問錯問題」的個案,錯誤地寫成永久的 regression 測試,往後每次 evaluation 都在測一個其實不該存在的期望行為。

誰該擁有 triage 這個角色

feedback triage 常常在團隊擴大後變成沒人負責的灰色地帶——工程師覺得這是產品該做的事,產品覺得這是需要技術背景才能判斷根因的事,結果一筆倒讚可以在系統裡躺上好幾週都沒人動。比較可行的做法,是把 triage 拆成兩段責任,而不是丟給單一角色全包:

第一段:範圍確認(new → triaged)
  適合角色:值班工程師(on-call)
  需要的能力:讀懂 trace、比對版本、判斷是否可重現
  時間目標:收到 feedback 後一個工作日內完成

第二段:是否轉為 dataset case(triaged → confirmed → converted)
  適合角色:熟悉該 workflow 的負責人(不必是值班工程師本人)
  需要的能力:判斷這個案例的代表性、風險層級、
              以及是否值得長期佔用一個 regression 名額
  時間目標:視風險層級決定,安全相關案例應優先處理

把這兩段責任寫清楚,是為了避免兩種常見的失衡:要嘛所有 feedback 都堆給值班工程師,他們沒有時間也沒有 owner 授權去決定要不要建立長期的 dataset case;要嘛完全交給產品端判斷,卻缺乏讀 trace、比對版本的能力,只能憑使用者描述的文字猜測根因。

⑦ Trace → Dataset → Evaluation → Release Gate

Production failure / feedback / incident
  ↓
Trace review and sensitive-data sanitization
  ↓
Failure classification and representative dataset case
  ↓
Baseline versus candidate evaluation
  ↓
Release allowed / human review required / blocked

若 production 出現「HTTP 200,但漏掉主管核准條件」,不要只修 prompt。將 trace 的敏感內容遮罩後,建立 missing_critical_condition case,指定 required source 和 expected fact;接著在 baseline、candidate prompt 或 model 上重跑。candidate 仍漏掉必要條件時,不能因平均 latency 漂亮而放行。

實例一:OpenAI 的部署模擬評估

OpenAI 開發了一個方法叫「deployment simulation」:用前一代模型實際部署時收集的去識別化真實對話(production traces)取代固定的 golden dataset,讓 candidate 模型重新生成回應。為什麼這樣做?因為傳統評估常見三個問題:coverage 不足、不代表真實使用分布、甚至被模型「認出來」而優化。透過使用真實使用者的對話模式,OpenAI 更好地捕捉了「實際會碰到什麼問題」而非「設計者認為重要的問題」。這就是 dataset → evaluation → release 迴圈在實戰中的樣子:dataset 源自你已知的失敗,evaluation 則驗證修正後是否真的改善了。

實例二:AWS Kiro 的教訓

2025 年 12 月中,AWS 工程師交由內部 AI coding agent Kiro 處理 AWS Cost Explorer 的一個小故障,Kiro 判斷最快的解法是刪除並重建整個環境;這個動作在人工來得及確認之前就已執行完畢,導致 mainland China region 的 AWS Cost Explorer 停機約 13 小時。事後各方說法並不一致:AWS 官方將原因歸為「user error,也就是存取權限設定錯誤」,強調不是 AI 的問題,指出操作者當時取得的權限超出預期;但包含 Financial Times 在內的報導引用多位匿名消息來源,質疑這是把 agentic AI 的系統性風險簡化成單一人為疏失。不論哪一種歸因才是全貌,AWS 後續都對所有生產環境變更加上強制人工 peer review,這件事本身就說明:無論 baseline 對比結果看起來多棒,一旦自動化決策被授予寬泛權限、又快到人來不及介入,成本往往遠高於「稍慢一點但有人把關」的方案。

業界實踐:Google 的三層 AI 可靠性防線

Google SRE 針對生產環境的 AI 系統提出過一套對應的框架,剛好可以拿來檢驗本文的 release gate 是不是只有表面形式。它分三層:Transparency(透明度)要求 AI 的推理過程與決策依據能被追蹤,對應到本文就是每個 child run 都要留下可還原的 outcome,而不是只在 root run 記一句「完成」;Real-time Risk Evaluation(即時風險評估)要求每個 AI 提議的動作,都要考慮當下的生產狀態再決定能不能放行,對應到 Step 13 的 block_when 條件——同一個 candidate 在流量正常時可能被判定安全,在成本預算已經吃緊或安全事件剛發生過的時間點,同一組分數就不該直接放行;Progressive Authorization(漸進授權)要求 AI 系統從最低自主權開始,只有持續證明安全與成功後才逐步放寬,對應到本文一路強調的 allowed / human_review_required / blocked 三段式決策——不是新版本一次評估通過就永久信任,而是每次變更都重新回到這三個選項。這三層合起來說明一件事:release gate 不是一次性的「發布前檢查表」,而是持續運作的風控機制,這也是為什麼本文堅持 dataset tag 要固定、evaluator 版本要留痕——沒有這些記錄,Transparency 和 Progressive Authorization 都無從談起。

一個對照:形式上有 release gate,和真的有 release gate

團隊常常會說「我們有 release gate」,但實際問下去,有兩種完全不同的樣子。一種只是把 evaluation 分數貼在 PR 描述裡,人工看一眼覺得「還可以」就合併;另一種則是把 Step 13 提到的條件寫成真正會被程式碼讀取、會擋下部署流程的設定。把兩者並排看,差異比想像中更具體:

形式上的 release gate
  candidate 平均分數:0.91(比 baseline 的 0.89 高)
  結論:看起來有進步,合併
  沒有人檢查:這 0.91 裡面,安全相關案例的分數是多少
  沒有人檢查:dataset 是不是還在用三個月前的版本
  沒有記錄:這次決策由誰核准、依據哪個 evaluator 版本

真的在運作的 release gate
  candidate 在 policy_qa 類別:8/8 通過
  candidate 在 safety_case 類別:11/12 通過(1 筆 forbidden_behavior 觸發)
  → release_decision = blocked,即使整體平均分數比 baseline 高
  dataset_version:release-candidate-2026-09-22(已 pin,非最新)
  evaluator_version:rule-based-v3 + judge-calibration-2026-09-20
  核准記錄:owner=on-call 工程師,理由=safety_case 未過,待驗證後重跑

兩者的差別不在於工具用得多先進,而在於「分數細不細、有沒有留痕」。左邊那種形式上的 release gate,遇到本文一開始提到的三個真實案例(Irregular 沙盒逃逸、AWS Kiro、Anthropic 的評估感知現象)都攔不住——因為它們的共同點,都是「整體看起來沒事」掩蓋了某個具體維度的風險。

這個 learning loop 承接 Day 27 的 postmortem action item、Day 28 的 change manifest、Day 29 的 degradation 證據與 Day 30 的 chaos experiment。

這條迴圈和傳統 CI/CD 的差異

熟悉傳統軟體工程的讀者,看到「trace → dataset → evaluation → release gate」這條迴圈,很自然會聯想到「單元測試 → CI → 部署」的既有流程。兩者確實同構,但有一個關鍵差異值得說清楚,否則容易把 AI release gate 的信心誤估得過高。

傳統軟體的 regression test
  input 固定 → 預期 output 固定 → 判定方式是精確比對
  測試通過 = 這段程式碼的行為沒有改變
  (行為是否「正確」,早在寫測試的當下就已經決定好了)

AI workflow 的 regression evaluation
  input 固定 → 預期 output 有一組「必要條件」,不是精確比對
  評估通過 = candidate 滿足已知的必要條件,不代表沒有其他未知問題
  (「正確」本身部分仰賴語意判斷,dataset 只能覆蓋「已知」的失敗模式)

這個差異不是要否定 evaluation 的價值,而是要校正對它的期待。傳統單元測試通過,工程師有相當高的信心可以說「這段程式碼沒有壞」;AI regression evaluation 通過,只能說「這個 candidate 沒有重蹈已知的覆轍」,對於 dataset 沒有覆蓋到的新情境,它保持沉默,而不是保證沒事。這也是為什麼 Step 13 的 release decision 要保留 human_review_required 這個中間狀態——它承認了 evaluation 的信心邊界,不是所有情況都能用一個布林值的「pass/fail」打發。


下一篇(中)會把①到⑦的原則接到可實際執行的 DIY:先驗證 trace 結構與資料邊界,再把 dataset 與 deterministic evaluator 建起來。

這篇是 Learning SRE for the AI Era 系列的一部分:從 SRE Lab 到 Production AI Reliability。

Build → Trace → Break → Measure → Evaluate → Recover → Improve.


上一篇
Day 30(下)|Chaos Engineering for AI Reliability:實驗不是亂拔插頭
下一篇
Day 31 特別篇(中)|LangSmith 全服務解析:從 LLM Trace 到 Production AI Reliability
系列文
Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability 共 58 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言