測驗:Agent 全鏈路追蹤與生產監控
共 5 題,點選答案後會立即顯示結果
1. 在 MLflow 中,一個多步驟 Agent 任務的 trace 會呈現成什麼樣的結構?
2. 為什麼 mlflow.anthropic.autolog() 必須在建立 ClaudeSDKClient 之前呼叫?
3. 在「跑完就結束」的短命子程序裡,為什麼要把 MLFLOW_ENABLE_ASYNC_TRACE_LOGGING 設為 false?
4. backend_run_id 這個 trace tag 的主要用途是什麼?
5. 文章把整個系列串成 GenAI 的 MLOps 迴圈,三個核心角色的分工是?
系列最終篇(第 4 篇,共 4 篇)|難度:L3-熟練
前置:已讀 #01~#03(tracing 基礎、評估、prompt registry),對多步驟 Agent 有基本概念
一句話說明
當 AI 幫你寫的不再是「呼叫一次 LLM」而是一個會自己呼叫工具、派子代理的 Agent 時,MLflow 的 trace 會長成一棵巢狀 span 樹;這篇教你怎麼讀懂這棵樹、怎麼確認短命程序真的有把 trace 送出去,以及上線後怎麼盯延遲、成本、token 與使用者回饋。
前三篇是「單次呼叫」的世界,這篇進到「一次任務、幾十個 span」的世界。重點不是教你怎麼寫追蹤,而是教你看懂 AI 產出的追蹤設定到底對不對、上線後儀表板該看什麼。
先建立心智模型:Agent 的 trace 是一棵樹
單次 LLM 呼叫的 trace 是一條線:輸入 → 模型 → 輸出。
Agent 不是。一個 Agent 任務裡,主代理會呼叫工具(讀檔、跑 bash、搜尋),還會用 Task 工具派出子代理去做子任務,子代理自己又會呼叫工具、呼叫 LLM。這些動作在 MLflow 裡是一棵有階層的樹:
claude_code_conversation ← root span(整個對話)
├── llm ← 主代理想了一下
├── tool_Read ← 主代理讀了一個檔
├── subagent_article-writer ← 主代理派出「文章撰寫」子代理
│ ├── llm ← 子代理自己在想
│ ├── tool_Write ← 子代理寫檔
│ └── tool_mc-quiz ← 子代理呼叫 skill 生測驗
└── subagent_reviewer-publisher ← 又派出「審稿發布」子代理
├── llm
└── tool_wordpress-blog ← 發布到 WordPress
這在幹嘛:每一個縮排代表「誰在誰底下做事」。subagent_* 底下的東西都是那個子代理做的,不是主代理做的。會讀這棵樹,你就能一眼看出任務卡在哪一步、哪個子代理花最久、哪次工具呼叫報錯。
| 名詞 | 白話 |
|---|---|
| span | 一個「做了一件事」的區塊,有開始、結束、耗時 |
| root span | 最外層那一個,代表整個任務 |
| 巢狀(nested) | span 裡面還有 span,形成父子階層 |
| span type | 這個 span 是哪種事:LLM(想)、TOOL(用工具)、AGENT(子代理) |
span 樹是怎麼被自動生出來的
你不用手動一個一個標 span。整合框架會用 autolog 自動幫你補。以本專案的 Claude Agent SDK 為例,只要一行:
import mlflow
mlflow.anthropic.autolog() # 這行之後,Agent SDK 的每次對話自動變成一條 trace
Code language: CSS (css)這在幹嘛:autolog() 會去「攔截」(patch)SDK 的 query() / receive_response(),你正常跑 Agent,它在旁邊默默把 span 樹組出來送到 MLflow。LangChain、LlamaIndex、OpenAI 也各有自己的 autolog(mlflow.langchain.autolog() 等),概念一樣。
認得就好(不同框架的名字):
mlflow.anthropic.autolog()、mlflow.langchain.autolog()、mlflow.openai.autolog()。你維護哪個框架就用哪個,行為都是「一行開啟、自動追蹤」。
🚩 紅旗一:autolog 放錯位置
# 🚩 紅旗:先建了 client,才開 autolog
client = ClaudeSDKClient(options=options)
mlflow.anthropic.autolog() # 太晚了!這個 client 不會被追蹤
Code language: PHP (php)autolog 的原理是「攔截建構子」,所以必須在建立 client 之前呼叫。本專案的註解就把這條寫死了:
def setup_mlflow() -> bool:
"""必須在建立 ClaudeSDKClient 之前呼叫——autolog() 是 patch __init__。"""
...
mlflow.anthropic.autolog()
Code language: JavaScript (javascript)如果你發現「程式跑了、任務也完成了,但 MLflow 就是沒有 trace」,第一個要檢查的就是:autolog 是不是在 client 之後才開的? 直接問 AI:
「我的
mlflow.anthropic.autolog()是在建立 client 之前還是之後呼叫的?貼給你看。」
短命子程序的三個致命細節
這是本篇最容易踩雷、也最值得投資注意力的部分。
情境:後端不是一個長期跑著的服務在追蹤,而是「每個任務開一個子程序、跑完就結束」(uv run python agent.py,跑完 exit)。程序活得很短,MLflow 預設是非同步送 trace——在背景慢慢送。程序一結束,背景還沒送完的 trace 就跟著死掉了,你什麼都看不到。
要在這種短命程序裡穩定拿到 trace,AI 應該幫你處理三件事:
1. 關掉非同步匯出(改成同步)
# 短命子程序:改為同步匯出 trace,避免程序結束前來不及送出
os.environ.setdefault("MLFLOW_ENABLE_ASYNC_TRACE_LOGGING", "false")
Code language: PHP (php)翻譯:「不要在背景慢慢送,收到就馬上送,送完才往下走。」對長期服務這樣做會拖慢效能,但對「跑完就死」的子程序,這是保命符。
2. 完整消費 receive_response()
async for msg in client.receive_response(): # 一定要迴圈到最後一則訊息
result = _handle_message(msg, stats) or result
Code language: PHP (php)這在幹嘛:autolog 的 trace 是在「回應完整收完」的那一刻才組出來的。如果你只讀前幾則訊息就 break、或根本沒去迭代這個回應,trace 就永遠不會生成。
🚩 紅旗二:偷懶只讀一部分回應
async for msg in client.receive_response():
print(msg)
break # 🚩 紅旗:只讀第一則就跳出,trace 不會產生
Code language: PHP (php)只要 AI 寫的程式沒有把 receive_response() 這個 async 迭代跑到自然結束,你的 trace 就會缺。看到 break 或提早 return 在這個迴圈裡,要特別警覺。
3. 結束前主動 flush
def flush_tracing() -> None:
"""程序結束前防禦性 flush,確保 trace / run 資料都送達伺服器。"""
import mlflow
mlflow.flush_trace_async_logging()
mlflow.flush_async_logging()
Code language: JavaScript (javascript)翻譯:「關門前再喊一聲『東西都送出去了嗎』,把還在管線裡的資料擠出去。」就算前面設了同步,這個 flush 是最後一道保險,通常放在 finally: 裡確保一定會執行:
try:
... # 跑 agent、消費回應
finally:
flush_tracing() # 不管有沒有出錯,關門前一定 flush
Code language: PHP (php)三件事一起看:同步匯出(設定)+ 完整消費回應(迴圈跑完)+ 結束前 flush(保險)。三個少一個,短命程序的 trace 就可能時有時無——而且這種 bug 超難抓,因為「有時候有、有時候沒有」。
用 trace tags 幫每條 trace 貼標籤
trace 產生後預設只有 SDK 給的資訊。但生產環境你會想問「這條 trace 是哪個專案跑的?對應後端哪一筆任務?哪些子代理參與了?」——這些要靠你自己補 tag。
tags = {
"project": project, # 哪個 agent 專案
"agent_input": agent_input[:250], # 使用者當初輸入什麼
"backend_run_id": os.environ.get("AGENT_RUN_ID", ""), # 對應後端任務 id
"subagents_used": ",".join(sorted(stats["subagents"])), # 這次用了哪些子代理
}
for trace_id in trace_ids:
tag_trace(trace_id, tags)
Code language: PHP (php)這在幹嘛:backend_run_id 是最關鍵的一個——它是你的後端資料庫任務和 MLflow trace 之間的橋。使用者回報「我那個任務結果很怪」,你拿他的 run id 就能反查到對應的 trace,直接看那次到底發生什麼。
拿 trace id 的時機有個坑
autolog 的 trace 是「回應收完後」才在背景建立的,所以你不能在對話進行中拿它,要事後取:
def get_last_trace_id() -> str | None:
import mlflow
return mlflow.get_last_active_trace_id() # 拿「剛剛結束」那條 trace 的 id
Code language: PHP (php)而且——每消費完一輪回應就要取一次。如果同一個 session 追問了好幾次(像本專案的「完成度防線」會追問模型繼續做),會產生好幾條 trace,每條都要各自收 id、各自貼 tag:
async def _consume_response(client):
async for msg in client.receive_response():
result = _handle_message(msg, stats) or result
trace_id = get_last_trace_id() # 這一輪的 trace id
if trace_id and trace_id not in trace_ids:
trace_ids.append(trace_id) # 收集起來,最後統一貼 tag
Code language: PHP (php)🚩 紅旗三:以為一個任務只有一條 trace
# 🚩 紅旗:只在最後拿一次 trace id
await consume(client) # 第一輪
await consume(client) # 追問第二輪
trace_id = get_last_trace_id() # 只拿到「最後一輪」,第一輪那條沒 tag 到
Code language: PHP (php)只要有多輪對話,就有多條 trace。程式若只在最後拿一次 id,前面幾輪的 trace 會變成「沒貼標籤的孤兒」,事後查起來對不上。看到多輪對話 + 只取一次 trace id,要警覺。
把 run 的指標記下來:延遲、成本、token
trace 是「這次做了什麼」的細節;run 的 metrics 是「這次花了多少」的總帳。生產監控主要盯的就是這三類數字:
def _log_result_metrics(result):
import mlflow
metrics = {}
for name in ("duration_ms", "num_turns", "total_cost_usd"):
value = getattr(result, name, None)
if value is not None:
metrics[name] = float(value)
usage = getattr(result, "usage", None) or {}
for key in ("input_tokens", "output_tokens",
"cache_read_input_tokens", "cache_creation_input_tokens"):
if (v := usage.get(key)) is not None:
metrics[f"usage_{key}"] = float(v)
mlflow.log_metrics(metrics)
Code language: JavaScript (javascript)| 指標 | 白話 | 為什麼要盯 |
|---|---|---|
duration_ms |
這次跑多久(毫秒) | 突然變慢=有東西出問題 |
total_cost_usd |
這次花多少美金 | 成本失控最早在這看到 |
num_turns |
來回了幾輪 | 輪數暴增=Agent 可能在鬼打牆 |
usage_input_tokens |
吃了多少輸入 token | prompt 有沒有莫名膨脹 |
cache_read_input_tokens |
命中快取的 token | 快取有沒有生效,直接影響成本 |
這在幹嘛:把這些 log_metrics 到每一條 run 之後,MLflow 的 UI 就能畫出「成本隨時間」「延遲分布」這類圖。上線後你不是等使用者抱怨才知道變慢,而是看儀表板的曲線。
📌 知道就好:
cache_read_input_tokensvscache_creation_input_tokens——一個是「這次讀到了快取」(省錢),一個是「這次建立了快取」(要花一點錢但之後省)。成本異常時這兩個是重要線索。
生產監控三支柱:指標、回饋、線上評估
到這裡你已經有了「每次任務的 trace + metrics」。要變成真正的生產可觀測性,還差兩塊:
1. 使用者回饋(feedback)
光有機器指標不夠——「跑得快、花得少」不代表「結果好」。你要能把使用者的評價綁回對應的 trace:
import mlflow
# 使用者按了「這篇文章不行」時
mlflow.log_feedback(
trace_id=trace_id, # 綁到那條 trace
name="user_rating",
value=False, # 或分數、或文字評語
)
Code language: PHP (php)這在幹嘛:這樣你就能在 MLflow 裡篩出「所有被使用者打槍的 trace」,回頭看它們有什麼共通點——是某個子代理常出包?某類輸入特別容易壞?這是持續改善的原料。
2. 線上 trace 抽樣做評估
你不可能人工看每一條 trace。作法是從線上流量抽樣一批 trace,餵給評估流程(就是系列 #02 教的那套評估)自動打分:
# 從生產 experiment 撈最近的 trace 出來
traces = mlflow.search_traces(
experiment_names=["autoblog-agents"],
filter_string="tags.project = 'class'",
max_results=100,
)
# 對這批 trace 跑評估(LLM-as-judge、規則檢查…)→ 得到線上品質分數
Code language: PHP (php)這在幹嘛:把「線上真實發生的 trace」當成評估資料集,定期跑評估,你就有一條持續的品質曲線,而不是只在上線前測一次。品質退步(例如換了模型版本後變差)能提早發現。
這就是整個系列串起來的 MLOps 迴圈
┌─────────────────────────────────────────────┐
│ │
▼ │
Prompt Registry (#03) │
│ 管理、版本化 prompt │
▼ │
Agent 上線執行 │
│ │
▼ │
Tracing (#01 + 本篇) ──→ 收 trace / metrics / feedback
│ │
▼ │
Evaluation (#02) ──→ 對線上 trace 抽樣打分 │
│ │
└──→ 發現問題 → 改 prompt → 發新版本 ───────────┘
Code language: PHP (php)這在幹嘛:tracing 負責「看見」,evaluation 負責「打分」,prompt registry 負責「改進與版本控制」。三者接成一個閉環,就是 GenAI 版的 MLOps——這也是整個系列最後想留給你的一張圖。
🚩 紅旗總表:讀 Agent 追蹤設定時要抓的包
| 紅旗 | 症狀 | 為什麼危險 |
|---|---|---|
| autolog 在 client 之後才呼叫 | 任務正常但沒 trace | patch 不到已建好的 client |
receive_response() 提早 break |
trace 缺失或不完整 | trace 在回應收完才生成 |
| 短命程序沒設同步匯出 / 沒 flush | trace 時有時無 | 背景送出來不及、程序就死了 |
| 多輪對話只取一次 trace id | 部分 trace 沒 tag | 每輪各是一條 trace |
flush 沒放在 finally |
出錯時 trace 遺失 | 例外路徑跳過了 flush |
| 追蹤失敗會把主流程一起弄掛 | Agent 因 MLflow 掛掉而失敗 | 觀測不該擋業務——本專案全用 try/except 吞掉 |
最後那條特別重要。看本專案每個追蹤函式都包在 try/except 裡、失敗只記 warning:
except Exception as e: # noqa: BLE001 — 觀測失敗不能擋主流程
logger.warning("MLflow 初始化失敗:%s", e)
...
return False # MLflow 掛了,agent 照樣跑,只是這次沒追蹤
Code language: PHP (php)這在幹嘛:可觀測性是「附加價值」,不能反過來變成「單點故障」。如果 AI 幫你寫的追蹤程式碼會在 MLflow 連不上時直接 crash 整個任務,那是設計錯誤——要求它把追蹤相關呼叫全部包進 try/except。
Vibe Coder 驗收檢查點
拿到 AI 寫好的 Agent 追蹤程式碼,照這幾步驗收:
- 確認 autolog 在 client 之前
- 確認短命程序三件事都在
- 實際跑一次,確認 trace 有進去
- 確認 trace 有貼上
backend_run_id - 確認追蹤壞掉不會擋任務
看不懂就這樣問 AI
「用白話解釋這段 MLflow 追蹤程式碼在幹嘛,我是初學者。特別說明為什麼 autolog 要在建立 client 之前呼叫。」
「這是一個跑完就結束的短命子程序,我要確保 MLflow trace 一定會送出去。幫我檢查這段有沒有:同步匯出、完整消費回應、結束前 flush,缺哪個幫我補上。」
「這條 MLflow trace 有巢狀的 subagent span。幫我讀這棵 span 樹,告訴我整個任務裡哪一步花最久、哪個子代理做了什麼。」
「幫我設計一個生產監控:對這個 agent 的每次執行記錄延遲、成本、token,並且能收集使用者回饋綁到對應 trace。用 MLflow。」
系列回顧
- #01:Tracing 基礎——單次 LLM 呼叫怎麼被追蹤成一條 span。
- #02:Evaluation——怎麼幫 LLM 輸出打分、建評估資料集。
- #03:Prompt Registry——prompt 的版本管理與治理。
- #04(本篇):Agent 全鏈路追蹤與生產監控——巢狀 span 樹、短命程序的 trace 保命三件事、生產監控三支柱,最後把四篇接成 GenAI 的 MLOps 迴圈。
你現在具備的能力:拿到一份 AI 寫的 Agent 追蹤與監控程式碼,能讀懂它的 span 樹、抓出短命程序漏送 trace 的坑、確認生產監控該有的指標與回饋都在位——這就是把「AI 幫我寫的 Agent」安心送上線的驗收底氣。
進階測驗:Agent 全鏈路追蹤與生產監控
共 5 題,包含情境題與錯誤診斷題。
1. 你的 Agent 任務跑完了、結果也正確,但 MLflow UI 上完全看不到這次的 trace。情境題
最該優先檢查的是?
2. 你在一個短命子程序裡追蹤 Agent,同一個 session 會追問模型多輪。你想確保每條 trace 都貼上 backend_run_id。情境題
正確的 trace id 收集時機是?
3. 生產環境要盯 Agent 的成本異常,同事說「這週帳單暴增但任務數沒變」。情境題
你會優先從哪個指標下手判斷是不是快取失效?
4. 一位工程師的短命 Agent 程序「有時候有 trace、有時候沒有」,難以重現。他的消費回應程式碼如下。錯誤診斷
最可能的根本原因是?
5. Code review 時你看到這段追蹤初始化,覺得有設計問題。錯誤診斷
這段最該修正的問題是什麼?