跳到主要内容

19 篇博文 含有标签「ci-cd」

查看所有标签

那些在本地通过但在 CI 中失败的编程智能体

· 阅读需 12 分钟
Tian Pan
Software Engineer

智能体(agent)生成的 diff 在你的电脑上显示为绿色。测试通过了,lint 通过了,开发服务器也干净地完成了热重载。你让它提交了 PR,九十秒后,CI 在一个与修改完全无关的步骤上报错变红了:缺少某个 CLI 工具、一个智能体从未声明过的新环境变量,或者 Node 版本解析结果不一致——因为你的 .nvmrc 是通过 runner 并不具备的全局 shim 进行解析的。智能体并没有写出有问题的 diff。它写出的是一个依赖于你机器环境的 diff,而你的机器和 runner 并不是同一台电脑。

“在我的机器上能运行”曾是一个人为 Bug。解决办法是保持纪律性——锁定版本、编写 Dockerfile、阅读 CI 日志。而编程智能体大规模地继承了这个 Bug,却丢弃了曾经用来弥补它的纪律性。因为智能体不知道它所依赖的东西哪些来自代码库,哪些来自你 shell 历史记录中的“温热沉淀物”。每个开发者的笔记本电脑都是一个配置独特的环境,智能体在不知不觉中吸收了这些环境。接着,同一个智能体在一个完全不具备这些条件的 runner 中运行,失败的表象看起来像是智能体的错,但实际上是由于没人写明的一份环境契约。

凌晨 3 点拥有合并权限的 CI Agent

· 阅读需 13 分钟
Tian Pan
Software Engineer

凌晨 3 点 17 分,一个不稳定测试被隔离了。On-call 轮值没有被叫醒,因为根本没东西失败——Agent 判定这次失败是噪声,自动开了一个标题为 chore: quarantine flaky test 的小 PR,用 ci-bot 这个 service account 把它 self-merge 了,然后继续盯着队列。六天之后,一个用户来反馈说某个功能从周二开始就坏了。那个测试不是 flaky,它是把一个真实回归挡在生产之外的唯一防线,而 Agent 那个 confidence threshold 设得刚好高到敢做决定,又刚好低到会判断错。

这是 agentic CI 中市场材料从不提及的那部分。在 2026 年,把 Agent 接进 pipeline 让它分流失败、对安全告警做依赖降级、提出依赖升级,在工程上其实很简单——工具齐了、集成只差一个 config 文件、生产力故事也是真的。没人写 runbook 的部分,是你刚刚引入的那一类新的操作主体:一个在凌晨 3 点没有任何人类同步在环、却拥有合并权限的角色,而你的 SRE 手册当初就是默认人类才是意图的来源。

Prompt 的 Pre-Commit Hooks:LLM 团队一直缺失的内环工具链

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开任何生产环境中的 LLM 代码库里的提示词文件,你会发现评审者的目光变得呆滞。这个 diff 是 15 行自然语言,其中包含一个微调过的 few-shot 示例,一条重新表述的指令,以及编辑器留下的一个多余的尾部空格。没有针对它的语法检查,没有 Linter 抱怨相互矛盾的指令,没有扫描器注意到 few-shot 示例包含上周二支持日志中真实客户的电子邮件地址,也没有冒烟评估(smoke eval)来确认这一更改不会导致系统实际提供的提示词延迟飙升。评审者凭感觉批准——就像 2008 年团队批准 HTML 模板的 diff 一样——然后在 6 小时后,生产遥测系统捕获到了回归。

围绕代码的内环工具(inner-loop tooling)已经成熟了 20 年。围绕提示词的内环工具则介于“我们在 git 中有一个 .md 文件”和“我们在入职后运行过一次 promptfoo”之间。这种差距正在扩大,因为在许多系统中,提示词现在是杠杆率更高的修改:一个 30 行的系统提示词更改比 1000 行的服务重写更能改变行为,而它的评审过程却像处理一份 Word 文档。

影子评估:当私有切片取代了你的评估汇总

· 阅读需 11 分钟
Tian Pan
Software Engineer

想要发现你的 AI 团队缺乏评测纪律,最快的方法就是分别在 Slack 私聊中询问三名工程师:“你上次的提示词(prompt)修改提升了质量吗?”——然后看着他们三个人都回答“是”,但给出的却是三个不同的数字,针对的是三个不同的切片(slice),在三台不同的笔记本电脑上运行,而且团队中没有其他人能复现这些结果。从教科书式的定义来看,这不单纯是评测问题。教科书会说你没有评测。而现实情况更糟:你有 太多 的评测,每个评测都是私有的,每个都能衡量一些真实的东西,但没有一个能汇总成组织可以据此制定计划的单一指标。

这就是“影子评测(shadow eval)”反模式,大多数 AI 团队在承认这一点之前,这种状态持续的时间比他们愿意承认的要长。它看起来效率很高——每个工程师都有一个 notebook,每个 PR 都附带一张通过率的截图,每次站会都会提到“在长尾切片上取得了胜利”——而且它能在季度评审中幸存下来,因为“我们做评测”的门槛太低了,只要运行任何内容都算。但组织得不到任何信号。领导层无法判断上个月的三次提示词修改是推动了产品进步还是原地踏步,因为三名工程师是根据三个私有切片进行衡量的,而且在切换文件的那一刻就停止了对之前基准(baseline)的追踪。

Prompt 修改不只是措辞变动:将 Prompt 视为软件的代码审查规范

· 阅读需 13 分钟
Tian Pan
Software Engineer

周二下午,一个只有六行代码的系统提示词(system prompt)编辑出现在了一个 Pull Request (PR) 中。Diff 只是普通的英文。两位评审者扫了一眼新的措辞,觉得读起来更自然,于是点击了批准。PR 在不到一分钟内合并。到了周五,客服开始收到关于智能体的工单:它突然拒绝总结超过一定长度的文档,不再引用来源,并莫名其妙地在每句回复开头都加上 “Certainly!” —— 这种行为没人要求过,Diff 中也无法预见。

当一个花了十年时间学习如何评审代码的团队,在面对提示词这一产物时,竟然退化到了第一周的水平,结果就是这样。Diff 看起来 毫无害处,因为它读起来像英语,而人类正是用眼睛来审阅英语的。让代码评审发挥作用的规范 —— 运行测试、检查影响范围、对 “小改动” 保持适当的怀疑 —— 并没有悄然转化。措辞变好了,但行为变差了,直到用户发现之前,没人注意到。

Eval-as-Code:当你的发布门禁只是某人笔记本电脑上的一个 Notebook

· 阅读需 14 分钟
Tian Pan
Software Engineer

决定一个模型是否上线生产环境的数字,是由运行在某个工程师 MacBook 上的 Jupyter Notebook 生成的。数据来源是 Slack 私聊中的一个 CSV 文件,评分则由一个没人固定版本的裁判模型完成。两周后,在工程师又动了三次 Notebook,且 API 供应商悄悄发布了一个微小的模型更新后,团队里已经没人能重现那个数字了——包括当初生成它的那个工程师。然而,那个数字就是准入闸门。它决定了 GPT-4o-mini 是否足以在客户支持流程中取代 GPT-4;它决定了新提示词模板的发布;它决定了微调模型的晋升。团队把它视为核心承重构件,却像对待便利贴一样存储它。

这就是“评估差距”(eval gap)。五年来,业界一直在将评估视为一个方法论问题——哪种评分技术、哪种裁判模型、哪种评分标准、哪种数据集——却几乎从未将其视为一个工程问题。但是,一旦你的评估套件开始充当生产发布的守门员,它就继承了生产栈其余部分所遵循的所有要求:可重现性、版本控制、所有权、可观测性、依赖管理、延迟与可靠性预算,以及一套在构建它的工程师离职后依然能运行的流水线。大多数团队完全跳过了这一层,只有在发生重大事故后才发现它的缺失——通常是评估分数显示绿色,而用户体验却是一片红色。

将 Eval 作为 Pull Request 评论而非任务:在代码审查中嵌入 LLM 质量门禁

· 阅读需 12 分钟
Tian Pan
Software Engineer

许多自称“有评估(evals)”的团队,其实际情况是:有一个仪表板,某人每周运行一次测试套件,然后将数据粘贴到没人看的 Slack 频道。评审人员批准提示词(prompt)更改时,甚至根本没看过它是否影响了测试套件,而回归问题(regression)两周后才在客户反馈单中显现。评估确实存在,但评估并未进入开发循环。

解决办法在于结构,而非意愿。只有当评估存在于变更发生的地方——即 Pull Request(PR)评论中,紧挨着代码差异(diff),并带有单个 PR 的增量变化和评审员无法忽视的回归提醒时,评估才能真正起到质量把关的作用。在其他任何地方,它们都只是表演性的产物:投入了大量精力构建,却什么也拦截不到。

为什么你的提示词库应该是 Monorepo,而不是 Cookbook

· 阅读需 13 分钟
Tian Pan
Software Engineer

我最近合作的一个团队有三个不同的“总结这份合同”提示词。一个存在于 Notion 页面中,法律科技小队将其复制粘贴到他们的服务里。一个存在于客户成功后端的 prompts/ 文件夹中,为了适应他们的语气偏好做了轻微修改。还有一个内联在数据团队 notebook 里的 Python 文件中,被硬编码在两个 f-string 插值之间。当 OpenAI 弃用了它们运行的所有模型时,迁移计划变成了一场 “Slack 考古” —— 必须追踪到每个所有者,重新评估每个变体,其中两个变体在生产环境中默默地出了一周的故障才被察觉。

这就是规模化后的提示词 Cookbook 的样子。对于十个提示词和一个团队来说,Cookbook 是合理的。但当提示词达到一百个、团队达到四个左右时,它们就会变得难以管理。当你运行一个 AI 组织时,你的 prompts/ 文件夹(装满 .md 文件)的表现就像 2008 年那种靠复制粘贴引入的第三方代码:每个消费者都有自己的快照,偏差(drift)是不可见的,而破坏性变更会以不可预测的方式向外扩散。

Eval 差异分析作为分支保护:交付分数变化,而非分数下限

· 阅读需 11 分钟
Tian Pan
Software Engineer

我曾合作过的一个团队拥有一套看起来很清爽的评估门禁(eval gate):每个 prompt PR 都必须在黄金测试集(golden set)上获得 0.85 以上的评分,否则合并按钮就会保持灰色。他们为此感到自豪。但在六周后,平均质量已从 0.93 悄然滑落至 0.87 —— 每个 PR 都通过了门槛,每个 PR 都成功上线,而且没有任何一个改动需要为这种质量回退负责,因为它们都没有违反规则。这个门槛是根据上个季度的质量快照设定的,而不是根据上周的质量。

这就是绝对阈值评估门禁的失效模式:一个将评分从 0.92 降低到 0.86 的 PR 可以绿灯通过,而一个将评分从 0.80 提升到 0.84 的 PR 却会被挡在门外。团队学到的是“只要过线就能发布” —— 这是一个关于质量的故事。但你真正需要的信号是“如果这个改动在重要的切片(slices)上没有发生回退就发布” —— 这是一个关于回退检测的故事。

测试覆盖率工具在十年前就解决了这个问题。它们报告相对于父提交(parent commit)的差异,并将其细化到每个文件。评估门禁还没赶上这个进度。

Prompt Linting 是 Eval 与生产环境之间缺失的一层

· 阅读需 12 分钟
Tian Pan
Software Engineer

事故报告读起来就像一个单元测试的恐怖故事。一次 Prompt 编辑作为“前置说明清理(preamble cleanup)”的一部分,删除了一段五行的安全条款。测试套件中的每个 Eval(评估)都通过了。每个 Judge(裁判)评分都保持在容差范围内。两周后,一个面向客户的助手产生了一个本该被拒绝的响应,这种响应会在深夜 11 pm 触发信任与安全(Trust & Safety)页面的报警。复盘将这次回归追溯到了一个 PR 中的单处删除,而当时没有任何人标记它,因为负责捕捉回归的套件对安全条款是否存在没有意见——它只对模型在套件记得询问的情况下的表现有意见。

!["https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=Prompt%20Linting%20%E6%98%AF%20Eval%20%E4%B8%8E%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B9%8B%E9%97%B4%E7%BC%BA%E5%A4%B1%E7%9A%84%E9%82%A3%E4%B8%80%E5%B1%82"]

这就是行为评估(behavioral evals)与结构正确性之间的鸿沟。Eval 衡量的是模型生成的内容;它们不衡量 Prompt 本身是什么。而 Prompt 就像代码一样,有一个独立于行为而存在的结构层——必须存在的章节、必须解析的引用、必须插值的变量、必须遵守的长度预算、以及绝不能出现的弃用标识符。当结构层断裂时,行为表现通常会在一段时间内保持绿色,直到生产环境中的某个边缘情况将故障暴露为事故。

Prompt 的语义差异分析:为什么 Git Diff 在提示词变更的影响上会误导你

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位队友提交了一个 PR,将你 Agent 的系统提示词(System Prompt)从 420 行重写为 380 行。Diff 是一片红绿交错的“惨状”:删除了段落、移动了章节、精简了语言。你批准了它,因为这些清理看起来很合理。一周后,退款请求的准确率下降了 8 个百分点,却没人能说出到底是哪一行导致的。

另一位队友在一条指令中添加了“简洁”(concise)这个词。Diff 只有三个字符。没人仔细审查它,因为几乎没有什么可看的。但这次修改导致 22% 的查询在工具调用(Tool-call)行为上发生了变化。

无法合并的智能体重构:为什么多文件差异会在衔接处崩溃

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个来自 AI 编程智能体的 40 个文件的重构任务摆到了你的桌面。你打开 PR,滚动查看差异(diff),每一个代码块(hunk)看起来都没问题。命名重构很一致,导入很整洁,测试在隔离状态下也能编译。你合并了代码。40 分钟后,主分支的 CI 变红了,因为同级包(sibling package)中的两个调用点仍然向一个现在需要四个参数的函数传递三个参数,而原本能捕获这一错误的类型检查器从未包含在智能体的内环(inner loop)中。

这是当今智能体编写的重构中最常见的失败模式,而且它与单个修改的质量几乎无关。每一个文件单独审查时,看起来都像是一个细心的人类写的。Bug 存在于“接缝”处——即来自不同文件的修改必须保持一致的边界。文件级的审查隐藏了接缝级的正确性,而大多数审查工作流都是围绕文件设计的。