Agent实战:会话、工具、MCP与记忆的统一设计

发布时间:2026/10/13 18:26:54
Agent实战:会话、工具、MCP与记忆的统一设计 1. 为什么值得把会话、工具、MCP、记忆放在同一层来设计如果你也在折腾 Agent应该能感受到一个尴尬单论会话、工具调用、MCP 接入、记忆存储每一块单独拿出来都有成熟实践但把四者放进同一个 Agent 时总会出现上下文翻车、工具结果污染对话、记忆召回没头没尾之类的怪问题。前段时间我用代号 Hermes Agent 做了一个相对完整的助手在会话、工具、MCP、记忆四个能力维度上都做了重构。这篇就把这几块的设计取舍和踩坑记录写出来给正在搭 Agent 的人一个可以直接落地的参考版本。很多人以为 Agent 就是大模型 Function Calling但真正跑起来以后会发现根本问题不在于模型能不能生成工具调用而在于四件事互相咬合得怎么样。会话解决的是用户怎么和系统交互工具解决的是模型怎么操作外部系统MCP 解决的是外部系统用什么样的标准姿势接入记忆解决的是系统怎么记住用户和上下文。四件事各自的难点明确但真正困难的是把它们放在同一套状态模型里协同处理不然就会出现每块看起来都能用连起来就散架的效果。1.1 从一次典型需求说起我之前接到一个内部需求做一个助手目标是让人通过自然语言完成三件事查知识库、建工单、记住每个人的习惯和偏好。表面看这些都是常规能力但把它拆开就会看到交叉问题。查知识库要用到文档检索对应记忆和 MCP建工单要调用业务流程系统对应工具用户连续说三句话每句都可能依赖前面提到的某个对象对应会话。任何一个环节没有跟其他环节联动最终体验就会像三个人接力传话传着传着就丢了。我还衡量过要不要直接用一个现成的编排框架。后来决定自己在 Hermes Agent 里做因为业务方对权限边界、超时、审计日志都有要求现成方案很难定制到这种颗粒度。结论是不要为了用 Agent 而 Agent先画清楚调用链和状态边界再决定自己写还是拿框架这件事比选模型重要得多。1.2 四块能力的边界与协同模型拿生活化的类比来说会话是门面工具是手MCP 是插座标准记忆是工作台和档案室。用户只跟门面说话门面需要调动后面的手脚去执行工具只要统一成了 MCP 这种标准接口就能像各种家电插到同一个插座上一样接入。工作台上摆着当前任务需要的临时数据档案室里存着长期需要保留的旧资料门面在接待时决定从哪边取什么数据。从实现上我习惯把调用链画成这样用户输入先进会话层会话层负责解析意图然后从记忆层召回相关的历史或偏好如果当前任务需要外部动作就交给工具/MCP 执行执行结果再回填给会话层组织最终回复回复完成后会话层把值得记住的信息交给记忆层写入。整个过程不是串行执行这么简单很多步骤会交错但流程图上必须存在明确的数据入口和数据出口不然 Agent 就是一个黑盒出了问题没法定位。模块类比核心问题常见错误会话门面多轮上下文如何组织无脑塞消息列表工具手模型如何安全、准确地执行动作结果不回填模型瞎编MCP插座标准外部服务如何即插即用每个服务写一套定制适配器记忆工作台档案室如何记录、召回、遗忘对话历史直接当记忆存储2. 会话能力从聊天框到对话状态管理会话层解决的核心问题不只是回答用户而是知道在什么前提下回答用户。传统聊天机器人把用户发来的每句话拼到 messages 数组里模型能理解但代价是 token 越来越多响应越来越慢甚至模型被淹没在旧消息里忘了最开始要求它遵守的格式。2.1 把多轮上下文变成显式状态在 Hermes 里我没有直接用数组作为会话状态而是维护一个显式的 SessionState 对象。这个对象里会区分当前正在做什么已经发生了什么用户提到了哪些实体。伪代码大概是interface Message { role: user | assistant | tool; content: string; } interface SessionState { sessionId: string; history: Message[]; currentPlan?: string; activeToolCalls: ToolCall[]; entities: Recordstring, unknown; lastInteractionAt: number; }为什么要区分history和currentPlan因为用户在一个复杂任务里模型需要同时维护任务目标和已执行的子步骤。如果这些都在 messages 里混着前面对话一旦被裁剪任务目标也就丢了。所以我在会话层单独维护一个currentPlan字段由一次意图识别过程生成在任务结束前不参与裁剪。2.2 上下文窗口的组装与裁剪上下文组装的优先级我一般是这样做系统提示词 平台注入的固定能力说明 当前任务计划与约束 从记忆层召回的相关片段 最近 N 轮对话 工具执行结果的最后摘要。这个顺序不是固定的但它符合一个直觉模型应该先看到不能违背的东西再看到能帮助它决策的辅助信息。裁剪策略上我有几个硬性指标。首轮请求的 token 预算我一般控制在模型上下文窗口的 50% 以内留下余量给工具返回和多轮对话积累。比如一个上下文窗口 128K 的模型我会让每一轮请求在组装完成后不超过 64K token。超出的部分不直接砍掉而是按优先级把最底层的旧对话做摘要压缩再把摘要放回去。注意系统提示词和当前任务计划绝对不能丢一旦丢了模型行为会立刻变形。实际字数换算上中文场景下可以粗略按一个汉字 1.5~2 个 token 来估算英文则按单词 1.3~1.5 个 token 估。这个换算不精确但做预算够用。如果发现首字延迟超过预期先看是不是把大量工具描述全塞进了系统提示词。工具描述是按需注入而不是全量注入这一点在第 3 节还会展开。2.3 流式输出与被打断的处理流式输出的坑我印象最深的是如果直接把实时生成的 token 追加到消息历史里在一次输出被用户打断后历史里就会残留一段半截话下一次对话模型会以为那是它自己说过的完整内容。Hermes 的做法是 stream buffer 和 history 分离只有流式结束真正完成之后才把完整内容 commit 到 history中途被打断只用保留被打断前的快照和意图下次继续时重新组织。用户打断本身也是一个会话状态变化。我会给每次输入分配一个递增的 turnId请求发出后如果用户插入新消息立刻中止旧请求把当前会话快照重放再拼上新消息重新跑一轮。这里要特别控制并发别让两轮推理同时写同一个 session否则会出现上下文相互覆盖的竞态问题。3. 工具能力让 Agent 真正具备执行力会话层理解完用户之后真正干活的是工具层。工具层这部分的常见误区是以为只要给模型开放一堆函数就能让 Agent 自动干活。实际实现时工具注册、参数校验、结果回填、失败降级每一步都要做到足够精确。3.1 工具注册把函数翻译成模型和系统都懂的契约工具有两个使用者一个是模型一个是执行器。这个细节很多人会忽略模型看到的工具是一段 JSON Schema 说明执行器看到的工具是一段可调用代码。所以工具描述的措辞要同时服务于这两个对象。给模型看的描述里我要求必须写明四件事这个工具什么时候用、参数的具体含义、返回结构长什么样、失败时会抛什么异常。比如一个创建工单的工具schema 大致长这样{ name: create_ticket, description: 在内部工单系统中创建工单。仅当用户明确要求创建或提交问题时使用不要在用户只是咨询时使用。返回票号 ticketId 和状态 status。, parameters: { type: object, properties: { title: { type: string, description: 工单标题不超过50字 }, priority: { type: string, enum: [low, medium, high] } }, required: [title, priority] } }description 里那句仅当用户明确要求创建时使用不是随便写的它对模型行为的影响远大于参数类型。模型判断工具触发条件时非常依赖描述里是否有触发边界所以每个工具的 description 我都会贴上正向触发条件和负向不触发条件。执行器端的注册则是一个函数表const toolRegistry: Recordstring, ToolDefinition { create_ticket: { execute: async (params) ticketsApi.create({ ...params }), permissions: [ticket:write], timeout: 10000 } };这里的permissions很重要我后面会单独说。toolTimeout 也是每个工具单独设置查询类工具可以放到 15~20 秒写操作类我一般设 10 秒以内避免模型以为超时还没结果的时候反复发起同样的操作。3.2 参数校验与执行结果回填之间的坑工具调用链路上第一道防线是模型生成的参数。可是模型生成的参数偶尔会存在幻觉比如把日期写成明天把金额单位搞错。Hermes 会在执行前加一道轻量校验基础类型由 JSON Schema 保证但业务规则需要在代码里校验。创建工单时 title 长度、priority 枚举值都不满足就直接返回校验失败不让脏参数打到业务系统。第二道防线是结果回填。执行成功后返回给模型的不应该是一句创建成功而是完整的结构化结果即使模型最终只需要其中的一部分。比如创建工单返回{ ticketId: T-2024-0017, status: created, assignee: ops-team }把原始结果放回消息列表里模型就能回答用户工单号多少这类追问。如果只回填一个 summary这一轮对话可能没问题但如果用户下两轮又追问细节模型只能靠猜。这里有个我吃过亏的点工具返回的原始 JSON 可能非常大比如查询接口返回几千条记录这时候全量塞进模型显然不现实。我的做法是给工具定义时增加一个summarize钩子工具侧先把结果压缩成 top 50 条或聚合统计再把压缩结果回填。压缩是在工具层做的不要依赖模型自己去总结否则 token 开销和准确率都很糟糕。3.3 工具失败的容错与写操作保护工具调用不可能永远成功。网络超时、鉴权失败、下游接口报错常见的失败类型我见过至少五种。我很少给 Agent 设置无条件重试只有满足幂等条件时才自动重试重试次数限制两次重试间隔采用退避策略。因为一旦一个非幂等的写操作被自动重试两次用户数据库里可能就多出三条重复记录。所以我在工具定义里显式声明idempotent: true/false。幂等操作自动重试非幂等操作则直接进入人工确认流程由 Agent 向用户发一次确认刚才的写入请求没有成功返回我要不要重新执行一次这种交互虽然多了一步但比起静默重复执行安全得多。写操作保护是工具层最容易被忽视的一点。给小开发者演示的时候大家最爱把查天气查路况这些只读工具接进去可真实业务工具里几乎都有删除用户修改配置发送消息这类敏感操作。Hermes 的做法是把工具分成两级read 级工具可以直接调用write 级工具默认要走内置的confirm工具由模型把待执行动作、目标对象、风险描述展示给用户用户确认后才真正放行。后面第 6 节的调用链里我会演示这个确认流程。4. MCP把外部能力统一成即插即用工具层有了明确的注册和执行规范之后接下来一个更现实的问题是企业里有一堆数据源和服务如果每个都写专属适配器工具层很快会被特异代码淹没。MCP 就是为了解决这个问题出现的标准化思路。4.1 先搞明白 MCP 解决的是哪一层问题MCPModel Context Protocol可以理解成一层给 Agent 提供外部能力的通信协议。它有点像行业里把不同硬件统一成标准接口的思路过去一个外设要接进电脑需要写专门的驱动后来接口统一了只要外设和电脑都支持同一套协议插上就能用。MCP 做的事情类似它定义了 Agent 和外部能力之间怎么发现、怎么握手、怎么调用。但这里有一个容易混淆的点MCP 不是一门必须安装在 Agent 里的功能组件而是一套双方都要遵守的通信标准。在 Hermes 里我把它定位成工具层的接入适配器。理论上我完全可以为每个业务系统手写一个 adapter代码也没多复杂但问题在于业务系统多了以后每套系统都会有自己的鉴权方式、接口风格和错误模型维护成本呈线性甚至超线性增长。MCP 的价值就是把复杂度收敛到协议这一层两边都实现标准Agent 侧就不需要知道每个业务系统到底是谁、走 HTTP 还是走命令行。4.2 Hermes 接入 MCP Server 的实操流程接入一个 MCP Server大致有三步初始化握手、发现能力、把能力注册进工具表。Hermes 侧维护一份 server 配置类似这样const mcpServers [ { id: docs, transport: stdio, command: mcp-server-docs, args: [--indexknowledge-base] }, { id: incident, transport: http, url: http://localhost:9000/mcp, headers: { Authorization: Bearer xxx } } ];初始化时Hermes 每个 server 建立连接后会发能力列表请求。这里有个实践细节不要在启动时把所有 server 的初始化都放在同步流程里任何单一 server 挂了都会拖垮整个 Agent 启动。我是用异步加载成功一个注册一个失败的标记为unavailable之后定时重连。MCP Server 暴露出来的工具进入 Hermes 工具表时我会加前缀比如docs.search变成mcp__docs__search。前缀有双重意义一是避免多个 server 里出现同名工具的冲突二是在模型选错工具时日志里能一眼看出工具来自哪个外部系统排查效率高很多。注册完成后对模型来说这些工具和本地工具没有区别它会根据 JSON Schema 正常决定何时调用。4.3 资源、提示词、工具三类原语各有用途MCP 约定里除了工具之外还有资源和提示词。资源是可提供上下文的文档/数据提示词是可复用的任务模板工具是可执行的操作。这三样我理解成同一个 Agent 从看资料、按套路行动、动手执行三个维度接入外部能力。有点像你去图书馆资源是书架上可供查阅的书提示词是馆里常见的检索指引工具是帮你去借书的机器人。在 Hermes 里资源通常会作为上下文按需注入到会话层比如用户问某个文档时通过resource://标识读取内容提示词往往配合特定场景使用比如新成员想快速了解项目现状就用预设模板来组织首次对话工具则直接进入工具注册表等待被模型调用。有些 MCP Server 实现的工具数量很多一上来暴露 100 多个工具容易把模型的选择空间撑爆我会在 Agent 侧做工具白名单 按会话动态生效的过滤只有当前任务可能涉及的工具才真正出现。MCP 还有一个必须提前规划的问题就是鉴权和限流。很多人的第一版 Agent 都是直连内部 MCP Serverapi key 散落在配置里安全问题后面才暴露。我后来在入口处加了一层网关统一做身份认证、请求限流和审计日志MCP Server 不再直接面对 Agent 进程。这一步看起来重但生产环境里真能救你很多次。5. 记忆让 Agent 拥有时间感会话、工具、MCP 这三块做完了Agent 已经有了与人对话、执行动作、接入外部服务的能力但这时它还是一个没有长期记忆的助手。用户上个月说过我更喜欢详细的技术文档这次再来问问题它完全不知道。记忆系统要处理的就是这个话题。5.1 工作记忆、情景记忆与语义记忆的区别我一开始犯过一个错误把对话记录直接扔进向量库当成记忆。结果检索出来的全是碎片既没有时间顺序也不能真正服务当前任务。后来我把记忆拆成了三层。工作记忆对应的是当前任务内的临时上下文本质上就是第 2 节说的 SessionState事情完结后不需要长期保存。情景记忆对应什么时间、和谁、发生了什么比如某天用户要求将报告同步给团队负责人这是有时间线的流水账。语义记忆则是对事实和偏好的提炼比如用户偏好半小时内的会议纪要。这三层写入策略完全不同。工作记忆随会话自然失效情景记忆通常由一段对话结束后的事件抽取产生语义记忆需要在多个情景之上做归纳和更新。Hermes 中我给记忆条目加了一个type字段写之前先判断属于哪一层避免把流水账当成长期偏好存进去。5.2 向量化存储与召回策略召回比存储更需要关注存储层我优先选开源向量数据库或者自建带 embedding 的检索服务选型时重点看三点中文分词和相似度计算是不是够用、能否支持元数据过滤、写入延迟高不高。Hermes 的 embedding 用本地部署的通用模型完成不直接依赖任何外部接口数据不出内网。写入时一条记忆只记一件事。比如用户说我叫小王喜欢长回答以后日报发到邮箱我不会合成一条而是拆成三条用户姓名、回答偏好、日报投递方式。分开写的好处是召回时可以按不同场景命中不同条目而不是每次都把一堆信息一起拽出来。召回策略上直接向量检索往往不够。原因是用户的表达和记忆里存的表达经常不一致比如记忆里写了偏好详细文档用户这次问给我整点干货看看embedding 相似度不一定能直接拉出来。我习惯先做一步查询改写把用户当前问题改写成几个可能的记忆查询再配合关键词和向量同时检索取并集后再按一个加权公式排序权重由相关分、最近访问时间、记忆重要度共同决定。时间衰减是个很有效的信号越久未访问的长尾记忆排序权重越低。5.3 记忆的写入、更新与遗忘不能只增不改写入动作有两种触发方式显式和隐式。显式是用户明确说记住……这句话本身就是一个指令我直接提取内容写入。隐式则复杂很多要在对话进行中或结束后异步判断哪些内容值得沉淀。Hermes 里我设置了在每轮回复完成后由一个独立的评估动作检查最近的对话产出候选记忆。为了保证不打扰主链路这一步走异步队列绝不阻塞用户回复。更新策略要解决新旧记忆打架。如果用户说以后用短信通知我而记忆里已经有一条偏好邮件通知正确的动作不是再加一条新的而是找到旧条目做更新或标记为废弃。我实现时用 LLM 做一次轻量判断比较候选记忆和已有记忆是否指向同一个约束如果是就更新 original 条目。遗忘机制同样不可少。对话记忆如果不做合并存储量会持续膨胀召回时还会互相干扰。做法分三种短期内的重复记忆自动合并超过一定时间未命中的低重要度条目进入归档用户随时可以发起忘记我之前提到的 xxx把对应记忆标记为删除删除后的条目不再参与召回。6. 四者协同一次完整调用链的复盘前面四部分拆开讲了很多但是 Agent 真正让用户觉得好用要看这四个模块在一条调用链里怎么配合。这一节我用一个业务场景完整走一遍。6.1 从一句话到一串动作的路径场景用户对助手说帮我总结一下上周的项目会议纪要然后把结论发给负责人另外下周记得提醒我补充后续计划。这句话里其实包含了一个读操作取会议纪要、总结、一个写操作发消息给负责人和一个记忆写入操作设置提醒。会话层先做意图识别解析出三个动作后生成一个currentPlan先生成总结、再发送、再写提醒然后任务进入执行阶段。记忆层先尝试召回项目会议纪要相关的文档标识召回命中了一个文档 resource接着工具层通过文档服务的 MCP 工具把那段纪要取出来。总结完成后Agent 调用发消息工具。注意这里是写操作会先触发内置的 confirm 动作把向负责人发送内容为 xxx 的消息展示给用户确认。我特意让流程里存在一道确认因为在实际使用中越是能在写操作上多问一句的 Agent越不容易闯祸。确认通过后工具层执行发送并把结果回填随后记忆层写入一条下周提醒补充后续计划的待办事件情境信息直接带上会话来源。最终会话层把所有分步结果汇总成一段自然语言回复用户看到的是一整个流畅过程。但内部日志里每一步都清晰可追踪。6.2 协同中容易漏掉的反馈闭环这个例子里还有一个容易被忽略的闭环如果文档 MCP 调用超时了会话层不该只是回一句抱歉出了点问题而应该把失败原因和处理路径写回记忆同时标记该资源暂时不可用下次先走缓存或备用检索通道。我在实践中发现Agent 的智能不仅体现在调用成功时把话说漂亮更体现在失败后的行为是否可预期、可恢复。所以我在 Hermes 的会话层加了一条兜底规则任何工具/MCP 返回非成功状态都会生成一个结构化的 failure record其中包含工具名、错误码、重试建议、是否已降级。这个 failure record 会同时用于三件事回填给模型组织用户话术、写入审计日志、触发记忆层判定是否需要把某某服务暂不可用标记为短期上下文避免同一轮对话里反复尝试同一个已失败的调用。这个闭环设计让 Agent 在生产环境里的稳定性有了本质提升。7. 常见问题与排查心得最后整理一份我在这套架构里实际踩过、也帮别人排查过的高频问题。这些问题不会出现在官方示例里但遇到的时候非常影响体验。7.1 上下文越界与提示膨胀现象最典型的是对话进行到后半段模型开始不遵守工具描述里的约束或者回复速度明显变慢。排查第一步是看 token 统计到底是历史消息吃掉了预算还是工具 schema 全量注入导致的。如果是前者压缩旧对话如果是后者启动工具按需注入。如果无论如何系统提示词都超过 4000 token建议拆职能把通用的输出规范放进 system把具体业务说明放到首次触发时追加不要一次全塞。7.2 工具调用结果不稳定、参数总出错碰到模型频繁传错参比如把日期格式写错、枚举值写了个不存在的不要急着怪模型。先回放请求日志看看工具描述里参数说明是否足够明确。我会检查两个点description 里是否写了期望格式JSON Schema 里是否给了default和examples。这两个字段对模型理解参数格式的帮助非常大经常比在代码里多写几行校验更管用。另一个常见原因是工具表里放了几十个工具模型在太大的选择空间里更容易出错我会先做按场景过滤。7.3 MCP 连接失败和初始化超时启动时一个 MCP Server 连接慢整个 Agent 半天没起来这种问题基本都会遇到。解决方法是异步初始化、单独管理连接状态、失败自动重连。还要检查版本兼容性MCP 客户端和 server 之间如果协议版本或能力协商不上会出现连上了但工具列表为空的诡异现象。发现工具列表为空时不要直接判断没有工具先去抓握手日志。7.4 记忆污染召回结果反而误导了对话记忆系统最难受的问题不是记不住而是记了不该记的召回了不该召回的。比如用户某次随口说了一句这个方案太复杂了结果被当成长久记忆存下来后续每次回答都引用用户认为方案复杂。我会在记忆写入链路加一个非常严格的判断只有带有明确偏好、明确指令或明确实体关系的内容才允许写入语义记忆普通情绪化表达一律不入库。召回时如果模型使用了记忆内容我要求回复中带一个不可见的来源标记方便前端调试时判断是哪条记忆在影响行为。用户说不对时系统提供一条纠正记忆的通道把错误条目直接作废。7.5 四模块互踢皮球一个请求在不同层之间丢信息最后一个比较玄学的现象是会话、工具、MCP、记忆单独测试都正常组合起来某个字段就丢了。我排查过很多次这类问题最终基本都指向同一个原因请求链路里传递的上下文对象结构不统一。工具层返回的数据没有保留原始 metadata记忆层召回的内容没有带 session 关联会话层组装时自然就拼不出完整信息。解决方式很简单但很有效定义统一的 Context 传输对象每一层在结束时都把自己这一侧产生的关键信息写进去下一层永远从同一个对象里读取而不是各自维护私有的临时变量。这个规范比任何花哨的框架都管用。8. 给正在设计 Agent 的人一点建议回到 Hermes 这个项目上我每个版块单独做完的时候其实并没有觉得特别惊艳。真正让我感觉成了的时刻是某次用户连续追问了三轮每一轮都涉及上一个工具调用产生的状态而且助手记得他上周说的偏好——那一刻我才明白会话、工具、MCP、记忆不是四个并列的特性它们是一台机器的四个齿轮。任何一个齿轮缺了齿整台机器都会卡顿。如果你也在搭 Agent我建议不要把四个模块分散到不同迭代里去做。先搭好统一的状态流转框架再逐块填入能力。先用最笨的办法把一条完整链路跑通哪怕只是查文档写一条记忆也比上来就接十几个工具、上百条记忆要有价值。踩过几次坑之后你会发现真正决定 Agent 上限的不是模型选得多大而是会话、工具、MCP、记忆这四块能不能稳定地咬合在一起。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询