ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AI应用排障之道:用可观测性为不确定性系统建立确定性

AI应用排障之道:用可观测性为不确定性系统建立确定性 最近在 AI 工程实践里我反复遇到同一个词可观测性。起因是一个很具体的场景——同事跑了一个基于大模型的问答功能Demo 阶段一切正常上线后却开始出现“薛定谔的输出”。同一个问题上午能答下午开始瞎编同一个用户换一种说法就得到完全不同的回答。更让人崩溃的是打开日志以后除了请求原文和模型返回你什么信息都拿不到模型当时收到的是什么 prompt、上下文被拼成了什么样、走的是哪个版本、用了哪些参数全部是空白。这不是模型能力的问题而是系统透明度的问题。传统软件里你可以靠堆栈、日志、复现步骤把 Bug 逼到墙角但在 AI 应用里模型输出天然带随机性调用链路上还叠着 Prompt 模板、知识库检索、工具调用、多轮 Agent 状态。任何一个环节看不见你都没办法判断问题出在哪一层。这也是我最近看到一个讨论标题时特别有共鸣的原因——它把 AI、确定性、可观测性以及“吃青菜”放在了同一句话里。乍一看有点跳跃但结合我做 AI 应用排障的经验越想越觉得它们是在讲同一件事AI 应用能不能被信任不取决于模型有多聪明而取决于你能不能看见它、复现它、并持续改进它。1. AI 应用最难复现的问题恰恰是 Demo 里最不会暴露的问题1.1 传统软件可以“重跑”AI 应用经常不能先回到最基础的差异。传统后端 Bug 的排查路径通常是这样拿到报错信息复现现场定位代码修复验证。因为代码是确定性的同一份输入、同一份代码、同一个数据库状态理论上能跑出同一个结果。所以“能不能复现”成了排查问题的第一把钥匙。但大模型应用不是这样。模型输出依赖采样、随机种子、温度参数、上下文顺序、模型版本甚至部署环境。你以为自己在复现实际上每一次复现都是一次新的采样。你可能在本地调试了半小时调整 prompt 后感觉“好了”结果一上线又是一套新行为。原因是这个“好了”没有被任何机制固定下来。这也是为什么现在很多团队做 AI 应用最头疼的不是模型效果差而是“不知道它为什么好也不知道它为什么坏”。效果差还能靠换模型、改 prompt 去试不知道原因连改的方向都没有。1.2 关键不是消灭随机性而是消灭“看不见”很多人听到“AI 不确定性”会下意识觉得那我把 temperature 调成 0 不就行了实际上调低温度只能让输出变得更集中并不能让链路变得可控。真正的问题在于链路里的大部分信息根本没有被记录。Prompt 模板是动态拼出来的记录了吗知识库检索返回了哪些片段按什么顺序拼进上下文记录了吗模型走的是 v1 还是 v1.3参数是默认值还是业务方改过多轮对话里历史消息被截断了多少这些看似琐碎的信息才是 AI 应用排查里真正需要的东西。所以把 Determinism 和 Instrumentation 放在一起讨论是有道理的AI 需要确定性但确定性的来源不是按住模型的随机性而是通过插桩——也就是 instrumentation——把系统每一层的状态固定下来、记录下来。确定性不是模型给你的是观测系统给你的。2. 可观测性的内核从“发生了什么”到“为什么是这种输出”2.1 插桩不是加日志而是给每次调用建立身份在传统可观测性体系里我们常说日志、指标、链路追踪是三根支柱。但在 AI 应用里单纯把这三件事做对还不够因为你还需要记录“模型内部是如何被喂进来的”。我建议把每个请求都当成一件有身份的事件来处理。也就是说从用户请求进入系统的那一刻起就分配一个 request_id之后每一个环节——Prompt 组装、上下文检索、模型调用、结果返回、用户反馈——都挂在同一个 request_id 下面。这个 ID 就是你在 AI 黑盒里打的手电筒。实际记录哪些字段可以根据场景取舍但下面这几类通常是底线请求侧用户原始输入、会话 ID、请求时间、用户标识脱敏后。上下文侧最终发给模型的完整 Prompt、检索到的知识片段及顺序、被截断的历史消息、上下文总 token 数。模型侧模型名称与版本、temperature、top_p、max_tokens、实际生成的 token 数、耗时、费用。输出侧模型原始输出、后处理结果、是否触发安全过滤、下游工具调用。反馈侧用户是否点赞或点踩、任务是否成功、人工修正结果。这些字段记录下来之后你才真正拥有了“为什么是这种输出”的原始证据。2.2 先回答三类问题再谈优化我平时在做 AI 应用排查时会先把问题分成三类判断优先级输入是否完整用户到底问了什么格式有没有被破坏。上下文是否正确发给模型的内容是不是业务想要的有没有被截断、拼接错乱、检索到无关片段。输出是否符合预期模型生成的结果本身有没有问题是否被后处理改写、过滤或丢失。这个顺序也是排查顺序。很多“模型变笨了”的反馈最后查下来都是上下文拼接出了问题而不是模型本身退化。如果没有 instrumentation你会在第一类和第二类问题上凭空猜测很久。注意不要一上来就怀疑模型“变笨了”。先确认你记录下来的输入、上下文和模型版本再判断是不是效果问题。3. 与概率系统共存不做伪确定性做“可复现的确定性”3.1 模型层的随机性锁不住系统层的确定性必须锁住有一类经验是把 temperature 调到 0结果仍然觉得“不稳定”。这不是玄学。大模型服务端的采样策略、量化方式、批处理负载、版本灰度都可能影响输出。你很难在模型层面获得绝对确定但你可以让系统层尽量确定。具体来说这几件事值得先做固定模型版本不要用 latest 这种会漂移的别名。固定 prompt 模板把模板当成代码来管理改模板要过评审、要留版本。固定上下文组装顺序比如知识库检索结果先按分数排序再按固定规则截断避免每次顺序不一样。固定 Agent 的工具定义和调用顺序至少在同一个版本内保持一致。这些做法的目的不是让模型输出一样而是让同一版本的输入尽量一致从而让问题可以归因。如果你连输入都每次不一样那任何反馈都无法判断是模型问题、上下文问题还是配置问题。3.2 用“输入快照”代替“重跑现场”既然模型输出很难一比一复现那就要换一种思路不再追求复现而是追求留下足够完整的“现场快照”。我一般会在每次模型调用前把最终拼好的 Prompt、检索片段、参数、模型版本写入日志或追踪系统调用后把输出、token 数、耗时、费用也一起写入。这样即使模型下一次输出不同你也能知道“上一次它是在什么输入条件下给出这个答案的”。如果你要认真评估某个 prompt 改动还可以把一批固定问题集跑一遍逐条对比输出。这就是一个很轻量的回归测试。它不能保证模型每次都一样但能让你在改 prompt 或者升级模型之后快速发现哪些问题上变好了、哪些问题上变差了。4. 最小可观测链路从一个请求到一批请求4.1 单条链路怎么搭从工程实现上看最小可观测链路可以分成五个节点。每个节点都记录到同一个 request_id 下入口接收请求生成 request_id记录原始输入。组装记录模板里的变量、检索到的上下文片段和顺序。调用记录模型版本、参数、发送给模型的最终 prompt。返回记录模型输出、后处理结果、token 数和耗时。闭环记录用户反馈、任务状态、人工修正。用一段很简化的 Python 示例来说明大概长这样import time import uuid def handle_query(user_input, retriever, llm, feedbackNone): request_id str(uuid.uuid4()) # 第 1 步入口 record(request_id, input, {user_input: user_input}) # 第 2 步上下文组装 context retriever.search(user_input) prompt build_prompt(user_input, context) record(request_id, context, { context_chunks: context, prompt: prompt, user_input: user_input }) # 第 3 步模型调用 start time.time() result llm.generate(prompt, temperature0.3, max_tokens512) latency time.time() - start record(request_id, model_call, { model_version: llm.version, temperature: 0.3, latency: latency, usage: result.usage }) # 第 4 步输出 record(request_id, output, {answer: result.answer}) # 第 5 步反馈 record(request_id, feedback, feedback or {}) return request_id, result.answer这段代码只是示例结构实际项目里record可以是日志、OpenTelemetry span也可以是专门的 AI 观测平台。核心不是工具而是每个环节都有记录、都有同一个 ID 贯穿。4.2 从一次链路到批量任务再到长期迭代单条链路跑通之后下一步是处理批量任务。批量任务和在线请求不太一样它往往没有“用户反馈”这个闭环所以需要额外记录 job_id、批次号、每一条数据的状态和失败原因。我见过很多批量任务跑完就完了结果过了两天发现有一半数据是异常输出却没办法定位是哪一批、哪一份输入导致的。这类问题通常都能靠一条“批次级日志”解决。再往后才是告警和评估。错误率升高、耗时暴涨、token 费用异常这些指标应该形成基础告警。但如果你的系统还没有稳定的 request_id没有记录输入输出就先不要急着上告警——告警只能告诉你出事了不能告诉你为什么出事。先让可观测性跑起来告警才有意义。建议批量任务先在小样本上验证比如 50 到 100 条确认输出和日志都正常再把量拉起来。不要一上来就全量跑否则异常数据会淹没日志也会造成不必要的 token 费用。5. 为什么大家都想跳过“吃青菜”这一步5.1 不想吃青菜通常有三个原因“吃青菜”用来形容那些不性感但必须做的工程工作特别贴切。放在 AI 应用里就是插桩、记录、测试、版本管理、评估、成本监控。这些东西不会出现在 Demo 演示里也不会让你在朋友圈晒出惊艳效果但它决定了一个 AI 项目能不能活着走到生产环境。大家为什么总是跳过这一步我观察下来有三个原因。第一Demo 太容易成功了。用现成的大模型 API 搭一个聊天机器人一个小时就能跑通。因为跑通太容易很多人会误以为“能用”就等于“可上线”。第二业务压力大。老板看到 Demo 之后会默认你已经完成 80%剩下的时间都在催你上线。第三工具链看起来太强了。AI 编程助手这类工具可以帮你快速生成代码但它不负责帮你建立观测体系。写代码变快了恰恰让你更容易跳过那些不产生短期亮点的工程步骤。5.2 不吃青菜的代价是所有优化都靠猜跳过 instrumentation 的代价不会立刻显现它会在你第一次需要改进效果时爆发。举个例子。你发现某个用户问题总是回答错误。你想优化 prompt但你不知道系统实际发送给模型的 prompt 是什么不知道上下文里有没有掺入无关内容不知道是不是历史消息截断导致关键信息丢了。你只能靠猜猜 prompt 写法、猜检索逻辑、猜是不是模型版本变了。一次猜对是运气长期靠猜就是在浪费团队时间。更麻烦的是一旦模型版本更新、检索算法调整或者 prompt 模板改动你没有任何办法判断哪一次改动是变好了还是变坏了。这就是“吃青菜”的价值它不一定能让你立刻变得更强但它能让你每一次调整都有参照、每一次失败都有原因、每一次成功都可以被复制。它区分了“碰运气”和“做工程”。6. 可观测性建设的边界先做对这几件事再谈全链路6.1 不是所有项目都需要全链路观测平台这里要说清楚一个边界可观测性要分阶段不是所有项目一上来就要上全套平台。如果你只是在本地试用、跑跑样例、验证某个 prompt 想法那用一个简单的日志文件记录输入输出就足够了。我通常会用一个本地 JSON Lines 文件或者直接打印到控制台每条记录带一个时间戳、一个 request_id。够用了。但如果你要把 AI 功能放进生产环境让真实用户使用那就至少要满足三件事第一有稳定的 request_id 贯穿调用链第二能查到任意一个请求的完整输入、上下文、模型版本和输出第三能对错误率、耗时、费用做基础告警。如果团队规模小可以用日志系统加简单脚本做到如果请求量很大再考虑专门的观测平台。我的建议是先有观测数据再选观测工具。没有数据的时候任何工具都救不了你有数据之后最普通的日志也能帮你解决大部分问题。6.2 落地顺序一个可以复用的六步框架如果你想在自己的项目里落地这套思路我建议按这个顺序推进不要跳步选一条真实业务链路最好是会持续迭代的那条比如核心问答或核心 Agent 流程。给每个请求加上 request_id贯穿所有环节。把输入、上下文、最终 prompt、模型版本、参数、输出、token 数、耗时记录下来。固定模型版本和 prompt 模板版本模板改动走版本管理。准备一个 30 到 100 条的小型评估集每次改动后跑一遍对比输出差异。当你能稳定回答“这个请求当时发生了什么”之后再上错误率、耗时、费用的告警。这套框架的核心逻辑是先解决“看不见”再解决“不可复现”最后才是“自动化告警和评估”。很多团队把顺序搞反了先上告警、先上评估平台结果告警响了一堆却因为缺少 request_id 和输入输出记录根本定位不了原因。AI、确定性、可观测性以及吃青菜其实都在回答同一个问题在概率模型构成的系统里人还能不能建立信任和控制感我的答案是能但要靠插桩和观测而不是靠“调低温度”这种表面做法。确定性不是模型的属性而是工程系统的属性。你没办法让大模型像传统函数一样稳定输出但你可以让每次调用都留下足够多的痕迹让任何一次异常输出都能被归因、被复盘、被改进。吃青菜不好吃但它是长肌肉的前提。AI 应用从“能跑”到“能信”之间差的不是更聪明的模型而是那些被所有人默认跳过、却又决定系统能否长期迭代的工程基本功。如果你正在做 AI 应用这周最值得做的一件事不是刷新的 prompt 技巧而是先给你的每次模型调用加上一个 request_id把输入输出记下来。半年后你再回头看会感谢这一步。
返回列表