【MLflow LLMs & Agents 實戰】#04 Agent 全鏈路追蹤與生產監控

測驗:Agent 全鏈路追蹤與生產監控

共 5 題,點選答案後會立即顯示結果

1. 在 MLflow 中,一個多步驟 Agent 任務的 trace 會呈現成什麼樣的結構?

  • A. 一條從輸入到輸出的直線
  • B. 一棵有階層的巢狀 span 樹,子代理與工具呼叫掛在對應的父 span 底下
  • C. 每個工具呼叫各自獨立成一條 trace,互不相關
  • D. 只有一個 root span,不含任何子項目

2. 為什麼 mlflow.anthropic.autolog() 必須在建立 ClaudeSDKClient 之前呼叫?

  • A. 因為 autolog 需要先讀取 client 的設定檔
  • B. 因為 client 建立後 MLflow 伺服器才會啟動
  • C. autolog 的原理是攔截(patch)建構子,client 建好後才開就攔截不到它
  • D. 順序其實無所謂,只是慣例

3. 在「跑完就結束」的短命子程序裡,為什麼要把 MLFLOW_ENABLE_ASYNC_TRACE_LOGGING 設為 false

  • A. 非同步是在背景送 trace,程序一結束背景還沒送完的 trace 就跟著丟失
  • B. 非同步模式會讓 trace 內容不正確
  • C. 同步模式可以讓 Agent 跑得更快
  • D. 因為短命程序不支援非同步 IO

4. backend_run_id 這個 trace tag 的主要用途是什麼?

  • A. 決定 trace 的過期時間
  • B. 控制 trace 是否要送到 MLflow
  • C. 標記 Agent 使用的模型版本
  • D. 當作後端任務與 MLflow trace 之間的橋,日後可用它反查對應 trace

5. 文章把整個系列串成 GenAI 的 MLOps 迴圈,三個核心角色的分工是?

  • A. tracing 打分、evaluation 版本控制、prompt registry 看見
  • B. tracing 負責「看見」、evaluation 負責「打分」、prompt registry 負責「改進與版本控制」
  • C. 三者都負責追蹤,只是輸出格式不同
  • D. tracing 收集回饋、evaluation 發布 prompt、registry 監控延遲

系列最終篇(第 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_tokens vs cache_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 追蹤程式碼,照這幾步驗收:

  1. 確認 autolog 在 client 之前
  2. 確認短命程序三件事都在
  3. 實際跑一次,確認 trace 有進去
  4. 確認 trace 有貼上 backend_run_id
  5. 確認追蹤壞掉不會擋任務

看不懂就這樣問 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。情境題

client = ClaudeSDKClient(options=options) mlflow.anthropic.autolog() async with client: await client.query(prompt) async for msg in client.receive_response(): handle(msg)

最該優先檢查的是?

  • A. MLflow 伺服器是否正在運作
  • B. autolog() 被放在建立 client 之後才呼叫,導致這個 client 沒被攔截追蹤
  • C. prompt 內容太長超出 token 上限
  • D. 沒有呼叫 mlflow.set_experiment()

2. 你在一個短命子程序裡追蹤 Agent,同一個 session 會追問模型多輪。你想確保每條 trace 都貼上 backend_run_id。情境題

正確的 trace id 收集時機是?

  • A. 在對話進行中隨時用 update_current_trace() 拿
  • B. 只在整個 session 全部結束後用 get_last_active_trace_id() 拿一次
  • C. 每消費完一輪回應就用 get_last_active_trace_id() 取一次並收集起來,最後統一貼 tag
  • D. 在建立 client 前先預留一個固定 trace id

3. 生產環境要盯 Agent 的成本異常,同事說「這週帳單暴增但任務數沒變」。情境題

你會優先從哪個指標下手判斷是不是快取失效?

  • A. duration_ms(延遲)
  • B. num_turns(來回輪數)
  • C. cache_read_input_tokens(命中快取的 token)掉了,代表快取沒生效、成本上升
  • D. output_tokens(輸出 token)

4. 一位工程師的短命 Agent 程序「有時候有 trace、有時候沒有」,難以重現。他的消費回應程式碼如下。錯誤診斷

async for msg in client.receive_response(): print(msg) if is_final(msg): break # 拿到最終訊息就跳出

最可能的根本原因是?

  • A. break 讓 receive_response() 沒被完整迭代,而 autolog 是在回應完整收完才組出 trace
  • B. print(msg) 會干擾 MLflow 的追蹤緩衝區
  • C. is_final() 判斷邏輯讓 trace 內容變髒
  • D. async for 不支援 MLflow 追蹤

5. Code review 時你看到這段追蹤初始化,覺得有設計問題。錯誤診斷

def setup_mlflow(): import mlflow mlflow.set_tracking_uri(TRACKING_URI) mlflow.set_experiment(EXPERIMENT) mlflow.anthropic.autolog() return True # 呼叫端: setup_mlflow() # 若 MLflow 連不上,這裡直接拋例外讓整個任務 crash

這段最該修正的問題是什麼?

  • A. 應該把 autolog() 移到函式最前面
  • B. 追蹤初始化沒有 try/except 保護,MLflow 掛掉時會把主業務流程一起弄掛——觀測不該變成單點故障
  • C. set_experiment 應該在 set_tracking_uri 之前
  • D. 函式不該回傳布林值

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *