Agent 产出验证机制设计:如何确保 AI 真的完成了任务

3 阅读1分钟

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 实战经验分享。