ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零构建生产级记忆型 AI Agent:AgentScope 架构、SSE 流式通信与 MCP 协议实战

2026/9/30 5:02:21 拓冰建站 浏览量
从零构建生产级记忆型 AI Agent:AgentScope 架构、SSE 流式通信与 MCP 协议实战 记忆型 AI Agent 这个词这两年快被说烂了但真正落到生产环境里能扛住多轮对话、能记住用户偏好、能在长会话里不丢上下文的实现其实没几个。大部分教程停留在调个 API 存个 list的水平一旦会话轮次上去、并发一上来记忆就开始串味、膨胀、甚至把整个上下文窗口撑爆。AgentScope 这个项目之所以值得单独拿出来聊是因为它把记忆当成一等公民来设计而不是事后打补丁。这篇内容我会从架构分层、记忆模型、流式通信、工具协议接入这几个角度把从零构建一个生产级记忆型 AI Agent这件事拆开讲透适合已经写过 demo 但想往生产推的开发者也适合想理解 Agent 工程化全貌的技术负责人。1. 为什么记忆是 Agent 从玩具走向生产的分水岭1.1 无状态 Agent 的天花板在哪里先说说大多数人对 Agent 的初始认知一个大模型 几个工具函数 一个 while 循环。这种结构在单轮任务里跑得很好比如帮我查一下明天天气然后订个提醒一次调用链就结束了。但只要任务跨越多个会话问题立刻暴露。我最早做客服类 Agent 的时候就踩过这个坑。用户第一天说我对花生过敏第二天再来问推荐个餐厅Agent 完全不知道过敏这回事推荐了一家主打花生酱的店。这不是模型能力问题是架构里根本没有跨会话记忆这个层。无状态 Agent 的天花板非常明确它只能处理当前这一轮输入能完整描述的任务。一旦任务需要历史信息、用户画像、长期偏好它就废了。更隐蔽的问题是即使你在单会话内维护了一个 messages 列表随着轮次增加这个列表会线性膨胀。一个跑了 50 轮的会话光历史消息就可能吃掉几万 token成本飙升不说模型对早期信息的注意力还会衰减。这就是所谓的上下文腐烂——信息还在但模型已经看不见了。1.2 记忆型 Agent 到底记的是什么很多人把记忆简单等同于保存聊天记录这是最大的误解。生产级 Agent 的记忆至少分四类每类的存储策略、检索方式、生命周期都不一样记忆类型内容示例存储位置生命周期工作记忆当前会话的对话轮次内存/Redis会话级情景记忆用户上次投诉的具体事件向量库数周到数月语义记忆用户是 VIP、偏好中文关系库/KV长期程序记忆处理退款的标准流程提示词/工具定义版本级工作记忆是正在想的事情景记忆是发生过的事语义记忆是关于用户的事实程序记忆是我会做的事。一个成熟的记忆型 Agent需要在这四类之间做读写调度什么时候把工作记忆压缩成情景记忆什么时候从语义记忆里捞用户画像注入提示词什么时候更新程序记忆。AgentScope 的设计思路就是把这几类记忆抽象成统一的接口让开发者可以按需组合而不是自己从零拼。这一点在后面讲架构时会展开。1.3 生产环境的三个硬约束为什么 demo 好写、生产难做因为生产环境有三个绕不开的约束成本约束。每次调用都把全部历史塞进去token 成本会随会话长度平方级增长。必须做记忆的压缩、摘要、检索只把相关的部分喂给模型。延迟约束。用户等不了 10 秒才看到第一个字。这就要求流式输出而流式输出和记忆检索是有冲突的——你得先检索完记忆才能开始生成但检索本身要时间。怎么平衡是个工程问题。一致性约束。并发场景下同一个用户的多个会话可能同时读写记忆稍不注意就会出现记忆串台或者写覆盖。这需要锁机制或者版本控制。这三个约束决定了记忆型 Agent 不能是加个数据库那么简单它需要一套完整的架构支撑。AgentScope 的价值就在于它把这套架构沉淀成了框架。2. AgentScope 的分层架构记忆、推理、通信各司其职2.1 从 DDD 视角看 Agent 的领域划分AgentScope 的架构设计明显借鉴了领域驱动设计DDD的思想把 Agent 系统拆成几个界限上下文。这不是为了炫技而是因为 Agent 系统天然存在多个关注点混在一起写必然变成一锅粥。核心的领域划分大致是这样Agent 领域负责推理循环和决策Memory 领域负责记忆的读写与检索Tool 领域负责工具的注册与调用Message 领域负责消息的序列化与传输。每个领域有自己的实体、值对象和仓储接口。举个具体的例子Memory 领域里记忆条目是一个实体它有内容、时间戳、类型、重要性评分这些属性记忆检索是一个领域服务它接收查询向量返回排序后的记忆列表。Agent 领域不关心记忆存在哪、怎么检索它只依赖 Memory 领域暴露的接口。这种解耦带来的好处是你可以把记忆后端从内存换成 Redis 再换成向量库Agent 的推理逻辑一行都不用改。我在实际项目里深刻体会到这种分层的重要性。早期我把记忆逻辑直接写在 Agent 的循环里后来想加一个记忆过期清理功能发现改动会波及整个推理流程。重构成分层之后清理逻辑只是 Memory 仓储的一个定时任务跟 Agent 完全隔离。2.2 记忆层的读写分离设计生产级记忆系统几乎都采用读写分离。写路径负责把新产生的对话、事实、事件落库读路径负责在需要时检索出相关记忆。这两条路径的优化目标完全不同写要快、要异步、不能阻塞主流程读要准、要相关、要控制返回量。AgentScope 在记忆写入上通常采用异步策略。Agent 生成回复后把这轮对话丢进一个写入队列由后台 worker 负责摘要、向量化、入库。这样主流程的响应延迟不受记忆写入影响。读路径则是在每轮推理开始前触发根据当前用户输入去检索相关记忆拼装成上下文。这里有个关键设计记忆的重要性评分。不是所有对话都值得长期记住。今天天气不错这种寒暄记了也是噪音。系统需要给每条记忆打分高分的长久保留低分的定期清理。评分可以基于规则比如包含用户偏好关键词的加分也可以基于模型判断让模型给这轮对话的重要性打个分。AgentScope 的记忆接口通常预留了 importance 字段就是这个用途。2.3 推理循环里的记忆注入时机记忆什么时候注入提示词是个很讲究的问题。注入太早模型还没理解当前问题记忆就是干扰注入太晚模型已经开始生成改不了了。实践中比较稳的做法是分两阶段注入。第一阶段在系统提示词里注入语义记忆用户画像、长期偏好这部分相对稳定每轮都带。第二阶段在用户消息之后、模型生成之前注入情景记忆与当前问题相关的历史事件这部分是动态检索出来的。AgentScope 的推理循环支持这种分阶段注入。它的消息构造流程大致是系统提示词含语义记忆→ 历史工作记忆经过压缩→ 检索到的情景记忆 → 当前用户输入。这个顺序不是随便定的它符合模型注意力的分布规律——重要的、稳定的信息放前面动态的、相关的信息放靠近问题的位置。提示情景记忆的检索一定要做相关性过滤和数量限制。我见过有人把检索到的 20 条记忆全塞进去结果模型被无关信息带偏回答质量反而下降。一般控制在 3 到 5 条按相关性排序。3. 记忆模型的核心机制压缩、检索与遗忘3.1 工作记忆的滑动窗口与摘要压缩工作记忆是最容易膨胀的部分。一个长会话跑下来几十轮对话全留着上下文直接爆掉。解决办法有两层滑动窗口和摘要压缩。滑动窗口很简单只保留最近 N 轮对话。但这样会丢失早期信息。所以需要配合摘要压缩当窗口滑动、旧消息要被丢弃时先用模型把它们摘要成一段简短的话存进情景记忆。这样既控制了上下文长度又没丢信息。具体实现上可以设定一个阈值比如工作记忆超过 10 轮就触发压缩。把最早的 5 轮对话喂给模型让它生成一段 100 字以内的摘要然后把摘要作为一条情景记忆存起来同时从工作记忆里删掉这 5 轮。这样工作记忆始终维持在可控范围。AgentScope 的记忆管理模块通常内置了这种压缩策略但参数需要根据业务调。摘要的粒度、触发的阈值、保留的轮数这些都要结合你的场景实测。客服场景可能 5 轮就该压缩因为对话密集而创作辅助场景可能 20 轮都不用压因为每轮信息量小。3.2 向量检索在记忆召回中的实际表现情景记忆的召回靠的是向量检索。把每条记忆向量化存进向量库查询时把用户输入也向量化算相似度取 top-k。听起来很美好但实际用起来有几个坑。第一个坑是语义漂移。用户说我上次说的那个事向量检索根本不知道那个事指什么因为查询本身信息量太低。这时候需要结合时间衰减和会话上下文来补。比如优先召回最近 24 小时内的记忆或者把当前会话的前几轮也纳入查询构造。第二个坑是相似度阈值难定。阈值太高召回不足太低召回一堆噪音。我的经验是不要用固定阈值而是用相对排序——永远取 top-k但给相似度设一个下限低于下限的即使排第一也丢弃。这样能避免矮子里拔将军。第三个坑是向量模型和生成模型不匹配。向量检索用的 embedding 模型和生成回复用的 LLM对语义的理解可能不一致。检索出来最相似的记忆未必是生成时最有用的。这个只能通过实测调优或者引入重排序rerank模型做二次筛选。3.3 遗忘机制不是所有记忆都值得留一个反直觉的结论好的记忆系统核心能力是忘。什么都记等于什么都没记因为检索时噪音太多。遗忘机制通常有三个维度时间衰减、重要性淘汰、容量上限。时间衰减是指记忆的相关性随时间下降检索时给老记忆降权。重要性淘汰是定期清理低分记忆。容量上限是给每个用户或每个会话设一个记忆条数上限超了就淘汰最不重要的。AgentScope 的记忆接口一般会暴露这些策略的配置项。实际调参时我建议先宽松后收紧——初期多记一点观察哪些记忆真正被召回、被使用然后逐步收紧淘汰策略。上来就激进清理容易把有用的信息误删。注意遗忘不等于删除。生产环境建议做软删除标记为失效但保留数据方便事后审计和回溯。真删了出了问题连排查依据都没有。4. SSE 与流式通信让记忆检索不阻塞首字响应4.1 为什么 Agent 场景必须用 SSEAgent 的响应往往很长而且生成过程是渐进的。如果用普通的 HTTP 请求-响应模式用户要等整个回复生成完才能看到体验极差。SSEServer-Sent Events就是解决这个问题的服务器可以持续向客户端推送数据片段客户端边收边渲染。为什么不用 WebSocket因为 Agent 场景大多是客户端发一次请求服务器持续推流的单向模式SSE 天然契合而且基于 HTTP穿透代理、负载均衡都更简单。WebSocket 是全双工用在 Agent 上属于杀鸡用牛刀还增加了连接管理的复杂度。AgentScope 的通信层对 SSE 支持得很好。它的消息流是结构化的每个事件有类型比如 thinking、tool_call、content、done客户端可以根据类型做不同的渲染。比如 thinking 事件可以显示正在思考tool_call 事件可以显示正在调用工具content 事件才是真正的回复内容。4.2 记忆检索与流式输出的时序协调这里有个容易被忽略的工程细节记忆检索是阻塞的流式输出要求尽快出首字。如果检索花了 2 秒用户就要盯着空白屏幕 2 秒。解决办法是分阶段推送。连接建立后先推一个正在检索记忆的事件让用户知道系统在工作。检索完成后推一个记忆加载完成的事件。然后开始生成逐字推送内容。这样用户感知到的等待时间大幅缩短因为界面一直在动。更进一步可以把记忆检索也做成流式的——先返回快速检索的结果比如从缓存里拿用户画像再返回慢速检索的结果向量库查询。AgentScope 的事件流设计支持这种渐进式推送。4.3 断线重连与状态恢复SSE 连接不稳定是常态尤其是移动网络。断线之后怎么恢复如果不管用户刷新页面就丢失了正在生成的回复。标准做法是给每个事件带一个递增的 id客户端记录最后收到的事件 id。重连时带上这个 id服务器从该 id 之后继续推送。这要求服务器端缓存最近的事件流。AgentScope 的消息层通常支持这种可恢复流。另一个细节是心跳。长时间没有数据推送时中间的网络设备可能主动断开连接。所以需要定期发送心跳事件比如每 15 秒一个空注释保持连接活跃。这个在 SSE 规范里是原生支持的用注释行就行。// 客户端处理 SSE 的典型逻辑 const eventSource new EventSource(/agent/stream?sessionIdxxx); eventSource.addEventListener(thinking, (e) { showStatus(正在思考...); }); eventSource.addEventListener(content, (e) { appendContent(JSON.parse(e.data).text); }); eventSource.addEventListener(done, (e) { eventSource.close(); }); eventSource.onerror () { // 触发重连带上 lastEventId reconnectWithLastId(eventSource.lastEventId); };5. MCP 协议接入让 Agent 的记忆和工具真正打通5.1 MCP 解决的到底是什么问题MCPModel Context Protocol这两年被讨论得很多但很多人没搞清它到底解决什么问题。简单说它标准化了模型如何访问外部资源和工具这件事。在 MCP 之前每个 Agent 框架都有自己的工具定义格式你想接一个数据库、一个文件系统、一个浏览器都得写适配层。MCP 把这些统一成一套协议工具提供方实现一次所有支持 MCP 的 Agent 都能用。对记忆型 Agent 来说MCP 的意义在于记忆本身也可以是一个 MCP 资源。用户的偏好、历史事件、知识库都可以通过 MCP 暴露给 Agent而不需要把记忆逻辑硬编码在 Agent 里。这样记忆的提供方和消费方彻底解耦。AgentScope 对 MCP 的支持让它可以作为一个 MCP 客户端去连接各种 MCP 服务器也可以把自己的能力暴露成 MCP 服务器供别人调用。这种双向能力在生产环境里很实用——你可以把公司内部的用户系统、订单系统都封装成 MCP 服务器Agent 通过统一协议访问。5.2 把记忆库封装成 MCP Server 的实践假设你有一个用户画像数据库想让它成为 Agent 可访问的记忆源。用 MCP 封装的话大致流程是首先定义资源Resource。用户画像可以暴露成一个资源URI 类似memory://user/{userId}/profileAgent 通过这个 URI 读取。然后定义工具Tool比如search_memory接收查询字符串返回相关记忆列表。最后定义提示Prompt把常用的记忆查询模式固化成模板。MCP Server 的实现可以用官方 SDK也可以用社区的各种语言版本。核心是实现几个 handler列出资源、读取资源、调用工具。Agent 侧只需要配置好 MCP Server 的地址就能自动发现这些能力。这里有个实践心得MCP 工具的粒度要适中。太细Agent 要调很多次才能完成一件事延迟高太粗一个工具干太多事Agent 难以组合。我的经验是按业务动作来划分比如查询用户偏好是一个工具更新用户偏好是另一个而不是读数据库这种底层操作。5.3 工具调用与记忆写入的联动工具调用会产生新的记忆。比如 Agent 调用了一个查询订单的工具返回了订单信息这个信息应该被记下来下次用户问我上次那个订单时能召回。AgentScope 的机制是工具调用的输入输出都会进入工作记忆然后由记忆管理模块决定是否沉淀为长期记忆。这里的关键是标记工具调用结果的重要性。不是所有工具结果都值得记查询天气的结果可能明天就失效了但查询用户会员等级的结果是长期有效的。实现上可以在工具定义里加一个memory_policy字段声明这个工具的结果是否入长期记忆、保留多久。这样记忆的沉淀策略就跟工具绑定而不是散落在各处。6. 从零搭建的实操路径与踩坑记录6.1 环境准备与依赖选型动手之前先把技术栈定下来。AgentScope 本身是 Python 生态的但如果你团队是 Java 背景也有对应的实现思路可以借鉴。核心依赖大致是这几块Agent 框架AgentScope 本体负责推理循环和消息编排向量库用于情景记忆检索轻量场景可以用内存版生产建议上专门的向量数据库KV 存储用于工作记忆和语义记忆Redis 是稳妥选择模型服务LLM 和 embedding 模型可以是云服务也可以是本地部署通信层SSE 服务端通常用 Web 框架自带的流式响应能力选型时最容易忽略的是向量库和 KV 的一致性。记忆写入时向量和元数据要同时落库如果一边成功一边失败就会出现检索得到但读不到内容的诡异问题。解决办法是用事务或者补偿机制写入失败时回滚。6.2 最小可运行版本的搭建步骤先跑通一个最小版本再逐步加记忆能力。步骤大致是搭一个基础的 Agent 循环能接收用户输入、调用模型、返回回复。这一步不涉及记忆先确保推理链路通。加入工作记忆用内存列表存当前会话的对话轮次每轮把历史拼进提示词。接入 SSE把回复改成流式推送验证客户端能正确渲染。引入向量库把每轮对话向量化存储实现基础的情景记忆检索。加入记忆注入逻辑在推理前检索相关记忆并拼进上下文。实现记忆压缩当工作记忆超阈值时触发摘要。接入 MCP把外部系统封装成工具供 Agent 调用。每一步都要单独验证不要一次性全上。我见过有人一口气把记忆、流式、工具全接上结果出问题根本不知道是哪一层的事。6.3 那些文档里不会写的坑坑一向量维度不匹配。换 embedding 模型时向量库里的旧数据维度对不上检索直接报错。要么重建索引要么做维度兼容层。生产环境换模型是大动作要提前规划。坑二SSE 的缓冲。很多 Web 框架默认会缓冲响应导致流式推送变成攒一批发一批用户看到的还是卡顿。要显式关闭缓冲设置正确的响应头。坑三记忆检索的并发。同一用户多个会话并发时检索和写入可能冲突。要用用户级锁或者乐观锁避免记忆串台。坑四摘要的信息损失。模型做摘要时可能丢掉关键细节比如具体的数字、日期。可以在摘要提示词里明确要求保留这些或者对关键信息单独存储。坑五MCP 连接的超时。外部 MCP Server 响应慢会拖垮整个 Agent。要设置合理的超时和降级策略MCP 不可用时 Agent 仍能基于已有记忆工作。提示上线前一定要做长会话压测。跑一个 100 轮的会话观察记忆增长曲线、响应延迟变化、token 消耗。很多问题只有在长会话下才暴露。7. 生产化的几个关键决策点7.1 记忆存储的成本与性能权衡记忆存储不是免费的。向量库的存储和检索都有成本尤其是数据量大之后。要在成本和性能之间找平衡。一个实用的策略是分级存储。热记忆最近、高频访问的放内存或 Redis温记忆放向量库冷记忆归档到对象存储。检索时先查热没有再查温冷记忆只在特定场景下按需加载。这样既保证了常用记忆的低延迟又控制了成本。另一个策略是记忆去重。用户可能反复说同一件事如果每次都存一条记忆库会迅速膨胀。写入前做相似度检查高度相似的合并或更新而不是新增。7.2 多租户下的记忆隔离如果 Agent 是给多个用户或组织用的记忆隔离是硬要求。A 用户的记忆绝不能被 B 用户检索到。隔离的实现层次有几个选择物理隔离每个租户独立库、逻辑隔离同库不同分区、行级隔离同表不同 tenant_id。物理隔离最安全但成本高行级隔离成本低但容易出 bug。生产环境建议至少做到逻辑隔离关键数据做物理隔离。AgentScope 的记忆接口通常支持传入租户标识检索时自动加上过滤条件。但要注意过滤条件必须在向量检索的层面生效而不是检索完再过滤——后者会导致召回数量不足。7.3 可观测性记忆系统的黑盒问题记忆系统最大的问题是看不见。用户问你为什么这么回答你很难解释因为检索到了三条记忆。所以可观测性至关重要。至少要记录这几类指标记忆写入量、检索命中率、平均检索延迟、记忆库大小增长曲线、被召回记忆的实际使用率。有了这些数据才能判断记忆策略是否合理。更进一步可以做记忆的可视化。把某个用户的记忆库展示出来看看都记了些什么哪些被频繁召回哪些从没被用过。这种直观的观察往往能发现策略上的问题。我在实际项目里就靠这个发现了一个问题大量记忆被写入但从未被召回原因是检索的查询构造有问题用户的实际问法和记忆的存储表述对不上。调整了查询构造策略后命中率明显提升。7.4 记忆的版本演进与迁移记忆的格式会随业务演进。今天存的是纯文本明天可能要加结构化字段。这就涉及记忆的版本管理和迁移。稳妥的做法是给每条记忆带一个 schema 版本号。读取时根据版本号做兼容处理写入时用最新版本。迁移可以后台异步做不影响线上服务。AgentScope 的记忆实体设计通常预留了扩展字段方便加元数据。但要注意扩展字段不要滥用否则记忆条目会变得臃肿检索效率下降。8. 学习路径与能力进阶建议8.1 分阶段的学习路线想真正掌握记忆型 Agent 的构建建议按这个顺序推进第一阶段理解基础。把 Agent 的推理循环、消息结构、工具调用搞明白。这个阶段不用碰记忆先让一个简单 Agent 跑起来。第二阶段掌握记忆。理解四类记忆的区别动手实现工作记忆和情景记忆体会压缩和检索的取舍。第三阶段打通通信。把 SSE 流式输出做扎实理解事件流的设计和断线恢复。第四阶段接入协议。学习 MCP把外部系统封装成 Agent 可用的工具和资源。第五阶段生产化。处理并发、隔离、可观测性、成本优化这些工程问题。每个阶段都要有可运行的产出不要只看文档。Agent 这东西不动手永远学不会。8.2 值得深入的方向记忆型 Agent 还有几个值得深挖的方向。记忆的主动学习——Agent 不只是被动记录还能主动判断哪些信息值得记、主动向用户确认。跨 Agent 的记忆共享——多个 Agent 之间如何共享用户记忆同时保证隔离。记忆的可解释性——让 Agent 能说清自己的回答基于哪些记忆。这些方向目前都还没有成熟的方案是很好的切入点。AgentScope 作为框架提供了基础设施但具体的策略和算法还需要开发者自己探索。8.3 一些过来人的建议最后分享几点个人体会。第一不要追求一步到位记忆系统是迭代出来的先跑通再优化。第二重视数据记忆策略的好坏最终要靠数据说话埋点要早做。第三保持简单能用规则解决的不要上模型能用 KV 的不要上向量库复杂度是生产环境最大的敌人。AgentScope 这类框架的价值是把通用的部分沉淀下来让你专注于业务特有的记忆策略。但框架不是银弹理解背后的原理才能在实际场景里做出正确的取舍。记忆型 Agent 的构建本质上是一场关于什么值得记住、什么时候想起、怎么用起来的持续权衡没有标准答案只有适合你场景的答案。