前段时间遇到一个小问题,后来发现这是个挺常见的坑,顺手整理一篇笔记。
文章目录
- 1 -> 引言
- 2 -> 先把“成功”从一段回复变成一种状态
- 3 -> 长任务的困难,在于错误会传播
- 4 -> 一个也能直接运行的验收小实验
- 5 -> 不同类型的检查,解决不同的问题
- 6 -> 从几条失败案例开始,比一开始追求大平台更有用
1 -> 引言
让 AI 写一段说明,检查起来并不难:读一遍,看看有没有答非所问。让它修改代码、更新数据、调用工具,事情就复杂了。它可能给出一份条理清楚的总结,声称已经做好全部步骤,可真正打开文件时,字段少了一列;进入系统查看时,记录还停留在旧状态;重新运行程序时,才发现所谓的成功只发生在它自己构造的样例上。
这类问题容易让人误以为,只要换一个更强的模型就会消失。更强的模型当然有帮助,但验收缺位是系统设计问题。只要“执行任务”和“宣布任务做好”由同一段自然语言包办,流畅的汇报就可能掩盖不完整的结果。
这篇文章讨论一个具体问题:当 Agent 也能连续使用工具、修改环境时,我们怎样建立一套能检查结果、发现遗漏、控制重试的验收方法。重点不是介绍某款产品,而是把“它说做完了”变成“我们能证明哪些部分做完了”。
2 -> 先把“成功”从一段回复变成一种状态
假设任务是把客户名单从 CSV 整理成 JSON。对于一个只得生成文本的聊天助手,返回一段看起来符合要求的 JSON,也许已经做好任务。对于一个要落地文件的 Agent,成功至少还包含:文件存在、编码正确、内容可解析、记录没有漏掉、重复记录按约定处理、没有写入不该出现的个人信息。
如果任务进一步变成“把名单导入业务系统”,成功的定义还要包括目标系统中真正存在这些记录。日志里出现“提交成功”和服务端实际保存成功,仍是不同的事情。
Anthropic 在 Agent 评估方法文章中区分了执行轨迹与最终环境状态。前者用于解释系统做了什么,后者用来判断目标是否达成。这种区分非常实用:评估不能只读模型的最后一句话,还要查它留下的实际结果。
也能用一个简单的任务契约把要求写出来:
项目一个可检查的约定输入固定版本的源文件及其校验值输出指定路径下的 JSON 文件内容规则保留合法 ID、按约定去重、字段类型正确保持不变不修改源文件,不改动无关目录外部动作本次不上传、不发送、不覆盖已有正式数据完成证据产物校验结果、错误清单、未处理事项
契约不是越长越好,而是要消除关键歧义。“正确去重”太笼统;“同一 ID 只保留更新时间最新的记录,时间相同时进入人工复核”才可以转化为测试。
还有一个常被忽略的问题:验收要求也可能写错。检查器完全按照错误需求工作,拿到的仍然是错误结果。因此,在写检查器前,最好先用几条正常、异常和边界样本确认口径。
3 -> 长任务的困难,在于错误会传播
一个十步任务不是十道互不相关的问答。第一步拿错了文件,第二步的统计就可能错,第三步又会把错误统计写进报告。最终输出越完整,错误有时越不容易被留意到。
用一个纯数学例子可以说明这种敏感性:如果每一步成功概率都是 99%,并且各步独立,那么 50 步全部成功的概率是 0.99 ** 50,约为 60.5%。这不是任何模型的实测结果,真实任务也通常不满足独立假设。它只提醒我们:单步表现很好,并不能直接推导出长流程可靠。
工程上可以把任务划成几个有明确产物的阶段,比如“解析输入、生成草稿、结构检查、内容检查、交付”。阶段之间不要只传一句“上一步完成”,应传递产物路径、版本标识和检查结果。下游拿到的东西越具体,越容易识别问题发生在哪里。
恢复点同样重要。一次网络超时不应该迫使系统从头重跑;一次解析失败也不应该直接跳到上传步骤。保存检查通过的中间结果,能把重试范围限制在真正失败的阶段。
不过,保存执行进度与允许重复执行是两回事。读取文件通常可以重复,发送通知或创建订单却可能产生重复副作用。涉及外部写入时,应使用业务侧的幂等标识和状态查询,不能仅凭“没有收到返回值”就再次提交。
4 -> 一个可以直接运行的验收小实验
下面不用接入模型,只模拟三个 Agent 交付物:一个合格、一个漏记录、一个把重复 ID 悄悄写了进去。三个交付物都声称“已完成”,检查器只看文件内容。
示例使用 Python 3.10 及以上版本和标准库,会在系统临时目录中创建测试文件,并在结束时自动清理。它演示的是确定性产物检查,不是通用 Agent 框架,也不证明文本语义已经被验证。
import json
from collections import Counter
from pathlib import Path
from tempfile import TemporaryDirectory
EXPECTED_IDS = {"A01", "A02"}
ALLOWED_FIELDS = {"id", "category"}
ALLOWED_CATEGORIES = {"product", "support"}
def verify(path):
errors = []
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
return [f"unreadable artifact: {type(exc).__name__}"]
if not isinstance(data, list):
return ["root must be a list"]
ids = []
for index, row in enumerate(data):
if not isinstance(row, dict):
errors.append(f"row {index}: must be an object")
continue
if set(row) != ALLOWED_FIELDS:
errors.append(f"row {index}: unexpected or missing fields")
item_id = row.get("id")
if not isinstance(item_id, str):
errors.append(f"row {index}: id must be a string")
else:
ids.append(item_id)
category = row.get("category")
if not isinstance(category, str) or category not in ALLOWED_CATEGORIES:
errors.append(f"row {index}: invalid category")
counts = Counter(ids)
duplicates = sorted(k for k, count in counts.items() if count > 1)
if duplicates:
errors.append(f"duplicate ids: {duplicates}")
if set(ids) != EXPECTED_IDS:
errors.append(
f"id mismatch: missing={sorted(EXPECTED_IDS - set(ids))}, "
f"extra={sorted(set(ids) - EXPECTED_IDS)}"
)
return errors
cases = {
"valid": [
{"id": "A01", "category": "product"},
{"id": "A02", "category": "support"},
],
"missing": [{"id": "A01", "category": "product"}],
"duplicate": [
{"id": "A01", "category": "product"},
{"id": "A01", "category": "product"},
{"id": "A02", "category": "support"},
],
}
with TemporaryDirectory() as directory:
for name, rows in cases.items():
path = Path(directory) / f"{name}.json"
path.write_text(json.dumps(rows), encoding="utf-8")
errors = verify(path)
print(name, "PASS" if not errors else "FAIL", errors)
assert bool(errors) == (name != "valid")
这里最重要的不是代码复杂度,而是验证依据来自事先确定的规则。Agent 不能通过在最后一行补一句“全部检查通过”,让丢失的记录重新出现。
示例故意保留了一个边界:它能验证分类值是否在允许范围内,却不能判断某条客户意见究竟应该属于 product 还是 support。要检验后者,得有标注样本、具体分类定义,或者人工复核。字段合法与语义正确不应被混成一个指标。
另一个边界是来源可信度。真实系统应从可信输入构建预期 ID,不能直接使用 Agent 同时生成的“预期结果”。否则,执行器漏掉一条记录,又在参考答案里漏掉同一条,检查器就会错误地放行。
还可以反过来测试检查器:主动删除一条记录、加入一个未知字段、把数字改成字符串、写入无效 JSON,看它是否都能发现。这种“故意破坏”的测试很重要。一个从来只看过正确产物的验证程序,可能只是恰好返回了通过,并没有真的覆盖关键失败方式。
在更复杂的场景中,可以给每个条件安排一个反例。比如要求不修改源文件,就在执行前后比较源文件校验值;要求只新增指定记录,就对比操作前后的可信快照。对真实外部系统,不应为了验证检查器而随意破坏正式数据,应使用隔离环境或可控的测试替身。
验证失败的返回信息也要能够指导修复。“检查不通过”太模糊;“缺少 A02,A01 重复出现,源文件未变化”才是有用反馈。修复器拿到的是具体差异,不必从头猜测,同时也避免它为了通过某一个检查而重新生成全部内容。
这里还存在检查范围的问题。若任务只要求格式转换,检查器不应顺便要求模型重新解释业务分类;若任务要求事实核验,只检查 JSON 格式又明显不足。验收标准应覆盖用户目标,但不能把没有授权的额外工作偷偷塞进“质量提升”里。否则系统会不断增加执行步骤,最后连最初要交付什么都变得模糊。
5 -> 不同类型的检查,解决不同的问题
能够交给程序判定的内容,优先使用确定性检查。比如 JSON 是否能解析、测试是否通过、记录数是否符合约定、目标数据库中是否出现重复键。程序检查的优势是可重复、成本低,失败时也通常能定位到具体规则。
开放式内容得另一种方法。研究报告是否遗漏重要反例,说明文档是否容易搞懂,往往没有一个唯一答案。这时可以让模型按明确的评分项辅助评审,但要保留原始证据,并用人工样本校准。让另一个模型给出“我觉得不错”,不是可靠的独立验证。
对于重要决策,人的作用也不只是最后点一下同意。人应能看到关键依据、反例和未解决的不确定性,并有足够空间否决默认建议。摘要可以节省阅读时间,但不应该把分歧和风险从界面里藏起来。
评估时还要区分两个容易混淆的目标:多次尝试中至少成功一次,和每次执行都稳定成功。前者适合探索性任务,后者更接近自动化服务的要求。生产流程不能靠“重跑十次总会有一次对”证明可靠,因为失败的九次可能已经修改了外部状态。
一个实用的评估表至少应同时记录:任务通过率、重复运行稳定性、人工接管率、耗时、成本和严重错误数。不能用一个综合分数掩盖越权写入、数据遗漏之类必须单独处理的问题。
6 -> 从几条失败案例开始,比一开始追求大平台更有用
初期没有必要建设庞大的评估系统。先找十几条真实任务,加入几条历史失败案例,写出完成条件和禁止动作,通常就能暴露许多问题。之后每修复一个缺陷,就把相应输入变成回归样本。
评估样本应该覆盖正常路径,也应该包括空文件、重复数据、工具超时、权限不足和来源冲突。对于不应继续的输入,正确结果可能就是停止并说明原因。把所有暂停都算成失败,会逼迫系统在信息不足时继续猜测。
还要留出未参与调试的测试集。如果提示词每改一次,就只看同一小组样例,很容易把流程调成“熟悉这些题”。对新项目、新表达方式和新边界的表现,才更接近上线后会遇到的情况。
最终,可靠 Agent 的价值不是从不出错,而是完成状态可核验、错误能够被发现、恢复动作受到约束、无法解决的问题会及时上交。一个诚实标明“这两条没有处理”的系统,往往比给出完整成功汇报却漏了数据的系统更值得使用。
感谢各位大佬支持!!!
互三啦!!!
这篇笔记就先到这里,后面用到新的思路或者发现有问题再补充。
评论 (0)
暂无评论