大模型应用可观测性与评测闭环实践:Langfuse+LangSmith+OTel落地指南

发布时间:2026/10/17 13:49:51
大模型应用可观测性与评测闭环实践:Langfuse+LangSmith+OTel落地指南 做LLM应用落地一年多我最大的感受是大模型应用的故障和传统软件根本不是一回事。服务没崩、接口200、延迟正常但回答就是错的、偏的、带着幻觉的——这种问题日志里看不到监控面板上也不会有告警。传统APM那套能告诉你系统活着但完全无法告诉你模型答得对不对。所以当我决定推进多轮对话产品的深度整改时第一件事就是搭一套可观测与评测闭环。标题里的24.8是我们这次落地实践的内部版本代号代表2024年8月这个周期内完成的一整套方案Langfuse承担运行时链路追踪和数据集沉淀LangSmith承担离线评测与回归中间用OpenTelemetryOTel统一埋点协议最后把Prompt A/B评测跑成常态化机制。这篇文章会把整个落地过程、选型逻辑、踩过的坑以及最终沉淀的流程完整写出来给正在做同类事情的人一份可直接参考的实践记录。1. 为什么大模型应用需要可观测评测闭环1.1 传统监控覆盖不了的盲区先讲一个我们线上真实遇到的例子。某个客服问答模块用户投诉机器人乱说话。点开服务端指标一看CPU正常内存正常请求成功率99.9%P95延迟800毫秒传统监控里全是绿灯。但翻具体会话记录就发现模型在某个多轮上下文里把用户取消了订单理解成了用户要求取消订单接着在这个错误前提下答了一整段。要不是用户投诉这个问题在监控里根本不会暴露。这就是大模型应用的本质差异系统的正确性不在请求有没有返回而在返回内容的质量。而内容质量又受Prompt、模型版本、上下文长度、检索结果、工具调用结果等多重因素影响任何一个环节偏差都会导致最终回答变化。要定位这类问题必须有能力从完整链路中回放每次模型调用的输入输出——这是链路级别的可观测性而不只是指标级别的监控。具体来说大模型应用的可观测性至少要能回答这几个问题一次用户请求系统按什么顺序执行了哪些步骤RAG检索、工具调用、多轮追问分别产生了什么中间结果每个环节消耗了多少Token、花了多长时间、用的是哪个模型和哪个Prompt版本模型输出到底长什么样用户对结果有没有明确的点赞或点踩同一个Prompt在改版前后线上真实流量的表现数据有没有变化没有这些数据优化模型应用就像闭着眼睛开车只能靠用户骂声来感知方向。这也是我坚持先补可观测能力、再谈优化的原因。1.2 可观测与评测其实是同一件事的两个面很多团队把可观测和评测当成两件事分开做实际落地会发现它们必须绑在一起。可观测回答的是实际上发生了什么评测回答的是发生的结果好不好。只有可观测没有评测你能看到某个会话里模型答错了但不知道这是偶发个案还是系统性回归只有评测没有可观测你只知道离线得分下降了但不知道线上真实环境下是哪一步引入的偏差。闭环的含义就是让两边互相喂数据。在24.8实践里跑通的循环是线上链路通过Langfuse追踪每次会话和模型调用的完整轨迹把用户明确点踩或明显答非所问的场景自动沉淀到评测集里评测集在LangSmith上定期跑回归和A/B对比新Prompt跑赢了就发布上线上线后再通过可观测链路确认实际效果发现新问题又回流到评测集。每次线上异常都能变成下一次迭代的输入优化节奏从拍脑袋改Prompt变成靠数据持续收敛。这里要强调一个认知评测不是发布前的临时检查而是持续运行的机制。Prompt会漂移、模型会升级、用户问题分布会变化只有把评测固定在闭环里才能及时发现这些变化。1.3 24.8项目的目标与边界动手之前我先把这次落地的边界划清楚避免做成一个大杂烩。核心目标就三条能看到所有线上请求都能按会话维度回放完整链路包括Prompt版本、模型参数、中间检索结果和工具返回值。能评判核心业务场景有可复用的评测集和评测标准每次Prompt改动都能自动跑出对比数据。能决策A/B对比有明确的胜出门槛杜绝我觉得新版更好这种主观判断。边界也划得很明确不做在线强化学习不做多模型自动路由不做全自动Prompt寻优。我们只要一个人工决策但数据充分的闭环。这个边界很重要因为大模型评测领域还有很多未解问题一上来就追求全自动很容易陷入无底洞最后连基础链路都没打通。2. 三个工具怎么分工协作2.1 Langfuse运行时链路追踪的主阵地Langfuse是目前大模型可观测领域少有的开箱即用且数据模型契合的开源方案。我们对它的定位很纯粹运行时数据的存储和分析底座。它原生的Trace、Span、Generation三级数据结构正好对应一次对话、一个处理环节、一次模型调用不需要自己造轮子。它还内置了Token统计、成本估算和基于Trace的评分功能可以直接把用户点赞/点踩作为分数写回链路。部署上我建议如果团队有基本运维能力优先自托管。原因是实际业务里的会话数据涉及用户隐私和业务敏感信息外发到第三方平台要过合规评估自托管之后也方便把追踪数据和内部已有的日志系统打通。Langfuse自托管本身不复杂一份docker-compose配置就能拉起Postgres、ClickHouse、Redis等依赖单机内存16G左右的规格支撑我们日均几万次模型调用的规模绰绰有余。另外有一个实用建议自托管之后要配置数据保留策略。Trace数据增长很快尤其是把输入输出都存下来的情况下一个月就能攒出好几个G。我们是线上全量链路保留30天评测集相关的重要Trace保留180天其余按天定期清理。2.2 LangSmith离线评测与回归的试验场LangSmith对LangChain生态支持很深入但不少非LangChain项目不知道它也能独立使用。我们的用法是把它当评测实验室主要不是做链路追踪把评测集放到Dataset里跑一批任务生成预测结果再用自带的EvaluatorLLM-as-Judge、精确匹配、正则、自定义打分函数对结果评分最后生成对比报告。它和Langfuse的区别我用一句话概括Langfuse是手术室里的监护仪告诉你手术过程中病人各项体征怎么变化LangSmith是检验科告诉你样本化验结果到底合不合格。两者数据模型和服务目的不同也不冲突。LangSmith对离线评测有一个很大的优势——跑完评测之后官方SDK可以直接把预测结果和Evaluator分数关联到Dataset的example上天然支持多次回归的历史对比方便看版本演进趋势。2.3 OTel统一埋点的地基为什么要引入OTel而不是直接各用各家的SDK原因很简单我不想把可观测性和具体产品锁死。Langfuse和LangSmith都有自己的SDK如果业务代码里到处直接调用它们的API将来换平台或者引入新组件埋点代码全得重写。OTel的价值在于它是行业标准先用OTel规范把应用的所有遥测数据分布式链路、指标、日志采集出来再通过Collector把数据路由给不同后端。Langfuse本身支持OTLP接入可以接收标准链路数据LangSmith侧则通过Dataset和Runs的API对接评测任务。虽然OTel对GenAI领域的语义约定LLM语义约定规范还在演进但基础的Trace、Span、Attribute模型已经够用。我们自己封装了一层轻量的采集函数把Prompt版本、模型名称、Token用量、上下文信息挂在Span的Attribute上这些字段不管后端是谁都能识别只是各平台展示方式不同。2.4 各司其职的总体数据流用文字描述整体数据流用户请求进入应用后通过OTel SDK自动埋点应用内的HTTP入口、OpenAI调用、检索调用都会生成SpanSpan通过OTLP协议上报到本地OpenTelemetry Collector。Collector做一次数据清洗和采样后把链路数据转发给Langfuse的OTLP接口。Langfuse为每条会话生成对应Trace关联会话ID、用户ID和Prompt版本标签。离线评测走另一条路径从生产链路里筛出bad case和典型样本导入LangSmith的Dataset跑评测脚本时同一批样本分别用Prompt A和Prompt B生成预测结果评测结果写回Dataset并生成对比报告。报告结论达到阈值后把新Prompt版本同步到Langfuse的Prompt管理功能线上流量按比例切到新版本新版本的线上表现继续通过Langfuse追踪形成完整闭环。三个工具的角色用一张表总结工具主要承担职责我们用它解决什么问题Langfuse运行时链路追踪、成本统计、线上评分回答线上实际发生了什么LangSmith数据集管理、离线评测、回归对比回答改动之后效果到底好不好OTel统一埋点标准、数据采集与路由避免绑定单一厂商让数据可迁移这个组合乍看有点重但跑起来会发现每层都有明确边界也都能独立替换。后面每一节展开落地细节。3. 落地步骤从埋点到数据打通3.1 基础环境准备先交代环境。服务端统一是Python 3.11用FastAPI对外提供服务模型调用主要是OpenAI兼容接口和本地私有化部署的模型。落地前先把三组依赖装好pip install langfuse langsmith opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-fastapi opentelemetry-instrumentation-openai这里有一个容易踩的坑OTel的instrumentation包版本必须和SDK版本匹配。有一次我因为instrumentation版本比SDK新两个版本结果HTTP请求的Span全部丢失服务端日志还完全看不出来debug了一下午才发现是版本兼容问题。经验是所有opentelemetry相关包统一锁定版本号升级时一起升不要单独升级某一个。自托管Langfuse我用官方提供的docker-compose涉及Postgres、ClickHouse、Redis和几个服务端组件。机器不用太高配8核16G内存足够支撑中等规模流量。启动后配置环境变量LANGFUSE_PUBLIC_KEYxxx LANGFUSE_SECRET_KEYxxx LANGFUSE_HOSThttps://langfuse.internal.example.com这三个变量是Langfuse服务端对外的访问凭证一定要走环境变量管理别写死在代码里防止泄露。3.2 OTel埋点接入自动加手动组合OTel接入分两层。第一层是自动埋点借助instrumentation库FastAPI的HTTP入口、OpenAI客户端的调用都会自动生成Span不需要手写逻辑。第二层是手动埋点业务里的关键信息比如是否命中缓存检索到了哪几篇文章上下文被截断的长度自动埋点拿不到必须手动挂到当前Span上。from opentelemetry import trace tracer trace.get_tracer(rag.pipeline) def do_search(query, top_k): with tracer.start_as_current_span(retrieve) as span: # 实际检索逻辑 docs vector_db.search(query, top_ktop_k) span.set_attribute(rag.top_k, top_k) span.set_attribute(rag.hit_count, len(docs)) span.set_attribute(rag.query_hash, hash(query)) return docs这里有个非常关键的点手动埋点一定要把输入输出摘要挂上去不能只挂耗时指标。很多团队只记录调用时长出问题的时候根本没法回放具体内容。Prompt版本标签、模型名称、输入Token数、输出Token数、是否触发重试这些字段在问题定位时比延迟值有用得多。我们内部有个不成文的规矩任何业务Span至少挂5个Attribute其中必须包含目的和关键输入摘要。3.3 Langfuse对接与链路关联Langfuse支持OTLP协议接入最省事的做法是让Collector直接转发。在OpenTelemetry Collector配置里把OTLP receiver收到的Span通过otlp exporter转发给Langfuse即可。但这里有个隐蔽的坑Langfuse对Trace ID格式有要求必须是合法的16字节hex字符串。如果上层框架生成了不符合规范的ID数据会被静默丢弃。OpenTelemetry SDK默认生成的Trace ID没问题但如果你用了某些中间件自造的ID就会踩坑。除了OTLPLangfuse官方Python SDK也提供了一种更省心的方式直接用装饰器给函数打点from langfuse.decorators import observe, langfuse_context observe() def generate_answer(context, query): langfuse_context.update_current_trace( user_idu_12345, session_ids_67890, metadata{prompt_version: v24.8.1} ) # 模型调用逻辑 return llm_client.chat(...)这种写法的好处是SDK内部自动把嵌套的模型调用串成父子Span不用自己管理上下文传播。缺点是代码里多了对Langfuse SDK的直接依赖。我的建议是混合使用用装饰器做粗粒度的会话/步骤切分用OTel自动埋点做细粒度的组件调用追踪两边通过统一的Trace ID关联。最终在Langfuse后台能看到一条完整Trace从HTTP请求到检索再到模型输出层级清晰。3.4 LangSmith对接与评测数据准备LangSmith接入相对独立大部分流程是脚本化的先从生产库导出评测样本构造Dataset再编写评测脚本对Dataset里的每条样本分别用两套Prompt跑模型最后把预测结果传给Evaluator打分。from langsmith import Client client Client() dataset client.create_dataset( dataset_namecustomer_service_v24.8, description24.8周期客服问答评测集 ) client.create_examples( dataset_iddataset.id, inputs[{question: q} for q in questions], outputs[{reference: r} for r in ref_answers] )这里强调一个原则评测样本必须带参考回答而且参考回答要经过人工确认。不能只靠脚本从日志里机械捞数据充当金标准评测集的Label本身有错后面测得再准都是白测。我们建评测集的时候找业务方抽两个人专门做了两轮标注复核宁可样本少一点也要保证每条Label质量可靠。3.5 数据关联与ID打通打通的关键是统一ID体系。做法是HTTP请求入口用中间件生成conversation_id把它设置成链路在Langfuse中的session_id同时挂到OTel Span的Attribute上LangSmith评测脚本记录conversation_id和trace_id再把评测结果和Langfuse里对应Trace关联起来。为什么要费劲做这个关联因为只有把离线评测分数和线上真实Trace对应上才能回答评测里得高分的样本线上实际表现是不是也好这类问题。我们后来建了一张简单的关联表一行记录包含conversation_id、trace_id、prompt_version_a、prompt_version_b、a_score、b_score、winner、evaluated_at。有了这张表任何一次评测结论都能反查到线上原始链路定位问题非常方便。4. Prompt A/B评测的完整实践4.1 评测维度与指标以及成本底线评估一个Prompt版本好不好不能只看一个笼统的得分。答得好不好本身是个复杂概念必须拆维度。我们根据客服问答场景拆成五个维度正确性、完整性、格式合规、安全正确拒答不当内容、运行成本。前四个维度用LLM-as-Judge打分每项0到100分成本维度直接算每次请求的平均Token消耗和调用耗时。这里有个实操细节LLM-as-Judge的评分卡不是越复杂越好。刚开始我设计了一个很复杂的评分卡要求裁判模型按七条标准逐项打分结果裁判模型自己输出都不稳定同一批样本跑两次分差能到十几分。简化成先给裁判一段总体理由再给0到100总分和分项拆解之后稳定性大幅提升。Judge模型的temperature必须设为0评测用的Judge模型版本要固定不能跟着线上模型一起升级否则每次打分基准都在变跨周期对比全部失真。另外一个必须设置的约束是成本底线。Prompt改版经常出现效果变好但成本翻倍的情况小流量实验期看不出来全量上线之后账单很难看。我们的规矩是同一场景下新版单次调用Token消耗不得超过旧版的1.2倍超了直接一票否决哪怕效果分再高也不能上。这个底线让团队少做了很多看似美好的改版。4.2 评测集构建从哪来、怎么筛、多少够用评测集是整个闭环的地基它比选什么评测工具更重要。我们构建评测集用了三个来源生产bad case从Langfuse后台筛选用户点踩、或答案被判为低分的会话。这是最重要的来源代表真实用户痛点。典型正向样本从高频问题里抽一批正常问答防止改版后为了修bad case把正常回答搞坏保住回归能力。对抗样本自己构造的边界情况比如超长上下文、诱导性发言、模糊指代刚才那个订单呢。这些在真实流量里占比不高但对发现Prompt漏洞特别有效。数量上第一版评测集建了300条左右。经验判断低于100条结论波动太明显一个bad case就能让总分浮动几个点500条以上收益递减但单次评测成本直线上升。300条是性价比不错的平衡点覆盖约30个二级场景每个场景10条样本。评测集维护也要制度化线上每发现新类型bad case就沉淀进评测集同时定期复查旧样本是否需要更新防止评测集被单一问题带偏。4.3 A/B实验流程离线评测加线上小流量验证Prompt A/B评测分两阶段。第一阶段是离线评测在LangSmith里对同一批样本用Prompt A和Prompt B各跑一遍得到两组成绩。第二阶段是线上验证把新版Prompt以10%流量灰度上线跑24到48小时用Langfuse对比新旧版本在真实流量下的用户反馈、延迟和成本。离线评测脚本的核心逻辑如下from langsmith import evaluate def predict_a(inputs): prompt build_prompt(template_a, inputs[question]) return {answer: llm_call(prompt)} def predict_b(inputs): prompt build_prompt(template_b, inputs[question]) return {answer: llm_call(prompt)} results_a evaluate(predict_a, dataset_namecustomer_service_v24.8, evaluators[llm_judge]) results_b evaluate(predict_b, dataset_namecustomer_service_v24.8, evaluators[llm_judge])两套Prompt跑同一个Dataset天然就是配对实验非常适合做逐条比较。要注意的是模型有随机性即便temperature0也不能保证两次输出完全一致尤其在推理模型上。所以对关键样本我会让同一版本跑2到3次取多数结果再进入统计降低抖动对结论的影响。线上灰度阶段重点盯三个指标用户显式反馈点赞/点踩率、平均每会话Token消耗、P95响应延迟。用户反馈虽然稀疏但信号最真实Token消耗和延迟是最直接的成本与体验指标。灰度期间只要新版差评率显著上升无论离线得分多高都立刻回滚——因为离线评测集不可能覆盖所有线上分布。4.4 结果分析与决策门槛评测数据出来后最难的环节是判断谁赢了。见过太多团队跑完A/B之后对着两个分数纠结半天最后还是拍脑袋。为了避免这种情况我在24.8实践里定了一套明确的决策流程。第一步看配对胜率。对300条样本逐条比较得分统计新版赢、平、输的条数。用配对检验的思路新版在超过55%的样本上得分不低于旧版且赢的条数明显多于输的条数才算有初步优势。胜率不到50%的直接淘汰不需要再看其他指标。第二步看加权综合分。给五个维度设权重正确性0.4、完整性0.2、格式合规0.1、安全0.2、成本0.1。加权后新版综合分必须高于旧版至少3分百分制这个门槛是为了避免高1分就上线这种被噪声驱动的决策。第三步看成本底线和回归项。逐项检查旧版得分反而下降的维度确认没有明显回退Token消耗增幅超过20%直接否决。三项全部通过才允许把新版流量从10%逐步提到50%再到100%。第一次完整跑完这套流程前后花了大约两周大部分时间在清洗数据和调Judge提示词。流程跑顺之后一次完整Prompt改版从提需求到决定是否上线压缩到两天以内。这就是闭环带来的实际收益。5. 常见问题与排查实录5.1 埋点数据丢了一部分怎么排查上线一周后发现Langfuse里的Trace数量比网关统计的请求数少约8%。第一反应是采样配置检查Collector配置发现采样率是100%排除。然后查Collector日志发现一部分数据导出时返回了405错误。进一步排查发现是Langfuse的OTLP端点对不同Content-Type处理有差异我们用gzip压缩上报时某些版本的服务端解压异常日志只打了一行警告数据就丢了。解决办法是把Collector的压缩方式改为none或者升级Langfuse镜像到修复版本。这类问题的教训是可观测系统本身也会出故障必须给可观测链路加健康检查。我们后来在Collector上挂了探活接口每天定时拨测一旦发现入库曲线有明显断崖就自动告警避免连监控都坏了还没发现的尴尬。5.2 Trace关联不上会话链路断成好几截断链问题最让人头疼。现象是Langfuse里同一会话的Trace被拆成好几条无法按时间顺序串成完整上下文。根因大多是上下文传播断裂异步任务、消息队列、多线程场景下traceparent没有正确传递到下游Span。我们有一个异步生成报告的功能任务由Celery执行子任务里的模型调用全部游离在主线Trace之外。解决思路是任务入队时把当前traceparent存到任务参数里任务执行时通过OpenTelemetry的上下文API恢复包一层公共函数自动完成。这类问题属于看起来不大、修起来费劲的典型建议在架构设计阶段就把异步链路和消息队列的上下文传播纳入考虑别等上线后再补。5.3 LLM-as-Judge结果不稳定同批样本两次跑分不一样这个问题的主要原因有三个Judge模型本身有随机性、Judge提示词措辞有歧义、样本顺序会影响判断倾向顺序偏差。应对措施分别是Judge模型固定版本且temperature0Judge提示词写成结构化评分卡明确每个分数档位的行为描述评测时对样本顺序做一次shuffle关键结论跑两轮Judge取平均对分差超过10分的样本抽出来人工复核。这里我想再强调一遍Judge模型和线上业务模型必须分开版本管理。有一次因为线上模型升级顺手把Judge模型也升了结果整周的评测分数体系全部需要重算白白浪费了不少时间。5.4 评测成本涨得太快怎么控制一次完整评测的成本可以算一下300条样本、两个Prompt版本、每版跑2次去抖再加上Judge调用模型调用量大约是300乘以2乘以2再加300算下来1500次左右。如果用中档模型、单次平均消耗2000 Token一次评测约300万Token。按当时价格估算单次离线评测成本在几十元人民币量级一天跑两轮完全可控。真正需要留意的是灰度阶段的成本。10%流量跑48小时客服场景一天几千次调用多出来的成本不大但一定要注意别让所有日活用户都路由到新Prompt。万一新版有严重问题影响范围会被瞬间放大。另外可以用Langfuse的Trace采样功能灰度期间只采集20%新版请求做分析样本来得没那么全但趋势判断完全够用。5.5 常见问题速查表现象可能原因排查动作Trace数量少于请求数采样配置、OTLP压缩、版本兼容查Collector日志验证压缩方式检查版本匹配会话链路断裂上下文传播丢失检查异步/队列场景的traceparent传递评测分数不稳定Judge随机性、顺序偏差固定模型、temperature0、跑多轮取平均数据全部丢失SDK初始化失败、密钥错误检查环境变量和初始化日志Prompt版本信息找不到Span Attribute未挂载核对埋点代码确认版本标签字段6. 这套闭环后续还能怎么扩展24.8这套闭环跑顺之后我们已经在规划两个扩展方向。一个是把评测自动化嵌入CI流程每次改动Prompt或模型配置时自动触发一轮小规模评测集回归把人工介入降到最低。另一个是在线反馈信号的挖掘当前用户点踩事件比较稀疏计划接入更多隐式信号比如用户追问次数、停留时长、是否复制答案把这些行为信号变成可量化的评测维度。需要提醒的是这两个扩展都依赖一个前提基础数据质量要过关。如果链路埋点、ID关联、评测集维护没有做到位再高级的功能都是在沙子上盖楼。所以我的建议是任何团队想做类似闭环先把埋点-关联-离线评测-灰度验证这套骨架跑通再谈扩展。最后按惯例分享一点个人体会可观测和评测闭环的价值不是让你立刻把所有问题都解决而是让你能越来越快地发现问题、定位问题、验证解法。这个过程没有太多玄学就是把数据老老实实采全、把评测标准定清楚、把决策门槛写明白然后持续转圈。踩过几次坑之后我才真正理解模型应用优化的核心能力和写代码一样都是根据反馈持续收敛而这套闭环就是那个反馈通道。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询