性基石:OpenTelemetry 學(xué)習(xí)與實(shí)踐)
一、背景單次 LLM 調(diào)用這種形態(tài)下鏈路線性日志即全景打一行日志就記錄「調(diào)用了什么模型、花了多久、返回了什么」就結(jié)束了用戶提問 ──→ 調(diào)用一次模型 ──→ 返回答案而 Agent 具備多輪推理、工具嵌套及強(qiáng)因果依賴等特征執(zhí)行過程呈樹狀拓?fù)淝液臅r(shí)跨度大扁平日志無法承載層級(jí)耗時(shí)統(tǒng)計(jì)與因果依賴關(guān)系的表達(dá)。從 1 次調(diào)用變成幾十次日志行數(shù)變多人眼看不過來從毫秒變成分鐘耗時(shí)本身成了需要定位的問題從線性變成有因果第 3 輪依賴第 2 輪的工具結(jié)果平鋪日志表達(dá)不了這種關(guān)系。日志記錄的是「發(fā)生了什么」而 Agent 的執(zhí)行還有一層結(jié)構(gòu)做了什么、按什么順序做、時(shí)間花在哪一步。OpenTelemetry簡(jiǎn)稱 OTel它用一套標(biāo)準(zhǔn)的數(shù)據(jù)模型把一次執(zhí)行記錄成一棵有層級(jí)、有因果、帶耗時(shí)的樹Trace / Span并統(tǒng)一描述、采集和傳輸。Agent 的執(zhí)行結(jié)構(gòu)本身就是一棵樹Agent 關(guān)心的「層級(jí) 因果 耗時(shí)」正好是 span 樹的三個(gè)能力有父子層級(jí)、有調(diào)用因果、有起止耗時(shí)。二、核心概念2.1 OTel 的定位與核心數(shù)據(jù)OTel 有點(diǎn)像 HTTP 協(xié)議HTTP 定義了請(qǐng)求/響應(yīng)長(zhǎng)什么樣、怎么傳但它不是瀏覽器、也不是服務(wù)器OTel 定義了運(yùn)行數(shù)據(jù)長(zhǎng)什么樣、怎么傳。OTel 記錄的最小單元叫span{ name: claude, trace_id: 4bf92f3577b34da6a3ce929d0e0e4736, span_id: 00f067aa0ba902b7, parent_id: 1c987a38b1408550, start_time: 2026-09-21T03:15:55.631Z, end_time: 2026-09-21T03:15:56.736Z, attributes: { gen_ai.request.model: claude }, events: [ { name: first-token } ], status: UNSET, kind: CLIENT }name這個(gè) span 在干什么顯示在瀑布圖上trace_id屬于哪一次完整調(diào)用整棵樹共享span_id自身編號(hào)parent_id父 span 的編號(hào)start_time / end_time時(shí)間區(qū)間相減就是耗時(shí)attributes附加的結(jié)構(gòu)化信息events瞬時(shí)事件statusUNSET / OK / ERRORkindCLIENT / SERVER 等后端靠它畫拓?fù)鋱D。兩個(gè)關(guān)鍵點(diǎn)span 是時(shí)間區(qū)間不是時(shí)間點(diǎn)。 這是它和日志最根本的區(qū)別。parent_id 是拼出一棵樹的唯一依據(jù)。 trace 一次完整調(diào)用產(chǎn)生的所有 span 拼成的樹trace_id表示屬于哪棵樹把散落的 span 歸攏成一個(gè) tracespan_id標(biāo)識(shí)自身parent_span_id指向父節(jié)點(diǎn)是拼樹的唯一依據(jù)。后端只做兩件事按 trace_id 分組 → 按 parent_id 建樹判斷根節(jié)點(diǎn)很簡(jiǎn)單看它有沒有父節(jié)點(diǎn)沒有父節(jié)點(diǎn)的就是根。2.2 上下關(guān)系確定在同一個(gè)程序里OTel 會(huì)自動(dòng)把新 span 接到當(dāng)前的父節(jié)點(diǎn)上with tracer.start_as_current_span(turn-0): # 當(dāng)前是 turn-0 with tracer.start_as_current_span(llm): # llm 自動(dòng)成為 turn-0 的子節(jié)點(diǎn) pass跨進(jìn)程就不一樣兩個(gè)程序互相看不見各記各的。比如 Agent 要查訂單它自己查不了得讓訂單服務(wù)去查。在 OTel 里這兩個(gè)值叫trace_id和span_id通常放在 HTTP 請(qǐng)求頭里SDK 會(huì)自動(dòng)帶上和讀取??邕M(jìn)程時(shí)如果沒有這個(gè)傳遞兩邊的記錄就永遠(yuǎn)對(duì)不上只會(huì)得到兩棵互不相干的樹。2.3 三大信號(hào)Trace、Metric、LogOTel 只定義三類數(shù)據(jù)叫信號(hào)它們通過trace_id關(guān)聯(lián)形成從粗到細(xì)的下鉆鏈Trace 和 Metric 的成本差異很直接100 萬個(gè)請(qǐng)求不可能每個(gè)都存一棵完整的樹metrics 只存聚合數(shù)字100 萬個(gè)請(qǐng)求也只是幾個(gè)數(shù)。2.4 組件與鏈路從 SDK 到 CollectorOTel 分兩個(gè)包opentelemetry-api只有接口opentelemetry-sdk給應(yīng)用依賴。如果 openai 庫內(nèi)部埋點(diǎn)、依賴的是api應(yīng)用裝了 SDK埋點(diǎn)生效沒裝API 調(diào)用變成空操作什么都不發(fā)生也不報(bào)錯(cuò)。這樣庫可以零成本內(nèi)置埋點(diǎn)。五個(gè)核心組件對(duì)應(yīng)代碼resource Resource.create({service.name: my-agent}) # ← Resource provider TracerProvider(resourceresource) # ← Provider provider.add_span_processor(BatchSpanProcessor(exporter)) # ← Processor Exporter trace.set_tracer_provider(provider) tracer trace.get_tracer(__name__) # ← TracerOTLP 和 Collector。OTLP 是傳輸協(xié)議。Collector 是獨(dú)立進(jìn)程生產(chǎn)環(huán)境常用它做四件事接收統(tǒng)一入口、處理采樣、脫敏、富化、路由一份數(shù)據(jù)發(fā)多個(gè)后端、緩沖。改策略不用改應(yīng)用——把采樣率從 10% 調(diào)到 5%改 Collector 配置即可不用重新部署 Agent。完整架構(gòu)三、解決的問題3.1 日志與 Trace 的分工出錯(cuò)看堆棧、看某個(gè)變量的值 → 日志看某一步花了多久、時(shí)間花在哪一步 → trace在幾十條運(yùn)行里找最慢的、統(tǒng)計(jì)工具失敗率、對(duì)比兩個(gè)模型 → trace。日志回答「發(fā)生了什么」trace 回答「為什么慢、誰拖累了誰」。兩者都真實(shí)存在不是二選一。OTel 不取代現(xiàn)有日志。掛一個(gè) handler業(yè)務(wù)代碼不用改日志同時(shí)進(jìn)兩個(gè)地方OTel 那份自動(dòng)帶上trace_id一條帶trace_id的日志把散落的文本變成樹上的一個(gè)錨點(diǎn)可以從日志直接跳到對(duì)應(yīng)的 trace。3.2 從日志到執(zhí)行樹同樣是記錄一次運(yùn)行日志是平鋪文本trace 是有層級(jí)的樹日志記錄發(fā)生了什么但很難表達(dá)這些事情之間的關(guān)系哪個(gè) LLM 調(diào)用屬于哪一輪、哪個(gè) Tool 是哪次決策觸發(fā)的、整個(gè) Agent 在哪里耗時(shí)。一個(gè)排障例子① Metric 發(fā)現(xiàn)異常P99 從 80ms 漲到 210ms慢在模型調(diào)用 ② Trace 定位到具體會(huì)話turn-1 里 tool:query_order 被調(diào)了 3 次每次都 1.5s ③ Log 找到根因從 trace_id 一鍵跳過來 ERROR query_order 超時(shí)第 3 次放棄 → 不是模型慢是訂單服務(wù)接口超時(shí)模型被迫反復(fù)重試3.3 Attributes記錄屬性信息span 自帶的信息只有四樣name → 這是什么操作只能有 1 個(gè)值 start / end → 什么時(shí)候、多久 parent_id → 誰在誰里面 attributes → 其余一切name必須低基數(shù)它是聚合主鍵只能承擔(dān)分類。實(shí)際要記錄的還包括模型、token 數(shù)、輪次、輸入輸出、失敗原因這些放在 attributes 里。同一棵 span 樹跑兩遍A 不 set 屬性、B set 屬性結(jié)構(gòu)、名字、耗時(shí)完全一樣差別在能回答的問題哪一步最慢兩者都能答這是唯一只靠時(shí)間就能答的token花費(fèi)、上下文第幾輪開始漲、哪個(gè)工具最容易失敗只有 B 能答。只看 span 結(jié)構(gòu)能分析的只有耗時(shí)要分析業(yè)務(wù)就得靠 attributes。屬性之間有依賴位置也有要求要畫 token 增長(zhǎng)曲線turn.index和gen_ai.usage.input_tokens必須同時(shí)存在少一個(gè)就只剩三個(gè)孤立數(shù)字排不出順序?qū)傩远加浟说珤戾e(cuò) span分析也用不了。Trace 決定能不能還原過程Attributes 決定能不能分析過程。四、編碼與實(shí)測(cè)4.1 字段規(guī)范核心是用行業(yè)約定的名字后端靠字段名自動(dòng)渲染面板——用model_name而不是gen_ai.request.model后端的面板、成本計(jì)算、LLM 視圖都會(huì)失效。LLM / Agent 場(chǎng)景已有標(biāo)準(zhǔn)OTel 語義約定模型 spangen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.server.time_to_first_token工具 spantool.name、tool.arguments輸入輸出input.value / output.value自定義業(yè)務(wù)字段加自己的前綴比如cs.intent、cs.prompt_version避免和規(guī)范撞名。是 span 還是 attribute用兩步判斷第一步這一步有自己的耗時(shí)嗎 是 → 開 span 第二步這是某一步的產(chǎn)出/結(jié)果嗎 是 → 掛 attribute有耗時(shí)的步驟用 span步驟的產(chǎn)出用 attribute。比如智能客服前置三步——意圖識(shí)別、query 改寫、槽位抽取——各自都耗時(shí)所以各是一個(gè) span各自的產(chǎn)出意圖、槽位是 attributewith tracer.start_as_current_span(cs:intent) as s: intent classify_intent(text) # ← 耗時(shí)發(fā)生在這一步 s.set_attribute(cs.intent, intent.name) # ← 產(chǎn)出掛屬性如果壓成一個(gè) span 三個(gè)屬性就丟掉了「哪一步最慢」而調(diào)優(yōu)時(shí)需要這個(gè)信息。4.2 完整示例一個(gè)智能客服 Agent初始化一個(gè)程序只做一次from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter resource Resource.create({service.name: my-agent-runner}) provider TracerProvider(resourceresource) provider.add_span_processor( BatchSpanProcessor(OTLPSpanExporter(endpointhttp://localhost:4318/v1/traces)) ) trace.set_tracer_provider(provider) # ← 全進(jìn)程只能調(diào)一次 tracer trace.get_tracer(__name__)把前面的規(guī)則串起來規(guī)范字段和業(yè)務(wù)自定義字段混用def handle_session(user_text: str) - str: # 根 span一次會(huì)話的邊界。業(yè)務(wù)屬性走自己的 cs. 命名空間 with tracer.start_as_current_span(agent:customer-service) as agent: agent.set_attribute(openinference.span.kind, AGENT) agent.set_attribute(cs.tenant_id, t-001) agent.set_attribute(cs.prompt_version, v3.2) # user.id / session.id 是高基數(shù) → 放進(jìn)日志不塞屬性 # 前置 pipeline每一步都有耗時(shí) → 各自一個(gè) span產(chǎn)出掛屬性 with tracer.start_as_current_span(cs:intent) as s: intent classify_intent(user_text) s.set_attribute(cs.intent, intent.name) with tracer.start_as_current_span(cs:slot_filling) as s: slots fill_slots(user_text) s.set_attribute(cs.slots, json.dumps(slots)) # 多輪生成規(guī)范字段掛在模型 span 上 for i in range(MAX_TURNS): with tracer.start_as_current_span(fturn-{i}) as turn: turn.set_attribute(turn.index, i) with tracer.start_as_current_span(claude) as llm: reply call_model(user_text, slots) llm.set_attribute(gen_ai.request.model, claude) llm.set_attribute(gen_ai.usage.input_tokens, reply.usage.input) llm.set_attribute(gen_ai.server.time_to_first_token, reply.ttft_ms) llm.set_attribute(llm.cost.usd, reply.cost_usd) llm.set_attribute(output.value, reply.text[:10240]) # 截?cái)?for call in reply.tool_calls: with tracer.start_as_current_span(ftool:{call.name}) as tool: tool.set_attribute(tool.name, call.name) tool.set_attribute(status, ok if execute(call).ok else error)生成的樹每個(gè)字段對(duì)應(yīng)一個(gè)具體問題這次會(huì)話花了多少tokengen_ai.usage.* llm.cost.usdtoken 花在哪一輪上面 turn.index必須同層前置三步誰最慢三個(gè) cs:* span 的時(shí)長(zhǎng)本身用戶意圖分布cs.intent哪個(gè)工具最容易掛tool.name statuswith tracer.start_as_current_span(turn-0) as turn:這行和with open()結(jié)構(gòu)一致括號(hào)里的字符串 → 是名字進(jìn)后端別人看 → 有約束低基數(shù) as 后面的變量 → 是把手只在代碼里用 → 隨便取零語義 with → 退出時(shí)自動(dòng)結(jié)束 span三個(gè)細(xì)節(jié)with 就是 span 的開始和結(jié)束。不需要自己調(diào) end()也不要漏掉結(jié)束——不 end() 的 span 永遠(yuǎn)不會(huì)被導(dǎo)出而且不報(bào)錯(cuò)。as 后面的名字隨便取它只是個(gè) Python 局部變量括號(hào)里的字符串才是 span 的名字。樹長(zhǎng)什么樣完全由代碼縮進(jìn)決定。4.3 三信號(hào)協(xié)同Metric 和 Log 怎么配合真實(shí)系統(tǒng)里三條信號(hào)一起用同一個(gè)事件寫三遍各寫各的。tracer trace.get_tracer(__name__) meter metrics.get_meter(__name__) logger logging.getLogger(__name__) # instruments模塊級(jí)各建一次別放在函數(shù)里 tokens meter.create_counter(gen_ai.client.token.usage, unit{token}) latency meter.create_histogram(gen_ai.client.operation.duration, units) with tracer.start_as_current_span(agent:customer-service) as agent: logger.info(會(huì)話開始) # ③ log —— 自動(dòng)帶上 trace_id with tracer.start_as_current_span(MODEL) as span: t0 time.monotonic() reply call_model(user_text) dt time.monotonic() - t0 span.set_attribute(gen_ai.usage.input_tokens, reply.usage.input) # ② metric —— 同一個(gè)事件換個(gè)記法從這一次變成一個(gè)數(shù)字 lbl {gen_ai.request.model: MODEL} tokens.add(reply.usage.input reply.usage.output, lbl) latency.record(dt, lbl)跑完之后手上同時(shí)有三樣?xùn)|西靠trace_id縫合trace是一棵樹metric是幾條數(shù)字log是幾行帶trace_id的文本。4.4 實(shí)測(cè)一次 Agent Trial鏈路是完整的SDK 埋點(diǎn) → OTLP/HTTP → Jaeger 進(jìn)程 → Jaeger 的 HTTP API → 取回來出圖。中間隔著一個(gè)真實(shí)的后端進(jìn)程驗(yàn)證的是端到端鏈路不是內(nèi)存里的對(duì)象。結(jié)果service.name agent-eval14 個(gè) span端到端771.8ms。其中 LLM 調(diào)用總共 120.1ms占比約 15.6%這次實(shí)驗(yàn)里 LLM 不是主要耗時(shí)來源。沒有 Trace容易先懷疑模型有 Trace 可以先確認(rèn)時(shí)間花在哪。4.5 能回答的問題與模型對(duì)比常用查詢模式都是按某個(gè)字段聚合哪一步最慢 → 按 span name 聚合耗時(shí)排序哪個(gè)工具最容易失敗 → 按 tool.name 分組統(tǒng)計(jì) status ERRORtoken 花在哪 → 按 turn.index 排開 input_tokens 看增長(zhǎng)曲線。對(duì)比對(duì)評(píng)測(cè)場(chǎng)景尤其有用最終分?jǐn)?shù) 輪數(shù) 總 token 工具調(diào)用 模型 A 0.85 3 2.1k 2 模型 B 0.85 12 11.4k 9 看起來一樣 差 4 倍 差 5 倍 差 4.5 倍分?jǐn)?shù)一樣成本差 5 倍這個(gè)差異日志里看不出來span 樹里能直接看到。形狀本身也是信息4.6 后端選型與落地節(jié)奏后端選型Jaeger入門、看瀑布圖開箱即用適合起步MLflow適合實(shí)驗(yàn)對(duì)比Langfuse是 LLM 專用面板字段約定不同要驗(yàn)證渲染效果自建 Collector 存儲(chǔ)等量上來再說。OTel 的失敗大多是「數(shù)據(jù)沒到」而不是「程序崩了」需要先有一個(gè)能看數(shù)據(jù)的地方否則問題出在哪都不好判斷。分階段落地① 先讓一條軌跡能看 ← 最重要成本低② 再批量導(dǎo)入歷史數(shù)據(jù)③ 再定字段規(guī)范④ 最后建對(duì)比工作流、上生產(chǎn)加 Collector、配采樣、接多后端OTel 的價(jià)值在第一次跑的時(shí)候不明顯只看到一棵樹會(huì)像「好看點(diǎn)的日志」。價(jià)值出現(xiàn)在有第二個(gè)版本可以對(duì)比的時(shí)候。所以先跑通、先有數(shù)據(jù)字段可以之后再補(bǔ)。五、總結(jié)OpenTelemetry 把一次 Agent 執(zhí)行記錄成一棵有層級(jí)、有因果、帶屬性的樹Trace / Span 還原「做了什么、什么順序、花了多久」Attributes 記錄「用的什么、token花了多少、為什么失敗」Metric / Log 分別回答「整體怎么樣」和「那一瞬發(fā)生了什么」靠 trace_id 串起來。一次 771.8ms 的 Agent TrialLLM 只占 120.1ms15.6%慢的不一定是模型。當(dāng) Agent 從「調(diào)用一個(gè)模型」變成「自主完成一項(xiàng)任務(wù)」理解它怎么跑就成了可觀測(cè)性的核心問題。學(xué)AI大模型的正確順序千萬不要搞錯(cuò)了2026年AI風(fēng)口已來各行各業(yè)的AI滲透肉眼可見超多公司要么轉(zhuǎn)型做AI相關(guān)產(chǎn)品要么高薪挖AI技術(shù)人才機(jī)遇直接擺在眼前有往AI方向發(fā)展或者本身有后端編程基礎(chǔ)的朋友直接沖AI大模型應(yīng)用開發(fā)轉(zhuǎn)崗超合適就算暫時(shí)不打算轉(zhuǎn)崗了解大模型、RAG、Prompt、Agent這些熱門概念能上手做簡(jiǎn)單項(xiàng)目也絕對(duì)是求職加分王給大家整理了超全最新的AI大模型應(yīng)用開發(fā)學(xué)習(xí)清單和資料手把手幫你快速入門學(xué)習(xí)路線:?大模型基礎(chǔ)認(rèn)知—大模型核心原理、發(fā)展歷程、主流模型GPT、文心一言等特點(diǎn)解析?核心技術(shù)模塊—RAG檢索增強(qiáng)生成、Prompt工程實(shí)戰(zhàn)、Agent智能體開發(fā)邏輯?開發(fā)基礎(chǔ)能力—Python進(jìn)階、API接口調(diào)用、大模型開發(fā)框架LangChain等實(shí)操?應(yīng)用場(chǎng)景開發(fā)—智能問答系統(tǒng)、企業(yè)知識(shí)庫、AIGC內(nèi)容生成工具、行業(yè)定制化大模型應(yīng)用?項(xiàng)目落地流程—需求拆解、技術(shù)選型、模型調(diào)優(yōu)、測(cè)試上線、運(yùn)維迭代?面試求職沖刺—崗位JD解析、簡(jiǎn)歷AI項(xiàng)目包裝、高頻面試題匯總、模擬面經(jīng)以上6大模塊看似清晰好上手實(shí)則每個(gè)部分都有扎實(shí)的核心內(nèi)容需要吃透我把大模型的學(xué)習(xí)全流程已經(jīng)整理好了抓住AI時(shí)代風(fēng)口輕松解鎖職業(yè)新可能希望大家都能把握機(jī)遇實(shí)現(xiàn)薪資/職業(yè)躍遷這份完整版的大模型 AI 學(xué)習(xí)資料已經(jīng)上傳CSDN朋友們?nèi)绻枰梢晕⑿艗呙柘路紺SDN官方認(rèn)證二維碼免費(fèi)領(lǐng)取【保證100%免費(fèi)】