Agent 交给团队用之后出事了算谁的:版本、灰度、回滚怎么定
数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。
一个 agent 在团队里出事,第一反应大多是”模型不行”或”这轮回答质量下降了”,但真正的原因往往是:没有人能准确说出线上此刻跑的到底是哪一版。 模型标识是一个值,指令文件是另一个值,工具清单、检索库快照、依赖版本、运行环境变量又各是一个值。这六样里任何一样悄悄变了,agent 的行为就会变,而你的发布记录上什么都没有。归因错了,接下来的动作就全错——换模型、改指令、加规则,折腾一周,问题还在。
所以交付一个 agent,第一件要立的规矩不是评测标准,也不是权限审批,而是版本口径:这一版 agent 由哪些东西共同构成,怎么打标,怎么在运行时读出来。这件事做不好,后面的灰度和回滚全是空话——你连回滚到哪儿都说不清。
说明一下本篇和站内两篇相邻内容的分工:AI 工具的团队治理讲的是工具选型、账号与预算这层组织约束,团队规则文件冲突讲的是多人共用一份规则文件时的合并与冲突。本篇不重复这两件事,只谈一个已经能跑的 agent 从个人手里交到团队手里之后的发布链路:版本怎么定义、怎么小步放量、出事怎么退回去、责任边界画在哪里。
一、先分因:出事了先判断是哪一类
不要一上来就改指令。先花十分钟做判别,把故障归到下面四类之一。
| 现象 | 大概率成因 | 怎么验证 | 处置动作 |
|---|---|---|---|
| 昨天好用今天变差,代码没动过 | 上游变了:模型服务端更新、检索库被重建、工具接口返回结构变了 | 挑一条留有历史轨迹的输入重跑,把新旧两份轨迹逐步比对,定位第一处分叉发生在哪一步;再单独用固定输入调一次那一步用到的工具,看返回结构变没变 | 先冻结放量,把变化的那一层锁到确定版本,再谈调优 |
| 只有部分人反馈异常,其他人正常 | 灰度分组污染或配置分裂:不同人拿到的指令文件、工具集、环境变量不一致 | 让报障的人和正常的人各跑一次同样任务,导出各自读到的版本标识做逐项比对 | 统一配置来源,异常组直接切回稳定版 |
| 回滚之后问题依旧 | 有不可逆的东西没跟着回滚:写入的数据、缓存、外部系统里的状态 | 检查回滚后 agent 读到的持久化数据是不是上一版写的 | 按数据先于代码的顺序处理,参考回滚后状态不一致的处理顺序 |
| 报错是 429/401/403/500 或连接类错误 | 不是 agent 的问题,是接入层:限流、鉴权、网络 | 绕开 agent,用最小请求直接打一次接口,看原始状态码;再抓一次未被包装的网络错误名(Node 侧常见 ETIMEDOUT、ECONNRESET,Python 侧多是超时类与连接类异常) | 走接口排查路径,别动 agent 本身 |
最后一行值得单独强调。429 是限流,401 是凭据无效,403 是有凭据但没权限,500 是服务端自己出错——这四种在 agent 的外层日志里可能都被包装成一句”任务失败”,你必须扒到原始状态码才能分清。自签证书导致的证书链校验失败也常被误报成”模型不可用”,那其实是内网代理的事。这类接入层故障要走接口排查的路子,和 agent 本身的逻辑无关。
判别的前提是你能拿到轨迹。如果一个 agent 出了问题只能靠用户复述,那它根本没到可交付状态,先补齐运行记录再谈发布。
二、版本:先定义”一版”到底是什么
大多数团队的 agent 没有版本,只有”最新的那个”。要改这一点,先把构成一版的东西列全,我的划法是六项:
- 模型标识(写死具体的模型名,不用会自动指向新版本的别名)
- 指令文件(系统指令、规则文件、角色设定,全部进版本库)
- 工具清单与各工具的接口契约版本
- 检索资料的快照标识(哪一次索引构建的结果)
- 运行时依赖版本(锁文件必须提交)
- 环境变量清单(只记录键名和来源,不记录值)
这六项合起来打一个标签,这才叫一版。落地方式很朴素:
# 发布前把构成物固化成一个可读的清单
git tag -a agent-2026.07.29-1 -m "model/instructions/tools/index snapshot"
git push origin agent-2026.07.29-1
然后让 agent 在运行时把自己的版本标识吐出来。这一步很多人省掉,代价是出事那天你只能靠回忆。最简单的做法是在启动时读一个注入的变量并打进每条运行日志:
export AGENT_RELEASE="agent-2026.07.29-1"
import os, json, logging
# 这一行别省:root logger 默认级别是 WARNING,不配 basicConfig 的话下面的 info 一条都不会输出
logging.basicConfig(level=logging.INFO)
release = os.environ.get("AGENT_RELEASE", "unknown")
logging.info(json.dumps({"event": "run_start", "release": release}, ensure_ascii=False))
有了这一行,前面判别表里”同样任务不同结果”的比对才做得下去:让两个人各跑一次,看日志里的 release 是不是同一个值。不一样,问题就找到了一半。
模型别名这一项要特别小心。用一个会随时间指向新权重的别名,等于把你的版本交给了别人管,某天行为变了你还查不出改动记录。相关的风险站内单独讲过:模型别名的风险。
三、灰度:按人群放量,按任务类型设门槛
agent 的灰度和普通服务不同:普通服务放 5% 流量就够采样,agent 的任务差异极大,5% 的随机流量可能全是简单任务,什么问题都暴露不出来。所以要按两个维度切:
按人群切。 第一批只给两三个愿意配合的人,条件是他们出问题会详细描述并愿意导出轨迹。第二批扩到一个完整小组,第三批全员。每一批之间至少隔够一个完整工作日,让低频场景有机会出现。
按任务风险切。 这一维和上面的人群维是叠加使用的,不是二选一:人群批次决定推进节奏,任务风险决定每一批里放开哪些能力。同一版 agent,只读类任务(查询、汇总、生成草稿)可以在人群维度上走快一档——第一批跑满一个工作日没异常,第二批就直接扩到全员;写入类任务(改代码、发消息、动数据、调外部系统)必须老老实实走满三批,并且在前两批里一律先要求人工确认,跑够一段时间再谈放开自动执行。这条比按人群切更重要,因为事故的严重度几乎完全由”它能写什么”决定,而不是由”它答得准不准”决定。人工确认这道闸门怎么设计,站内有专门一篇:Agent 的人工介入设计。
灰度期间要盯的不是准确率一个数字。至少同时看四条线:任务完成率、工具调用失败率、单任务的步数分布、人工接管次数。步数分布是最灵敏的一条——行为变坏往往先表现为”绕路变多”,而完成率还没掉下来。这四条线的取值不需要精确,需要的是每一版都用同样口径采集,能横向比。
灰度还有一条容易被忽略的纪律:灰度组和稳定组不能共用可写的状态。 如果新版 agent 往同一张表、同一个知识库、同一个工单系统里写东西,那么它污染的数据会流回稳定组,你的对照实验就毁了,回滚也救不回来。共用只读资源可以,共用可写资源不行。
四、回滚:先想清楚哪些东西回不去
回滚的技术动作很简单,难的是判断”能不能回”。发布前就把构成物分成两类:
可回滚的:模型标识、指令文件、工具开关、依赖版本、路由配置。这些改一个变量或退一个标签就回去了。
不可回滚的:agent 已经写出去的东西。已提交的代码、已发送的消息、已修改的业务数据、已推给下游的事件。这些只能补偿,不能撤销。
所以回滚流程的正确顺序是:先停止新任务进入(把入口关掉,而不是直接杀进程,让在跑的任务跑完或标记中断),再切回上一版标签,最后处理这一版已经产生的副作用。顺序反了会出现”回滚了但脏数据还在继续被读”的局面。
# 退回上一个已知良好的发布标签(检出标签会进入 detached HEAD,
# 它的用途是从这一版重新构建部署,而不是把分支的历史改回去)
git checkout agent-2026.07.28-2
# 若污染已经进入主干,用 revert 生成反向提交,保留可追溯记录
git revert --no-commit <commit-sha>
git commit -m "revert: 撤销 <commit-sha> 引入的改动"
第二段里的 --no-commit 只把反向改动放进暂存区、不自动提交,所以必须自己补一次 git commit;要撤多个提交时这样能合成一次提交。如果被撤的是合并提交,还得用 -m 1 指定保留哪个父分支,否则 git 会直接报错拒绝执行。
用 revert 而不是强推重写历史,是因为多人协作时重写历史会让别人的本地分支和线上对不上,制造出第二起故障。冲突时留下的 <<<<<<<、=======、>>>>>>> 标记必须人工确认,不要让 agent 自己合——它常常两边都保留一部分,产出一个语法通过但语义错乱的文件。
回滚点要提前约定死。我的习惯是写进发布记录里一句话:这一版的回滚目标是哪个标签,谁有权按下回滚,按下之后通知谁。 这三件事没写清楚,真出事时前十分钟全花在找人上。
五、责任怎么划:三条边界
“出事了谁负责”这个问题,如果答案是”AI 负责”,那等于没人负责。可执行的划法是把责任拆成三段,每段有明确的人:
发布责任:谁按的发布,谁对这一版的构成物负责。包括模型标识写死没有、锁文件提交没有、灰度范围设对没有。这个人不需要懂模型,需要懂流程。
内容责任:agent 产出的东西合并进主干、发给客户、写进数据库之前,是谁点的同意。点同意的人对内容负责,和这段内容是人写的还是 agent 写的无关。这条要写进团队约定,否则一定会出现”我以为它检查过了”。
运行责任:线上跑着的 agent 出现异常,谁在盯、多久响应、什么阈值触发回滚。这条要落到具体的人和具体的时间窗,不能挂在一个组名上。
三段之外还有一条兜底原则:不要让 agent 拥有任何一个人都没有的权限。 如果 agent 的凭据能做到组里没人能做的操作,那么出事时既没人能预料,也没人能收场。给它的凭据要按最小可用范围单独申请,不要复用某个人的高权限账号。
六、什么情况下别再折腾
排查是有止损点的。出现下面任何一条,停手回滚,不要继续在线上调:
- 同一个现象你已经改了三次都没复现规律。 这说明你面对的是随机性,不是 bug。继续改只会叠加变量,把版本搞得更不可比。回到上一个稳定版,在离线环境用固定输入复现。
- 上游有一层不在你控制内且已经确认变过。 模型服务端行为变了、外部接口结构变了、检索库被人重建了——这时候你在 agent 侧的任何调整都是在追一个移动靶。先把那一层锁死或者绕开。
- 已经产生了不可逆的副作用,而且还在扩大。 立刻切断入口,优先止血,故障分析放到后面。判断标准很直接:这一分钟过去,需要人工补偿的工作量是不是在增加。
- 超过半天没有明确进展。 半天是我的经验阈值,不是什么定律,你可以按自己团队定。关键是事先定好,而不是在焦头烂额的时候临时决定还要不要再试一次。
- 改动开始触碰指令文件的核心结构。 在故障期间大改指令,等于发布一个未经灰度的新版本。要改就走完整流程,不要走应急通道。
还有一种”换条路”的情况:如果一个任务连续多轮都要人工大幅修改产出,那它可能本来就不适合交给 agent 全自动跑。把它降级成”agent 出草稿、人来定稿”,比继续调要划算得多。承认这一点不丢人,比硬撑着让它自动执行然后每周救火强。
七、避坑清单
坑一:用会自动更新的模型别名。 为什么会踩:图省事,写别名以后不用改配置。代价是某天行为变了,你的版本库里一行改动都没有,查不出原因。 怎么避:配置里写死具体模型标识,升级当作一次正式发布走灰度。
坑二:指令文件散落在各人本地。 为什么会踩:早期都是一个人在调,改文件比改代码方便,就没进版本库。等第三个人加进来,三份指令已经各不相同。 怎么避:指令文件和代码同仓同分支,改动走同样的评审流程。谁本地改了不提交,就当作没改过。
坑三:灰度组和稳定组共用可写数据。 为什么会踩:为了”环境一致”,直接连同一套数据库。 怎么避:只读资源可以共用,可写资源必须隔离。做不到隔离,就把灰度改成只读任务先跑。
坑四:回滚只回代码,不回数据。 为什么会踩:默认代码是唯一的状态载体。但 agent 会往外写,写出去的东西不在版本库里。 怎么避:发布前列一张”这一版会写哪些地方”的清单,回滚流程按这张清单逐项处理。
坑五:把日志级别开到最大当成可观测性。 为什么会踩:出事时觉得日志越多越好。结果是关键那条被淹了,查询也慢。 怎么避:日志按事件结构化,至少保证每次运行的开始、每次工具调用、每次决策分支、结束状态四类事件可查询,其余降级。
坑六:凭据写在指令文件或配置文件里跟着仓库走。
为什么会踩:本地跑通了就顺手提交了。
怎么避:只从环境变量或密钥服务读取,仓库里只记键名。已经提交过的一律作废重发,改 .gitignore 不等于删掉——历史里还在。
坑七:没有人被指定为”按下回滚的人”。 为什么会踩:觉得这是显然的事,谁发现谁弄。真出事时大家都在等别人拍板。 怎么避:写进发布记录,具体到人名,并且约定这个人按下回滚不需要再找任何人批准。
坑八:把评测当成一次性动作。 为什么会踩:上线前跑过一轮评测,觉得就此过关。但 agent 的行为会随上游漂移。 怎么避:留一组固定输入作为基线,每次发布和每周定期各跑一次,比对轨迹而不只是比对最终答案。
收束:发布前的十条自检
把 agent 交给团队,本质上是把一个会自己动手的东西接进别人的工作流。它的风险不来自”答得不够好”,而来自”没人说得清它是什么、它动了什么、怎么让它停下”。上面所有内容压成一张发布前的自检清单:
- 这一版的六项构成物是否都固化并打了标签
- 运行日志里能否读到版本标识
- 模型标识是否写死而不是别名
- 指令文件是否全部在版本库里
- 依赖锁文件是否已提交
- 灰度按人群和任务风险两个维度都切了吗
- 灰度组和稳定组是否共用了可写资源
- 写入类操作是否设了人工确认
- 回滚目标标签、按下回滚的人、通知对象是否都写清楚了
- 这一版会写到哪些外部系统,是否列成清单
十条里只要有一条答不上来,就先别发。发布这件事的成本是线性的,事故的成本是指数的。