这次我们来看一个更偏工程视角的 AI 话题:AI 系统的不确定性、可观测性和插桩。这个选题来自一场技术分享,Charity Majors 把 AI、Determinism、Instrumentation 放在一起聊。它没有给你一个可以下载的模型,也没有一键启动的 WebUI,但它解决的问题比“跑通一个 demo”更接近生产:当模型输出不固定、链路又长又黑,你靠什么判断系统是正常还是异常?
先说结论:AI 应用真正难的不是把模型调通,而是让模型在复杂链路里可复现、可观测、可控。模型输出不确定,prompt 改一个词结果就变;用户输入分布和测试集不一致,线上效果立刻衰减;模型更新后没人知道哪个版本正在服务。这些问题的共同答案,就是确定性和插桩。这篇内容适合正在做 AI 应用、模型部署、算法平台或 SRE 的同学,前半部分讲思路,后半部分给可落地的插桩模板、指标设计和排查清单。
我会按这个顺序展开:先给一张核心要点速览,再分析为什么 AI 系统更需要可观测性,然后讲怎样设计插桩数据、怎样定义关键指标,接着给出模型部署、批量任务、API 调用时的工程化做法,最后是常见问题排查和最佳实践。里面包含 JSON 日志示例、Python 元数据记录示例和批量任务伪代码,都是通用模板,需要按你实际项目的接口和路径调整。
1. 核心要点速览
这个选题不是模型跑分,也不是工具安装教程。它的核心是一套工程方法论。所以我不堆模型名和版本号,而是先把关键维度拉一张表。
| 维度 | 要点 |
|---|---|
| 核心问题 | AI 在生产环境中的不确定性和黑盒问题 |
| 关键词 | AI、Determinism、Instrumentation、可观测性 |
| 核心判断 | 确定性需要被设计和记录,插桩是 AI 工程实践的入场券 |
| 落地产物 | 结构化日志、链路追踪、关键指标、实验记录、回归测试 |
| 适用角色 | 后端工程师、平台工程 / SRE、AI 应用开发者、技术管理者 |
| 典型场景 | LLM 对话服务、批量推理任务、模型版本升级、Prompt 调优、成本分析 |
| 不适用场景 | 只做离线实验、不关心线上质量、不需要跨团队协作的临时脚本 |
从工程实践的角度看,AI 系统最大的问题不是“模型效果不够好”,而是“效果好或不好都说不清原因”。传统后端有明确的调用链、状态码和异常堆栈,AI 系统则经常只有一堆 prompt 和一堆输出,坏了不一定抛异常,只是答案变差了。
所以,可观测性不是“上了监控就行”,而是要把每一次推理变成可以被查询、被对比、被重放的数据。这也是插桩在 AI 工程实践中被反复强调的原因:没有数据,就没有确定性;没有确定性,就没有稳定可迭代的 AI 服务。
你可以在任意 LLM、图像模型或语音模型项目里套用这套框架。它不绑定具体技术栈,也不需要立刻引入重量级平台。先从规范日志和记录元数据开始,已经能解决大部分问题。
2. 为什么 AI 系统比传统系统更需要可观测性
2.1 从“函数调用”变成“概率推理输出”
传统后端服务里,一个函数输入固定参数,输出基本可预期。即使出现错误,也有异常类型、堆栈和错误码可以定位。AI 服务不是这样。每次推理都带着概率采样,温度、随机种子、批处理顺序都可能影响输出。即使同一个 prompt,上一次和下一次也可能给出不同的答案。
这意味着,线上出现一个坏结果时,你不能简单地说“代码出 bug 了”。它可能是 prompt 的问题、模型版本的问题、输入分布变化的问题、采样参数的问题,甚至只是用户的表达方式超出训练分布。没有可观测数据,你只能靠重新复现这个 case 去猜。
更麻烦的是,AI 模型的坏结果往往不是“500 错误”,而是“200 响应的内容不对”。传统监控里的错误率、延迟、流量这类黄金信号,只能告诉你服务是否可用,不能告诉你模型回答质量是否下降。可观测性在 AI 场景里,必须覆盖到语义层和决策层。
2.2 看不见的输入漂移和模型漂移
模型训练时的数据分布和线上真实分布永远有差距。上线初期可能不明显,随着用户增长、时间推移,输入的自然语言表达、图片风格、语音口音都会逐渐变化。这种现象叫输入漂移。模型本身也会漂移,比如你在某个版本的权重上做推理,一个月后供应商更新了服务端模型,输出风格和稳定性都变了。
这些漂移不会直接报错,但会慢慢侵蚀业务效果。比如客服机器人开始答非所问,审核模型误判率上升,推荐系统点击率下降。要发现漂移,只能靠持续的指标采集和对比。你至少要记录每个请求的关键输入特征、输出特征、模型版本和推理参数,才能回答“这个变化是什么时候开始的”。
可观测性的本质,是让看不见的问题变成可以查询的表格。AI 可观测性的本质,则是让模型在每一层都留下可查证的脚印。
2.3 成本和质量纠缠在一起
传统服务里的成本相对稳定,接口调用次数和资源用量基本成正比。AI 服务不同,token 数量直接影响成本,而 token 数量又由输入长度、系统 prompt、模型版本、用户行为共同决定。一个使用人数不多的功能,可能因为 prompt 太长、输出 token 上限设置过高,产生远超预期的账单。
成本和质量还经常互相影响。为了省成本,你可能缩短输出长度,结果答案质量下降;为了提高质量,你增加更复杂的 prompt,结果 token 消耗变大。如果可观测性里只有延迟和错误率,没有 token 消耗和单请求成本,优化时就会像盲人摸象。
所以我建议,AI 服务的标准日志里必须包含 prompt_tokens、completion_tokens、总 token 数,并尽可能计算出单次推理成本。这样才能把“质量变差了”和“成本变高了”放在同一张表里分析。
3. 确定性:AI 工程实践里最容易忽略的复现问题
3.1 AI 确定性到底指什么
Determinism,确定性,指的是“同样的输入和条件,是否得到同样的输出”。传统代码里这是默认行为,到了 AI 场景反而成了稀缺品。模型的推理结果受采样参数、随机种子、浮点计算顺序、硬件型号甚至批处理大小影响,同一个 prompt 在不同环境下可能得到不同结果。
确定性之所以重要,是因为它直接决定了你能不能调试。如果同一个问题在线上复现不出来,所有的性能排查、质量评估、安全审查都会变得异常困难。你可以接受模型有一定随机性,但必须知道随机性来自哪里,并且有能力在需要时固定它。
一个可落地的做法是:为每次推理生成一个 inference_id,并记录完整的上下文。这样无论结果好坏,你都能回到真实的请求现场,而不是对着一条孤零零的日志猜测。
3.2 每次推理至少要记录什么
下面这段 Python 代码是一个通用的推理元数据构建示例,比较适合作为记录的最小集合。实际字段按项目调整。
import hashlib import time import uuid def build_inference_metadata(prompt, model_name, model_version, temperature=0.0, seed=42): prompt_hash = hashlib.sha256(prompt.encode("utf-8")).hexdigest()[:16] return { "inference_id": str(uuid.uuid4()), "timestamp": time.time(), "model_name": model_name, "model_version": model_version, "prompt_hash": prompt_hash, "temperature": temperature, "seed": seed, "max_tokens": 512, "stop_sequences": ["\n\n"], }这段代码解决三个问题:一是每条推理都有全局唯一 ID,方便和链路追踪关联;二是记录模型版本,方便后续做回归对比;三是记录 prompt 的哈希值,既能在不暴露完整文本的前提下判断“哪些请求用了相同 prompt”。
如果你在图像生成、语音合成等场景,可以类似地记录输入图哈希、参考音频哈希、分辨率、步数、采样器等关键参数。原则是一样的:保证实验结果可以被复盘。
3.3 不要迷信“温度设为 0 就完全确定”
很多团队会把 temperature 设为 0,以为这样输出就完全稳定。实际上,温度只是影响随机性的一个因素。GPU 算子、并行推理顺序、模型量化、API 服务端的隐藏更新,都可能带来细微差异。对于大部分业务场景,温度设为 0 能显著降低随机性,但你不能把它当作绝对保证。
更可靠的做法是:把确定性问题拆成“我们能否复现”和“我们需要多大程度稳定”两个层次。像分类、抽取这类结构化任务,要求严格稳定;像文案润色、创意生成这类任务,接受一定随机性,但每次推理都必须记录参数,否则后面没法归因。
另外,如果你的应用依赖外部模型 API,确定性会更难控制,因为你无法控制服务端的 batch 和推理实现。这时候要在应用层做兜底:保存输入和输出快照,定期对关键 case 做对比测试,而不是假设同一个请求永远返回同一个结果。
4. 插桩不是记日志:AI 可观测性的数据采集设计
4.1 传统系统日志和 AI 事件日志的差别
传统日志经常是“发生了什么异常”的记录,格式相对简单:时间、级别、消息、堆栈。AI 场景需要的是“一次推理完整旅程”的记录,它更像事件流,而不像错误日志。
一个 AI 事件至少应该包含以下几个维度:
- 请求入口信息:用户、会话、trace_id、来源渠道。
- 模型调用信息:模型名、模型版本、温度、seed、max_tokens、停止词。
- 输入信息:prompt 或输入的哈希、长度、关键特征。
- 输出信息:输出文本或结果的哈希、长度、token 数。
- 性能信息:首 token 延迟、总延迟、排队时间。
- 成本信息:prompt_tokens、completion_tokens、估算成本。
- 质量信息:人工评价、点赞点踩、是否安全、是否触发兜底逻辑。
传统日志只要做到“能定位异常”,AI 事件日志要做到“能还原现场”。两者差别很大,前者是事后查错,后者是事中和事后的综合复盘。
4.2 一份可落地的 AI 结构化日志模板
JSON 格式是最容易落地的结构化日志格式,方便写入 Elasticsearch、ClickHouse、Loki 或各类云日志平台。下面是一个最小示例:
{ "timestamp": "2025-01-01T10:00:00Z", "trace_id": "abc123", "inference_id": "uuid-001", "event": "llm_completion", "model": "gpt-4o-mini", "model_version": "2025-01-01", "prompt_hash": "f1a2b3c4d5e6", "prompt_tokens": 230, "completion_tokens": 120, "total_tokens": 350, "latency_ms": 850, "first_token_latency_ms": 120, "cost_usd": 0.0002, "temperature": 0.0, "seed": 42, "response_hash": "a1b2c3", "user_feedback": null, "status": "success" }字段不是固定的。你可以按业务加入 input_schema、output_schema、错误码、重试次数、缓存命中与否等。
这里特别提醒:不要把完整的原始 prompt、用户隐私数据、文件内容直接写进日志。建议对敏感内容做哈希或脱敏处理,日志平台也要做权限控制。合规问题是 AI 工程实践里绝不能忽略的一环。
4.3 插桩密度:全量、采样和摘要
很多团队担心“全量插桩”会带来大量日志成本。确实,AI 日志字段多、单条体积大,全量保存到生产环境压力不小。比较稳妥的策略是分级处理:
- 全量记录:inference_id、trace_id、model_version、token 数、延迟、状态这些基础字段。
- 采样记录:原始输入输出中按比例采样,比如 10% 或 1%,用于质量评估。
- 摘要记录:对完整 prompt 和输出做哈希、长度统计,用于分布分析。
- 关键 case 全保留:触发异常、用户反馈差、成本异常高的请求,一定要完整保存。
这个思路既控制了存储成本,又保留了关键时刻的完整现场。不要一上来就“全部都要”,也不要因为成本干脆什么都不记。根据实际流量的量级逐步加大密度,比一开始追求完美更现实。
4.4 隐私和合规边界
采集 AI 推理数据时,要特别注意数据合规。用户输入的内容可能包含个人信息、商业机密,输出内容也可能涉及版权问题。建议遵循以下原则:
- 能脱敏就脱敏,能哈希就哈希。
- 日志平台做访问控制,禁止随意查询原始输入。
- 批量任务里的输入文件,处理完要及时清理或加密归档。
- 涉及人脸、声音、私有文档的场景,必须先确认是否有合法授权。
- 如果使用第三方模型 API,要明确数据是否会被用于训练,必要时用私有化部署方案。
这不是可有可无的“政治正确”,而是生产系统能不能长期运行的基础条件。插桩采集到的数据越敏感,系统在隐私保护上的要求就越高。
5. 落地一套 AI 可观测指标体系
5.1 通用性能指标
传统可观测性里的指标在 AI 服务里依然有效。我建议把以下几类作为最低标准:
| 分类 | 指标 | 含义 |
|---|---|---|
| 流量 | 请求数、并发数、token 消耗总量 | 看服务用量和成本趋势 |
| 性能 | P50/P95/P99 延迟、首 token 延迟、排队时间 | 判断用户体验和瓶颈 |
| 错误 | 请求失败率、超时率、异常响应率 | 判断服务是否可用 |
| 资源 | CPU、内存、显存、磁盘 | 判断部署资源是否充足 |
特别是首 token 延迟,对 LLM 流式输出非常重要。传统服务的“整体延迟”在流式场景下不够直观,用户往往更关心第一个字什么时候出现。这个指标应该在应用层埋点计算,而不是只依赖模型调用耗时。
5.2 AI 特有质量指标
通用指标只能告诉你“系统是不是活着”,AI 特有指标才能告诉你“系统是不是输出得对”。质量指标因业务而异,下面列出几类常见方向:
- 语义相似度或人工评分:适用于聊天、客服、内容生成。
- 结构化字段准确率:适用于提取、分类、标签任务。
- 用户反馈信号:点赞/点踩、复制、采纳、二次编辑率。
- 安全指标:违规内容拦截率、幻觉率、敏感信息泄露率。
- 成本指标:单请求 token 数、单请求成本、成本环比变化。
这些质量指标不能只靠模型离线评估,必须和线上真实用户行为绑定。比如对话机器人可以记录“用户是否重新提问”,推荐系统可以记录“推荐内容的点击率”,内容生成工具可以记录“用户是否再次编辑”。这些行为信号比任何离线评估都更接近业务结果。
5.3 阈值怎么定、报警怎么设
很多团队一上来就设一堆阈值,结果要么告警风暴,要么阈值形同虚设。更稳妥的做法是先记录两周基线数据,再看指标分布定阈值。
比如延迟类指标,可以先看 P95 基数,再设“超过基线 20% 持续 5 分钟”之类规则;成本类指标,先看日均 token 消耗,再设“单日成本超出预期 30%”的额度告警;质量类指标,如果人工评分还没有建立,就先别急着报警,先把数据收集起来。
报警不是目的,定位才是。每条报警都应该能跳转到对应的 trace_id、inference_id 和原始事件日志。如果报警只能告诉你“服务变慢了”,但你不能定位是某个模型版本、某类 prompt 还是某类输入造成的,那这个报警价值很低。
6. AI 模型部署、批量任务与接口调用的工程化
6.1 模型发布前的回归验证
AI 模型升级不能只看离线指标。版本上线前,应该准备一套固定回归测试集,覆盖典型场景和边界情况。回归的目的是比较新旧版本在同一批输入上的输出差异,尤其是关键业务字段和安全性。
回归测试不一定要自动化得很复杂。最基础的做法是维护一批固定 prompt 或输入文件,每次模型升级后跑一遍,人工或半自动地检查输出是否符合预期。如果输出质量明显变化,就要追查是新模型的问题,还是有意的 prompt 变化。
建议把回归测试的输入、输出、模型版本、时间都记录下来。这样后续如果有人问“这个输出是哪个版本生成的”,你能给出明确答案。
6.2 金丝雀发布先跑小流量
模型服务不像普通后端服务,切换版本后不能保证输出行为一致。直接全量上线有风险,更稳妥的方式是金丝雀发布:先在少量流量上切到新模型,和旧模型的结果做对比,确认关键指标没有恶化,再逐步扩大流量。
实际操作中,可以在网关或应用层加一个分流开关,按用户 ID、请求 ID 的哈希百分比控制新模型流量。同时把新旧两个版本的结果都记录到可观测平台,比较 token 消耗、延迟、用户反馈、安全命中率等指标。
金丝雀发布需要可观测数据支撑,否则你不知道该在什么时候回滚。这里又回到了插桩的必要性:没有版本号、没有 trace_id、没有结果对比,你只能用“感觉新模型好像不太行”来决策,这显然不是工程化的方式。
6.3 批量任务的任务状态与幂等设计
批量推理是 AI 工程实践里很常见的一类场景:大量文档解析、批量图片处理、离线内容审核、TTS 音频生成等。批量任务比在线接口更容易出问题,因为任务量大、执行时间长、失败原因多,而且中断后不好恢复。
批量任务至少要有以下字段:task_id、输入文件引用、任务状态、重试次数、幂等键、开始时间、结束时间、最后一次错误信息。下面是一个参考字段结构:
import hashlib import uuid def create_task(input_ref, max_retry=3): task = { "task_id": str(uuid.uuid4()), "input_ref": input_ref, "input_hash": hashlib.sha256(input_ref.encode("utf-8")).hexdigest()[:16], "status": "pending", "retry_count": 0, "max_retry": max_retry, "idempotency_key": hashlib.sha256(input_ref.encode("utf-8")).hexdigest(), "created_at": None, "started_at": None, "finished_at": None, "last_error": None, } return task幂等键的设计非常关键。比如处理同一个文档时,网络中断导致任务重试,如果不带幂等键,就可能重复生成、重复计费。生产级任务队列通常会设置一个唯一业务键,重复投递时直接被丢弃或返回已有结果。
批量任务的状态机建议至少包含:pending、running、succeeded、failed、retrying、dead。dead 队列用来存放多次重试仍然失败的任务,方便人工介入。所有状态变更都需要有时间戳和错误信息,这样排查时才能快速定位是哪一个文件、哪一次执行、什么原因失败。
6.4 API 接口调用与重试策略
AI 服务通常会暴露 HTTP API。调用方需要注意超时、重试和限流问题。下面是一个简单的 Python 调用模板:
import requests def call_completion_api(prompt, temperature=0.0, timeout=30): url = "http://127.0.0.1:8000/v1/completion" payload = { "prompt": prompt, "temperature": temperature, "max_tokens": 512, } try: response = requests.post(url, json=payload, timeout=timeout) response.raise_for_status() return response.json() except requests.exceptions.Timeout: return {"error": "timeout", "prompt": prompt[:50]} except requests.exceptions.HTTPError as exc: return {"error": f"http_error: {exc.response.status_code}"}实际使用时需要按项目的接口地址、鉴权方式和参数名调整。这里重点想说的是两个工程原则:
- 网络错误可以重试,但要把超时时间设短,并加指数退避,避免雪崩。
- 业务错误不能盲目重试。比如模型返回内容不合规、被限流、参数错误,重试只会增加成本。
如果有缓存,还要注意缓存键的设计。缓存键应包含模型名、模型版本、prompt 哈希、采样参数等,否则新版本模型上线后,还可能命中旧版本的结果缓存,造成版本混乱。
6.5 接口服务的安全边界
对外或对内提供 AI 接口时,安全边界不能省:
- 接口要加鉴权,避免被任意调用刷成本。
- 如果接口面向公网,建议做速率限制、配额管理和访问来源限制。
- 批量上传的输入文件要做类型检查、大小检查和内容安全扫描。
- 涉及肖像、声音、版权素材的任务,必须保留授权记录,不能把“用户提交了”当成“用户有权使用”。
这是 AI 工程实践里容易被忽略但风险很高的部分。代码和模型再完善,如果接口裸奔,出的就是安全事故。
7. 常见问题与排查方法
下面按实际工程中经常出现的问题整理一张排查表。它不绑定具体平台,只提供排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 相同模型相同 prompt 结果不一样 | 采样参数、随机种子、批处理顺序、API 服务端隐藏更新 | 对比两次推理的 metadata 和请求参数 | 固定 temperature/seed,记录模型版本,关键任务使用缓存 |
| 模型升级后线上结果变差 | 没有做回归测试就直接全量上线 | 查看生产流量中的 model_version 分布 | 建立固定回归测试集,先金丝雀发布再逐步放量 |
| 接口偶发超时 | 服务冷启动、排队过长、下游模型 API 抖动 | 查看排队时间、首 token 延迟、下游调用日志 | 加超时和重试,做负载均衡,必要时扩容 |
| 批量任务卡住不结束 | 任务缺少状态机、没有超时回收机制 | 查看任务状态是否长期停留在 running | 增加任务超时、重试和死信队列 |
| 日志查询不到或者难定位 | 没有 trace_id / inference_id,日志字段不统一 | 检查日志是否关联到同一个请求上下文 | 在入口生成请求级 ID,全链路透传上下文 |
| 成本突然上涨 | prompt 变长、输出过多、模型版本成本上涨、被恶意调用 | 按 model、接口、用户维度分组统计 token 成本 | 设置 token 上限、限流、配额管理,建立成本告警 |
| 显存或内存不足 | 并发过高、模型过大、批量数设置过大 | 观察资源曲线和请求并发量 | 降低 batch size、模型量化、排队限流、扩容 |
| 模型输出质量不稳定 | 输入漂移、prompt 设计脆弱、模型版本变动 | 对比输入分布、输出分布、用户反馈指标 | 建立质量基线,定期回归,对关键场景做兜底规则 |
这里需要特别提醒一点:如果你的系统里 trace_id 和 inference_id 还没有贯通,排查任何 AI 问题时都会非常吃力。你可以在网关层生成统一请求 ID,然后通过日志库的上下文传递到下游,把一次用户请求涉及的所有模型调用串起来。这是一切高级监控、评估和归因的基础。
8. 吃你的蔬菜:AI 工程实践里的最佳习惯
8.1 把基础工程放在追新模型前面
“Eating your broccoli”翻译成工程语言,就是:做那些不好看、不性感、但必须做的基础工作。每天追新模型、换更强的 prompt 技巧,当然让人兴奋;但如果没有版本管理、没有结构化日志、没有回归测试、没有批量任务的可重试机制,新模型带来的收益很快会被混乱抵消。
从长期看,一个团队能不能稳定交付 AI 能力,取决于基础工程做得有多扎实。固定的模型版本、固定的数据采集流程、固定的发布流程,这些都比某一个模型的精度提升更重要。不是反对用新模型,而是要让新模型在新的可观测机制下安全上线。
我最常看到的翻车案例是:团队花大量时间调 prompt,线上效果一旦变差,没有人能回答“是不是模型服务端偷偷换了版本”或者“哪些用户输入导致输出漂移”。这类问题靠换模型解决不了,只能靠可观测性解决。
8.2 团队落地建议
如果团队刚开始建设 AI 可观测性,不要一口吃成胖子。建议分三步走:
第一步,从一次推理的最小元数据开始。为每个请求补上 inference_id、model_version、prompt_hash、token 数和时间戳。
第二步,把日志格式统一成 JSON,并把 trace_id 或业务请求 ID 透传到所有下游服务。这一步完成后,至少可以回答“这个用户这次请求经历了什么”。
第三步,把模型发布流程和回归测试打通。每个模型版本上线前必须跑固定测试集,上线后保留一段时间的流量对比,再决定是否全量。
这三步做完,你已经超过很多“只跑 demo、只调 prompt”的团队了。后面再上自动评估、异常检测、成本分析平台,都是水到渠成的事。
8.3 最值得先做的三件事
如果只能做三件事,我强烈建议按下面的顺序来:
第一,固定模型版本和关键推理参数。不要在线上“随机使用最新模型”,每次调用都要能追溯到具体版本。
第二,给每次推理生成一个唯一 ID,并把它写入日志、缓存键、任务状态和错误信息里。这个 ID 是所有排查工作的锚点。
第三,先把结构化日志模板做出来。JSON 里哪怕先只有十个字段,也比散落的、没有上下文的文本日志强得多。有了结构化数据,后面用工具分析或写脚本统计都顺畅。
这三件事都不需要引入大型平台,也不需要一两天就能全部做完。但它们决定了你在生产环境里能不能快速定位一个 AI 故障,决定了你换模型、调 prompt、分析成本时有没有依据。先不要急着上复杂平台,也不要把精力全放在追最新模型上。把插桩和确定性这两件基础工作吃透,比任何一次模型升级都更值。
建议收藏备用,尤其是你正在把 AI 功能推到生产环境的时候。先把最小可观测闭环跑通,再谈更高级的自动评估和成本优化,这条路更稳。