批量 API 操作应先展示目标吗?
批量 API 操作不应只有一个数量。了解 AI 代理如何预览确切的目标集合、绑定审批,并安全处理部分变更。

AI 代理绝不能把「归档不活跃账户」这类模糊请求,直接变成没有边界的写入调用。在更改大量记录之前,它应展示确切的目标集合,保留所展示的选择结果,并要求确认,而且确认只能适用于这组记录。
直到第一次错误的筛选条件匹配到真实客户、测试租户,或工单中没人提到的例外账户之前,这听起来可能有些过于谨慎。人类也会犯同样的错误,但代理可以以机器速度发送请求,并在第一次错误结果出现后继续执行。真正重要的控制措施,不是请求前那句礼貌的警告,而是在决定影响对象与实际更改对象之间建立一条可供审核的边界。
数量无法描述影响范围
数量只能告诉你一项操作会影响多少条记录,却不能告诉你具体是哪一些。「482 个订阅」听起来可能很合理,但其中可能包含企业租户、因法律原因必须保留的暂停账户,或请求根本没有提到的地区中的记录。
请要求提供目标清单。对于小规模操作,清单可以列出每个标识符,并提供足够的上下文,让审核者能够识别错误。对于更大规模的操作,应在可导出的审核界面中展示完整列表,同时提供状态、租户、地区或负责人等有用的分组。不要让人仅凭筛选字符串去推断哪些记录属于结果。
一份有用的预览应回答五个具体问题:
- 哪个 API 资源和环境将接收写入操作?
- 是什么选择器生成了这组记录?
- 集合中有哪些记录?它们是否同时具备稳定 ID 和人类可读字段?
- 每条记录将收到什么变更?
- 哪些记录因明确规则而被排除?
最后一项可以发现一种尴尬的失败:请求在技术上完全正确,却仍然违背了意图,因为隐藏的例外从未进入选择器。如果操作人员说「所有未付款发票,争议中的除外」,预览就应明确报告被排除的争议发票。保持沉默会让审核者猜测代理究竟理解了例外,还是忘记了它。
不要把样本和清单混为一谈。在 10,000 条记录的更新中只展示前 20 条,几乎无法证明什么。样本有助于发现明显的问题,但保存的选择结果必须覆盖所有可能被更改的记录。
预览和提交必须绑定到同一个选择结果
只有当提交操作能够证明自己处理的是已审核集合时,预览才有意义。如果代理在 14:00 预览了一次搜索,然后在 14:05 写入前重新运行同一个搜索,它影响的可能已经是不同的人群。新记录可能进入筛选条件,现有记录可能改变状态,分页顺序也可能发生变化。
最安全的 API 设计是创建服务端选择快照。预览端点返回选择 ID、有效期、成员数量以及修订号或摘要。提交端点接收这个选择 ID,并在快照过期或发生变化时拒绝请求。
POST /v1/subscriptions/selections
Content-Type: application/json
{
"filter": {
"status": "past_due",
"region": "eu",
"exclude_tags": ["disputed", "legal_hold"]
},
"fields": ["id", "customer_name", "status", "amount_due", "tags"]
}
合理的响应大致如下:
{
"selection_id": "sel_7f2c",
"expires_at": "2025-03-08T15:00:00Z",
"count": 482,
"digest": "sha256:4c76...",
"records": [
{"id":"sub_104","customer_name":"Northwind Parts","status":"past_due","amount_due":3100,"tags":[]},
{"id":"sub_219","customer_name":"Orchard Studio","status":"past_due","amount_due":450,"tags":[]}
]
}
records 数组可以通过游标分批返回,但选择 ID 必须指向完整的冻结成员集合,而不只是当前页面。审核界面可以翻页查看结果,却不能改变正在审核的对象。
审批后,代理提交保存的选择结果,而不是原始筛选条件:
POST /v1/subscriptions/bulk-actions
Content-Type: application/json
Idempotency-Key: 9b03c6f0-7dfa-4f22-b0e5-4b52ca4f1a51
{
"selection_id": "sel_7f2c",
"expected_digest": "sha256:4c76...",
"action": {"type": "pause_collection", "reason": "approved credit hold review"}
}
服务器应在摘要不匹配时返回冲突响应并拒绝请求。成功请求应返回操作 ID 和每条记录的结果位置,而不只是 { "ok": true }。笼统的成功消息会掩盖部分完成,而这正是批量工作的常见失败方式。
如果供应商 API 无法创建快照,代理仍可通过保存有序 ID 列表、对规范化表示进行哈希,并将这个 ID 列表发送到写入端点,来绑定这次操作。但这种方式有局限,包括 URL 和请求体大小限制、记录过期,以及只接受筛选条件的 API。在这些情况下,不要假装它与快照控制等价。就在每个有明确边界的批次执行前重新预览,一旦成员发生变化就停止。
稳定分页决定审核是否有意义
对于会更改数据的审批决策,偏移分页是很差的基础。假设代理列出第一页,看到第 1 到 100 条记录,随后另一个进程归档了开头附近的 20 条记录。当代理请求偏移量 100 的页面时,已经向前移动的记录可能被跳过。插入记录也可能造成重复。审核时的数量可能仍然接近原值,看起来一切正常,但具体成员已经变化。
使用服务签发的游标,并向 API 提供商确认该游标是否从快照读取。仅仅编码排序位置的游标,在排序字段变化时仍可能漂移。按可变的 updated_at 字段排序尤其危险,因为拟执行的操作本身可能会更新时间戳。
如果 API 由你控制,请明确提供以下属性:
- 快照标识符或不可变的高水位标记。
- 基于不可变标识符的确定性排序。
- 一项有效期设置,过期后强制重新预览,而不是悄悄返回更新的数据。
- 一个响应字段,用来说明调用方读取的是否是一致快照。
RFC 9110 将 POST、PUT、PATCH 和 DELETE 等方法归为不安全方法,因为它们可能改变服务器状态。这种分类本身不是工作流设计,但它支持一条实用规则:不要因为代码把列表端点和不安全方法放在相邻位置,就把两次调用当成一个原子操作。
对于既不提供稳定游标,也不提供选择快照的外部 API,应缩小范围,直到有人能够审核每个请求。用代理端缓存和长循环来绕过限制很有诱惑力,但这种方案往往会创建第二个没有事务边界的数据库。当记录在执行中途发生变化时,它也无法提供权威答案。
审批必须描述变更,而不只是记录
审核者需要同时审批成员范围和操作效果。「对 482 条记录应用变更」这句话没有实际意义,除非界面说明操作会删除、禁用、重新分配、扣费、发布,还是修改某个字段。请列出每个将发生变化的字段的当前值和拟定新值。如果所有记录都收到相同更新,也要提供简洁的汇总。
区分绝对写入和条件写入。绝对写入表示无论预览后发生了什么,都执行 status = archived。条件写入则表示「只有当 status 仍为 inactive 且 version 仍为 17 时才归档」。条件写入通常更安全,因为其他人修改记录后,它会安全地失败,而不是继续执行。
如果 API 支持,请在每次变更中使用版本号、ETag 或最近一次已知修订号。这不能替代目标清单,它解决的是另一个问题:一条已审核的记录在开始执行时,可能已经不再符合条件。
一条精简的审批记录可以表示为:
{
"request_id": "req_91a8",
"selection_id": "sel_7f2c",
"selection_digest": "sha256:4c76...",
"target_count": 482,
"action": {
"type": "pause_collection",
"precondition": {"status": "past_due"}
},
"approved_by": "operator account identifier",
"approved_at": "2025-03-08T14:16:02Z"
}
不要让代理对同一集合重复使用这份审批来执行不同操作。暂停收款、发放额度和删除记录的后果各不相同,即使目标列表完全一致也一样。审批不仅要绑定选择摘要,也要绑定规范化的操作载荷。
时间限制同样重要。如果审批一直有效,直到代理自行决定使用它,人类当时的审核就会变成一项长期权限。请根据操作类型设置较短的审批有效期,选择结果变化时使审批失效,并在代理实质性修改操作后要求重新决策。
部分完成需要日志和停止规则
每项批量操作最终都会遇到速率限制、超时、验证失败或网络中断。最危险的做法,是在不知道哪些记录已经更改的情况下重试整个任务。这会造成重复扣费、重复通知,或产生误导性的审计轨迹。
为请求的操作分配一个操作 ID 和一个幂等键。记录每个目标的结果:成功、失败、因前置条件改变而跳过,或服务未返回持久结果因而状态未知。「未知」不等于失败。重试前,应先把它视为需要调查的状态。
在执行前就设定停止规则。合理的规则可以是,遇到授权失败或意外的结构化响应这类结构性错误时停止,同时允许收集孤立的验证失败,供后续审核。避免使用笼统的「出错后继续」设置,因为它会把一次未识别的 API 合约变化,变成长串受损记录。
考虑一个常见的失败场景。代理为 800 个用户账户预览角色变更,然后开始客户端循环。前 300 个请求成功后,一次部署改变了端点行为,使缺少字段时的默认角色从预期的查看者变成管理员。接下来的响应看起来仍然成功。如果循环继续,错误就会扩散。如果代理记录每次响应的结构,并在合约不同于已审批操作时停止,影响范围就能在第一条异常响应处终止。
对于破坏性操作,应在执行前设计补偿方案。补偿请求需要逐条记录保存原值,不能只模糊地承诺之后有人可以撤销。即使如此,也不要把回滚称为无害操作。之后发生的合法编辑可能使盲目恢复变得错误,邮件或导出等外部副作用也可能完全无法撤销。
读取权限同样可能造成严重问题
团队往往会认真确认删除操作,却不确认选择过程。这忽略了一个事实:代理可能需要获取完整的客户列表、个人地址、付款状态或内部备注,才能生成预览。预览应提供足够的信息,让人能够识别记录,而不是底层 API 提供的所有字段。
请有意识地要求一个范围尽量窄的字段集。稳定 ID、显示名称、状态、归属信息,以及与拟议变更相关的值,通常就够了。不要把密钥、令牌、自由文本备注和无关的个人数据放进选择结果。这样既能减少审核噪声,也能降低代理在后续消息中重复这些信息的可能性。
同样的原则也适用于筛选条件。代理不应因为无权查看某个字段,就扩大查询范围。如果它无法证明某条记录是否属于选择结果,应明确提出这一歧义,等待人类解决。猜测不是运营判断。
请把环境视为清单的一部分。生产、预发布和沙盒环境可能暴露完全相同的资源名称。应将目标主机或账户标识符与目标数量和操作摘要放在一起。工程师曾经因为预览把环境当成背景信息,而把一份完全合理的记录列表批准到了错误的环境。
代理应拥有调用权限,而不是掌管凭据
持有宽泛 API 令牌的代理,可以在任何人看到目标集合之前发起批量调用。你可以在这种设计周围添加提示和日志,但凭据仍然给这个进程留下了绕过控制的路径。应将凭据交给一个操作执行器,由它在请求到达外部 API 之前决定拒绝还是批准。
对于受支持的 HTTP 和 SSH 操作,Sallyport 采用了这种方式:代理通过 MCP 连接发起请求,而应用保留凭据并执行操作。它支持逐会话授权和可选的逐次调用密钥审批,适合这样的工作流:代理可以准备批量请求,却不应获得可重复使用的秘密材料。
这项审批并不是完整的批量安全设计。HTTP 调用的审批卡无法告诉审核者一个筛选条件会返回 10 条还是 10,000 条记录,除非代理已经先生成并保留目标清单。可以用网关控制权限,再让应用工作流绑定预览、选择、审批和提交。
审计记录也需要两个层次的细节。一份日志应展示请求工作的代理运行过程和审批人。另一份日志应展示每次外部调用,包括选择摘要、操作 ID、端点和结果状态。发生事故时,调查人员需要同时回答「哪个进程发起了请求?」和「哪些记录发生了变化?」
当意图变得模糊时,批量工作流应安全停止
设计代理工作流时,不要让它从自然语言请求直接进入变更操作。下面的顺序刻意保持枯燥,因为枯燥的流程更容易调查。
- 代理将请求转换为选择器、拟议变更、环境和排除项。如果其中任何一项仍然含糊,就请求澄清。
- 代理创建稳定的选择结果,并获取所有成员的审核字段。它记录选择 ID、摘要、查询条件、时间戳和分页完整性。
- 代理展示目标清单和拟议效果。人类要么批准这一确切组合,要么拒绝。
- 代理发送提交请求,其中包含选择引用、预期摘要、操作载荷、幂等键,以及可用时的记录版本。
- 代理分别报告已完成、失败、跳过和未知的结果。它绝不能把部分结果包装成「任务已顺利完成」的乐观表述。
当命令之后才计算目标时,不要审批「运行清理脚本」这种原始命令。这种做法很受欢迎,因为它快速且熟悉,尤其是对已经信任脚本的团队而言。但它失败的原因在于,审批覆盖的是代码文本,而不是实时数据集。确认和执行之间发生的一点数据变化,就可能让已批准的命令执行未经批准的工作。
对于周期性任务,可以预先定义范围狭窄的选择器和最大数量,并在选择结果超过该上限或包含不熟悉的类别时要求审核。最大数量是护栏,不是授权。目标清单仍然是证明任务实际触及了谁的证据。
第一项实现工作很简单:让批量端点返回选择标识符和摘要,然后拒绝没有重复提交这两个值的请求。有了这份合约,代理、控制面板和脚本都能遵守一条清晰而坚固的边界。
常见问题
AI 代理在执行批量 API 变更前是否需要审批?
对于具有破坏性或会对外产生影响的变更,是的。代理应生成一份有明确边界的记录清单,或生成可复现的选择器结果,保存其摘要,然后等待与该确切结果绑定的审批。只有数量无法告诉操作人员影响范围内具体包含哪些记录。
审批批量更新时,数量够用吗?
记录数量只能用来做合理性检查,不能代表目标集合。两个各含 500 条记录的选择结果,可能影响完全不同的客户、地区或账户状态。请审核标识符、一个有用的展示字段,以及生成这些记录的选择规则。
批量操作的审批记录应包含哪些内容?
保存规范化后的选择请求、预览返回的有序标识符、响应时间戳,以及这些材料的加密摘要。同时保存请求执行的变更和审批人身份。这样,即使之后实时数据库发生变化,你仍能证明当时人看到的内容。
批量预览后记录发生变化怎么办?
不要悄悄复用原审批。重新运行预览并计算新的摘要,如果成员发生变化就再次请求审批。如果 API 支持服务端快照或修订令牌,请在提交请求中一并发送,让服务器能够拒绝过期操作。
代理应使用批量端点,还是逐条循环处理记录?
当服务端批量端点能够接受已保存的选择结果或修订令牌,并返回每条记录的结果时,优先使用它。客户端循环只适合少量、可逆的操作,并且请求需要具备幂等性,同时要配合严格的速率限制和逐条记录已完成项的日志。在循环中处理部分失败更容易出错。
代理可以审批分页后的目标列表吗?
只有当 API 提供稳定游标或快照边界时,分页才安全。其他用户创建、删除或重新排序记录时,偏移分页可能跳过或重复行。无法保持稳定的列表不适合用于先确认再提交的工作流。
代理批量变更使用多大的批次才安全?
批次大小控制的是操作风险,不是授权质量。较小的批次可以减少重试、速率限制造成的影响,以及错误请求带来的损失,但每个批次仍必须有可识别的选择结果和明确的影响范围。不要把一个未经审核的大操作拆成许多未经审核的小操作,然后称其更安全。
批量变更的审计日志应保留多久?
保留审批日志和执行日志的时间,应以组织调查账户变更和履行运营义务所需的期限为准。至少要保留足够的信息,将请求、预览摘要、审批人、执行结果和后续回滚工作关联起来。批量变更后不久就删除证据,会抵消收集这些记录的意义。
批量读取操作也需要确认吗?
当批量读取结果包含个人、财务或内部数据时,同样需要明确边界。代理只应接收识别和审核记录所需的字段,人类也不应为了查看少数候选项而导出完整数据集。读取通常破坏性较小,但仍可能造成信息泄露。
可逆的批量变更仍然需要人工审批吗?
可逆性很有帮助,但不能免除审批要求。回滚可能覆盖之后的合法修改,在记录已删除时失败,或者无法撤销通知和下游任务等副作用。请在正向操作执行前审核它,同时为仍然出错的情况保留经过测试的补偿路径。