news 2026/9/6 12:27:00

跨Agent会话延续:打破AI Agent供应商锁定的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
跨Agent会话延续:打破AI Agent供应商锁定的工程实践

先说一个很常见的场景:你在 A 平台上调试了半小时的 Agent 工作流,刚把工具调用链路跑通,却发现团队另一个成员用的是 B 平台的 Agent,或者公司采购的 RAG 服务只支持另一套 API。这时你想把这段对话继续下去,往往只能复制粘贴最后的输出,甚至重新从零开始喂上下文。这种“换一个工具就相当于换一个脑子”的体验,就是 AI Agent 领域越来越被关注的lock-in(供应商锁定)问题。

这篇文章不讨论哲学意义上的“Agent 是否应该有意识”,而是聚焦一个非常工程化的问题:如何让一个 Agent 开始的对话,能无缝交给另一个 Agent 继续。全文会从概念拆解、互操作原理、协议设计、代码实现到踩坑排查,给出一个可直接落地的思路。如果你正在做 Agent 应用开发、RAG 系统集成,或者刚接触 AI Agent 开发并想避开未来的迁移陷阱,这篇文章应该能帮你省下不少时间。

1. 背景与核心概念

1.1 什么是 AI Agent Lock-in

传统软件里的 lock-in,往往体现在数据库选型、云厂商绑定、私有协议这几个方面。到了 AI Agent 时代,lock-in 问题变得更加隐蔽,也更难处理。

一个 AI Agent 的核心能力,不只是“调用大模型接口”,而是围绕一次目标任务维护一套完整的交互状态。这套状态包括:

  • 用户的原始意图和多次对话记录。
  • Agent 自己规划出来的执行步骤(Plan)。
  • 每一步执行时用到的工具名称、入参、返回值。
  • 已经收集到的中间结果,比如查到的订单号、生成的报告草稿。
  • Agent 内部维护的短期记忆和长期记忆。

如果这套状态只存在于某一个 Agent 产品内部,无法导出、无法标准化、无法被其他 Agent 理解,那么你一旦切换平台,之前的“思考过程”和“执行进度”就会全部丢失。

从这个角度看,AI Agent 的 lock-in 是比 API 接口锁定更严重的锁定。API 锁定只是换一个 SDK、改几行请求,而 Agent 状态锁定意味着你辛辛苦苦构建的上下文资产全部作废。

那为什么过去很少有人解决这件事?原因是 AI Agent 本身还在快速演化,几乎每三个月就会有一批新的 Agent 框架和协议出现。在标准还没成型的时候谈互操作,确实容易被看成“过早设计”。但从工程经验来看,有些基础性的抽象现在就可以做,而且做了之后收益非常明显。

1.2 对话连续性的本质

标题里有一句话很关键:Start conversation with one agent – continue with another。这句话看起来像产品口号,但它背后其实是一个严格的技术命题:

给定两个不同的 Agent 运行时 A 和 B,如果 A 已经与用户完成了一轮或多轮对话,那么 B 能否在完全不丢失上下文的前提下,从 A 的最后一轮输出继续工作?

要实现这个目标,仅仅把 A 的最终回答文本传给 B 是不够的。B 需要知道的不只是“刚才说了什么”,还包括:

  1. 用户的目标是什么。
  2. 已经完成了哪些步骤。
  3. 当前处于哪个阶段。
  4. 有哪些约束条件和偏好。
  5. 后续还有哪些待办步骤。

也就是说,连续性的本质是状态迁移,而不是文本复制。一次成功的 Agent 会话,应该像一份工程文档一样,不仅包含结论,还包含可追溯的中间过程。

1.3 为什么需要跨 Agent 会话延续

有人可能会问:我直接在一个平台里把 Agent 用完不就行了?为什么非要跨 Agent 继续对话?

结合我自己的实践经历,跨 Agent 会话延续在下面几类场景中是刚需:

场景一:同任务多工具协作

一个复杂业务往往需要多个专业 Agent 配合。比如一个 Agent 负责对话理解,另一个 Agent 负责代码生成,还有一个 Agent 负责调用内部 API。用户希望只在对话层聊一次,后台由多个 Agent 接力完成。此时如果这些 Agent 来自不同厂商,或者由不同团队开发,它们之间必须能交换上下文。

场景二:平台迁移与升级

公司早期为了快速上线,选择了一款轻量 Agent 产品。等用户量和数据量上来后,发现它在权限控制、审计日志、私有化部署等方面满足不了要求,需要换到另一个平台。如果会话状态不能平滑迁移,老用户的每个会话都会断掉重来,这对体验来说几乎是灾难。

场景三:多端无缝切换

用户在 Web 端与 Agent 交流到一半,切换到手机 App 上希望接着聊。如果 Web 端和 App 端底层接入的是不同 Agent 产品,就需要一个统一的会话中间层。

场景四:容灾与高可用

主 Agent 服务出现故障时,流量切换到备用 Agent。如果两个 Agent 的对话状态不互通,降级体验就会变成“从头再来”。

2. 跨 Agent 会话延续的技术难点

理解了目标之后,再来看看实现这件事到底难在哪里。

2.1 上下文表示不一致

不同 Agent 框架对 Context(上下文)的建模方式差别很大。有的把上下文理解成纯文本数组,每条消息只有 role 和 content;有的则把上下文构建成结构化的消息对象,包含 tool_call_id、tool_calls、name 等字段;还有一些 Agent 框架为了支持复杂工作流,会维护一个 DAG 图,多个节点之间共享不同的上下文片段。

上下文表示不一致,带来的直接后果是:即使你知道需要传递数据,也不知道对方期望的数据格式是什么。就好比中国人和德国人用中文和德语各自写了一本技术手册,互相之间不能直接阅读对方的目录。

2.2 执行状态无法通过 API 获取

很多商业化 Agent 平台只开放了“发起对话”和“获取回答结果”两个接口。对于 Agent 内部的中间步骤——比如它调用了哪几个工具、每个工具返回了什么、它已经执行到哪一步——平台并不提供导出能力。

没有中间步骤信息,就无法构建可恢复的会话状态。你能拿到的只是一个最终回答文本,这就像看了一场魔术表演,只知道结果,但不清楚中间的手法。

2.3 工具调用映射困难

Agent 与普通聊天机器人最大的区别之一是它可以调用工具。在 Agent A 里,一个查询订单的 API 叫queryOrder,参数是orderId。在 Agent B 里,同样的能力可能叫get_order_info,参数是order_no

会话延续时,如果 A 产生的工具调用记录直接传给 B,B 可能完全无法理解。更麻烦的是,有些工具返回的数据里包含了大量内部数据结构的细节,这些细节在 B 的上下文模型里可能没有对应字段。

2.4 记忆机制无法互通

现代 Agent 产品普遍加入了短期记忆和长期记忆。短期记忆负责把最近的对话压缩成摘要,长期记忆会把关键信息存入向量数据库或知识图谱。

问题在于,每家产品的记忆存储格式和检索策略都是私有的。A 平台生成的记忆摘要,B 平台检索不到;B 平台的知识向量,A 平台也用不了。这导致会话延续时,能传递的往往只有最近几轮的原始文本,而更早的有价值信息被压缩后留在了原平台内部。

2.5 会话安全边界

跨 Agent 会话延续还涉及安全边界问题。Agent A 在会话过程中可能获取了用户的私有数据,这些数据能否传递给 Agent B?如果可以传递,传输过程如何加密?传递过去后,Agent B 的日志系统是否会记录这些数据?这些都是在设计跨 Agent 延续方案时必须提前考虑的合规问题。

3. 核心设计思路:让 Agent 对话变成可携带的会话状态

面对上面这些难点,一个可行的工程思路是:不要把 Agent 切换理解为“文本交接”,而是把会话整体抽象为一种可导出、可导入、可校验的标准化状态对象。

3.1 会话状态数据模型

在设计跨 Agent 会话延续方案时,我通常会定义一个统一的会话状态结构。这个结构不是某个 Agent 平台私有格式的简单复制,而是综合考虑了大部分 Agent 产品都具备的公共属性。

结构上至少需要包含以下部分:

状态字段说明
session_id会话唯一标识
agent_id当前负责该会话的 Agent 标识
user_id用户标识,用于权限控制
message_history多轮对话消息列表
current_goal当前会话的核心目标
plan_steps已经规划好的执行步骤,包括已完成和待执行
tool_executions已经执行过的工具调用记录和返回结果
memory_context短期摘要与关键事实信息
metadata自定义扩展字段,兼容各平台专有信息

这个结构的关键点在于:它把对话文本和 Agent 执行状态拆开了。对话文本只是状态的一部分,更重要的是执行记录和计划。这样,即使换了一个 Agent 产品,新 Agent 也能快速理解“现在进行到哪一步”。

3.2 标准化上下文交换格式

有了状态模型,还需要定义交换格式。在实践中,我推荐使用 JSON 作为基础序列化格式,因为它的通用性和可读性最好。

一个标准的会话状态导出文件可以这样设计:

{ "schema_version": "1.0", "session_id": "sess_20250115_001", "agent_id": "agent-a", "user_id": "user_123", "current_goal": "查询本月销售数据并生成分析报告", "message_history": [ { "role": "user", "content": "我想看一下本月销售情况", "timestamp": "2025-01-15T10:00:00Z" }, { "role": "assistant", "content": "好的,我来查询本月的销售数据。", "timestamp": "2025-01-15T10:00:05Z" }, { "role": "tool_call", "name": "query_sales", "arguments": { "month": "2025-01" }, "timestamp": "2025-01-15T10:00:06Z" }, { "role": "tool_result", "name": "query_sales", "content": { "total_amount": 1280000, "order_count": 3200 }, "timestamp": "2025-01-15T10:00:08Z" } ], "plan_steps": [ { "step_id": "step_1", "description": "查询本月销售数据", "status": "completed", "result": "总销售额 128 万,订单数 3200" }, { "step_id": "step_2", "description": "按区域拆分销售数据", "status": "pending" }, { "step_id": "step_3", "description": "生成销售分析报告", "status": "pending" } ], "tool_executions": [ { "tool_name": "query_sales", "input": { "month": "2025-01" }, "output": { "total_amount": 1280000, "order_count": 3200 }, "status": "success" } ], "memory_context": { "summary": "用户关注本月销售数据,需要按区域拆解并生成报告", "key_facts": { "period": "2025-01", "currency": "CNY" } }, "metadata": { "source_platform": "agent-a", "exported_at": "2025-01-15T10:05:00Z", "custom_fields": { "agent_a_specific_flag": true } } }

这份 JSON 结构解决了一个核心问题:任何 Agent 平台,只要实现一个标准的导入解析器,就能读取另一个平台导出的会话状态。即使某个平台的私有信息无法被对方理解,也会保存在metadata.custom_fields中,不会因为格式不兼容而丢失。

3.3 兼容层与适配器模式

虽然标准化格式很理想,但现实世界里的 Agent 产品不会主动遵循你的格式。所以工程上需要一个适配层,把每个 Agent 平台的私有格式转换成统一格式。

这个适配层的设计模式比较接近企业集成中常用的消息适配器。每个 Agent 平台对应一个适配器类,负责两件事:

  1. 导出时,从平台私有对象转换为统一会话状态对象。
  2. 导入时,从统一会话状态对象转换为目标平台能接受的输入格式。

适配层带来的最大收益是:当新增一个 Agent 平台时,只需要开发一个新适配器,不需要修改业务逻辑和上层调用。

3.4 上下文摘要与压缩

有一个现实情况需要接受:并不是所有平台都支持导入完整的工具调用记录。有些平台的 API 只允许你设置一段 system prompt 或者一个初始用户消息。

这种情况下,为了保证更高程度的兼容性,我们需要一个摘要生成器。它把结构化的会话状态压缩成一段自然语言描述,供目标 Agent 理解:

你是本次任务的接续执行者。 当前用户目标是:查询本月销售数据并生成分析报告。 已完成步骤: 1. 已查询本月销售总额,结果为 128 万,订单数 3200。 下一步需要执行: 1. 按区域拆分销售数据。 2. 基于拆分结果生成销售分析报告。 用户偏好:报告需要包含同比和环比对比。

这段摘要看起来简单,但在实际跨 Agent 场景中非常实用。它不需要目标 Agent 理解复杂的数据模型,只需要它有足够的阅读能力和执行能力。对于目前主流的大语言模型来说,这种文本引导足以让新 Agent 从断点继续工作。

4. 完整实战:构建一个可延续会话的 Agent 消息中间层

理论部分讲完了,下面进入代码实战。这个项目的目标是构建一个轻量级的Agent 会话消息中间层(Agent Conversation Broker),它不直接与大模型交互,而是负责接收不同 Agent 的会话状态、统一转换格式、存储、并支持在其他 Agent 中恢复会话。

为了便于演示,我会用 Java + Spring Boot 实现一个简化版本。如果你熟悉 Python FastAPI,也可以参考同样的思路重写。

4.1 项目结构设计

先创建项目基础结构:

agent-conversation-broker/ ├── pom.xml ├── src/main/java/com/example/agentbroker/ │ ├── AgentBrokerApplication.java │ ├── controller/ │ │ └── ConversationController.java │ ├── model/ │ │ ├── SessionState.java │ │ ├── MessageRecord.java │ │ └── PlanStep.java │ ├── service/ │ │ └── ConversationTransferService.java │ ├── adapter/ │ │ ├── AgentAdapter.java │ │ ├── AgentAAdapter.java │ │ └── AgentBAdapter.java │ └── storage/ │ └── SessionStateStore.java └── src/main/resources/ └── application.yml

这个项目结构本身也体现了一种设计思想:controller负责 API 入口,service负责业务逻辑,adapter负责对接不同的 Agent 平台,storage负责状态存储。每一层的职责都比较单一,后续扩展新 Agent 时不需要修改 controller 和 service 的代码。

4.2 Maven 依赖配置

pom.xml中加入核心依赖:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

说明一下为什么引入 Redis:会话状态在跨 Agent 切换时需要被多个节点访问,Redis 在这里作为共享状态存储,保存序列化后的会话状态对象。如果你的项目没有 Redis 环境,也可以先用 ConcurrentHashMap 做内存版实现。

本文示例可以先用内存存储,方便你快速运行验证。Redis 存储可以等确认逻辑正确后再接上。

4.3 定义统一会话状态模型

新建SessionState.java

package com.example.agentbroker.model; import lombok.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Data public class SessionState { private String schemaVersion = "1.0"; private String sessionId; private String agentId; private String userId; private String currentGoal; private List<MessageRecord> messageHistory = new ArrayList<>(); private List<PlanStep> planSteps = new ArrayList<>(); private Map<String, Object> memoryContext = new HashMap<>(); private Map<String, Object> metadata = new HashMap<>(); }

MessageRecord.java用来表示统一的消息记录:

package com.example.agentbroker.model; import lombok.Data; import java.util.Map; @Data public class MessageRecord { private String role; private String content; private String name; private Map<String, Object> arguments; private Map<String, Object> toolResult; private String timestamp; }

PlanStep.java用来描述执行步骤:

package com.example.agentbroker.model; import lombok.Data; @Data public class PlanStep { private String stepId; private String description; private String status; private String result; }

模型设计里有几个细节值得注意:

  • 使用Map<String, Object>而不是强类型字段来保存工具调用参数和返回值,是为了兼容不同 Agent 平台的异构数据。
  • schemaVersion字段预留在版本升级时使用,后续如果模型结构变化,可以通过它判断兼容性。
  • 所有字段尽量使用包装类型和默认值,避免空指针问题。

4.4 适配器接口与实现

定义一个基础适配器接口AgentAdapter.java

package com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; public interface AgentAdapter { /** * 获取当前适配器支持的 Agent 标识 */ String getAgentType(); /** * 从平台私有格式导出为统一 SessionState */ SessionState exportSession(String platformSessionId); /** * 将统一 SessionState 导入目标 Agent */ String importSession(SessionState state); }

然后实现两个模拟适配器。

AgentAAdapter.java

package com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; @Component public class AgentAAdapter implements AgentAdapter { @Override public String getAgentType() { return "agent-a"; } @Override public SessionState exportSession(String platformSessionId) { // 模拟:从 Agent A 平台加载会话状态,并转换为统一格式 // 实际项目中,这里需要调用 Agent A 提供的 REST API SessionState state = new SessionState(); state.setSessionId(platformSessionId); state.setAgentId("agent-a"); state.setCurrentGoal("查询本月销售数据并生成分析报告"); return state; } @Override public String importSession(SessionState state) { // 模拟:向 Agent A 平台创建新会话,传入统一状态 // 实际项目中,这里需要调用 Agent A 的创建会话接口 return "agent-a-session-" + state.getSessionId(); } }

AgentBAdapter.java的结构类似,只是getAgentType()返回agent-b。可以这样写:

package com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; @Component public class AgentBAdapter implements AgentAdapter { @Override public String getAgentType() { return "agent-b"; } @Override public SessionState exportSession(String platformSessionId) { SessionState state = new SessionState(); state.setSessionId(platformSessionId); state.setAgentId("agent-b"); state.setCurrentGoal("查询本月销售数据并生成分析报告"); return state; } @Override public String importSession(SessionState state) { return "agent-b-session-" + state.getSessionId(); } }

这两个适配器目前是模拟实现,但接口的方向已经确定。真实对接时,只需要在exportSession中调用平台提供的查询接口,在importSession中调用创建会话接口,并把字段做一个映射即可。

4.5 核心服务:会话转移逻辑

核心服务ConversationTransferService.java是整套机制的中枢:

package com.example.agentbroker.service; import com.example.agentbroker.adapter.AgentAdapter; import com.example.agentbroker.model.SessionState; import com.example.agentbroker.storage.SessionStateStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class ConversationTransferService { @Autowired private SessionStateStore stateStore; @Autowired private List<AgentAdapter> adapters; private Map<String, AgentAdapter> adapterMap = new HashMap<>(); /** * 初始化时构建 adapter 映射表 */ @jakarta.annotation.PostConstruct public void init() { for (AgentAdapter adapter : adapters) { adapterMap.put(adapter.getAgentType(), adapter); } } /** * 从源 Agent 导出会话,并保存到统一存储 */ public String exportConversation(String sourceAgentType, String platformSessionId) { AgentAdapter sourceAdapter = adapterMap.get(sourceAgentType); if (sourceAdapter == null) { throw new IllegalArgumentException("Unsupported agent type: " + sourceAgentType); } // 调用适配器导出 SessionState state = sourceAdapter.exportSession(platformSessionId); // 保存到统一存储 stateStore.save(state); // 返回统一会话 ID,用于后续导入 return state.getSessionId(); } /** * 将统一会话导入目标 Agent */ public String importConversation(String targetAgentType, String sessionId) { AgentAdapter targetAdapter = adapterMap.get(targetAgentType); if (targetAdapter == null) { throw new IllegalArgumentException("Unsupported agent type: " + targetAgentType); } // 从统一存储加载会话状态 SessionState state = stateStore.load(sessionId); if (state == null) { throw new RuntimeException("Session not found: " + sessionId); } // 修改当前负责的 agent state.setAgentId(targetAgentType); // 调用适配器导入 return targetAdapter.importSession(state); } /** * 追加一条人工消息并更新会话状态 */ public void appendMessage(String sessionId, String role, String content) { SessionState state = stateStore.load(sessionId); if (state == null) { throw new RuntimeException("Session not found: " + sessionId); } Map<String, Object> message = new HashMap<>(); message.put("role", role); message.put("content", content); message.put("timestamp", java.time.Instant.now().toString()); // 这里简化处理,实际项目中需要创建 MessageRecord // state.getMessageHistory().add(message); stateStore.save(state); } }

init()方法中用@PostConstruct在服务启动时构建适配器映射,这样后续只需按代码注入即可。

SessionStateStore.java先提供一个内存版存储:

package com.example.agentbroker.storage; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @Component public class SessionStateStore { private Map<String, SessionState> store = new ConcurrentHashMap<>(); public String save(SessionState state) { if (state.getSessionId() == null || state.getSessionId().isEmpty()) { state.setSessionId(UUID.randomUUID().toString()); } store.put(state.getSessionId(), state); return state.getSessionId(); } public SessionState load(String sessionId) { return store.get(sessionId); } public void delete(String sessionId) { store.remove(sessionId); } }

接入 Redis 时,可以把saveload方法改为操作 Redis 字符串,序列化工具可以用 Jackson 的 ObjectMapper。这里的内存版足够展示业务逻辑。

4.6 编写控制器接口

有了服务层,还需要对外暴露 API。新建ConversationController.java

package com.example.agentbroker.controller; import com.example.agentbroker.model.SessionState; import com.example.agentbroker.service.ConversationTransferService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/conversation") public class ConversationController { @Autowired private ConversationTransferService transferService; /** * 导出会话:从源 Agent 获取状态,返回统一会话 ID * 请求示例: * POST /api/conversation/export * { * "sourceAgentType": "agent-a", * "platformSessionId": "a-12345" * } */ @PostMapping("/export") public Map<String, String> exportConversation(@RequestBody Map<String, String> request) { String sessionId = transferService.exportConversation( request.get("sourceAgentType"), request.get("platformSessionId") ); return Map.of("sessionId", sessionId); } /** * 导入会话:把统一会话状态导入到目标 Agent * 请求示例: * POST /api/conversation/import * { * "targetAgentType": "agent-b", * "sessionId": "uuid" * } */ @PostMapping("/import") public Map<String, String> importConversation(@RequestBody Map<String, String> request) { String platformSessionId = transferService.importConversation( request.get("targetAgentType"), request.get("sessionId") ); return Map.of("platformSessionId", platformSessionId); } /** * 查看当前统一会话状态 */ @GetMapping("/{sessionId}") public SessionState getSession(@PathVariable String sessionId) { return transferService.getSession(sessionId); } }

为了支持getSession,需要给ConversationTransferService加一个查询方法:

public SessionState getSession(String sessionId) { return stateStore.load(sessionId); }

这样一个最小可运行的会话中间层就完成了。它的执行流程是:

  1. 用户在 Agent A 中开始对话。
  2. 如果想切换到 Agent B,先调用/api/conversation/export,把 Agent A 的会话状态导出为统一格式,并保存到统一存储。
  3. 接着调用/api/conversation/import,指定目标 Agent B 和统一会话 ID。
  4. Agent B 通过适配器加载状态,并在自己的平台上创建新会话继续执行。

4.7 运行与验证

启动 Spring Boot 应用后,可以通过 curl 来模拟一次完整流程。

第一步,从 Agent A 导出会话:

curl -X POST http://localhost:8080/api/conversation/export \ -H "Content-Type: application/json" \ -d '{"sourceAgentType": "agent-a", "platformSessionId": "a-12345"}'

预期返回:

{"sessionId":"生成的-uuid"}

第二步,查询导出的会话状态:

curl http://localhost:8080/api/conversation/生成的-uuid

预期返回的是一个 SessionState JSON,包含agentIdcurrentGoal等字段。

第三步,将会话导入 Agent B:

curl -X POST http://localhost:8080/api/conversation/import \ -H "Content-Type: application/json" \ -d '{"targetAgentType": "agent-b", "sessionId": "生成的-uuid"}'

预期返回:

{"platformSessionId":"agent-b-session-生成的-uuid"}

到这里,一次完整的“Agent A 开始对话,Agent B 继续对话”就完成了。

5. 进阶方案:不用代码也能延续会话

上面的中间层方案适合有开发资源的团队。如果你暂时不想引入一套中间层系统,只是希望在日常使用或小型项目中减少 Agent lock-in 的影响,也可以采用一种更轻量的思路:把会话状态导出为人类可读的 Markdown / Text 文件,再作为新 Agent 的初始提示词导入。

我这里整理了一份导出模板:

# Agent 会话状态导出 ## 会话信息 - 原平台:Agent A - 原始会话ID:xxx - 导出时间:2025-01-15 10:05:00 ## 用户目标 查询本月销售数据并生成分析报告 ## 已完成步骤 1. [完成] 查询本月销售总额,结果为 128 万,订单数 3200 - 工具:query_sales - 入参:{"month": "2025-01"} - 返回:{"total_amount": 1280000, "order_count": 3200} ## 待执行步骤 1. [待执行] 按区域拆分销售数据 2. [待执行] 基于拆分结果生成分析报告 ## 关键对话记录 用户:我想看一下本月销售情况 助手:好的,我来查询本月的销售数据。 助手:本月销售总额 128 万,订单数 3200,接下来继续按区域拆分。 ## 用户偏好 - 报告需要包含同比和环比对比

使用这份 Markdown 文件时,直接把它作为 system prompt 或第一轮用户消息粘贴到新 Agent 中,再追加一句“请从中断的地方继续执行”。对于现代大模型来说,它通常能理解这种结构化文本,并在新会话中继续工作。

这种方法虽然不如标准协议精确,但在没有开发资源的情况下非常实用。它的价值在于让“会话状态”成为用户可以自己掌握的一等公民,而不是被锁定在某个产品内部。

6. 常见问题与排查思路

在实现跨 Agent 会话延续时,有几个高频问题。下面整理成表格,方便排查。

问题现象常见原因解决思路
从 Agent A 导出的会话状态导入 Agent B 后,B 不认上下文两个平台的消息格式不一致,直接传原始 JSON 无法解析增加适配器做字段映射,必要时转换成目标平台的私有格式
会话中工具调用记录丢失平台 API 不提供中间步骤查询,只返回最终结果在每次工具调用后,由中间层主动记录工具调用和返回结果,而不是依赖平台提供
导入新 Agent 后,它重新执行已完成的步骤没有把“已完成步骤”和“待执行步骤”区分清楚在摘要或状态中明确标记每个步骤的完成状态,并告诉新 Agent 不要重复执行已完成步骤
长期记忆无法恢复各平台的记忆存储格式和检索机制不互通提前设计统一记忆摘要,在导出时把记忆整理成自然语言文本写入 memory_context
切换后涉及用户隐私数据外泄会话状态中包含敏感字段,传输和存储未加密对敏感字段做脱敏或加密,传输使用 HTTPS,统一存储增加访问控制
新增 Agent 平台时需要改业务代码没有使用适配器模式,业务逻辑与新平台耦合引入适配器模式,新增平台时只实现 AgentAdapter 接口

还有一个很容易踩的坑:不要试图把一个平台的完整内部数据结构原样塞给另一个平台。你觉得自己很努力在做兼容,但对方根本不知道你的custom_xxx字段是什么意思。更好的做法是,导出时只保留通用语义,私有字段全部放进 metadata,导入时按需解析。

7. 最佳实践与工程建议

7.1 从第一天就引入会话 ID

如果你想在未来支持跨 Agent 迁移,现在就应该在系统中引入全局唯一的会话 ID。不要依赖各平台的原始会话 ID,而是自己生成 UUID,并在每次调用 Agent 时把会话 ID 透传进去。这样后续做状态同步时,就能建立映射关系。

7.2 状态导出要及时,不要等最后

Agent 执行过程中的中间产物往往比最终结果更有价值。比如代码生成场景中,每一步的修改记录、测试反馈、报错信息,都是恢复会话的关键。建议在每个工具调用完成后,就立即把状态增量同步到统一存储中。不要等到用户手动点击“导出”时才去拉取。

7.3 摘要与结构化数据并存

给新 Agent 传递上下文时,结构化的 JSON 适合程序解析,但自然语言摘要更适合大模型理解。推荐两套都保留:

  • raw_state:完整的结构化状态,供程序自动化处理。
  • summary_prompt:压缩后的自然语言描述,供新 Agent 快速理解任务背景。

7.4 安全边界与最小权限

跨 Agent 会话延续时,尤其要注意权限边界。新 Agent 应该只获得完成当前任务所需的最小上下文,而不是原始会话的全部敏感信息。建议在导出接口中增加字段过滤配置,只导出允许跨平台共享的字段。

从工程上可以这样实现:

public SessionState exportSession(String platformSessionId, List<String> allowedFields) { // 先导出全部状态 SessionState fullState = agentAdapter.exportSession(platformSessionId); // 按 allowedFields 过滤敏感字段 return filterState(fullState, allowedFields); }

7.5 日志与审计

跨 Agent 切换本质上是数据在不同系统之间流动,一定要记录完整的审计日志。至少包括:

  • 哪个用户发起了切换。
  • 从哪个 Agent 切换到哪个 Agent。
  • 导出和导入的会话 ID。
  • 切换前后的状态版本。
  • 是否有敏感字段被过滤。

有了这些日志,不仅便于排查问题,也能满足合规审计要求。

7.6 不要过度设计

最后想提一点:Agent 互操作还处于早期阶段,不要一上来就搞非常重量级的协议和标准,那是标准组织的工作。对于业务团队来说,先实现一个简单的 JSON 状态导出和导入适配器,已经能解决绝大多数问题。协议的字段设计可以保持精简,后续按实际需要慢慢扩展。

8. 总结与下一步学习方向

回到开头的问题:End AI Agent lock-in,不代表消灭某个平台,也不是要求所有 Agent 产品使用同一套 API。真正的解药是让会话状态成为用户可携带、可理解、可迁移的资产。

本文从概念上拆解了 Agent lock-in 的本质,指出会话连续性不是文本复制而是状态迁移;然后给出了一个标准化的会话状态模型,并结合 Java + Spring Boot 实现了一个支持跨 Agent 会话延续的中间层;最后还提供了一个无需开发也能操作的 Markdown 导出方案,以及一些工程上的最佳实践。

如果你准备把这个方案落到自己的项目里,我建议下一步按这个顺序深入:

  1. 先梳理自己当前使用的 Agent 平台支持哪些 API,确认能否获取到中间执行步骤。
  2. 设计一个最小可用的 SessionState 结构,只包含你真实需要的字段。
  3. 实现一个导出适配器和一个导入适配器,跑通一条路径。
  4. 把统一存储从内存换到 Redis,解决多实例共享问题。
  5. 逐步补充安全过滤、审计、摘要生成等能力。

随着 AI Coding Agent、Agent Workflow 编排工具的快速迭代,未来一定会有更多场景需要我们像管理数据库事务一样管理 Agent 上下文状态。在标准成熟之前,先从自己的项目里建立一套可交换的会话状态模型,是一件投资回报非常高的工程决策。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 12:26:32

【Rust入门知识点学与练】第15课:泛型 Generics

引语 根据之前的课程进度&#xff0c;第15课是泛型 Generics。这是一个文本创作类任务&#xff0c;我需要按照之前课程的格式来编写泛型的教学内容&#xff0c;包括知识点讲解、代码示例和练习题。 知识点1&#xff1a;泛型函数 泛型让你写出适用于多种类型的代码&#xff0c;避…

作者头像 李华
网站建设 2026/9/6 12:22:44

矩阵拼团系统设计:先付款先排队的订单排序与自动返本算法

技术摘要 矩阵拼团通过"先付款先排队"的订单时间戳排序&#xff0c;实现消费者返本与商家快速清库存的双赢。消费者付款后按时间精确排队&#xff0c;后续订单触发前面的订单返本&#xff0c;形成"人人有回报"的机制。本文从排队算法视角&#xff0c;拆解矩…

作者头像 李华
网站建设 2026/9/6 12:22:15

RTX 4090上2小时从零训练64M中文小模型全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 12:18:03

PTP授时服务器时间溢出解析:从原理到部署排查

1. 问题背景&#xff1a;PTP授时服务器也会被“时间溢出”咬一口PTP&#xff08;Precision Time Protocol&#xff0c;精确时间协议&#xff09;这几年在广播、专业音视频、工业控制、数据采集里基本成了标配。以前大家习惯把PTP授时服务器当成“一个会输出高精度时间的黑盒子”…

作者头像 李华
网站建设 2026/9/6 12:13:17

聚搜云:8卡H100服务器适合什么业务?企业真的需要8张GPU吗

聚搜云&#xff3b;JuSouYunClouD -聚搜云&#xff08;深圳&#xff09;信息有限公司&#xff3d;可根据企业实际业务需求、模型规模、显存要求、GPU数量及部署场景&#xff0c;提供相关大厂GPU云服务器、GPU算力及企业级计算产品&#xff0c;覆盖&#xff1a;V100、T4、A10、A…

作者头像 李华
网站建设 2026/9/6 12:12:09

从零搭建全能Agent:腾讯云AI Skills实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华