測驗:認識 GenAI 觀測 Tracing 入門
共 5 題,點選答案後會立即顯示結果
1. 文中把 Trace 與 Span 的關係比喻成「帳單」與「明細」。下列哪個描述最正確?
2. 依文章說法,要怎麼一眼分辨程式碼在做「傳統 ML 追蹤」還是「GenAI 觀測」?
3. 關於自動追蹤 autolog(),下列哪一項是文中強調的「啟用時機」關鍵?
4. 你想追蹤「一整個自己寫的函式」,最省事的手動追蹤寫法是哪個?
5. 打開 MLflow UI 的 Traces 分頁 debug 時,文章建議的閱讀重點是什麼?
系列第 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 是帳單上的每一筆明細。 你要找「錢花在哪 / 時間耗在哪」,就是攤開明細一筆一筆看。
✅ 必看懂:trace、span、inputs/outputs、span 是巢狀的。 📌 知道就好:span 還有 SpanType(LLM、TOOL、CHAIN、RETRIEVER…),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() # Anthropic(Claude)
mlflow.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 就照這個掃):
- 先看整條 trace 的總時間跟總 token —— 確認這次到底貴不貴、慢不慢。
- 找最寬的那條(時間最長的 span) —— 延遲的兇手通常一眼就看出來,是某個 tool 卡住還是 LLM 生成太久。
- 找紅色 / ERROR 的 span —— 哪一步噴錯,點開看它的 inputs 是什麼、錯誤訊息是什麼。
- 點開可疑 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_uri,trace 進了本地資料夾,你在遠端 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.trace、start_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 題,包含情境題與錯誤診斷題。