Day 5 提過 OpenTelemetry GenAI 語意慣例定義的階層結構,這篇把它展開成實際的 Trace 設計。依照這套慣例,一次 Agent 執行會產生三個層次的 Span:
invoke_agent(最外層):代表整次 Agent 呼叫,從接到請求到產出最終回應。這是整棵樹的根。
chat(子層):每一次 LLM 呼叫。一次 Agent 執行可能有多個 chat span——Planner 決定下一步要做什麼是一次、根據工具回傳結果重新評估又是一次。
execute_tool(子層):每一次工具呼叫。這層的屬性應該記錄呼叫了哪個工具、參數是什麼、執行結果如何。
invoke_agent (customer_support_agent) [2,840ms]
├── chat (gemini-...) [620ms]
│ └── 決策:需要查詢知識庫
├── execute_tool (search_knowledge_base) [180ms]
│ └── 回傳 3 筆結果
├── chat (gemini-...) [710ms]
│ └── 決策:檢索相關性不足,需要進一步確認
├── execute_tool (fetch_account_status) [890ms] ← 最慢的一步
└── chat (gemini-...) [440ms]
└── 決策:產生最終回應
光看這棵樹,你就能立刻回答幾個問題:整體花了 2,840ms、最慢的環節是 fetch_account_status(890ms)、這次執行做了三次 LLM 呼叫。這些在扁平的日誌裡要拼湊半天,在 Trace 視覺化裡一眼就看到。
依照 GenAI 語意慣例,chat span 上應該掛的核心屬性包括:gen_ai.request.model(用了哪個模型)、gen_ai.usage.input_tokens 與 gen_ai.usage.output_tokens(Token 用量)、gen_ai.response.finish_reasons(模型為什麼停止生成)。
finish_reasons 這個屬性特別值得注意——它能告訴你模型是正常結束、還是被截斷、還是觸發了安全過濾。如果你發現某類任務的 finish_reason 異常地常出現非正常值,那本身就是一個值得追查的訊號。
再次呼應 Day 5 與 Day 11 的原則:完整的 Prompt 與回應內容不該直接掛在 Span 屬性上——屬性會被索引、有大小限制、且可能含個資。GenAI 語意慣例的做法是把內容放進 Span 的事件(event),這樣可以在 Collector 層級過濾,不用改應用程式碼。
待實測提醒:GenAI 語意慣例仍在 Development/Experimental 階段,Span 名稱與屬性命名未來仍可能調整,不同 SDK 之間也可能存在命名落差。發布前請實際跑一次,確認你使用的 SDK 產出的屬性名稱與本文描述一致。