ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Langfuse 实战:LLM 应用链路追踪与离线评估指南

Langfuse 实战:LLM 应用链路追踪与离线评估指南 1. 为什么 LLM 应用需要 trace 和离线评估1.1 从能跑通到敢上线之间的鸿沟我见过太多团队做 LLM 应用的状态本地 demo 跑得飞起一上生产就各种翻车。用户问了个稍微绕一点的问题模型答非所问明明上周测试还好的 prompt这周换了模型版本输出质量断崖式下跌老板问咱们这个问答准确率多少没人答得上来。这不是模型不行而是可观测性缺失。传统后端服务有日志、有链路追踪、有监控大盘一个请求进来经过哪些服务、每步耗时多少、哪一步报错一目了然。但 LLM 应用不一样——它的核心逻辑是一段文本进去一段文本出来中间还夹着 prompt 模板渲染、检索召回、工具调用、多轮对话状态管理。如果只打印最终输出你根本不知道问题出在哪一环。Langfuse 解决的正是这个问题。它给 LLM 应用提供两样东西trace链路追踪和离线评估offline evaluation。前者让你看清每一次调用的完整链路后者让你用数据集批量跑分量化应用质量。1.2 trace 到底记录了什么很多人第一次听到 trace会以为就是日志。其实差别很大。日志是离散的文本行而 trace 是一棵有层级结构的调用树。举个实际例子。一个典型的 RAG 问答请求在 Langfuse 里会呈现成这样一棵树Trace根节点用户问公司年假怎么算Span检索阶段向量库查询召回 5 篇文档耗时 120msGenerationLLM 调用把问题和文档拼成 prompt调用模型返回答案记录 token 用量、耗时、成本Span后处理格式化输出、敏感词过滤每个节点都能挂载输入、输出、元数据、耗时、token 数。你点开任意一个节点就能看到当时喂给模型的完整 prompt 是什么、模型返回的原始内容是什么。这一点极其关键——很多 LLM 的 bug 不是模型的问题而是 prompt 拼装出了问题比如检索召回了不相关的文档、模板变量没替换、多轮对话历史被截断。1.3 离线评估和在线 trace 的分工trace 是事后诸葛亮它记录已经发生的真实请求。而离线评估是提前演习你准备一批带标准答案的测试用例让应用批量跑一遍用评分器evaluator自动打分。两者配合起来才完整trace 帮你发现线上哪些请求表现异常把这些异常样本沉淀成数据集再用离线评估验证你的修复方案是否真的有效。没有 trace你不知道该测什么没有离线评估你改完 prompt 只能靠感觉判断好坏。提示不要一上来就追求全自动评估。先用 trace 把线上真实流量看清楚人工标注几十条样本比盲目搭建复杂评估流水线有用得多。2. Langfuse 的接入方式与最小可用配置2.1 部署形态怎么选Langfuse 提供两种使用方式云托管版和自托管版。选哪个取决于你的数据敏感度和团队规模。维度云托管版自托管版上手成本注册即用5 分钟接入需要 Docker 环境约半小时数据存放存在第三方服务器完全在自己内网成本免费额度 按量付费服务器成本开源免费维护无需操心需自己升级、备份适合场景快速验证、个人项目企业内网、数据合规要求高自托管用 Docker Compose 起一套最省事。核心组件包括 Web 服务、Postgres 数据库、ClickHouse存 trace 数据、Redis队列和对象存储存大 payload。如果你只是本地玩玩官方提供的一键 compose 文件足够跑起来。2.2 拿到 API Key 并初始化客户端在 Langfuse 控制台创建项目后会得到一对密钥public key和secret key。这两个值通过环境变量注入千万不要硬编码进代码提交到仓库。export LANGFUSE_PUBLIC_KEYpk-lf-xxxx export LANGFUSE_SECRET_KEYsk-lf-xxxx export LANGFUSE_HOSThttps://cloud.langfuse.com # 自托管改成自己的地址Python 侧初始化from langfuse import Langfuse langfuse Langfuse( public_keyos.environ[LANGFUSE_PUBLIC_KEY], secret_keyos.environ[LANGFUSE_SECRET_KEY], hostos.environ[LANGFUSE_HOST], )这里有个容易忽略的点Langfuse 客户端默认是异步批量上报的。也就是说你调用完 API 后数据不会立刻出现在控制台而是攒一批再发。这在生产环境是好事不阻塞主流程但调试时会让你以为没接上。解决办法是开发阶段手动调用langfuse.flush()强制刷新。2.3 用装饰器快速包住一个函数最省事的接入方式是装饰器。假设你有一个调用 OpenAI 的函数from langfuse.decorators import observe, langfuse_context from openai import OpenAI client OpenAI() observe() def ask_llm(question: str) - str: resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: question}], ) return resp.choices[0].message.contentobserve()会自动为这次调用创建一个 trace并把函数入参、返回值、耗时都记录下来。如果函数内部又调用了别的被observe()装饰的函数它们会自动嵌套成父子 span。这就是零侵入接入的魅力——你几乎不用改业务逻辑。不过要注意装饰器方案对 OpenAI 的调用只能记录到函数级的输入输出拿不到 token 用量和成本。想要更细的粒度得用 OpenAI 集成。2.4 用 OpenAI 集成拿到 token 和成本Langfuse 提供了对 OpenAI SDK 的 drop-in 替换from langfuse.openai import openai resp openai.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 你好}], namegreeting-call, # 给这次调用起个名字方便在控制台筛选 )把import openai换成from langfuse.openai import openai其余代码一行不用改。这样每次调用都会自动上报模型名、prompt、completion、prompt tokens、completion tokens、总成本、延迟。控制台里能直接按模型、按时间聚合看成本趋势。注意这个替换只对 OpenAI 官方 SDK 生效。如果你用的是其他厂商的 SDK 或者自己封装的 HTTP 请求需要手动用generation接口上报或者用装饰器包一层。3. 把一次 RAG 请求拆成可观测的调用树3.1 手动创建 trace 和 span 的时机装饰器虽好但有些场景它覆盖不到。比如你的检索逻辑散落在好几个函数里或者你想给 trace 打上业务标签用户 ID、会话 ID、租户 ID这时候就需要手动控制。trace langfuse.trace( namerag-qa, user_iduser_123, session_idsession_abc, metadata{tenant: acme, env: prod}, ) retrieval_span trace.span(nameretrieval, input{query: question}) docs vector_store.search(question, top_k5) retrieval_span.end(output{doc_ids: [d.id for d in docs]}) gen trace.generation( nameanswer-generation, modelgpt-4o-mini, input[{role: user, content: build_prompt(question, docs)}], ) answer call_model(...) gen.end(outputanswer, usage{input: 800, output: 120})user_id和session_id这两个字段特别有用。前者让你能按用户维度看质量分布后者让你能把多轮对话串成一条完整会话。排查某个用户反馈答得不好这类问题时直接按 user_id 过滤所有相关 trace 一目了然。3.2 嵌套 span 的层级设计原则层级不是越深越好。我见过有人把每个函数都包一层 span结果一棵树几十层看都看不清。合理的做法是按有独立意义的处理阶段来切分。一个 RAG 应用我通常切成这几层检索retrieval向量查询、重排序上下文组装context assembly把召回文档拼进 prompt生成generationLLM 调用后处理postprocess格式化、过滤工具调用类的 Agent 应用则按每一轮思考 每一次工具调用来切。关键是让每个 span 的边界对应一个你能独立评估、独立优化的环节。如果某个环节你永远不会单独看它的输入输出那它就不该单独成 span。3.3 给 trace 打标签和评分trace 创建后可以随时追加标签和评分trace.update(tags[production, v2-prompt]) trace.score(nameuser_feedback, value1, comment用户点了赞)标签tag是筛选利器。你可以给不同 prompt 版本打不同 tag对比两个版本的 trace 质量。评分score则把主观判断量化后面做评估分析时能直接聚合。用户反馈是最廉价也最真实的评分来源。点赞点踩、复制、重新生成这些交互信号都值得上报成 score。积累一段时间后你就有了一批带真实标签的样本比任何人工构造的测试集都宝贵。4. 离线评估从数据集到自动打分4.1 数据集怎么攒离线评估的第一步是准备数据集。Langfuse 里的 dataset 就是一组 item每个 item 有 input 和可选的 expected_output。攒数据有三条路从线上 trace 沉淀看到某条 trace 表现好或差一键加入数据集。这是最推荐的方式因为样本来自真实分布。手工构造针对边界情况、易错场景人工写测试用例。合成生成用强模型基于文档生成问答对适合冷启动阶段快速铺量。我个人的经验是先攒 30 到 50 条高质量样本就能跑出有意义的评估结果。不要一上来追求几百上千条标注成本高且边际收益递减。等评估流程跑顺了再逐步扩充。dataset langfuse.create_dataset(namerag-qa-v1) dataset.create_item( input{question: 年假怎么算}, expected_output入职满一年享 5 天年假每满一年递增 1 天上限 15 天。, )4.2 跑一次批量评估评估的本质是对数据集里每个 item跑一遍你的应用拿到实际输出再用评分器打分。dataset langfuse.get_dataset(rag-qa-v1) for item in dataset.items: trace item.run() # 自动关联 trace 和 dataset item answer my_rag_app(item.input[question]) trace.update(outputanswer)item.run()这个设计很巧妙——它创建的 trace 会自动带上 dataset item 的关联信息。这样你在控制台里能直接看到这条测试用例对应的实际输出是什么、评分多少不用自己维护映射关系。4.3 评分器的三种类型评分器决定了评估的质量。按实现方式分三类规则型评分器用代码判断比如精确匹配、包含关键词、JSON 格式是否合法、长度是否超限。优点是快、便宜、确定性强缺点是只能覆盖硬性指标。def exact_match(output, expected): return 1.0 if expected in output else 0.0模型型评分器LLM as Judge让一个强模型来打分。适合评估答案是否切题语气是否得体这类主观维度。关键是评分 prompt 要写得足够具体给出明确的评分标准和示例否则模型打分飘忽不定。judge_prompt 你是评分员。根据参考答案判断模型回答是否正确。 参考答案{expected} 模型回答{output} 只输出 0 到 1 之间的分数1 表示完全正确。人工评分最准但最贵。适合小批量校准用来验证自动评分器是否靠谱。我的建议是组合使用规则型做第一道过滤格式、长度、关键词模型型做质量打分人工抽查校准。三者结果都上报到 Langfuse在同一个 trace 下对比。4.4 评估结果怎么看跑完评估Langfuse 会按 dataset run 聚合展示。你能看到每个 item 的实际输出和各项评分整个 run 的平均分、分数分布不同 run 之间的对比比如 prompt v1 vs v2对比功能是精髓。改完 prompt 后跑一次新 run直接和旧 run 并排看哪些用例变好了、哪些变差了一目了然。这比感觉好像好了一点靠谱一万倍。提示评估不是一次性的。每次改 prompt、换模型、调检索参数都应该跑一遍回归评估防止改好一个场景却弄坏了另一个。5. 实操中踩过的坑和应对经验5.1 数据没上报先查 flush 和网络最常见的坑是代码跑完了控制台啥也没有。按这个顺序排查是不是异步没刷新开发环境手动langfuse.flush()或者进程退出前调用。密钥和环境变量对不对LANGFUSE_HOST自托管时最容易写错云版和自托管地址不通用。网络能不能通自托管在内网本地开发机可能访问不到需要配代理或端口转发。SDK 版本Langfuse SDK 迭代较快老版本 API 可能和新文档对不上锁一个稳定版本。我踩过最隐蔽的一次是容器里环境变量注入了但值末尾带了个换行符导致鉴权一直失败日志里还看不出明显报错。后来打印出来才发现。所以密钥类配置一定要 trim。5.2 大 payload 拖慢上报LLM 的 prompt 和 completion 动辄几千 token如果每次都完整上报数据量会很吓人。Langfuse 支持对 payload 做截断或脱敏。langfuse Langfuse( ..., masklambda data: {**data, api_key: ***}, )对于超长文本可以只上报前 N 个字符加省略标记。但要注意评估时可能需要完整输出所以别截得太狠。我的做法是生产环境截断、评估环境全量。5.3 评估分数不稳定怎么办模型型评分器最大的问题是分数抖动。同一个回答跑两次可能一个 0.8 一个 0.6。原因通常是评分 prompt 太模糊。解决办法给评分标准加锚点明确写出0.9-1.0 是什么样、0.5-0.7 是什么样。降低温度评分调用把 temperature 设成 0。多次采样取平均同一回答评 3 次取均值成本换稳定性。用更强的模型当裁判裁判模型至少要比被测模型强一个档次。5.4 别把 trace 当日志用最后说个观念问题。有人把 Langfuse 当成更花哨的日志系统什么都往里塞结果 trace 树又乱又慢。trace 应该聚焦在LLM 相关的调用链路上普通的业务日志、数据库慢查询这些还是交给专业日志系统。Langfuse 的价值在于它理解 LLM 应用的语义——token、成本、prompt、completion、评估分数。把这些用好比堆砌数据重要得多。6. 把 trace 和评估串成持续改进的闭环6.1 一个可落地的工作流把前面这些串起来我实际用的工作流是这样的生产环境全量开 trace打上版本 tag。每周挑出低分 trace 和用户负反馈样本加入数据集。针对问题改 prompt 或检索逻辑本地跑离线评估。评估通过后灰度上线新版本打新 tag。对比新旧 tag 的线上 trace 质量确认改进真实有效。这个闭环跑起来后LLM 应用的迭代就从玄学调参变成了数据驱动。每次改动都有据可依出了问题也能快速定位。6.2 成本和质量要一起看Langfuse 的 trace 里带了 token 和成本数据这点别浪费。我经常做的一个分析是把成本和质量放在一起看。有些场景用便宜的小模型质量只掉一点点成本降一大截那就果断换。有些场景小模型质量崩了那就老老实实用大模型。这种决策靠拍脑袋不行得靠数据。在 Langfuse 里按模型维度聚合质量分和成本并排一放答案自然就出来了。6.3 从小处着手如果你刚开始接触 Langfuse别想着一步到位搭全套。我的建议是分三步走第一步只接 trace把线上真实请求看清楚这一步就能发现一堆之前没意识到的问题。第二步攒 30 条数据集跑一次最简单的规则评估把流程跑通。第三步引入模型评分器建立回归评估习惯。每一步都能独立产生价值不用等全套搭完才见效。工具是为人服务的别被工具绑架。
返回列表