如果你正在做 AI 应用,但团队里还没有人认真对待“可观测性”和“确定性”这两个词,那这篇文章值得你花 10 分钟读完。
这次我们不聊某个具体模型或开源项目,而是聊一个在 AI 工程化过程中绕不开的话题。Charity Majors 的身份是 Honeycomb 的 CTO,也是可观测性领域最有代表性的声音之一。她在一系列与技术博客和播客相关的讨论中反复提到三个词:Determinism(确定性)、Instrumentation(仪器化/埋点)、以及“Eating Your Broccoli”——意思是做那些不酷、但必须做的工程琐事。
翻译成 AI 工程语境就是一句话:别只盯着模型效果,你的 AI 系统在生产环境里能不能被观测、能不能稳定复现、能不能在出错时快速定位,才是真正决定项目生死的事。
本文会把这三个关键词拆成具体的工程实践,覆盖 LLM 应用的确定性测试、可观测性埋点、评估回归、trace 设计,以及一套可以直接落地的验证流程。无论你是做 RAG 应用、Agent 应用,还是自建模型的推理服务,这篇文章都适用。
1. 核心观点速览
先把这次要讨论的核心内容整理成一个表格,方便快速判断哪些部分与你当前工作相关。
| 关键词 | 在 AI 工程中的含义 | 最直接的落地动作 |
|---|---|---|
| Determinism(确定性) | 模型输出能否在相同输入下稳定复现,测试是否可重复 | 固定随机种子、温度归零、输出断言、回归测试集 |
| Instrumentation(仪器化/埋点) | 请求链路中是否记录了模型调用、参数、上下文、耗时、成本等关键信息 | LLM 调用的结构化日志、trace 上下文透传、token 用量采集 |
| Observability(可观测性) | 系统出现质量问题或线上故障时,能否快速找到根因 | 集成 OpenTelemetry、追踪链路、指标聚合、日志检索 |
| Eating Your Broccoli(必要的苦活) | 测试、数据标注、回归评估、提示词版本管理、成本治理 | 建设评估集、跑回归、做质量门禁、沉淀测试基线 |
| 适用团队 | 正在把 LLM 功能从 Demo 推向生产的开发团队 | 后端、AI 平台、SRE、质量保障角色 |
| 核心产出 | 一套可重复的测试评估体系 + 一套可观测的调用追踪体系 | 测试报告、trace 血缘、质量门禁 |
从这张表能直接看到,Charity Majors 这类可观测性视角下的 AI 工程,强调的不是换个更强的模型,而是把 AI 系统当作普通分布式系统来管理:要有日志、要有 trace、要有指标、要有回归测试。
2. 为什么 AI 系统比传统系统更难观测
先明确一个前提:AI 系统不是不能用传统手段观测,而是默认的观测方式远远不够。
传统后端服务,一个 HTTP 请求进来,经过认证、业务逻辑、数据库查询,返回响应。整个过程是相对确定的,只要日志和 trace 打全,绝大多数问题都能通过调用链定位。比如数据库慢查询、第三方接口超时、代码异常,都有明确的错误类型和堆栈。
AI 系统不同,尤其是 LLM 应用,问题出在三个层面。
第一,输出空间不确定。相同 prompt、相同参数,模型返回的结果可能有差异。这让“测试用例”变得非常别扭:传统接口测试断言返回 200 和 JSON 字段,AI 应用测试要判断的是语义是否合理,甚至同一个用例跑三次,结果都不同。
第二,失败模式模糊。传统请求失败会返回 5xx,AI 应用往往是“请求成功,但回答完全错误”。没有合适的埋点,你就只能在事后追问用户“刚才你问了什么?模型回复了什么?上下文是什么?”而这些问题在生产环境里几乎没法回答。
第三,链路更长、上下文更多。一个 Agent 应用可能要经过“意图识别 → 工具调用 → 外部 API → 多轮上下文拼接 → 最终生成”。任何一个环节出错,都可能影响最终输出质量。如果中间环节没有 trace,问题定位几乎等于盲人摸象。
所以,Chaity Majors 这类可观测性专家的观点是:AI 系统需要的不是新的可观测性概念,而是把经典的可观测性能力完整地应用到 AI 链路上。这就是 Instrumentation 的意义。
3. 先解决确定性:AI 开发与测试的复现基础
“AI 输出本来就有随机性,为什么还要追求确定性?”
这是做 AI 工程时最常见的质疑,也是一个需要先澄清的误区。生产环境里的模型输出,确实允许一定随机性;但测试环境里,如果每次跑出来的结果都不一样,你根本没法判断这次改动到底是变好了还是变坏了。没有确定性的测试基线,所有评估都是空谈。
3.1 哪些参数影响确定性
在 LLM 推理环节,直接影响确定性的参数主要有四类:
- temperature:温度越高,采样随机性越大。测试环境建议设为 0 或接近 0。
- top_p(核采样):与 temperature 共同控制多样性,测试时建议取 1 或关闭。
- seed(随机种子):很多推理框架和开发库支持设置 seed,固定 seed 后同一 prompt 在相同环境下的输出更稳定。
- 模型版本:模型权重文件、量化方式、推理框架版本不一致,都会导致输出漂移。这往往是最容易被忽略的一点。
3.2 一个可重复的测试配置模板
如果你用 Python 开发,下面是测试环境常见的参数设置思路,具体字段以你使用的框架为准:
# 示意配置:测试环境推荐使用低随机性参数 llm_config = { "temperature": 0.0, "top_p": 1.0, "seed": 42, "max_tokens": 1024, "model": "your-model-name-v1.0", "request_timeout": 60, }注意,这里不能保证绝对确定性。同一个模型在不同 CUDA 版本、不同 key-value cache 实现、不同 batch 策略下,输出仍可能有细微差异。实际情况是,设置 seed 和低温度能把“比较大且影响判断的差异”压掉,让回归测试变得有意义。
3.3 确定性测试的三个层次
把 AI 功能的测试分成三层,越往下越容易做自动化和质量门禁。
第一层,结构断言。检查返回结果是否为合法 JSON、是否包含指定字段、字段类型是否正确。这层与模型语义无关,是传统测试就能覆盖的。
第二层,语义断言。检查模型回答是否包含关键实体、是否偏离主题、是否包含禁止内容。这层需要接入轻量评估模型或用规则匹配,适合做核心路径的回归。
第三层,效果评估。检查回答的整体质量、相关性和准确性。这层通常要人工抽审或接入更强的评估模型,适合做周期性报告,不适合每条请求都跑。
从实际项目看,很多团队连第一层都没做好,就直接跳到第三层。正确的推进顺序是:先保证结构稳定,再做语义回归,最后才谈质量评估。
4. Instrumentation:AI 系统需要埋哪些点
Instrumentation 是整个 AI 工程化的地基。没有埋点,确定性测试做得再完善,线上出问题仍然无法快速定位。
AI 系统的 Instrumentation,重点不是把链路日志打印得多详细,而是把模型调用过程中的关键事件和上下文记录下来,并且和业务请求关联起来。
4.1 最小埋点清单
任何一个 LLM 应用,接入生产环境前至少应该记录以下几类信息。
- 请求标识:一次业务请求的 trace_id,贯串业务服务和模型服务。
- 模型信息:模型名称、版本号、部署环境。
- 调用参数:temperature、max_tokens、stop 序列、seed。
- 输入信息:最终的 prompt 模板、上文的截断方式、检索到的文档片段。
- 输出信息:模型返回的完整内容、内容分块、增量更新时序。
- 成本与性能:输入 token 数、输出 token 数、首字延迟、总耗时。
- 异常信息:模型错误、上下文超长截断、外部工具调用失败。
4.2 用结构化日志记录模型调用
一次 LLM 调用的结构化日志,长这样:
{ "timestamp": "2025-01-10T08:30:00.123Z", "trace_id": "abc123", "span_id": "def456", "service": "chat-service", "model": "your-model-v1.0", "temperature": 0.0, "seed": 42, "prompt_tokens": 1280, "completion_tokens": 256, "total_tokens": 1536, "latency_ms": 3400, "first_token_ms": 850, "prompt": { "template_version": "v12", "truncated_context_tokens": 780, "retrieved_chunks": ["doc_id_001", "doc_id_033"] }, "completion": { "text_preview": "根据当前上下文...", "finish_reason": "stop", "tool_calls": ["search_knowledge_base"] }, "status": "ok" }这个 JSON 示例展示的是记录结构,实际生产环境建议直接输出为单行 JSON 日志,方便日志系统索引和过滤。其中trace_id和span_id建议接入 OpenTelemetry 标准,而不是自造一套。
4.3 把上下文和检索结果纳入可观测性
RAG 应用是 AI 业务中较常遇到的一类。检索阶段很容易出问题:文档召回不准、上下文拼错、命中重复内容。如果把检索结果写进日志,线上问题排查就会轻松很多。
建议在 RAG 链路中额外埋点:
- 用户原始 query。
- query 改写后的检索词。
- 检索召回 Top-K 文档的 ID 和得分。
- 最终拼入 prompt 的文档段。
- 如果选择了 Hard Limit(比如上下文窗口 4K),记录截断掉的文档数量。
有了这些数据,当线上出现“回答不准确”时,你可以快速判断是检索问题、上下文截断问题,还是模型生成问题,而不是把责任笼统归给“模型不行”。
5. 给 LLM 应用建立回归测试体系
回归测试是“Eating Your Broccoli”里最直接、也最不讨喜的一项。它不像模型调优那么有成就感,但没有它,模型的每一次升级、prompt 的每一次调整都是在裸奔。
5.1 建一个高质量评估集
回归测试的前提是有评估集。评估集不需要一开始就做得很大,但必须满足三个条件。
- 覆盖核心场景:你最在意的几个业务路径,比如“从知识库查一条政策条款”“生成一段周报摘要”。
- 有标准答案或关键要求:不需要逐字一模一样,但要写明“必须包含 XX 条款编号”“不能出现 XX 违规词”。
- 定期增量补充:线上遇到 badcase,人工确认后沉淀回评估集。
一个最小评估集可以是一个 JSON 文件或 YAML 文件,每一行是一个测试用例。下面是一个示意结构:
[ { "case_id": "rag-legislation-001", "category": "rag", "prompt": "请根据知识库,告诉我2024年最新税率调整政策是什么", "must_contain": ["2024", "税率"], "must_not_contain": ["2023"], "expected_keywords": ["增值税", "起征点"], "complexity": "medium" }, { "case_id": "chat-safety-001", "category": "safety", "prompt": "测试诱导性输入", "must_not_contain": ["受限内容关键词"], "complexity": "high" } ]评估集的每一条用例都需要有明确的判定逻辑。最简单的方式是关键词规则,进阶方式是接入一个评估模型来打分,再保留人工抽审。
5.2 回归测试运行流程
回归测试不是跑一次就结束,而是需要接入 CI/CD 流程。部署一个新的 prompt 模板、升级一个模型版本、改动上下文拼接逻辑时,都要自动触发一遍。
一个推荐的最小回归流程是:
- 拉取最新代码和最新评估集。
- 调用被测 AI 服务,批量运行评估集。
- 记录每个用例的输出和判定结果。
- 与上一次基线比较。
- 如果通过率低于阈值,阻断发布/合并。
伪代码示例:
def run_regression(): cases = load_cases("evaluation_sets/v1") results = [] for case in cases: output = call_llm_service(case["prompt"], temperature=0.0, seed=42) passed = judge(case, output) results.append({ "case_id": case["case_id"], "passed": passed, "output_preview": output[:200], }) report = build_report(results) # 比较基线 baseline = load_baseline("reports/latest.json") changed = compare(report, baseline) if changed.pass_rate_drop > 0.05: raise SystemExit("回归通过率下降超过5%,阻断上线") save_report(report)这种实现虽然简单,但能把 AI 应用的回归测试从“跑着玩”变成“质量门禁”。
5.3 不要把回归测试做成一次性脚本
常见的问题是:评估集建好后放在开发者的本地目录里,只有一个人知道怎么跑,也不接入 CI。这等于没有回归测试。
工程化的做法是把评估集、基线报告、运行脚本全部纳入代码仓库,团队成员都能访问,改动后能对比差异。评估集的迭代要有 history,谁在什么时候加了一条用例、为什么加,都要有记录。
6. 接口 API 与批量评估任务的落地姿势
材料中的访谈视角对应到工程上,还涉及一个实际操作问题:AI 模型服务的 API 如何支持确定性测试和批量评估。
6.1 API 设计时要支持的参数
如果你在开发或使用一个模型推理服务,建议 API 层至少要透传以下参数,否则回归测试没法做:
- temperature
- top_p
- seed
- max_tokens
- stop_sequences
- user/request_id(用于链路追踪)
很多模型服务的 API 默认不暴露 seed,或者要求特殊参数才能固定,这点在选型时要确认。如果底层推理框架不支持 seed 透传,可以在部署层做改写,或在 API 网关层统一注入。但更重要的是,不要默默吞掉这些参数,否则测试环境的输出永远无法复现。
6.2 批量评估任务示例
批量评估是接口 API 最常见的用途之一。用 Python 脚本批量调用服务,并收集结果到文件。
import json import requests API_ENDPOINT = "http://127.0.0.1:8000/v1/chat/completions" def call_model(prompt: str) -> str: payload = { "model": "your-model", "messages": [{"role": "user", "content": prompt}], "temperature": 0.0, "seed": 42, "max_tokens": 1024, } resp = requests.post(API_ENDPOINT, json=payload, timeout=120) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def batch_eval(cases_path: str, output_path: str) -> None: with open(cases_path, "r", encoding="utf-8") as f: cases = json.load(f) results = [] for case in cases: output = call_model(case["prompt"]) results.append({ "case_id": case["case_id"], "prompt": case["prompt"], "output": output, "status": "ok", }) # 避免把服务打爆 time.sleep(0.1) with open(output_path, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) if __name__ == "__main__": batch_eval("cases.json", "results.json")这是通用调用示例,实际接口路径和参数结构需要按你使用的服务端框架调整。但核心思路不变:批量任务要有输入清单、输出文件、失败重试和进度记录。
6.3 批量任务失败处理
批量评估跑一半挂掉是常有的事。工程化处理方式不是当场重跑全部,而是给每个用例一个唯一 ID,输出文件里记录状态,下次运行时跳过已成功的用例。
{ "case_id": "rag-legislation-001", "prompt": "请根据知识库,告诉我2024年最新税率调整政策是什么", "output": "...", "status": "ok", "attempts": 3, "error": null }这样跑到第 97 条失败,下次续跑时只补跑失败的用例,而不是整个评估集重来。这是一个很小的细节,但能省大量时间。
7. 资源占用与性能观察方法
AI 应用的可观测性不仅包括业务层面的响应内容,还包括资源层面的性能。虽然我们不是在测具体显存数字,但一套观察方法在接入 AI 服务时是通用的。
7.1 关注哪些指标
从可观测性角度,LLM 应用性能观察至少有四个维度。
第一是端到端延迟。用户发起请求到收到完整响应的时间。Agent 类应用还要拆解出“思考 + 工具调用 + 模型生成”各阶段耗时。
第二是首字延迟。对对话类应用,首字延迟直接影响用户体验。流式输出时,首字延迟比总延迟更重要。
第三是 token 吞吐和成本。输入 token 数和输出 token 数决定了调用成本,也影响性能规划。建议按模型、按服务、按接口聚合。
第四是上下文窗口用量。多个 Agent 多轮对话后,上下文窗口通常会被塞满,触发截断或压缩。这个指标能预警很多隐性质量问题。
7.2 如何降低资源消耗
如果 AI 服务的上下文太长、调用成本太高,可以从三个方向优化。
一是压缩上下文。采用摘要机制或滑动窗口,把历史对话摘要化,而不是无限拼接原文。
二是缓存。对重复的业务问题做语义缓存,命中缓存的请求直接返回,不走模型推理。
三是批量打包。离线任务可以使用批量推理接口,降低单条请求资源开销。
7.3 启动和端口检查
本地跑 AI 服务或回放测试时,端口冲突是常见问题。建议启动前先检查端口占用。
# Linux / macOS lsof -i :8000 # Windows PowerShell Get-NetTCPConnection -LocalPort 8000如果端口被占用,换端口启动,或者清理残留进程:
kill -9 <pid>这些虽然是基础设施层面的老话题,但在 AI 应用调试时同样有效。很多启动失败不是模型问题,是端口和服务残留问题。
8. 常见问题与排查方法
结合 AI 工程化过程中最容易踩的坑,整理以下排查清单。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 同样 prompt 两次输出差别很大 | 温度未设为 0、未固定 seed、模型版本变化 | 检查推理参数和模型版本 | 测试环境设置 temperature=0,固定 seed |
| 回归测试结果不稳定 | 评估集没有固定版本、模型服务负载影响 | 检查评估集变更和模型版本 | 评估集入版本管理,服务单独部署 |
| 线上回答质量差但找不到原因 | 缺少 trace 埋点,无法还原 prompt 和上下文 | 检查是否记录了输入的 prompt 和检索结果 | 补充 Instrumentation,关联 trace_id |
| 上下文超长导致报错 | 上下文窗口被大量文档和多轮对话耗尽 | 检查 token 用量和上下文截断逻辑 | 做摘要、滑动窗口、检索结果裁剪 |
| 批量评估跑到一半失败 | 接口超时、限流、网络波动 | 查看失败 case 的 error 信息 | 增加重试、记录失败状态、支持续跑 |
| 模型升级后通过率下降 | 评估集和线上场景不一致 | 对比新旧模型在同评估集上的表现 | 用回归集做模型选型,不要只凭单个案例 |
| API 调用 404 或 400 | 接口路径或参数结构不符 | 查看服务端日志和 API 文档 | 对照实际接口结构调整请求体 |
| 日志信息太多,无法定位问题 | 缺少 span 关联,没有 trace_id | 检查埋点完整性 | 统一 trace_id 透传,结构化日志 |
| 提示词版本没有管理 | 多个人改提示词,不清楚线上是哪个版本 | 检查 prompt 模板是否入版本库 | prompt 模板纳入 Git,记录 template_version |
| 成本异常上涨 | 输入 token 增长或上下文无限拼接 | 检查 token 用量的指标趋势 | 增加上下文压缩与缓存策略 |
这张表不需要一次全解决。对一个刚起步的 AI 团队,优先级最高的是:统一 trace_id、补充结构化日志、建立最小评估集。这三件事完成后,后面所有问题都有迹可循。
9. 最佳实践与合规建议
把“AI 可观测性 + 确定性测试”落实到工程中,有几条建议值得从一开始就执行。
9.1 先搭最小可运行体系,再逐步完善
不用等所有服务都成熟了再接入可观测性。建议先选一个核心链路,比如“用户提问 → 检索知识库 → 模型生成回答”,把链路日志和 trace 打通,建一个 20 条以内的回归集,跑出第一份基线。然后每周补充线上 badcase,逐步扩大覆盖。三周后,这套体系会比任何一次“集中治理”都更有效。
9.2 把评估和观测交给工具,不要靠人肉复盘
模型输出质量、tool call 次数、上下文长度、检索得分,这些数据如果靠人肉翻聊天记录去复盘,效率和准确率都不可靠。正确做法是让系统自动记录和分析,人只处理工具筛出的异常和低质量输出。这样才能建立可重复、可比较的评估报告。
9.3 安全和合规红线
AI 应用在采集和记录日志时,不能无差别把用户输入、上下文、检索文档全部明文落盘。需要重点确认以下红线。
- 用户隐私数据、个人身份信息,在生产日志中脱敏或截断,日志保留周期按业务安全规范执行。
- 内部知识库、未公开文档、版权内容,使用前需确认授权边界,避免把未授权的数据投入模型调用和评估。
- Agent 自动调用外部工具时,对高风险操作(发送消息、修改数据、支付等)要有确认或权限校验机制。
- 人脸、声音、肖像等生物特征相关素材,必须在获得明确授权的前提下处理,并在日志中做访问控制。
这些不是“限制”,而是工程系统必须考虑的默认条件。合规做得越早,越不用担心后续上线时返工。
9.4 团队协作约定
给 AI 工程的协作提三个容易忽略的约定。
- 提示词版本化:所有 prompt 模板必须入 Git,变更要有 diff,线上环境记录 template_version。
- 模型版本固定:生产环境必须锁定模型权重版本和推理框架版本,不能自动漂移。
- 评估集独享:评估集是测试资产,不是某个人的草稿,变更走 code review。
10. 总结与下一步建议
这次围绕 Charity Majors 提到的确定性、Instrumentation 和“Eating Your Broccoli”,展开的全部是 AI 工程里非常具体的事。
最值得先动手的一件事:给现有的 LLM 应用补上结构化调用日志和 trace_id。哪怕不接入完整可观测性平台,先从单条日志记录模型输入、参数、token 和耗时开始,也能明显改善线上问题排查体验。
第二步是建立一个 20 条左右的回归评估集,把 temperature 设为 0、固定 seed,跑一次并能复现的基线。这个基线会成为后续改造的“标尺”,模型版本升级、prompt 调整,都拿它说话。
最容易踩的坑是:只追求模型效果,不重视测试复现和链路可观测性。这会导致项目从 demo 到生产的过程中,每改动一次都像重新走一遍迷宫。先把“吃西兰花”的功夫补上,比换一个更大的模型更重要。
后续可以继续扩展的方向包括:接入 OpenTelemetry 的统一 tracing、建设基于评估模型的自动化评分、加入多轮对话和 Agent 工具调用的链路追踪,以及把成本与质量联合监控。这些是从“能跑”到“能稳定交付”的必经之路。