
LLM 可觀測性中的 Sessions、Traces 與 Spans:其中一個不是結構層級
Datadog 的術語頁面,也就是 LLM observability sessions traces spans 這個查詢在 Google 上的第一名結果,只定義了這三個詞當中的兩個。不是三個。缺席的那個對應到 gen_ai.conversation.id,而它之所以缺席,是因為 OpenTelemetry 規範從來沒有把它做成一個結構層級。如果你需要先理解可觀測性本身的存在理由,從這裡開始。這篇文章從那篇停下的地方接續:資料模型。
重點整理
- Span 嵌套在 trace 之內;trace 群組成 session。嵌套由最內層向外:先是 span,然後 trace,再是 session。
- 一個 span 是一項計時的操作。一個 trace 是一次端到端請求。一個 session 是一場多輪對話。
- OpenTelemetry 的 GenAI 約定定義了 span 與
gen_ai.conversation.id屬性。它們沒有定義 session 層級。 - Trace ID 與 span ID 透過 context 自動傳播。Session ID 不會。每一輪都要你親自設定。
Sessions、Traces、Spans 一次看懂
在 LLM 可觀測性中,span 是一項計時的操作(一次模型呼叫、一個檢索步驟),trace 是一次請求產生的 span 樹,而 session 把同一場對話中的許多 trace 歸為一組。嵌套向內進行:span 在 trace 之內,trace 在 session 之內。第三個分組,就是那個表裡不一的。
| 層級 | 包裝了什麼 | 存活多久 | 誰設定 ID | 回答什麼問題 | 每場對話的典型數量 |
|---|---|---|---|---|---|
| Session | 一場使用者對話中的許多 trace | 數分鐘到數天;在無活動逾時或明確關閉時結束(由廠商定義) | 你,手動,在每一輪 | 整場對話成功了嗎? | 1 |
| Trace | 一次端到端請求或一輪 | 數毫秒到數秒 | 自動(SDK / OTel) | 這一輪發生了什麼? | 通常 5–20 |
| Span | 一項操作:一次檢索、一次模型呼叫、一次工具呼叫 | 亞毫秒到數秒 | 自動(SDK / OTel) | 哪一步慢了、錯了,或貴了? | 每個 trace 大約 3–30 |
這些數量與壽命數字,是你在 RAG 聊天機器人或 agent 迴圈中可以預期的典型範圍,不是受控測試的量測結果。你的數字會不同。不會不同的是:Session 那一列就是規範中不是結構層級的那個,而「Sessions:你的工具大概自己發明的層級」這一節會證明這件事。
什麼是 Span?什麼又是 Span 的 Kind?
Span 是一項計時的操作,帶有名稱、起始時間戳記、結束時間戳記、狀態碼,以及一包鍵值屬性。在 LLM 追蹤中,有用的資料住在屬性裡:gen_ai.usage.input_tokens、gen_ai.usage.output_tokens 和 gen_ai.request.model 告訴你這項操作花了多少成本、是哪個模型跑的。
一個 span 是一項操作,不是一次函式呼叫
每個 span 都帶著一個 parent span ID 指標(root span 上為空),由它建立出樹。屬性包是開放的:你需要什麼 context 就附什麼。OpenTelemetry GenAI span 約定(狀態:Development)要求每個 GenAI span 都有 gen_ai.operation.name 和 gen_ai.provider.name,並建議加上上述的 token 用量屬性。
來自 Datadog 術語頁面的一條實務規則:LLM、Workflow 和 Agent span 可以擔任 root span;Tool、Task、Embedding 和 Retrieval span 不行。那是 Datadog 的規則,不是通則,但它是唯一明講的廠商,而這條規則讓你免於建出一棵從沒有 parent 的工具呼叫開始的 trace。
Span kinds:同一個想法,五套詞彙
每個工具都需要一種方式來表達「這個 span 是一次模型呼叫」與「這個 span 是一次檢索」的區別。只是它們對用詞沒有共識:
| 工具 | 它對「操作種類」的用詞 | 值 |
|---|---|---|
| OpenTelemetry GenAI | gen_ai.operation.name 屬性 | 15 個公認值(chat、embeddings、execute_tool、invoke_agent、retrieval,還有 10 個);若適用就必須使用其中之一,沒有適用時才允許自訂值 |
| Datadog | Span kind | LLM, Workflow, Agent, Tool, Task, Embedding, Retrieval |
| OpenInference / Phoenix | Span kind | CHAIN, LLM, TOOL, RETRIEVER, RERANKER, EMBEDDING, AGENT, GUARDRAIL, EVALUATOR, PROMPT |
| Langfuse | Observation type | generation, span, event |
| LangSmith | Run type | LLM, chain, tool, retriever |
OpenInference 規範列出十種 kind。Datadog 列出七種。OTel 走第三條路:它的 GenAI 屬性登錄表為 gen_ai.operation.name 公布了 15 個公認值(chat、create_agent、create_memory、create_memory_store、delete_memory、delete_memory_store、embeddings、execute_tool、generate_content、invoke_agent、invoke_workflow、plan、retrieval、search_memory、text_completion),並聲明若其中之一適用,就必須使用該值;只有在沒有適用值時才可以使用自訂值。所以它是一個半開放的列舉,而不是沒有列舉。三份清單,三種長度,彼此之間毫無對齊。如果你正在選工具,這個詞彙落差比功能清單更要緊,因為你的儀表板和警示過濾器都要以它為鍵。
什麼是 Trace?樹形結構為什麼要緊?
Trace 是一次請求產生的 span 樹。一個 root span 位於頂端;其他每個 span 都透過 parent-span-ID 的邊掛在它底下。樹形結構就是重點所在:平坦的日誌告訴你某個東西慢了,但樹告訴你哪一步慢了、哪一步產出了錯誤的輸出。
chat_request (root) 2,340ms
├── retrieval 410ms
│ └── rerank 85ms
├── chat gpt-4o 1,720ms
└── tool_call: search_calendar 190ms讀完那棵樹,診斷立刻清楚:74% 的延遲落在模型呼叫,而不是檢索。五個時間戳記的平坦日誌給你相同的總和,卻不給你任何歸因。
Agent 迴圈讓這棵樹比單純的 RAG 請求更深更寬。每一次工具呼叫都產出自己的子樹;一個五步驟的 agent 輪次可以輕易在一個 root 之下產出 30 個以上的 span。這很正常,也正是底下那個 span 顆粒度問題存在的原因。
追蹤與日誌之間的區別在這裡也要緊:日誌記錄事件,追蹤記錄因果。如果你還在決定什麼該記日誌、什麼該追蹤,我們的 LLM 日誌最佳實踐 一文劃出了那條界線。
Sessions:你的工具大概自己發明的層級
不是。Session 不是 OpenTelemetry GenAI 約定中的結構層級。規範定義的是 span 和 gen_ai.conversation.id 屬性(條件式要求、「當可用時」、狀態:Development),描述為用來關聯訊息的對話或執行緒唯一識別碼。各家廠商再在該屬性之上建出自己的 session 物件。這個 SERP 上沒有其他人坦白說明規範的狀態,所以在這裡講清楚。
其結果,就是整篇文章存在所要傳達的那句話:
Session 是一個分組鍵,不是 parent span。它不會像 trace ID 那樣傳播;每一輪都要你自己設定。
漏掉一輪,那一輪就掉出 session。它沒有自動的 context 傳播。
Session 何時開始、何時結束?
由廠商定義。有些工具在第一個帶著新 conversation ID 的 trace 出現時開啟 session,並在無活動逾時時關閉(Langfuse 預設為一個可設定的時間窗)。其他則要求明確的關閉呼叫。規範對生命週期什麼也沒說,因為規範根本不把 session 建模成物件。
什麼會跨輪保留,什麼不會?
模型的上下文視窗不是 session。Session 是獨立 trace 之上的一個分組鍵。每一輪有自己的 trace、自己的 root span、自己的 token 計數。會保留的,是你蓋在每個 root span 上的 conversation ID 屬性。不會保留的:延遲、token 用量、span 結構。那些都是每個 trace 自己的。
Session 層級的指標測的是什麼?
單一 trace 測不到的東西:解決率(這場對話解決了使用者的問題嗎?)、抵達答案的輪數(使用者拿到所需之前經過了多少 trace?)、以及放棄的對話(沒有結束訊號的 session)。在 session 層級對即時 trace 執行評估,就是你捕捉多輪失敗的方式,那些失敗逐輪來看都沒問題。
程式碼,不綁廠商
這個片段只使用穩定的 OTel 原語。沒有廠商 SDK。它為一輪建立一個 root span、為檢索建立一個子 span、為模型呼叫建立一個子 span,並設定 gen_ai.conversation.id,讓三輪落進同一個 session:
from opentelemetry import trace
tracer = trace.get_tracer("my-llm-app")
SESSION_ID = "conv-8f3a2c" # same value on every turn
def handle_turn(user_message: str):
with tracer.start_as_current_span("chat_request") as root:
# You set this. It does not propagate automatically.
root.set_attribute("gen_ai.conversation.id", SESSION_ID)
with tracer.start_as_current_span("retrieval") as ret:
ret.set_attribute("gen_ai.operation.name", "retrieval")
docs = retrieve(user_message)
with tracer.start_as_current_span("chat gpt-4o") as llm:
llm.set_attribute("gen_ai.operation.name", "chat")
llm.set_attribute("gen_ai.provider.name", "openai")
llm.set_attribute("gen_ai.request.model", "gpt-4o")
response = call_model(user_message, docs)
llm.set_attribute("gen_ai.usage.input_tokens", 1_204)
llm.set_attribute("gen_ai.usage.output_tokens", 312)
return response用相同的 SESSION_ID 呼叫 handle_turn 三次,在任何讀取該屬性的後端中,三個 trace 都會歸到同一個 session 之下。換掉 ID,你就開啟了一個新 session。整個機制就是這樣。
我們並排讀了五家廠商的文件。它們沒有共識。
2026-07-30,我們並排讀了 Langfuse、LangSmith、OpenInference / Phoenix 和 Datadog 現行的資料模型文件,加上 OpenTelemetry GenAI span 規範。五家中的四家把同一個物件叫成不同的名字。只有一家把 session 當作一等物件,而不是一個屬性。Datadog 的術語頁面,也就是這個查詢在 Google 上的第一名結果,根本沒有定義 session。
| 概念 | OTel GenAI semconv | Langfuse | LangSmith | OpenInference / Phoenix | Datadog |
|---|---|---|---|---|---|
| 整場對話 | gen_ai.conversation.id 屬性 | Session(trace 的選配分組) | Thread(透過 session_id / thread_id 中繼資料) | session.id span 屬性 | 術語頁面未定義 |
| 一次請求 | Trace | Trace | Trace(「一個 run 的集合」) | Trace | Trace |
| 一項操作 | Span | Observation(span / generation / event) | Run(「代表單一工作單位的 span」) | 帶有 span kind 的 span | 帶有 span kind 的 span |
關於第一列的一個來源附註:OpenInference 的 session.id 不在上面連結的 traces 規範中,那份規範涵蓋十種 span kind。它定義在相鄰的 OpenInference 語意約定檔案中,是 session 的唯一識別碼。兩個檔案,同一份規範。
跨廠商比較不是我們發明的;FutureAGI 也發布了一張 OTel 對廠商的表。我們的兩項新增是 session 那一列(FutureAGI 跳過了它)以及同字不同義的陷阱:Langfuse 的「observation」和 LangSmith 的「run」與 span 是同一個物件,而 Datadog 和 OpenInference 的 span kind 是同一個想法的不同詞彙。
Langfuse 叫它 observation,LangSmith 叫它 run,Datadog 叫它 span。同一個物件,三份在你遷移那天壞掉的儀表板。
那是我們對遷移成本的解讀,不是廠商的說法。但這就是原因:以「observation」或「run」為鍵的儲存過濾器、評估設定和警示規則,會在你換工具那天停止運作。你重新命名的不是一個欄位。你重新命名的是一個層級。如果你正在衡量那兩個特定工具,我們的 Langfuse 對 LangSmith 比較 更深入談了這個分歧。
讀者的技術棧裡可能也已經有 Opik、PostHog、Sentry 或 Weights & Biases;Google 把這四家都與 llm tracing 關聯,而每一家對這些概念的映射都略有不同。在挑選合適的那一家?我們的可觀測性平台總覽涵蓋了整個領域。
一個時效附註:GenAI 約定已經搬進了自己的儲存庫,離開了主 semantic-conventions repo。舊的 opentelemetry.io/docs/specs/semconv/gen-ai/ 路徑現在只帶有一個指標。
哪個 ID 去哪裡?
Trace ID 識別一次請求,並透過 context 自動傳播。Span ID 識別該 trace 內的一項操作,同樣自動。關聯 ID(或請求 ID)來自你的 web 層,在追蹤開始之前就有,它也是最常被人跟 trace ID 搞混的那個。Session ID 是異類:由你設定,手動,在每一輪。
| ID | 誰設定 | 範圍 | 容易被搞混成 |
|---|---|---|---|
| Trace ID | 自動 | 一次請求;透過 context 傳播 | 來自 web 層的關聯 ID |
| Span ID | 自動 | 一項操作 | , |
| Parent span ID | 自動 | 建立出樹;在 root span 上為空 | , |
| Session / conversation ID | 你,手動,每一輪 | 許多 trace | 被以為會自動傳播。它不會。 |
| User ID | 你,手動 | 許多 session | Session ID |
| Request / correlation ID | 你的 web 層,在追蹤開始之前 | 一次 HTTP 請求 | Trace ID(這是最大的一個) |
實務規則:把 gen_ai.conversation.id 當作 span 屬性附在每一輪的 root span 上,並把 user ID 蓋在旁邊。漏掉一輪,你的 session 層級指標就會靜悄悄地失去那一輪。
一個關於基數的警告:User ID 和 session ID 是高基數的值。這對你後端的索引帳單有影響,那是下一節的問題。
Span 應該多細?
兩種失敗模式,都很常見:
Span 過多。 每次函式呼叫一個 span,會給你一條 400 個 span、沒人讀得下去的 trace,和一筆沒人簽核過的逐 span 帳單。託管後端(Datadog、Langfuse Cloud)按 span 量計價。一個把每次字串拼接都檢測進去的嘮叨 agent 迴圈,一個下午就能燒穿免費額度。
Span 過少。 「整條鏈」一個 span,告訴你它慢了,但不告訴你哪裡。你到頭來又把 print 語句加了回去,而那正是追蹤本來要取代的東西。
經驗法則(它是經驗法則,不是量測結果):在發生決策或外部呼叫的邊界上設 span。
- 檢索步驟:設 span。
- Rerank 呼叫:設 span。
- 每次模型呼叫:設 span。
- 每次工具呼叫:設 span。
- 每次 guardrail 檢查:設 span。
- 純粹的處理序內轉換(字串格式化、JSON 解析、prompt 組裝):附在 parent span 上的屬性,不是它們自己的 span。
關於基數、取樣和保留:
- 高基數屬性(user ID、完整 prompt)會膨脹儲存成本。對它們取樣或截斷。
- 多數後端讓你可以在 trace 層級取樣。保留 100% 的錯誤 trace;對成功路徑取樣。
- 保留窗口各異:免費額度 7 天,付費 30–90 天。在你需要資料之前就先決定。
關於 span 量與逐 span 計價背後的實際成本模型,見我們的 LLM 成本監控指南。我們不在這裡重建它。
Techsy 怎麼做
在客戶的 agent 工作上,我們標準化為三條規則:
- 一輪一個 trace。絕不把兩個使用者輪次併進一個 trace,即使 agent 在內部循環。
- 一個蓋在每個 root span 上的 session ID,在應用碼中設定,絕不假設它會傳播。
- Span kind 維持在一個小的固定集合(檢索、推論、工具、guardrail),讓儀表板在廠商更換後仍能存活。
第三條規則是團隊會跳過的那條,也是救下一次遷移的那條。如果你的 span 詞彙綁死在一家廠商的列舉上,每個警示和儲存的視圖都會在你換工具那天壞掉。
如果你正在建一個 agent 系統,想要對追蹤架構的第二意見,取得免費諮詢。
關於作者
Mert Batur 是 Techsy.io 的共同創辦人,團隊為 B2B 客戶交付 AI agent、自動化系統與語音/SDR 流程。他寫的文章都關於 Techsy 團隊在生產環境實際使用的 LLM 工具鏈。歡迎在 LinkedIn 上聯繫。
常見問題
分散式追蹤中的 span 是什麼?
Span 是一個計時的工作單位:它有名稱、起始時間、結束時間、狀態和一組屬性。Span 透過 parent-span-ID 的參照彼此連結,形成一棵樹。在 LLM 應用中,一個 span 通常包裝一次模型呼叫、一次檢索,或一次工具調用。
Datadog 中的 span 是什麼?
在 Datadog 的 LLM Observability 中,span 是同樣的計時操作,但 Datadog 加了一套 span kind 分類:LLM、Workflow、Agent、Tool、Task、Embedding 和 Retrieval。只有 LLM、Workflow 和 Agent kind 可以擔任 root span。這套分類是 Datadog 特有的;它不是 OpenTelemetry 標準的一部分。
可觀測性的四大支柱是什麼?
四大支柱是日誌、指標、追蹤,以及(看採用誰的框架)剖析或事件。追蹤是本文所在的支柱。LLM 的情況增加了一個轉折:token 用量和模型識別是 trace span 上的屬性,不是獨立的指標串流,這把本來是兩個支柱的東西折進了一則查詢。
可觀測性的四個黃金訊號是什麼?
延遲、流量、錯誤和飽和度。對 LLM 系統而言,延遲指首 token 時間與總生成時間;流量指每個模型每秒的請求數;錯誤指失敗的 span(狀態碼 ERROR);飽和指 token 預算耗盡或佇列深度。訊號相同;單位不同。
Session 是 OpenTelemetry 規範的一部分嗎?
不是作為結構層級。OTel GenAI span 約定把 gen_ai.conversation.id 定義為條件式要求的屬性(「當可用時」),用來關聯一場對話或執行緒中的訊息。它位於 span 上。像 Langfuse 和 LangSmith 這樣的廠商在它之上建出自己的 session 或 thread 物件。
Trace ID、span ID 和關聯 ID 之間有什麼差別?
Trace ID 識別一次請求,並自動傳播到所有下游服務。Span ID 識別該 trace 內的一項操作。關聯 ID(或請求 ID)由你的 web 層在追蹤開始之前產生,是最常被人誤認為 trace ID 的那個值。它們在範圍上重疊,但來源不同。
一個 trace 應該有多少 span?
沒有固定答案,但典型範圍是 RAG 請求 3–30,帶有多次工具呼叫的 agent 迴圈 10–50 以上。經驗法則:對外部呼叫和決策點設 span,不對處理序內轉換設。如果你的 trace 超過 100 個 span,你大概檢測過頭了。
Langfuse 的「observation」和 span 是同一個東西嗎?
是。Langfuse 的 observation 與 OTel 的 span 是同一個物件:一項帶有屬性的計時操作。Langfuse 把 observation 分成三種類型(generation、span、event),而 OTel 使用 gen_ai.operation.name。如果你正在評估會讀取你 trace 的工具,我們的 LLM 評估工具總覽 涵蓋了哪些同時接受兩種詞彙。
如何把一場多輪聊天機器人對話歸進一個 session?
在每一輪的 root span 上設定相同的 conversation 識別碼。用 OTel 的術語,那就是 gen_ai.conversation.id。在 Langfuse 中,你在建立 trace 時傳入 session_id。在 LangSmith 中,你設定 session_id 或 thread_id 中繼資料。漏掉一輪,那一輪就掉出分組。
如果我只處理單輪請求,我需要 session 嗎?
大概不需要。Session 的存在是為了把多個 trace 關聯進一場對話。如果每個請求都是獨立的(一個分類 API、一個一次性摘要器),trace 層級的指標就夠了。當你需要跨輪指標時再加 session:解決率、抵達答案的輪數,或對話層級的成本。我們的 LLM 評估指南 涵蓋了 session 層級評估何時值得做。
精簡版
Span 嵌套在 trace 之內;trace 群組成 session。嵌套是真的,但規範只把三個層級中的兩個結構化。gen_ai.conversation.id 是你自己設定的屬性,不是會自動傳播的 parent span。而你今天選的廠商對這些物件的命名,會和你 18 個月後換的那家不同,所以讓你的 span 詞彙保持小而可攜。