会话管理与持久化:保存和恢复对话
学习如何保存和恢复 Agent 会话,实现长时间运行的任务。
会话管理与持久化:保存和恢复对话
前言
在前面的文章中,我们学习了 Agent4J 的各种功能。但有一个问题:如果程序退出了,对话历史就丢失了。
对于简单的问答,这没问题。但对于长时间运行的任务,比如代码重构、数据分析,你可能需要:
- 保存当前进度,下次继续
- 在多个地方访问同一个会话
- 实现会话的版本管理
Agent4J 的会话序列化功能可以帮你解决这些问题。
会话序列化
保存会话
AgentClientSession session = agent.createSession();
// 进行一些对话
session.command("请帮我分析这个项目")
.then(handler)
.error(errorHandler);
// 保存会话
String json = session.serialization();
// 保存到文件
Files.writeString(Paths.get("session.json"), json);
// 或者保存到数据库
database.saveSession(sessionId, json);
恢复会话
// 从文件读取
String json = Files.readString(Paths.get("session.json"));
// 或者从数据库读取
String json = database.getSession(sessionId);
// 恢复会话
AgentClientSession restored = agent.getSessionFromSerialization(json);
// 继续对话
restored.command("继续刚才的任务")
.then(handler)
.error(errorHandler);
序列化的内容
会话序列化会保存:
- 对话历史(所有消息)
- 当前上下文
- 工具调用记录
- Plan 执行状态
- Sub-Agent 信息
但不会保存:
- Agent 的配置(名称、描述、模型等)
- 工具和技能的定义
- 回调处理器
这意味着你需要用同一个 Agent 实例来恢复会话。
实际应用
应用 1:长时间运行的任务
假设你有一个需要几小时才能完成的任务:
public class LongRunningTask {
private AgentClient agent;
private String sessionId;
public void startTask() {
agent = createAgent();
AgentClientSession session = agent.createSession();
// 开始任务
session.command("请帮我重构整个项目,包括代码审查、bug 修复、性能优化")
.then(new AgentResultHandler() {
public void onMessage(String msg) {
System.out.print(msg);
}
public void onPlanStepComplete(Plan plan, int current, int total, String step, String result) {
// 每完成一步,保存会话
saveSession(session);
System.out.println("步骤 " + current + " 完成,已保存进度");
}
})
.error(e -> {
e.printStackTrace();
// 出错时也保存会话
saveSession(session);
});
}
private void saveSession(AgentClientSession session) {
String json = session.serialization();
Files.writeString(Paths.get("task-session.json"), json);
}
public void resumeTask() {
// 恢复会话
String json = Files.readString(Paths.get("task-session.json"));
AgentClientSession session = agent.getSessionFromSerialization(json);
// 继续任务
session.command("继续刚才的重构任务")
.then(handler)
.error(errorHandler);
}
}
应用 2:会话版本管理
你可以保存多个版本的会话:
public class SessionVersionManager {
private String baseDir = "sessions";
public void saveVersion(String taskId, String version, AgentClientSession session) {
String dir = baseDir + "/" + taskId;
Files.createDirectories(Paths.get(dir));
String json = session.serialization();
Files.writeString(Paths.get(dir + "/" + version + ".json"), json);
}
public AgentClientSession loadVersion(AgentClient agent, String taskId, String version) {
String json = Files.readString(Paths.get(baseDir + "/" + taskId + "/" + version + ".json"));
return agent.getSessionFromSerialization(json);
}
public List<String> listVersions(String taskId) {
return Files.list(Paths.get(baseDir + "/" + taskId))
.map(path -> path.getFileName().toString().replace(".json", ""))
.collect(Collectors.toList());
}
}
使用示例:
SessionVersionManager manager = new SessionVersionManager();
// 保存版本
manager.saveVersion("refactor-task", "v1", session);
manager.saveVersion("refactor-task", "v2", session);
// 加载版本
AgentClientSession v1 = manager.loadVersion(agent, "refactor-task", "v1");
应用 3:多端访问
如果你有一个 Web 应用,可以在多个地方访问同一个会话:
@RestController
public class AgentController {
@Autowired
private AgentService agentService;
@PostMapping("/api/chat")
public ResponseEntity<String> chat(@RequestBody ChatRequest request) {
// 获取或创建会话
AgentClientSession session = agentService.getSession(request.getSessionId());
// 发送消息
StringBuilder response = new StringBuilder();
session.command(request.getMessage())
.then(new AgentResultHandler() {
public void onMessage(String msg) {
response.append(msg);
}
})
.error(e -> {
response.append("错误:").append(e.getMessage());
});
// 保存会话
agentService.saveSession(request.getSessionId(), session);
return ResponseEntity.ok(response.toString());
}
}
@Service
public class AgentService {
private Map<String, String> sessionStore = new ConcurrentHashMap<>();
private AgentClient agent;
public AgentClientSession getSession(String sessionId) {
String json = sessionStore.get(sessionId);
if (json != null) {
return agent.getSessionFromSerialization(json);
}
return agent.createSession();
}
public void saveSession(String sessionId, AgentClientSession session) {
String json = session.serialization();
sessionStore.put(sessionId, json);
}
}
应用 4:会话导出与分享
你可以把会话导出为文件,分享给别人:
public class SessionExporter {
public void exportToFile(AgentClientSession session, String filename) {
String json = session.serialization();
// 格式化 JSON
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.readValue(json, ObjectNode.class);
String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
Files.writeString(Paths.get(filename), prettyJson);
}
public AgentClientSession importFromFile(AgentClient agent, String filename) {
String json = Files.readString(Paths.get(filename));
return agent.getSessionFromSerialization(json);
}
}
会话清理
长时间运行的应用需要注意会话清理:
public class SessionManager {
private Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
public void cleanup() {
long now = System.currentTimeMillis();
long maxAge = 24 * 60 * 60 * 1000; // 24 小时
sessions.entrySet().removeIf(entry -> {
SessionInfo info = entry.getValue();
return (now - info.getLastAccessTime()) > maxAge;
});
}
private static class SessionInfo {
private String sessionId;
private String sessionData;
private long lastAccessTime;
// getter 和 setter
}
}
性能考虑
序列化大小
会话序列化可能会产生较大的 JSON 字符串,特别是对话历史很长时。建议:
- 定期清理不需要的对话历史
- 对于非常长的对话,考虑拆分成多个会话
- 使用压缩算法减小存储空间
存储选择
根据应用规模选择合适的存储:
- 文件系统:适合单机应用
- Redis:适合分布式应用
- 数据库:适合需要持久化的应用
- 对象存储:适合大规模应用
并发安全
如果多个线程可能同时访问同一个会话,需要考虑并发安全:
public class ThreadSafeSessionManager {
private final ConcurrentHashMap<String, String> sessionStore = new ConcurrentHashMap<>();
private final AgentClient agent;
public AgentClientSession getSession(String sessionId) {
String json = sessionStore.get(sessionId);
if (json != null) {
return agent.getSessionFromSerialization(json);
}
return agent.createSession();
}
public synchronized void saveSession(String sessionId, AgentClientSession session) {
String json = session.serialization();
sessionStore.put(sessionId, json);
}
}
最佳实践
1. 定期保存
对于长时间运行的任务,建议定期保存会话:
session.command(task).then(new AgentResultHandler() {
private int messageCount = 0;
public void onMessage(String msg) {
messageCount++;
// 每 10 条消息保存一次
if (messageCount % 10 == 0) {
saveSession(session);
}
}
});
2. 保存时机
在关键节点保存会话:
- 每个 Plan 步骤完成后
- 每个 Sub-Agent 完成后
- 发生错误时
- 用户主动保存时
3. 版本管理
对于重要的任务,建议保留多个版本:
- 每个重要步骤完成后保存一个版本
- 保留最近 N 个版本
- 定期清理旧版本
4. 错误恢复
实现错误恢复机制:
public class ResilientSession {
public void executeWithRecovery(AgentClient agent, String task) {
try {
AgentClientSession session = loadOrCreateSession(agent);
session.command(task)
.then(handler)
.error(e -> {
// 保存会话
saveSession(session);
// 记录错误
logError(e);
// 尝试恢复
retryOrNotify(task);
});
} catch (Exception e) {
// 初始化失败,创建新会话
AgentClientSession session = agent.createSession();
session.command(task)
.then(handler)
.error(errorHandler);
}
}
}
总结
会话管理与持久化是 Agent4J 的重要特性,让你能:
- 保存和恢复对话
- 实现长时间运行的任务
- 支持多端访问
- 实现会话版本管理
合理使用这些特性,可以构建更强大、更可靠的 Agent 应用。