智能体说“任务完成”,数据库却没有:如何用可验证提交消除假成功

2026-10-04 33 预计阅读时间: 1 分钟
来源: huggingface.co AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

AI 智能体很擅长生成一段听起来确定无疑的回答,但“我已经完成了”并不等于状态真的写入数据库。工具调用超时、事务回滚、重复执行、写错记录,甚至仅仅误读了返回值,都可能让智能体宣布成功,而业务系统仍停留在原地。

从这个标题揭示的问题出发,真正需要解决的不是如何让模型更自信,而是如何让“完成”成为一个可以被数据库证明的事实。

“调用成功”和“业务完成”是两件事

一个常见的智能体执行链路如下:

  1. 模型决定调用 complete_order 工具;
  2. 工具向数据库发送更新;
  3. 工具返回一段文本;
  4. 模型根据文本回答“订单已完成”。

危险通常出现在第 3 步。如果工具只返回 OK,智能体无法知道:

  • SQL 是否真的更新了目标记录;
  • 更新影响了 1 行还是 0 行;
  • 事务是否已经提交;
  • 记录是否处于允许转换的前置状态;
  • 网络超时发生在提交之前还是之后;
  • 重试是否造成了重复扣款或重复创建数据。

因此,面向智能体的写操作不应该只返回自然语言,而应该返回结构化、可核验的执行证据。例如:

{
  "ok": true,
  "operation_id": "op_01JXYZ",
  "entity_id": "order-42",
  "previous_state": "pending",
  "current_state": "completed",
  "affected_rows": 1,
  "committed": true,
  "verified": true
}

这里最重要的字段不是 ok,而是 affected_rows、committed 和 verified。它们分别回答了“写中了吗”“提交了吗”“提交后读到的结果符合预期吗”。

把成功条件写进数据库操作

不要让智能体先读取状态,再在应用层判断,最后无条件更新。读取和更新之间可能有其他进程修改同一行,形成典型的竞态条件。

更稳妥的方式是使用条件更新:只有当前状态符合预期时,数据库才执行状态转换。

UPDATE orders
SET status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE id = :order_id
  AND status = 'pending';

执行后必须检查受影响行数:

  • 1:本次操作完成了合法状态转换;
  • 0:订单不存在,或者它已经不再是 pending;
  • 大于 1:通常意味着约束或查询条件存在严重问题。

如果不同失败原因会触发不同恢复动作,可以在更新失败后读取当前状态,但不要把“影响 0 行”直接解释为成功。即便当前状态已经是 completed,也需要判断它是否由同一个操作完成,这就需要幂等键或操作记录。

一个可运行的“提交后验证”示例

下面的 Python 示例只使用标准库和 SQLite,可以直接保存为 verified_agent_write.py 后运行。实际接入智能体时,可以把 complete_order() 暴露为工具函数,但应原样保留结构化结果,不要先压缩成一句“完成了”。

import json
import sqlite3
import uuid

DB_PATH = "agent_demo.db"


def init_db():
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute("DROP TABLE IF EXISTS orders")
        conn.execute("DROP TABLE IF EXISTS operations")
        conn.execute("""
            CREATE TABLE orders (
                id TEXT PRIMARY KEY,
                status TEXT NOT NULL,
                completed_by_operation TEXT
            )
        """)
        conn.execute("""
            CREATE TABLE operations (
                operation_id TEXT PRIMARY KEY,
                entity_id TEXT NOT NULL,
                result_json TEXT NOT NULL
            )
        """)
        conn.execute(
            "INSERT INTO orders(id, status) VALUES (?, ?)",
            ("order-42", "pending"),
        )


def complete_order(order_id, operation_id):
    with sqlite3.connect(DB_PATH) as conn:
        conn.row_factory = sqlite3.Row

        # 同一个操作被重试时,返回第一次执行的结果。
        cached = conn.execute(
            "SELECT result_json FROM operations WHERE operation_id = ?",
            (operation_id,),
        ).fetchone()
        if cached:
            return json.loads(cached["result_json"])

        cursor = conn.execute("""
            UPDATE orders
            SET status = 'completed', completed_by_operation = ?
            WHERE id = ? AND status = 'pending'
        """, (operation_id, order_id))

        affected_rows = cursor.rowcount
        row = conn.execute(
            "SELECT id, status, completed_by_operation FROM orders WHERE id = ?",
            (order_id,),
        ).fetchone()

        verified = (
            affected_rows == 1
            and row is not None
            and row["status"] == "completed"
            and row["completed_by_operation"] == operation_id
        )

        result = {
            "ok": verified,
            "operation_id": operation_id,
            "entity_id": order_id,
            "affected_rows": affected_rows,
            "committed": verified,
            "verified": verified,
            "current_state": row["status"] if row else None,
            "error": None if verified else "State transition was not applied",
        }

        # 操作结果和业务更新处于同一事务中。
        conn.execute(
            "INSERT INTO operations(operation_id, entity_id, result_json) VALUES (?, ?, ?)",
            (operation_id, order_id, json.dumps(result)),
        )
        return result


if __name__ == "__main__":
    init_db()
    operation_id = str(uuid.uuid4())

    first = complete_order("order-42", operation_id)
    retry = complete_order("order-42", operation_id)

    print("first:", json.dumps(first, ensure_ascii=False, indent=2))
    print("retry:", json.dumps(retry, ensure_ascii=False, indent=2))

运行:

python verified_agent_write.py

这个示例体现了三个关键约束:

  • 条件更新:只有 pending 才能转换为 completed;
  • 提交后验证:重新读取记录,并核对状态和操作 ID;
  • 幂等重试:相同 operation_id 不会再次执行副作用,而是返回已有结果。

示例为了便于运行使用 SQLite。生产系统如果使用 PostgreSQL、MySQL 或分布式服务,还需要结合事务隔离级别、锁策略、唯一约束和超时语义进行调整。

智能体不应自行“脑补”成功

工具协议也需要明确约束模型行为。可以在工具说明或系统提示中加入类似规则:

只有当工具结果同时满足以下条件时,才可以向用户声称操作完成:
- ok == true
- committed == true
- verified == true
- affected_rows == 1

如果结果未知、超时或验证失败,不得表述为成功。
应告知用户当前状态未知或操作未完成,并返回 operation_id 供查询。
不得因为工具调用已发出,就推断数据库已提交。

这段规则无法代替数据库约束,但能防止模型把模糊结果包装成确定事实。尤其是超时场景,正确状态往往不是“失败”,而是“未知”:请求可能已经提交,只是响应丢失。此时盲目重试会产生双重副作用,而携带同一幂等键重试才更安全。

对于跨服务流程,单个数据库事务通常覆盖不了整个操作。可以这样实践:

  • 写业务数据和 outbox 事件时使用同一事务;
  • 消费者用事件 ID 去重;
  • 为长流程记录 pending、running、succeeded、failed 等明确状态;
  • 由独立查询接口根据 operation_id 返回权威状态;
  • 不让智能体根据日志片段或 HTTP 200 自行推断最终结果。

上线前检查清单

把智能体接入真实写操作前,至少检查以下事项:

  • 数据库是否用唯一约束、外键和状态条件保护业务不变量;
  • 工具是否返回受影响行数、操作 ID、提交状态和验证结果;
  • 每个有副作用的调用是否接受幂等键;
  • 超时是否被建模为“结果未知”,而不是直接重试;
  • 是否能够按操作 ID 查询最终状态;
  • 模型是否被禁止在缺少证据时声称成功;
  • 日志是否同时记录会话 ID、工具调用 ID、操作 ID和目标实体 ID;
  • 高风险操作是否需要人工确认或二次审批。

可靠的智能体不是一个更会说“完成了”的模型,而是一套让它无法在证据不足时宣称完成的系统。数据库约束负责守住事实,幂等机制负责承受重试,验证步骤负责连接执行结果与用户表述。只有这三层都成立,“任务完成”才不再是一句猜测。


相关推荐