AI 智能体很擅长生成一段听起来确定无疑的回答,但“我已经完成了”并不等于状态真的写入数据库。工具调用超时、事务回滚、重复执行、写错记录,甚至仅仅误读了返回值,都可能让智能体宣布成功,而业务系统仍停留在原地。
从这个标题揭示的问题出发,真正需要解决的不是如何让模型更自信,而是如何让“完成”成为一个可以被数据库证明的事实。
“调用成功”和“业务完成”是两件事
一个常见的智能体执行链路如下:
- 模型决定调用
complete_order工具; - 工具向数据库发送更新;
- 工具返回一段文本;
- 模型根据文本回答“订单已完成”。
危险通常出现在第 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;
- 高风险操作是否需要人工确认或二次审批。
可靠的智能体不是一个更会说“完成了”的模型,而是一套让它无法在证据不足时宣称完成的系统。数据库约束负责守住事实,幂等机制负责承受重试,验证步骤负责连接执行结果与用户表述。只有这三层都成立,“任务完成”才不再是一句猜测。