【MLflow LLMs & Agents 實戰】#01 認識 GenAI 觀測:Tracing 入門

測驗:認識 GenAI 觀測 Tracing 入門

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

1. 文中把 Trace 與 Span 的關係比喻成「帳單」與「明細」。下列哪個描述最正確?

  • A. Trace 是樹上的一個節點,Span 是整棵樹
  • B. Trace 是一整次請求的完整紀錄,Span 是樹上代表某一步驟的節點
  • C. Trace 與 Span 是同義詞,只是新舊寫法不同
  • D. Trace 只記 token,Span 只記延遲

2. 依文章說法,要怎麼一眼分辨程式碼在做「傳統 ML 追蹤」還是「GenAI 觀測」?

  • A. 只要有 import mlflow 就是 GenAI 觀測
  • B. 看有沒有連到 tracking server
  • C. 看到 log_metric 是傳統追蹤;看到 trace / span / autolog() 就是 GenAI 觀測
  • D. 看有沒有用 GPU 訓練

3. 關於自動追蹤 autolog(),下列哪一項是文中強調的「啟用時機」關鍵?

mlflow.anthropic.autolog() client = anthropic.Anthropic()
  • A. autolog() 必須在對應的 client 建立之前呼叫
  • B. autolog() 一定要放在檔案最後一行
  • C. 每次呼叫模型前都要重新 autolog() 一次
  • D. autolog() 只能對 OpenAI 有效

4. 你想追蹤「一整個自己寫的函式」,最省事的手動追蹤寫法是哪個?

  • A. 在函式裡到處呼叫 log_metric()
  • B. 用 mlflow.start_span() 包住每一行
  • C. 在函式上加 @mlflow.trace 裝飾器,參數與回傳值會自動記成 inputs/outputs
  • D. 呼叫 autolog() 就會自動追蹤自家函式

5. 打開 MLflow UI 的 Traces 分頁 debug 時,文章建議的閱讀重點是什麼?

  • A. 從第一個 span 逐字讀到最後一個,一個都不能漏
  • B. 只看總 token,其他不重要
  • C. 先看模型名稱是否正確
  • D. 找「最寬的那條(最慢)」跟「紅色那條(出錯)」,再點開 inputs/outputs 看真相

系列第 1 篇,共 4 篇|難度:L3-熟練

前置知識:基本 Python、對 LLM API 呼叫(OpenAI / Anthropic)有概念、知道 MLflow tracking server 的存在。

一句話說明

MLflow Tracing 就是「LLM 版的行車紀錄器」:每一次呼叫模型或跑一段 Agent 流程,它會自動錄下「誰呼叫誰、輸入輸出是什麼、花了幾秒、燒了幾個 token、哪一步噴錯」,最後在 MLflow UI 攤成一棵可以點開的樹。

如果你已經熟悉傳統 MLflow(log_param / log_metric / log_model),本篇要建立的心智模型是:傳統追蹤記的是「一次訓練」的靜態結果,Tracing 記的是「一次請求」的動態過程。這兩者在 MLflow 裡是並存的兩套東西,不要混為一談。


為什麼要多一套「Tracing」?先看它跟傳統 ML 追蹤差在哪

傳統 ML 實驗你在乎的是:這組超參數跑出來的 accuracy 是多少?所以你記的是一組扁平的數字(metrics)跟設定(params)。

但 LLM / Agent 應用的痛點完全不同。一個「幫我查天氣再訂餐廳」的 Agent 請求,背後可能是:

使用者問一句
 └─ LLM 決定要呼叫 tool
     ├─ 呼叫 get_weather()      ← 這裡慢了 3 秒
     └─ 呼叫 search_restaurant() ← 這裡回傳了空結果
 └─ LLM 根據 tool 結果再生成一段回覆  ← 這裡 token 爆量

你想 debug 的問題是「為什麼這次回答很爛 / 很慢 / 很貴」,答案藏在這棵呼叫樹的某個節點裡。用傳統的扁平 metrics 根本表達不出這種巢狀結構,所以 MLflow 另外做了 Tracing。

傳統 ML 追蹤 GenAI Tracing
記錄單位 一次訓練 run 一次請求(trace)
資料形狀 扁平的 params / metrics 巢狀的 span 樹
你在意什麼 最終指標好不好 哪一步慢 / 錯 / 貴
典型欄位 accuracy、lr、epochs inputs、outputs、tokens、latency、error

📌 **一句話收斂**:看到 log_metric 是傳統追蹤;看到 trace / span / autolog() 就是 GenAI 觀測。


核心名詞:Trace 與 Span(只要記兩個)

這一節是整篇的地基,其他都是它的變化:

  • Trace(軌跡)一整次請求的完整紀錄。一個使用者問句進來到回覆出去,就是一條 trace。它是那棵樹的「樹根 + 整棵樹」。
  • Span(跨度)=樹上的一個節點,代表「一個步驟」。一次 LLM 呼叫是一個 span、一次 tool 呼叫是一個 span、你自己包的一個函式也可以是一個 span。span 會巢狀:外層 span 底下可以有子 span。

每個 span 身上通常掛著這些資訊:

span
├─ name        這步叫什麼(例如 "get_weather")
├─ inputs      這步收到什麼
├─ outputs     這步吐出什麼
├─ attributes  額外標記(token 數、模型名、自訂 tag…)
├─ start/end   起訖時間 → 相減就是延遲
└─ status      OK 還是 ERROR
Code language: JavaScript (javascript)

用一句白話翻譯:Trace 是一份帳單,Span 是帳單上的每一筆明細。 你要找「錢花在哪 / 時間耗在哪」,就是攤開明細一筆一筆看。

必看懂tracespaninputs/outputs、span 是巢狀的。 📌 知道就好:span 還有 SpanTypeLLMTOOLCHAINRETRIEVER…),UI 會用不同顏色標,遇到再查。


自動追蹤(autolog):一行搞定,也是 vibe coder 最常看到的寫法

AI 幫你接 LLM 觀測時,九成會用這招。它的樣子是:

import mlflow

mlflow.set_tracking_uri("http://192.168.50.190:5000")  # 指向你的 tracking server
mlflow.set_experiment("my-genai-app")                   # 這些 trace 歸到哪個實驗

mlflow.openai.autolog()   # ← 關鍵這行:從此所有 OpenAI 呼叫都自動產生 trace
Code language: PHP (php)

逐行翻譯

mlflow.set_tracking_uri("http://192.168.50.190:5000")
# 「trace 錄好之後寄到哪台 server」。沒設就存本地。

mlflow.set_experiment("my-genai-app")
# 「這些 trace 掛在哪個實驗底下」,方便之後在 UI 分類找。

mlflow.openai.autolog()
# 「幫 OpenAI SDK 裝上竊聽器」。之後任何 client.chat.completions.create(...)
# 都會被自動攔截、記成一條 trace,你的業務程式碼一個字都不用改。
Code language: PHP (php)

啟用之後,你正常呼叫模型就好,trace 是背景自動產生的:

from openai import OpenAI

client = OpenAI()
resp = client.chat.completions.create(          # 這行沒有任何 mlflow 字樣
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "用一句話解釋什麼是 trace"}],
)
# ↑ 但因為上面 autolog() 開了,這次呼叫已經被錄成一條 trace 送到 server
Code language: PHP (php)

換一家 provider,只換那一行

autolog按 SDK 提供的,換供應商就換前綴:

mlflow.openai.autolog()      # OpenAI
mlflow.anthropic.autolog()   # AnthropicClaudemlflow.langchain.autolog()   # LangChain
mlflow.llama_index.autolog() # LlamaIndex
mlflow.dspy.autolog()        # DSPy
Code language: CSS (css)

本專案的 Agent 就是用 mlflow.anthropic.autolog()(因為底層是 Claude Agent SDK)。

辨識重點:autolog 要在「client 建立之前」啟用

這是驗收 AI 代碼時最容易被忽略的紅旗,先記住這個順序:

# ✅ 正確順序
mlflow.anthropic.autolog()   # 先裝竊聽器
client = anthropic.Anthropic()   # 再建 client
# → client 出廠時就被監聽,之後的呼叫都有 trace

# 🚩 錯誤順序
client = anthropic.Anthropic()   # 先建 client(此時還沒被監聽)
mlflow.anthropic.autolog()   # 太晚了
# → 這顆 client 可能整個沒被追蹤到,UI 上一片空白
Code language: PHP (php)

這也是為什麼本專案的 observability.py 特別註明:autolog() **必須在 ClaudeSDKClient 建立前呼叫**。看到 AI 把兩行順序寫反,就要抓出來。


手動追蹤:autolog 蓋不到的地方自己補

autolog 只認得「它支援的 SDK」。可是你的程式裡一定有自己寫的函式——資料清洗、組 prompt、呼叫自家 API、後處理——這些 autolog 看不到。想讓它們也出現在 span 樹上,就要手動追蹤。有兩種寫法。

寫法 A:@mlflow.trace 裝飾器(最常見、最省事)

在函式上面加一行裝飾器,這個函式的每次呼叫就自動變成一個 span:

import mlflow

@mlflow.trace   # ← 「這個函式每次被呼叫都幫我記成一個 span」
def build_prompt(question: str, context: str) -> str:
    return f"根據以下資料回答:\n{context}\n\n問題:{question}"
Code language: CSS (css)

裝飾器會自動把函式的參數收成 span 的 inputs、回傳值收成 outputs,你什麼都不用多寫。

想加標記或指定名字,就傳參數:

@mlflow.trace(name="組裝提示詞", span_type="CHAIN", attributes={"team": "search"})
def build_prompt(question: str, context: str) -> str:
    ...
Code language: JavaScript (javascript)

翻譯:「這個 span 在 UI 上叫『組裝提示詞』、類型標成 CHAIN、附一個 team=search 的標籤。」

寫法 B:mlflow.start_span() context manager(要更細的控制時)

當你想追蹤的不是「一整個函式」,而是「函式裡的一小段」,或需要在過程中動態塞資料,就用 with 區塊:

import mlflow

def answer(question: str):
    with mlflow.start_span(name="retrieve") as span:   # 進入區塊 = span 開始
        span.set_inputs({"question": question})        # 手動記這步的輸入
        docs = my_vector_db.search(question)           # 你的實際工作
        span.set_outputs({"doc_count": len(docs)})     # 手動記這步的輸出
    # 離開 with 區塊 = span 自動結束、時間自動算好
    return docs
Code language: PHP (php)

逐行翻譯(這段是手動追蹤的精華)

with mlflow.start_span(name="retrieve") as span:
# 「開一個叫 retrieve 的 span,計時開始」。span 這個變數就是這個節點的把手。

    span.set_inputs({"question": question})
# 「這步的輸入是這包東西」。dict 想放什麼放什麼。

    span.set_outputs({"doc_count": len(docs)})
# 「這步的輸出是這包東西」。

# with 結束 → span 自動 end、latency = end - start 自動填好、status 預設 OK
Code language: PHP (php)

兩種寫法怎麼選?

需求 用哪個
「整個函式」就是一個步驟 @mlflow.trace 裝飾器(省事)
只想包函式裡的一小段 mlflow.start_span()
要在過程中動態 set 東西 mlflow.start_span()

巢狀是自動的:一個被 @mlflow.trace 的函式,去呼叫另一個被 @mlflow.trace 的函式,MLflow 會自動把後者接成前者的子 span,你不用手動串父子關係。


在 MLflow UI 讀懂一棵 span 樹

trace 送上去後,打開 MLflow UI,進入該 experiment,切到 Traces 分頁(跟平常看 Runs 的分頁不同,別找錯)。點一條 trace,你會看到左邊一棵樹、右邊一個細節面板。

實戰時的閱讀順序(debug 就照這個掃):

  1. 先看整條 trace 的總時間跟總 token —— 確認這次到底貴不貴、慢不慢。
  2. 找最寬的那條(時間最長的 span) —— 延遲的兇手通常一眼就看出來,是某個 tool 卡住還是 LLM 生成太久。
  3. 找紅色 / ERROR 的 span —— 哪一步噴錯,點開看它的 inputs 是什麼、錯誤訊息是什麼。
  4. 點開可疑 span 的 inputs / outputs —— 「餵進去的 prompt 長怎樣、模型到底回了什麼」,這是 LLM debug 最值錢的資訊,因為問題常常是 prompt 組錯、context 塞了垃圾。

一句話心法:Traces 分頁不是拿來欣賞的,是拿來「找最寬的那條」跟「找紅色那條」的。


🚩 驗收 AI 代碼時的紅旗清單

AI 幫你接 MLflow tracing 時,這幾種寫法看起來都會跑,但會讓你「以為有在追蹤,其實沒有」或「追蹤到一半就斷」。挑相關的抓:

紅旗 1:autolog 寫在 client 之後

client = anthropic.Anthropic()
mlflow.anthropic.autolog()   # 🚩 太晚,這顆 client 可能沒被追蹤
Code language: PHP (php)

→ 跟 AI 說:「把 autolog() 移到所有 client 建立之前。」

紅旗 2:忘了設 tracking_uri,trace 默默存本地

mlflow.openai.autolog()
# 🚩 沒有 set_tracking_uritrace 進了本地資料夾,你在遠端 server UI 上永遠找不到
Code language: CSS (css)

→ 問 AI:「這段 trace 會送到哪?我要它送到我的 tracking server。」

紅旗 3:span 裡把錯誤吞掉,status 永遠 OK

with mlflow.start_span(name="call_api") as span:
    try:
        do_something()
    except Exception:
        pass   # 🚩 錯誤被吞,這個 span 在 UI 上顯示成功,你被騙了
Code language: PHP (php)

→ 出錯的 span 應該讓它反映 ERROR,不要靜靜吞掉,否則 tracing 失去意義。

紅旗 4:把一大包東西塞進 inputs / attributes

span.set_inputs({"whole_dataframe": df.to_dict()})  # 🚩 幾 MB 塞進 trace
Code language: PHP (php)

→ trace 是給人快速掃的,不是資料倉庫。塞太大既拖慢又難讀,記關鍵欄位就好。

紅旗 5:短命腳本沒 flush 就結束,trace 沒送出去

# 一個跑完就退出的 script
resp = client.chat.completions.create(...)
# 🚩 程式立刻結束,背景還在非同步送 trace 的執行緒被砍掉,server 上什麼都沒有
Code language: PHP (php)

→ 短命子程序要嘛關掉非同步匯出(MLFLOW_ENABLE_ASYNC_TRACE_LOGGING=false),要嘛結束前呼叫 flush。這正是本專案 observability.py 特別處理的坑。


Vibe Coder 驗收檢查點

拿到 AI 寫的「加上 MLflow tracing」的代碼,照這張表驗收:

  • [ ] 順序對嗎:所有 xxx.autolog() 都在對應的 client 建立之前
  • [ ] 寄到哪:有沒有 set_tracking_uri(...) 指向你要的 server?跑一次後去 UI 的 Traces 分頁,看得到剛剛那條嗎?
  • [ ] 樹長對嗎:點開 trace,你自己寫的關鍵函式(不是 SDK 的)有出現成 span 嗎?沒有就是手動追蹤沒加。
  • [ ] 錯誤看得到嗎:故意讓某步噴錯跑一次,那個 span 在 UI 上是紅色 / ERROR 嗎?如果顯示成功,代表錯誤被吞了(紅旗 3)。
  • [ ] 可以直接問 AI 的驗收句:把代碼貼給 AI 問——「這段程式產生的 trace 會不會漏掉我自己寫的函式?autolog 的啟用時機對嗎?」

看不懂就這樣問 AI

「用白話一段一段解釋這段 MLflow tracing 的 code 在幹嘛,特別是 autolog()@mlflow.tracestart_span() 各自負責什麼,我是初學者。」

「這段程式跑完後,我應該在 MLflow UI 的哪個分頁、看到幾層的 span 樹?幫我畫出來。」

「幫我檢查這段 tracing 有沒有常見錯誤:autolog 順序、tracking_uri、錯誤有沒有被吞、短命腳本要不要 flush。」


小結與下一篇

  • Trace = 一次請求的完整紀錄;Span = 樹上的一個步驟,兩個名詞撐起整套 GenAI 觀測。
  • autolog() 一行接管主流 LLM SDK 的自動追蹤,重點是要在 client 建立前啟用、記得設 tracking_uri
  • 手動追蹤@mlflow.trace(包整個函式)或 mlflow.start_span()(包一小段、動態塞資料)把自家函式也放上 span 樹。
  • 在 UI 的 Traces 分頁,debug 就是「找最寬的(慢)」跟「找紅色的(錯)」,再點開 inputs / outputs 看真相。

下一篇(#02)我們會把這套 tracing 接到真實的 Agent 流程上,看多個 tool 呼叫如何組成一棵完整的 span 樹,以及怎麼從 trace 反推「Agent 為什麼做了這個決定」。

進階測驗:認識 GenAI 觀測 Tracing 入門

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. 你的 Agent 有一個自己寫的 rerank() 函式,autolog 抓不到它,你想在 span 樹上看到它的執行、並在過程中動態記錄「候選數量」。最適合的做法是? 情境題

def rerank(candidates): # … 排序邏輯 … return top
  • A. 呼叫 mlflow.openai.autolog() 就會自動追蹤 rerank()
  • B. 在 rerank() 裡用 log_metric() 記候選數
  • C. 用 with mlflow.start_span() 包住邏輯,並用 span.set_inputs / set_outputs 記候選數
  • D. 完全不用管,UI 會自己推斷

2. 使用者抱怨某次 Agent 回答又慢又貴。你打開該次 trace,想最快定位延遲兇手。應該先做什麼? 情境題

  • A. 從樹根第一個 span 逐字閱讀到底
  • B. 找時間最長(最寬)的那個 span,通常就是延遲兇手,再點開它的 inputs/outputs
  • C. 直接改用 log_param 記錄參數
  • D. 重跑十次取平均延遲

3. 你想同時觀測 Claude 的自動呼叫與自家的 build_prompt() 函式。下列規劃哪個正確? 情境題

  • A. 只加 mlflow.anthropic.autolog() 就能連自家函式一起追蹤
  • B. 只在 build_prompt() 加 @mlflow.trace,Claude 呼叫就會自動出現
  • C. 兩者無法同時追蹤,只能二選一
  • D. 用 anthropic.autolog() 追 Claude、再對 build_prompt() 加 @mlflow.trace,巢狀關係會自動串起

4. 這段程式跑完後,同事在遠端 MLflow server 的 UI 上完全找不到任何 trace。最可能的原因是? 錯誤診斷

import mlflow from openai import OpenAI mlflow.openai.autolog() client = OpenAI() resp = client.chat.completions.create(model=”gpt-4o-mini”, messages=[…])
  • A. autolog() 寫在 client 之後,順序錯了
  • B. 沒有 set_tracking_uri(),trace 存到本地,遠端 server UI 當然看不到
  • C. gpt-4o-mini 不支援 tracing
  • D. 少了 @mlflow.trace 裝飾器

5. 某個 call_api 步驟其實常常失敗,但 UI 上這個 span 一直顯示成功(status OK)。看這段程式,問題出在哪? 錯誤診斷

with mlflow.start_span(name=”call_api”) as span: try: do_something() except Exception: pass
  • A. start_span 不能包 try/except
  • B. span 名稱不能有底線
  • C. except 用 pass 把錯誤吞掉,span 沒反映 ERROR,讓失敗被偽裝成成功
  • D. 缺少 set_inputs 所以無法判斷狀態

發佈留言

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