从 OpenTelemetry 到 Langfuse:企业级 Agent 可观测性实践
发布时间:2026/10/1 21:23:13来源:尧图网络
1.1 OTel 和 Langfuse 的关系一句话概括OTel 负责「怎么标准化记录」Langfuse 负责「怎么把记录理解成 Agent 能分析的样子」。OpenTelemetry → Agent 执行记录成通用 Trace / SpanLangfuse → 在它的基础上补 Agent 语义并做分析而且 Langfuse 本身就是基于 OTel 实现的。1.2 Langfuse 的定位Langfuse 是一个面向 LLM / Agent 应用的可观测性与分析平台。它把 OTel 的通用 span 转换成带 Agent 语义的 observationLLM 调用、工具调用、Agent 决策并在 trace 上挂上 session、user、score 这些分析维度。回到一次具体的 Agent 执行。OTel 能告诉你哪一步慢、哪一步报错、调用层级是什么。但对一个 LLM / Agent 应用真正想知道的往往是另一批信息第一次 LLM 的输入是什么、输出是什么为什么决定调用这个 ToolTool 的参数和返回分别是什么每一步用了多少 Token、花了多少钱这次会话整体效果怎么样、属于哪个用户OTel 的 span 知道「这是一次调用、耗时 50ms」但不知道「这是一次 LLM 调用用了 n 个 token成本 m 」。这些字段属于 Agent 语义要么自己定义要么交给上层平台。Langfuse 的核心作用就是把这层补齐OTel 原生能直接接收 OTel 的 OTLP 数据已有的埋点可以复用Agent 视角开箱即用generation / tool / agent 分类型展示token、成本、会话面板自动渲染开源可自建支持私有化部署数据不出内网不止 tracing还涵盖评估Evaluation、Prompt 管理、数据集与实验。二、核心原理2.1 LangfuseLangfuse 不是另一套 Trace 系统底层核心还是基于 OpenTelemetry 实现的。具体表现为三点一次请求在 Langfuse 里的 Trace ID和 OTel 的trace_id是同一个值Langfuse 里的一个Observation就是 OTel 的一个 span 在 Langfuse 侧的表示Langfuse 还能直接作为 OTel 后端使用——把 exporter 指向它的 OTLP 端点原生 OTel 的 span 也能进 Langfuse。2.2 Observation TypeOTel 的 span 只有通用类型Langfuse 给它补上了类型标签也就是as_typespan 通用步骤非 LLM 操作generation LLM 调用额外带 model / token / costtool 工具调用带输入输出agent 能自主决策、调用工具的执行单元chain 多个步骤之间的衔接embedding 生成向量guardrail 内容防护、越狱检测generation是其中最关键的一类。它不是「多了一种 span」而是在 span 上固定了一组 LLM 专属字段model 用的哪个模型input / output 发给模型的和模型返回的usage_details 输入 / 输出 token 数cost_details 本次调用的成本有了这组字段token 统计、成本计算、模型对比这些Langfuse 的 Dashboard 能自动渲染。2.3 Trace 级维度Agent 的效果往往要跨多次请求看所以 Langfuse 在 trace 上挂了几个聚合维度session_id 把同一个会话的多轮请求归到一起user_id 关联到具体用户tags 自定义分类标签score 人工或自动评估的结果前面的 Observation Type 解决「单次执行看到了什么」这几个维度解决「多次执行放在一起怎么比较」。2.4 数据结构把同一个 Agent 的数据分别送进 Jaeger 和 Langfuse形状是一样的都是树状能回答的问题不同。Jaeger 面向系统哪里慢、哪里报错、调用链是什么、服务之间怎么调用Langfuse 面向 Agent模型到底说了什么、为什么调用这个 Tool、Tool 返回了什么用了多少 Token、这次请求花了多少钱不同 Model / Prompt 的效果对比、某个 Session 的整体体验两者的关注点可以概括成OpenTelemetry → 系统发生了什么Langfuse → Agent 做了什么、效果怎么样三、编码实现下面用一个真实的LLM → Tool → LLM链路对比不用单次函数调用因为多步结构才能体现差异。3.1 Agent 本身原生没有可观测性的 Agentclass ProductAgent: name product-agent def __init__(self, llm, search_tool): self.llm llm self.search_tool search_tool def run(self, query): # 第一次 LLM理解需求决定是否调用 Tool response self.llm.chat( messages[ {role: system, content: 你是电商商品助手需要时调用商品搜索工具。}, {role: user, content: query}, ], tools[self.search_tool], ) if response.tool_calls: tool_call response.tool_calls[0] # 执行 Tool products self.search_tool(**tool_call.arguments) # 第二次 LLM根据 Tool 结果生成最终回答 return self.llm.chat( messages[ {role: user, content: query}, {role: assistant, tool_calls: [tool_call]}, {role: tool, content: products}, ] ) return response执行链路User → LLM → Tool → LLM → Answer3.2 原生 OpenTelemetryOTel 只提供通用 span所以 LLM、Tool 的语义和字段都要自己定义with tracer.start_as_current_span(product-agent): with tracer.start_as_current_span(llm) as span: response llm.chat(...) span.set_attribute(gen_ai.request.model, claude-haiku-4-5) span.set_attribute(gen_ai.usage.input_tokens, response.usage.input_tokens) span.set_attribute(output.value, response.content) with tracer.start_as_current_span(search-product) as span: products search_tool(**tool_call.arguments) span.set_attribute(tool.name, search_product) span.set_attribute(tool.arguments, str(tool_call.arguments)) span.set_attribute(output.value, str(products)) with tracer.start_as_current_span(llm) as span: final_response llm.chat(...) span.set_attribute(output.value, final_response.content)得到的树product-agent├── llm├── search-product└── llm结构没问题耗时和父子关系都能看。但有一批东西需要应用自己建设这是 Agent 还是普通 span这是 LLM 调用还是普通 spanToken 怎么统一统计Cost 怎么算Session、User 怎么关联Evaluation 挂在哪最终这些字段怎么渲染成面板OTel 提供的是最标准和最基础能力这些 Agent 专项能力需要自己编码实现。3.3 Langfuse同一个 Agent用 Langfuse SDK 写from langfuse import get_clientlangfuse get_client()with langfuse.start_as_current_observation( as_typeagent, nameproduct-agent): with langfuse.start_as_current_observation( as_typegeneration, nameunderstand-query, modelclaude-haiku-4-5 ) as generation: response llm.chat(...) generation.update( inputquery, outputresponse.content, usage_details{ input: response.usage.input_tokens, output: response.usage.output_tokens, }, ) with langfuse.start_as_current_observation( as_typetool, namesearch-product ) as tool: products search_tool(**tool_call.arguments) tool.update(inputtool_call.arguments, outputproducts) with langfuse.start_as_current_observation( as_typegeneration, namegenerate-answer, modelclaude-haiku-4-5 ) as generation: final_response llm.chat(...) generation.update( inputproducts, outputfinal_response.content, usage_details{ input: final_response.usage.input_tokens, output: final_response.usage.output_tokens, }, )代码结构和 OTel 几乎一样仍然是「开 observation → 做事 → 更新字段」。差别在于as_type直接声明这是agent/generation/tool平台能识别并按类型渲染generation自带model、usage_detailstoken 和成本自动算input/output是固定字段模型到底说了什么一眼可见。如果还想把多轮会话和用户关联起来加一层 trace 级属性即可from langfuse import propagate_attributeswith propagate_attributes(user_idu_123, session_ids_abc): agent.run(query)同一个会话的多轮请求会自动归到s_abc下面板上就能按 session 看整体效果。四、实践落地4.1 痛点问题单个 Agent 直接写with langfuse...没问题。但企业里业务场景一般都需要 Multi-Agent 架构MasterAgent├── ProductAgent├── OrderAgent├── RecommendAgent├── QAAgent└── AfterSaleAgent如果每个 Agent 都手写 Langfuse业务代码会变得繁琐冗余业务逻辑 Langfuse 异常处理 Trace 管理 Metrics Logging一旦要改字段规范、换后端、加采样就要修改每个 Agent 代码。4.2 定义 BaseAgent 基类第三章的埋点直接写在执行流程里业务和可观测性混在一起。第一反应是抽一个基类把 agent 级的 observation 提到父类class BaseAgent: name base-agent def run(self, query): with langfuse.start_as_current_observation( as_typeagent, nameself.name ): return self.execute(query)class ProductAgent(BaseAgent): name product-agent def __init__(self, llm, search_tool): self.llm llm self.search_tool search_tool def execute(self, query): # 第一次 LLM理解需求决定是否调用 Tool response self.llm.chat( messages[ {role: system, content: 你是电商商品助手需要时调用商品搜索工具。}, {role: user, content: query}, ], tools[self.search_tool], ) if response.tool_calls: tool_call response.tool_calls[0] # 执行 Tool products self.search_tool(**tool_call.arguments) # 第二次 LLM根据 Tool 结果生成最终回答 return self.llm.chat( messages[ {role: user, content: query}, {role: assistant, tool_calls: [tool_call]}, {role: tool, content: products}, ] ) return response业务 Agent 里已经看不到 langfuse剩下的全是业务。agent 级的 observation 由基类统一开内部的 generation / tool observation 由 LLM client 和工具包装统一产生——包装一次所有 Agent 共用。但基类的问题也很直接。它只管住了 agent 这一层其他横切能力如果也往基类里塞BaseAgent├── Langfuse├── Logging├── Retry├── Timeout├── Metrics├── Guardrail└── Cache一个 Agent 想加只属于自己的能力就得改基类影响所有子类。4.3 Middleware Hook更合适的做法是不靠继承用一个 Runner 在运行时给 Agent 包一层 MiddlewareAgent 只负责业务Middleware 负责横切能力。ProductAgent 不再继承 BaseAgentclass ProductAgent: name product-agent def __init__(self, llm, search_tool): self.llm llm self.search_tool search_tool def run(self, query): response self.llm.chat( messages[ {role: system, content: 你是电商商品助手需要时调用商品搜索工具。}, {role: user, content: query}, ], tools[self.search_tool], ) if response.tool_calls: tool_call response.tool_calls[0] products self.search_tool(**tool_call.arguments) return self.llm.chat( messages[ {role: user, content: query}, {role: assistant, tool_calls: [tool_call]}, {role: tool, content: products}, ] ) return responseMiddleware 只做一件事包一层 observationclass LangfuseMiddleware: def __init__(self, langfuse): self.langfuse langfuse def __call__(self, agent, query, next): with self.langfuse.start_as_current_observation( as_typeagent, nameagent.name ): return next()Runner 在运行时把中间件串起来runner AgentRunner(middlewares[LangfuseMiddleware(langfuse)])runner.run(ProductAgent(llm, search_tool), query)执行过程以后新增一个 OrderAgent只要写好run、接进 Runner 就自动带上可观测性不用继承任何基类也不用写一行可观测性代码。Hook 是同一个思路的另一种写法在 Agent 生命周期的关键节点回调class LangfuseHook: def before_run(self, agent, query): ... def after_run(self, agent, query, result): ... def on_error(self, agent, query, error): ... plaintext Hook → 偏生命周期事件监听Middleware → 偏执行链包装与控制只做 Trace、Logging、Metrics两者都够用。如果要把 Tracing、Retry、Timeout、Guardrail、Fallback、Cache 统一承载Middleware 更适合作为 Agent Runtime 的统一扩展机制。4.4 最终形态整个方案从「给一个 Agent 加埋点」演进成「Runtime 自带可观测性」到这一步Langfuse 不再只是某个 Agent 的监控工具而是整个 Agent Runtime 的一项基础能力。五、总结把 OTel 和 Langfuse 放在一起是一条连续的路OpenTelemetry给 Agent 建可观测性底座Langfuse让这套底座真正服务于 LLM / Agent落到 Multi-Agent 生产环境接入方式的演进是手动埋点 → BaseAgent → Middleware / Hook → Agent Runtime核心结论只有一句从单个 Agent 到几十个 Agent业务代码里不应该到处出现with langfuse...可观测性应该是 Agent Runtime 的基础能力而不是每个 Agent 自己负责的一段业务代码。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
网站建设高端定制企业官网