尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI应用不确定性排查思路:用可观测性埋点与trace还原现场

AI应用不确定性排查思路:用可观测性埋点与trace还原现场 AI 应用上线后最让人头疼的不是模型效果差而是系统行为开始变得“不确定”。同一个 prompt上午跑和下午跑结果不一样同一个用户请求有时候流畅返回有时候触发重试有时候直接超时。做传统后端出身的同学习惯用日志、监控、链路追踪定位问题到了 AI 应用里这套老办法经常失灵。Charity Majors 关于 AI、确定性和 instrumentation 的讨论核心其实就一句话AI 带来的不确定性必须用更扎实的埋点和更完整的上下文去对冲而不是靠拍脑袋调 prompt。这篇文章就围绕这个主题拆一拆 AI 应用到底该怎么埋点、怎么复现问题、怎么把“吃青菜”这件事变成工程习惯。1. 为什么传统排查方式在 AI 系统里失效了1.1 传统系统的“确定性”被当成理所当然传统后端系统里一个请求从网关进来经过服务 A、服务 B、数据库最后返回结果。这个链路里绝大多数逻辑是确定性的同样的输入、同样的代码版本、同样的数据状态就应该得到同样的输出。就算出现 bug也基本可以靠“复现”来定位。复现这个词很重要。传统排查流程里工程师拿到一个线上问题第一件事就是找出当时的请求参数在测试环境重放一遍。能稳定复现就能加日志、打断点、缩小范围一步步把问题定位到某一行代码。这套流程之所以成立是因为系统行为在“确定性”上站得住。可一旦把大模型接进来确定性就变成了概率事件。你不能拿着同样的 prompt 去重放然后指望每次都得到同样的回答。这不是代码 bug而是模型推理的本质特性。1.2 大模型推理是一条概率事件链从请求发出到最终返回中间有太多环节会改变输出结果模型权重版本。同一个模型名线上切了微调版本、量化版本或者新 checkpoint结果就可能变。prompt 模板版本。哪怕只改了一个标点、一个空格、一个 role 字段输出都可能漂移。采样参数。temperature、top_p、max_tokens、seed 是否固定都会影响生成结果。上下文长度和截断策略。RAG 检索出来的段落顺序、是否被截断也会改变模型看到的内容。上游依赖状态。向量库里的数据更新了、embedding 模型换了检索结果就变了。网络和重试。第一次超时后重试第二次可能走了不同节点返回内容也可能不同。这里面任何一环变化最终都可能表现为“同样的输入输出不一样”。如果系统没有把这一整条链路记录下来出问题的时候你连“当时到底发生了什么”都说不清楚。1.3 所以问题的关键不是让 AI 变确定而是把不确定性记录下来有人会觉得那把 seed 固定住、把 temperature 调到 0是不是就确定了其实没那么简单。模型推理还受批处理顺序、显存状态、上游检索结果等外部条件影响。你能做到的不是消灭随机性而是把每一次决策的上下文完整保留下来让自己事后能回答三个问题这个请求当时用了哪个模型版本用了哪版 prompt输出了什么内容花费了多少 token耗时多少回答不了这三个问题就不算具备排查 AI 应用问题的基本能力。这也正是 Charity Majors 强调 instrumentation 的原因AI 系统越不确定就越需要确定性强的观测数据来兜底。2. Instrumentation 不是打日志而是给不确定性建立“对照样本”2.1 只记录输入输出等于什么都没记很多团队接入大模型时日志就一行prompt: xxx, response: xxx。这种日志在开发阶段看起来够用一旦上线就废了。因为线上请求量大完整 prompt 和完整响应动辄几千字直接打到普通日志里既占空间又没法检索。更关键的是你只记了“输入输出”没记“参数条件”。等到用户投诉说“上次回答得挺好这次怎么变成这样了”你翻出日志只能看到两段文本根本不知道两次请求用的模型版本是不是同一个温度参数是否一致RAG 检索出来的上下文到底长什么样。真正的 AI instrumentation要把请求的“环境条件”一并记录下来。这不是额外增加的工作量而是排查问题时的必要条件。2.2 一条 AI 请求应该记录哪些字段我按实践优先级列一下能全做最好做不到就先做前面几项。字段说明优先级trace_id关联整条业务链路必须能串起前后端必须span_id / parent_span_id标记当前 LLM 调用所在位置必须model_name / model_version记录实际使用的模型和版本必须prompt_version记录 prompt 模板版本不是整段 prompt 全文必须请求参数temperature、top_p、max_tokens、seed 等必须prompt_tokens / completion_tokens输入和输出 token 数估算成本用必须latency网络耗时、排队耗时、生成耗时分开记建议检索上下文摘要RAG 场景下记录召回文档 id 和片段 hash建议retry_count / fallback_used是否重试、是否走了降级模型建议响应摘要完整响应太长就截断但要保留可回查方式建议用户反馈点赞、点踩、人工标注可选但价值高可选这里最容易忽略的是 model_version 和 prompt_version。很多团队把 prompt 直接写在代码里改了也不留版本模型更新了也不通知下游。结果就是线上出问题时你连“用户看到的是哪一版的模型输出”都查不出来。2.3 结构化日志别用纯文本拼日志格式建议用 JSON一条 LLM 调用的 span 大概是这个感觉{ trace_id: t_8f3a1c9e, span_id: s_2b74d01a, parent_span_id: s_9c18e52f, service: ai-chat-service, name: llm.completion, start_time: 2025-01-08T10:23:11.482Z, duration_ms: 3421, attributes: { model: gpt-4o, model_version: 2025-01-05, prompt_version: chat_v3, temperature: 0.3, max_tokens: 1024, seed: null, prompt_tokens: 856, completion_tokens: 412, latency: { queue_ms: 120, network_ms: 88, inference_ms: 3213 }, rag_docs: [doc_1024, doc_981, doc_1055], retry_count: 0, fallback_used: false }, output_summary: 用户您好关于配额调整的问题……截断, output_hash: sha256:9f3c... }用 JSON 的好处有两个一是各种日志平台、链路追踪系统都能直接消费二是字段可以按需补齐不需要改行格式。后续要加“是否命中缓存”“用的是哪个 embedding 模型”这类字段直接往 attributes 里加就行。3. AI 应用的可观测性要分三层建设3.1 第一层所有 LLM 调用都变成带上下文的 trace这一层是地基。目标很简单业务代码里每一次调用大模型都必须能在一个 trace 里被看到。你不需要在每个服务里自己造轮子可以直接用 OpenTelemetry 这类标准协议把 LLM 调用封装成一个 span。这样做的意义在于当用户说“我这边回答错了”的时候你可以顺着 trace_id 把整条链路拉出来看到用户请求进了哪个服务、调了哪次模型、检索了哪些文档、最终做了什么决策。没有这个基础后面所谓的“根因分析”都是空谈。我一般会建议团队先挑一个最核心的场景做试点比如智能客服或内容助手把这一条链路的 trace 跑通再推广到其他场景。不要一开始就要求所有业务都接那样子会陷入“规范做了半年线上还是没数据”的死循环。3.2 第二层prompt 版本和模型版本要纳入版本管理大模型应用和传统应用有一个很大的不同业务逻辑不只是写在代码里的还写在 prompt 里。很多团队把 prompt 当成一个可以随便改的字符串今天改语气明天加规则后天又怕改坏了回滚。问题是没有一个“版本号”挂着你根本不知道线上跑的是哪一版。所以第二层要做的事是把 prompt 当作代码来管理。每次修改都生成一个新版本号调用时把版本号带进 span。模型侧的版本也一样模型服务升级、微调、切换量化版本都要在配置里标明并在日志中体现。见过太多这样的情况产品经理说“用户反馈回答变差了”技术排查一圈最后发现是模型服务商在后台悄悄把模型版本升级了而应用代码没有任何感知。如果 LLM 调用的 span 里有 model_version这个问题五分钟就能定位没有的话可能要排查好几天。3.3 第三层评估集和回归测试是“吃青菜”的核心动作Charity Majors 说的“eating your broccoli”放在 AI 工程里最典型的就是两件事一是上面说的埋点二是评估集和回归测试。为什么这么比喻因为这两件事都很枯燥不会给产品带来即时的新功能也不会让 demo 更惊艳。你要花时间整理一批固定的测试用例定义什么是“好答案”每次改 prompt、换模型、调参数都重跑一遍。听起来像功课但它是 AI 应用能长期稳定运行的关键。一个最小可用的评估集可以很简单30 到 50 条覆盖主要业务场景的测试输入每条输入配好期望的行为判断标准比如“必须包含订单号”“不能出现价格猜测”“遇到骂人内容要拒绝回答”每次变更后自动或手动跑一遍对比结果是否出现回归。有了这个评估集你才能回答“这个 prompt 到底是改好了还是改坏了”。否则你每次优化 prompt 都只是靠几个 sample 的感觉上线后用户一多问题立刻暴露。4. 实际落地步骤先单条再批量再进生产4.1 最小可观测改造先让单条请求带 trace id不要一上来就搭平台、上监控大屏。我建议先从最小闭环开始在业务代码里给一次 LLM 调用加上 trace id 和结构化埋点。流程可以拆成四步在请求入口生成 trace_id并注入到后续所有调用。封装一个 LLM 调用函数统一记录模型名、版本、请求参数、token 数和耗时。把返回结果做摘要和 hash和 span 关联存储。跑一条测试请求确认在日志系统里能完整看到这条链路。这一步做完你就已经比 80% 的团队强了。因为你至少能回答“每次调用到底发生了什么”。这里有一个常见的坑封装函数时只记成功请求不记失败请求。结果就是线上出错时日志干干净净一点线索都没有。正确的做法是失败和异常也要记而且要记失败的类型、阶段、错误码和重试次数。失败往往比成功更需要观测数据。4.2 批量任务和线上流量怎么接单条请求跑通后再考虑批量任务和线上流量。批量任务的场景比如离线批量生成内容摘要、大批量打标签不能直接套用在线单请求的日志方式因为量大了以后完整日志成本很高。批量任务我建议这样处理每条任务都复用同一个 job_id再给每条具体记录加 item_id。抽样保留完整输入输出其余记录元信息和结果状态。重点记录失败数据包括失败原因、失败发生在哪一步、是否可重试。任务跑完后出具统计报告成功数、失败数、平均耗时、token 总量、成本估算。线上场景则要考虑采样率。高流量场景不需要每条请求都留全量日志核心 span 可以全量记录元信息完整 prompt 和响应做采样比如 10% 到 20%。但用户反馈、异常请求、走 fallback 链路的请求必须全量留痕因为这些才是排查重点。4.3 判断标准什么算“观测到位”做完改造后可以用几个问题来验收随机拿一个线上 trace_id能不能在五分钟内还原完整调用链路能不能看出这次调用用的模型版本、prompt 版本、采样参数能不能知道这次调用的 token 消耗和成本能不能看出是否重试、是否降级、检索到了哪些文档出错的请求有没有留下失败原因和现场上下文五个问题都能回答“能”观测基础就算打牢了。5. AI 应用出问题先看哪一层5.1 现象分类和初步判断AI 应用的问题表面看起来五花八门归类后其实就那么几类。现象优先检查方向常见原因同输入输出不一致模型版本、prompt 版本、采样参数模型变更未记录或 prompt 未版本化回答质量突然下降检索上下文、模型升级、prompt 改动RAG 数据变更或上游模型静默升级请求超时或失败网络、限流、重试策略、模型服务状态并发过高超时设置过短模型服务不稳定token 消耗暴增prompt 长度、上下文累积、循环调用上下文未压缩或代码里出现重复调用用户反馈内容不对评估集覆盖、RAG 召回质量、prompt 约束判断标准未定义或评估集没覆盖该场景5.2 按顺序排查别一上来就归因“模型随机”这是最容易犯的错误。很多团队一遇到 AI 问题就说“这是模型随机性没法定位”。实际上大部分问题都是有明确原因的。我建议按这个顺序排查先看输入条件。prompt 用的是哪个版本temperature 设了多少模型版本是什么这些在 span 里都能查到。再看数据条件。如果是 RAG 场景检索到了哪些文档文档版本有没有更新有没有内容被截断再看代码路径。这次调用是否走了缓存是否走了降级模型重试逻辑是否生效再看资源条件。模型服务有没有限流显存和内存有没有异常超时时间是不是设置得过短最后再看模型本身。如果以上都正常再考虑是不是模型升级导致的行为漂移用评估集做对比确认。很多看似“随机”的问题排查到最后要么是 prompt 版本没固定要么是检索内容变了要么是超时和重试策略写得不对。真正的模型随机性并没有想象中那么频繁。5.3 当问题确实来自模型行为漂移时怎么办如果评估集对比后确认是模型升级或行为漂移导致的问题处理方式不是马上改 prompt而是先确认影响范围受影响的是所有请求还是特定输入类型线上流量的错误率、超时率、用户反馈率有没有变化新旧模型版本并存是否可以灰度切换在关键业务场景里我建议保留模型版本的回退能力并建立“变更前跑评估集”的流程。模型商升级版本不等于你的业务必须跟随。绑定版本、先评估、再灰度是成本最低的稳妥方案。6. 边界与经验不要把不可复现当借口6.1 哪些问题不该归因于“模型随机”“模型随机”这四个字很容易成为偷懒的借口。我见过一个团队用户反馈同一类问题反复出现他们一直说是 LLM 输出不稳定。后来排查发现他们每次调用都把用户历史消息全量塞进上下文token 数一路涨到了模型上下文窗口边缘旧消息被截断模型自然就“失忆”了。这根本不是随机性是上下文管理缺失。类似的案例还有prompt 里用了时间占位符但服务端时间和用户本地时间不一致缓存命中策略写反了有的用户命中缓存有的没命中还有 fallback 逻辑里主模型超时后切到小模型但前端还是按主模型的标准渲染。这些问题全都能通过观测数据定位。所以我的经验是当一个问题看起来“无法复现”时先回到观测数据把输入条件、数据条件、代码路径、资源条件逐层排除。大部分不确定都是记录不全造成的假象。6.2 把“吃青菜”变成团队习惯最后说一点团队层面的体会。可观测性建设不是一次性的项目而是习惯。我比较推荐几个做法代码评审时把“LLM 调用是否带埋点”作为必检项。每次新增 AI 场景先定义评估集再写业务代码。每次修改 prompt 或切换模型必须留下一行变更记录。每周看一眼 token 成本和失败率趋势而不是等出事故再查。线上任何一次用户反馈都尽量关联到当时的 trace_id。这些动作单独看都很琐碎连起来就是一套能扛住线上风险的体系。说到底AI 应用的不确定性不会消失你能做的就是通过 instrumentation 把不确定性变成可查询、可对比、可回溯的数据。埋点不是给监控平台看的是给未来的自己排查问题用的。踩过几次坑之后我发现很多 AI 应用的问题不是模型能力不够而是前置观测没有做干净。与其等到线上出问题再手忙脚乱不如从今天开始先给每一次 LLM 调用加上 trace、版本号和结构化日志。这碗“青菜”越早吃越划算。
返回列表