引言:长时任务是一座必须跨过的桥
如果你做过AI Agent开发,一定遇到过这种情况:一个复杂的多步骤任务跑了20分钟,马上就要出结果了,突然网络抖动,LLM API超时,整个任务前功尽弃。用户骂街,老板质疑,你只能苦笑着重新开始。这篇文章讲的就是如何让你的AI Agent具备「断点续跑」能力——任务中断后,从断点恢复,而不是从头再来。
一、为什么AI Agent的长时任务这么脆弱
传统软件的重试机制,直接套到AI Agent上基本都会翻车。原因有三点:
- 状态不可序列化:Agent的中间推理状态、工具调用上下文、LLM的thinking过程,这些东西不是简单的变量,很难存到数据库里
- 非幂等工具调用:你的Agent可能已经给第三方API发了请求,重试一次就变成发两次,数据就重复了
- Token消耗不友好:从头重试意味着重新跑一遍所有的LLM调用,Token费用直接翻倍
我自己踩过最惨的一次坑:一个RAG问答任务,跑了35分钟(大量文档分块+多轮推理),最后一步写数据库时网络断了。重试,又35分钟。老板问我为什么一个问答要花70分钟,我无法回答。
二、断点续跑的核心设计思路
断点续跑的本质是把任务的执行过程变成可持久化的状态机。每次进入下一个节点前,先把当前状态存下来。中断后恢复时,从存储中读出状态,从断点继续。
2.1 状态模型设计
一个可续跑的Agent状态需要包含以下字段:
interface AgentCheckpoint {
taskId: string; // 任务唯一ID
currentNode: string; // 当前执行到的节点
context: Record; // 全局上下文(变量、中间结果)
toolCallHistory: Array<{ // 工具调用历史(用于幂等判断)
toolName: string;
params: any;
result: any;
timestamp: number;
}>;
llmCallHistory: Array<{ // LLM调用历史(避免重复推理)
prompt: string;
response: string;
tokenUsage: number;
}>;
createdAt: number;
updatedAt: number;
} 2.2 检查点(Checkpoint)存放位置的选择
| 存储方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 本地SQLite | 零依赖,读写快,支持原子操作 | 分布式场景不支持 | 单机Agent,快速原型 |
| Redis | 高性能,支持TTL自动清理 | 需要额外部署 | 生产环境,高并发 |
| PostgreSQL | 支持JSONB,查询灵活,事务保证 | 重量级 | 企业级,需要复杂查询 |
我的建议:前期用SQLite快速验证,上线后用Redis。PostgreSQL有点杀鸡用牛刀,除非你的检查点数据需要复杂关联查询。
三、实战:给OpenClaw Agent加断点续跑
下面是我在一个真实项目中用的实现方案,经过生产验证。
3.1 检查点管理器
const fs = require('fs');
const path = require('path');
class CheckpointManager {
constructor(dbPath = './agent_checkpoints.db') {
this.dbPath = dbPath;
this.initDB();
}
initDB() {
// 使用SQLite存储检查点
const Database = require('better-sqlite3');
this.db = new Database(this.dbPath);
this.db.exec(`
CREATE TABLE IF NOT EXISTS checkpoints (
task_id TEXT PRIMARY KEY,
node_name TEXT NOT NULL,
context_json TEXT NOT NULL,
tool_history_json TEXT,
llm_history_json TEXT,
created_at INTEGER,
updated_at INTEGER
);
`);
}
save(taskId, nodeName, context, toolHistory, llmHistory) {
const stmt = this.db.prepare(`
INSERT INTO checkpoints
(task_id, node_name, context_json, tool_history_json, llm_history_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(task_id) DO UPDATE SET
node_name = excluded.node_name,
context_json = excluded.context_json,
tool_history_json = excluded.tool_history_json,
llm_history_json = excluded.llm_history_json,
updated_at = excluded.updated_at
`);
stmt.run(
taskId,
nodeName,
JSON.stringify(context),
JSON.stringify(toolHistory),
JSON.stringify(llmHistory),
Date.now(),
Date.now()
);
}
load(taskId) {
const row = this.db.prepare(
'SELECT * FROM checkpoints WHERE task_id = ?'
).get(taskId);
if (!row) return null;
return {
taskId: row.task_id,
currentNode: row.node_name,
context: JSON.parse(row.context_json),
toolCallHistory: JSON.parse(row.tool_history_json || '[]'),
llmCallHistory: JSON.parse(row.llm_history_json || '[]'),
};
}
delete(taskId) {
this.db.prepare('DELETE FROM checkpoints WHERE task_id = ?').run(taskId);
}
}
module.exports = CheckpointManager;3.2 在Agent执行循环中嵌入检查点
关键点:在每个节点执行前保存检查点,而不是执行后。这样即使节点执行到一半崩溃,重启后还能从当前节点重新执行(配合幂等工具调用)。
async function executeAgentTask(taskId, workflow) {
const cpManager = new CheckpointManager();
let checkpoint = cpManager.load(taskId);
// 第一次执行 vs 恢复执行
let currentNodeIndex = 0;
let context = {};
let toolHistory = [];
let llmHistory = [];
if (checkpoint) {
// 恢复模式:从断点继续
currentNodeIndex = workflow.nodes.findIndex(n => n.name === checkpoint.currentNode);
context = checkpoint.context;
toolHistory = checkpoint.toolCallHistory;
llmHistory = checkpoint.llmCallHistory;
console.log(`[恢复] 从节点 ${checkpoint.currentNode} 继续任务 ${taskId}`);
} else {
// 首次执行:创建检查点
cpManager.save(taskId, workflow.nodes[0].name, {}, [], []);
}
// 执行工作流
for (let i = currentNodeIndex; i < workflow.nodes.length; i++) {
const node = workflow.nodes[i];
// ⚠️ 关键:进入节点前先保存检查点
cpManager.save(taskId, node.name, context, toolHistory, llmHistory);
try {
const result = await executeNode(node, context, toolHistory, llmHistory);
// 更新上下文
context = { ...context, ...result.contextUpdate };
// 记录工具调用历史(用于幂等判断)
if (result.toolCalls) {
toolHistory.push(...result.toolCalls);
}
// 记录LLM调用历史(避免重复推理)
if (result.llmCall) {
llmHistory.push(result.llmCall);
}
} catch (err) {
console.error(`[中断] 节点 ${node.name} 执行失败:`, err.message);
// 检查点已经保存,下次可以从这个节点恢复
throw err;
}
}
// 任务完成,删除检查点
cpManager.delete(taskId);
console.log(`[完成] 任务 ${taskId} 执行成功`);
}四、三个必须注意的坑
坑1:工具调用的幂等性
这是断点续跑最容易翻车的地方。假设你的Agent调用了一个「发送邮件」的工具,执行成功但还没保存检查点就崩溃了。重启后从检查点恢复,又调用一次「发送邮件」,用户就收到两封邮件。
解决方案:给每个工具调用加幂等键(Idempotency Key)。
async function idempotentToolCall(toolName, params, toolHistory) {
// 生成幂等键
const idemKey = crypto.createHash('sha256')
.update(toolName + JSON.stringify(params))
.digest('hex');
// 检查历史中是否已经执行过
const existing = toolHistory.find(h =>
h.idemKey === idemKey && h.status === 'success'
);
if (existing) {
console.log(`[幂等] 工具 ${toolName} 已执行过,直接返回历史结果`);
return existing.result;
}
// 执行工具调用
const result = await callTool(toolName, params);
// 记录到历史
toolHistory.push({
idemKey,
toolName,
params,
result,
status: 'success',
timestamp: Date.now(),
});
return result;
}坑2:LLM调用的缓存
断点续跑后,Agent可能会重新执行一遍之前已经做过的LLM推理,浪费Token。解决方法是把LLM的(prompt, response)对缓存起来。
注意:不是所有LLM调用都能缓存。如果prompt里包含时间戳、随机值,或者模型本身有随机性(temperature > 0),缓存的结果可能不适用。我只缓存temperature=0的确定性调用。
坑3:检查点存储的并发安全
如果你的Agent是多实例部署(比如K8s里跑多个Pod),检查点的读写会有并发问题。两个实例同时更新同一个task的检查点,状态就乱了。
解决方案:用数据库乐观锁或者分布式锁。我用一个简单的version字段做乐观锁:
-- 检查点表增加version字段 ALTER TABLE checkpoints ADD COLUMN version INTEGER DEFAULT 0; -- 更新时带version检查 UPDATE checkpoints SET node_name = ?, context_json = ?, version = version + 1 WHERE task_id = ? AND version = ?; -- 如果受影响行数为0,说明版本冲突,需要重试
五、超时配置的最佳实践
光有断点续跑还不够,你还需要合理的超时配置,避免任务无限期挂起。根据我的经验:
- LLM调用超时:设置30-60秒,超时后走重试(最多3次)
- 工具调用超时:设置60-120秒,超过后记录失败,任务进入「待恢复」状态
- 整个任务超时:设置2-6小时(根据业务而定),超过后标记为「已放弃」
超时后的处理策略:
- 如果是网络超时:自动重试,指数退避(1s, 2s, 4s...)
- 如果是业务错误(比如参数错误):不重试,直接失败
- 如果是LLM限流(429错误):等待一段时间后重试,或者切换到备用模型
六、总结:让AI Agent真正生产可用的最后一公里
断点续跑听起来是个小功能,但它是AI Agent从Demo走向生产的关键一步。没有它,你的Agent就是个精致的花瓶——好看但经不起折腾。
核心要点回顾:
- 把Agent的执行过程变成可持久化的状态机
- 每个节点执行前保存检查点
- 工具调用必须幂等,用Idempotency Key保证
- LLM调用结果可以缓存(temperature=0的场景)
- 超时配置要分层:LLM层、工具层、任务层
做了这些,你的AI Agent才能真正在生产环境跑得稳、跑得久。
相关阅读:AI Agent工作流性能监控与优化实战 | AI Agent多轮对话上下文管理实战 | AI工作流自动编排实战:多Agent协作的架构设计
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论