寫於 2026 年 8 月 18 日,9 月才上線(部落格的發佈額度在 8 月中用完了,要等 9 月 1 日重置才發得出去)。文中對 OpenTelemetry GenAI 語義慣例的描述以 8 月 18 日的規格原文為準,你讀到時文件可能已經改版。

Google 搜「OpenTelemetry GenAI semantic conventions」,排在前面的連結有機會把你帶到一個只剩十一行字的頁面。整頁內容就是一句話:這裡搬走了,去新的 repo 看。

1
2
3
4
5
# Moved: Generative AI semantic conventions

GenAI semantic conventions have moved to the OpenTelemetry GenAI
semantic conventions repository. This page has moved and is no longer
maintained in this repository.

搬去的地方是 open-telemetry/semantic-conventions-genaigh api 查得到它的出生日期:2026 年 5 月 5 日建的 repo,Apache-2.0,到 8 月 17 日還有 push,259 顆星。文件沒說為什麼要搬出來,所以我也不猜。

值得注意的是它搬走之後留下的東西:一份 986 行的 agent span 規格,一份 1332 行的 MCP 慣例。這兩份東西回答的問題,正是自己動手寫 agent 時會撞上的那一個。

這條時間線的起點:每個人都在自己命名

三個星期前我在這裡寫過怎麼把 Claude Code 接上 OpenTelemetry(那篇在這裡)。開一個環境變數就能收到 claude_code.token.usageclaude_code.cost.usageclaude_code.session.count 這些指標。接進 Grafana,主管就看得到團隊每天燒多少錢。那套很好用,我現在還在用。

問題出在那個前綴。claude_code. 這五個字,只有 Claude Code 會吐。

你自己寫的 agent 不會吐。你在 Bedrock 上跑的那條路不會吐。你為了成本控制加的 Gemini fallback 更不會吐。等你把這三種東西擺在同一張儀表板上,會發現查詢語句要寫三份、panel 要建三組、加總 token 得先在後端做一次欄位對映。

這種痛苦有一個很好認的特徵:每加一個模型供應商,工作量是線性成長的。線性成長的維護成本,通常代表你在做本來應該由標準去做的事。

規格長什麼樣:先看 agent 的那三個動作

規格把 agent 的行為收斂成幾個 operation。gen_ai.operation.name 這個屬性的合法值有 create_agentinvoke_agentexecute_toolgenerate_contentfetch_response

span 的名字怎麼取,規格寫得很具體。照抄原文:

The gen_ai.operation.name SHOULD be invoke_agent.

Span name SHOULD be invoke_agent {gen_ai.agent.name} if gen_ai.agent.name is readily available. When gen_ai.agent.name is not available, it SHOULD be invoke_agent.

翻成人話:你的 trace 上會出現一個叫 invoke_agent code-reviewer 的 span,底下掛幾個 execute_tool 的子 span。多輪對話用 gen_ai.conversation.id 串起來,多個 agent 組成的流程用 gen_ai.workflow.name 串起來。

這個結構有點像餐廳的點單流程。invoke_agent 是「這桌客人下了一張單」,execute_tool 是「廚房裡的某個爐台開始動」,conversation.id 是桌號,workflow.name 是「今天這場宴席」。以前這些東西全部混在一坨 log 裡,你只知道總共花了幾分鐘,不知道時間耗在哪一個爐台。

屬性的全集大概三十個,這幾個我覺得台灣團隊會最有感:

gen_ai.provider.name 的合法值是列舉的,寫得死死的:anthropicopenaiaws.bedrockazure.ai.inferenceazure.ai.openaigcp.gen_aigcp.vertex_aicoheredeepseekgroqibm.watsonx.aimistral_aiperplexityx_ai。同一張圖表要同時容納三家模型,靠的就是這個欄位能 group by。

gen_ai.usage.input_tokensgen_ai.usage.output_tokens 沒什麼意外。真正讓我停下來看第二次的是這兩個:gen_ai.usage.cache_read.input_tokensgen_ai.usage.cache_creation.input_tokens

prompt caching 的成本歸因,終於有跨廠商的欄位名可以用了。做過這件事的人就知道,快取讀取跟快取建立的計價不一樣,混在一起算等於白算。而各家 SDK 回傳這兩個數字的欄位名長得完全不同。現在它們有標準名字,儀表板上那條「快取幫我省了多少」的線可以直接畫,不用每換一家就改一次 transform。

MCP 呼叫也不再是黑盒

那份 1332 行的 mcp.md 是這次搬家後我覺得最實用的部分。它給 MCP 定了一套 OTel 慣例,span 名字的格式照抄原文是:

Span name SHOULD follow the format {mcp.method.name} {target}

mcp.method.name 的值就是 MCP 協定本來的方法名:initializetools/calltools/listresources/readprompts/get,加上一串 notifications/*。屬性有 mcp.protocol.versionmcp.request.idmcp.resource.urimcp.session.id。另外還定了四個 metric:mcp.client.operation.durationmcp.client.session.durationmcp.server.operation.durationmcp.server.session.duration

意思是 MCP server 的呼叫可以掛在同一棵 trace 上,跟 agent 的 span 併在一起看。你的 agent 慢,到底慢在模型推論還是慢在某個 MCP server 撈資料庫撈太久,這件事本來只能靠猜或自己埋 log,現在有標準的地方可以量。

現在接它,你要先知道一件事

這份規格不能當成定案的標準來接。

文件裡的徽章分布很說明問題。gen-ai-agent-spans.md 這份規格,Development 徽章出現 202 次,stable 只有 14 次。而新 repo 到目前為止沒發過任何一個 release,releases/latest 直接回 404。舊的主 repo semantic-conventions 倒是照常在發版,最新是 8 月 4 日的 v1.44.0,但 GenAI 那部分已經不在裡面了。

202 比 14。這個比例的意思是,你今天照著文件寫下去的欄位名,絕大多數都還在 Development 狀態,隨時可能改名或改語義。

務實的接法有三個。第一個是釘 commit,不要寫「我們遵循 OTel GenAI 慣例」這種沒有版本的句子,要寫清楚你對照的是哪一個 commit hash 的文件。第二個是把欄位名集中在一個常數檔裡,不要讓 gen_ai.usage.cache_read.input_tokens 這種字串散落在三十個檔案。第三個是先接標了 stable 的那少數幾處,Development 的那批等它穩了再說。

第二點是老工程師的肌肉記憶了。規格還在動的時候把字串散開,等它改名那天你會需要一場全 repo 的搜尋取代。然後在某個角落漏掉一個,過三個月才在儀表板上發現有條線是空的。

誠實講一下這篇的邊界

我沒有把這套規格實際接上任何一個自己的 agent。這篇的內容全部來自規格原文與 gh api 查到的 repo 元資料。上面那些行數、徽章次數與屬性清單是對規格原文逐行統計出來的,不是我把它接上去跑出來的結果,我沒有寫過一行 instrumentation 程式碼。上面那些「怎麼務實地接」是從別的規格演進踩過的經驗推出來的,不是這一套的實測結果。

另外要補一句避免誤會:這篇跟三個星期前那篇 OTel 文的分工不一樣。那篇講的是怎麼消費 Claude Code 已經吐出來的 claude_code.* 指標,視角是成本管理。這篇講的是你自己寫的 agent 該產出什麼欄位名,視角是除錯與可攜性。一個是給主管看的報表,一個是給自己看的 trace。

新 repo 開了三個半月,259 顆星,零個 release。下一個 release 一發,才會知道那 202 個 Development 裡面有幾個要改名字。

來源