Agent 产出验证机制设计:如何确保 AI 真的完成了任务
📖 踩坑实录系列,详细过程见公众号「Wesley AI 日记」,微信搜索关注。
标签:AI Agent、Agent验证、任务完成验证、OpenClaw、LLM工程
前言:一次让我怀疑一切的汇报
3 月 15 日,我的小红书互动 Agent 汇报:「账号 B 的点赞、收藏、评论任务已完成。」
我打开小红书,找到那篇笔记——
零评论。零点赞。零互动。
什么都没有。
后来排查发现:MCP 服务根本没有在运行。Agent 无法执行任何操作,但它没有报错,而是直接返回了「完成」。
这不是 Bug,这是 Agent 执行幻觉(Execution Hallucination) 的典型案例:任务没有完成,但任务状态显示为「已完成」。
本文从工程角度,系统梳理 Agent 产出验证机制的设计方案。
核心问题:「完成」的语义歧义
在多 Agent 系统中,「完成」这个概念存在根本性的歧义:
Agent 的「完成」= 执行了操作 ≠ 操作真的生效了
这种语义分裂在以下场景特别危险:
| 场景 | 假完成的表现 | 实际影响 |
|---|---|---|
| 外部 API 调用 | API 调用成功但结果未持久化 | 数据丢失 |
| MCP 工具调用 | 工具服务未运行,静默失败 | 任务未执行 |
| 文件写入 | 写入操作看似成功但路径错误 | 数据丢失 |
| 发布操作 | 发布请求发出但服务器拒绝 | 内容未发出 |
| 数据库更新 | 事务回滚但 Agent 不知情 | 数据不一致 |
传统软件的错误是「执行报错」,Agent 的执行幻觉是「执行看起来成功」。这使得验证责任无法推给异常处理,必须主动设计。
验证机制的三个层级
根据验证的时机和方式,可以分为三个层级,层级越高,防护越可靠。
层级一:前置验证(Pre-execution Check)
在任务执行之前,验证执行环境是否就绪。
class PreExecutionChecker:
"""任务前置验证器"""
def check_mcp_service(self, service_name: str) -> bool:
"""检查 MCP 工具服务是否运行"""
try:
response = requests.get(
f"http://localhost:{MCP_PORT}/health",
timeout=3
)
return response.status_code == 200
except (requests.ConnectionError, requests.Timeout):
return False
def check_auth_token(self, token: str) -> bool:
"""验证认证 Token 是否有效(非过期)"""
try:
resp = api_client.verify_token(token)
return resp.valid and not resp.expired
except Exception:
return False
def check_cookie_identity(
self,
cookie: Cookie,
expected_account_id: str
) -> bool:
"""通过 API 验证 Cookie 对应的账号身份"""
actual = api_client.get_current_user(cookie)
return actual.account_id == expected_account_id
def run_all_checks(self, task: Task) -> CheckResult:
"""执行所有前置检查,任何失败立即返回"""
if not self.check_mcp_service(task.required_service):
return CheckResult.fail(
"MCP 服务未运行,任务中止。不允许降级。"
)
if not self.check_auth_token(task.auth_token):
return CheckResult.fail(
"认证 Token 无效或已过期,任务中止。"
)
if task.target_account_id:
if not self.check_cookie_identity(
task.cookie, task.target_account_id
):
return CheckResult.fail(
"Cookie 账号身份与目标账号不匹配,任务中止。"
)
return CheckResult.pass_all()
关键原则:前置验证失败时,必须 Fail Fast,不允许降级(如「随便找一个可用的 Cookie」),因为降级在身份敏感场景比直接失败更危险。
层级二:后置验证(Post-execution Verification)
任务执行后,通过独立手段验证产出物真实存在。
设计原则:验证必须独立于执行路径。如果执行和验证共享代码路径,执行里的 Bug 会同时污染验证结果。
class PostExecutionVerifier:
"""任务后置验证器"""
def verify_xhs_post_published(
self,
note_id: str,
account_id: str
) -> VerificationResult:
"""
验证小红书帖子是否真实发布。
独立调用 get_user_notes API,不信任 publish API 的返回值。
"""
# 等待平台处理(发布有延迟)
time.sleep(10)
# 独立查询验证
notes = xhs_api.get_user_notes(account_id, limit=5)
note_ids = [note.id for note in notes]
if note_id in note_ids:
return VerificationResult.success()
else:
return VerificationResult.fail(
f"帖子 {note_id} 在账号 {account_id} 中未找到。"
f"发布可能未生效,请人工确认后再决定是否重试。"
)
def verify_file_written(
self,
file_path: str,
expected_content_hash: str
) -> VerificationResult:
"""验证文件写入是否成功(内容完整性校验)"""
if not os.path.exists(file_path):
return VerificationResult.fail("文件不存在")
actual_hash = compute_md5(file_path)
if actual_hash != expected_content_hash:
return VerificationResult.fail("文件内容哈希不匹配,可能写入不完整")
return VerificationResult.success()
def verify_api_state_updated(
self,
resource_id: str,
expected_state: dict
) -> VerificationResult:
"""通过独立 GET 请求验证资源状态已更新"""
actual_state = api_client.get_resource(resource_id)
for key, expected_value in expected_state.items():
if actual_state.get(key) != expected_value:
return VerificationResult.fail(
f"字段 '{key}' 未更新: "
f"期望={expected_value}, 实际={actual_state.get(key)}"
)
return VerificationResult.success()
关键点:验证失败后,Agent 不应该自行重试,而应上报给 CEO Agent 或人类决策。自动重试在没有互斥锁的情况下,会导致重复执行(如发布了多次相同内容)。
层级三:持续巡检(Continuous Auditing)
对于高频、高价值的任务,在执行后一段时间内持续监控状态。
class ContinuousAuditor:
"""持续状态巡检器"""
def audit_published_content(self):
"""
每小时检查已发布内容的状态。
防止「发布时正常,后来被平台删除」的情况被忽视。
"""
published_today = db.get_today_published_records()
for record in published_today:
current_state = platform_api.check_content_status(record.post_id)
if current_state.deleted or current_state.hidden:
alert_manager.send_alert(
level="P2",
message=f"已发布内容状态异常: {record.post_id}",
detail={
"title": record.title,
"published_at": record.published_at,
"current_state": current_state
},
notify_channel="feishu"
)
def audit_scheduled_tasks(self):
"""检查定时任务是否按时执行"""
now = datetime.now()
for task in cron_tasks.get_all():
last_run = task.last_successful_run
expected_interval = task.interval_minutes
if last_run is None:
continue
elapsed = (now - last_run).total_seconds() / 60
# 超过预期间隔的 150% 视为可能卡住
if elapsed > expected_interval * 1.5:
alert_manager.send_alert(
level="P3",
message=f"任务 {task.name} 可能卡住,已 {elapsed:.0f} 分钟未执行"
)
防止虚假完成:四条铁律
基于上述验证机制,提炼出四条防止虚假完成的设计铁律:
铁律 1:「汇报完成」必须后于「验证通过」
❌ 错误流程:执行 → 汇报完成 → 验证(如果有时间的话)
✅ 正确流程:执行 → 验证通过 → 汇报完成
Agent 的汇报状态机:
class TaskStatus(Enum):
PENDING = "pending"
EXECUTING = "executing"
VERIFYING = "verifying" # 新增:验证中间状态
COMPLETED = "completed" # 只有验证通过后才能设置
FAILED = "failed"
NEEDS_HUMAN = "needs_human" # 需要人工决策
在 SOUL.md 或 System Prompt 中写入:
## 任务完成铁律
报告「任务完成」之前,必须:
1. 通过独立 API 或工具查询,确认产出物真实存在
2. 验证结果与任务目标完全匹配
**禁止**:在验证步骤之前,向 CEO 或用户汇报「任务已完成」。
**验证失败时**:上报状态,等待 CEO 决策。不自行重试。
铁律 2:环境检查前置,失败则中止
所有涉及外部操作的任务,必须先完成环境检查:
## 任务前置检查清单(必须全部通过)
- [ ] 目标 MCP 服务是否运行?(curl http://localhost:{port}/health)
- [ ] Cookie/Token 是否有效且未过期?(调用 verify 接口)
- [ ] 目标账号身份是否与预期匹配?(独立 API 查询)
- [ ] 目标资源是否存在且可访问?(预检查,而非执行时才发现)
任何一项失败 → 任务中止,上报原因,不继续执行。
铁律 3:发布类操作必须有互斥锁
class PublishLockManager:
"""发布互斥锁管理器,防止重复发布"""
def acquire_lock(self, content_id: str) -> bool:
"""
获取发布锁。使用 mkdir 的原子性确保互斥。
同一 content_id 同时只允许一个发布操作。
"""
lock_dir = f"/tmp/publish-locks/{content_id}"
try:
os.makedirs(lock_dir, exist_ok=False) # exist_ok=False 确保原子性
return True
except FileExistsError:
return False
def release_lock(self, content_id: str):
"""释放锁"""
lock_dir = f"/tmp/publish-locks/{content_id}"
if os.path.exists(lock_dir):
os.rmdir(lock_dir)
def check_already_published(self, content_id: str) -> bool:
"""检查内容是否已经发布成功"""
result_file = f"/tmp/publish-results/{content_id}.json"
if os.path.exists(result_file):
with open(result_file) as f:
result = json.load(f)
return result.get("status") == "published"
return False
发布前必须同时检查锁和历史结果,防止网络超时后重试触发重复发布。
铁律 4:验证失败 = 上报,不等于重试
def handle_verification_failure(task: Task, error: str):
"""验证失败的处理逻辑"""
# 记录事件
logger.error(f"任务验证失败: {task.id}, 原因: {error}")
# 更新任务状态
task.update_status(TaskStatus.NEEDS_HUMAN, reason=error)
# 上报给 CEO Agent 或人工
ceo_agent.report({
"type": "verification_failure",
"task_id": task.id,
"task_type": task.type,
"error": error,
"executed_at": task.executed_at,
"recommendation": "请人工确认是否真实执行,决定是否重试"
})
# 不要自行重试
# ❌ task.retry()
验证粒度设计
不同类型的操作需要不同粒度的验证:
| 操作类型 | 验证方式 | 验证时机 | 失败处理 |
|---|---|---|---|
| 内容发布 | 独立 API 查询帖子存在 | 执行后 30-60 秒 | 上报人工 |
| 文件写入 | 文件存在 + MD5 校验 | 执行后立即 | 自动重试(有上限) |
| 数据库更新 | 独立查询字段值 | 执行后立即 | 上报人工 |
| 消息发送 | 查询发件箱/已发列表 | 执行后 10 秒 | 上报人工 |
| 外部 API 调用 | 验证返回值 + 独立查询 | 执行时同步 | 上报人工 |
与 CEO Agent 的集成:验证结果路由
在多 Agent 系统中,验证结果需要有明确的路由规则:
class VerificationRouter:
"""验证结果路由器"""
def route_result(
self,
result: VerificationResult,
task: Task
):
if result.success:
# 成功:更新状态,通知 CEO
self.mark_task_completed(task)
self.notify_ceo_success(task, result)
elif result.retry_eligible:
# 可安全重试(无副作用风险)
self.schedule_retry(task, max_retries=2)
elif result.needs_human:
# 必须人工决策
self.escalate_to_human(task, result.error_detail)
else:
# 任务失败
self.mark_task_failed(task, result.error_detail)
self.notify_ceo_failure(task, result)
def retry_eligible(self, result: VerificationResult) -> bool:
"""
只有以下情况允许自动重试:
1. 网络超时(不是业务逻辑失败)
2. 无副作用风险(幂等操作)
3. 未获取发布锁(不是已经发布了)
"""
return (
result.error_type == ErrorType.NETWORK_TIMEOUT
and task.is_idempotent
and not result.lock_held_by_other
)
实战效果
实施上述验证机制后,我们的 OpenClaw Agent Team 的执行可靠性有了显著提升:
修复前的问题:
- Agent 在 MCP 未运行时,仍汇报「互动任务完成」
- 同一内容因网络超时重试,发布了 4 次
- 发布到了错误账号,系统无感知(无日志异常)
修复后的机制:
- 前置环境检查拦截了 ~95% 的「MCP 未运行」类假完成
- 发布锁 + 历史结果检查,完全消除重复发布
- Cookie 身份强验证,账号错发变为物理不可能
总结
Agent 产出验证机制的核心思想只有一条:
完成汇报,必须后于独立验证。任何情况下,「执行」不等于「生效」。
三层验证架构(前置 → 后置 → 巡检)构成了完整的防护体系,四条铁律(汇报后于验证、环境检查前置、发布互斥锁、失败上报不重试)确保了边界条件的安全处理。
这些机制在 OpenClaw 上实际运行,代码模式可直接参考迁移。
📖 详细踩坑日记 → 公众号「Wesley AI 日记」,微信搜索关注,每周 AI Agent 实战经验分享。