1. 从标题拆解一个智能体框架的四大支柱
第一次看到“Hermes Agent能力篇:会话、工具、MCP、记忆”这个标题,我脑子里冒出来的第一个判断是:这是一篇讲智能体运行时骨架的拆解文,而不是那种泛泛而谈的“AI能做什么”的科普。会话、工具、MCP、记忆这四个词,恰好对应了一个智能体从“能对话”到“能干活”再到“能记住上下文”的完整能力链条。如果你正在自己搭一个智能体系统,或者准备把某个大模型接入到实际业务流程里,这四个模块基本就是你绕不开的核心工程量。
我先把这四个词翻译成人话。会话解决的是“一次交互怎么组织”,包括消息怎么存、上下文怎么裁剪、多轮对话怎么保持连贯。工具解决的是“智能体怎么调用外部能力”,比如查数据库、发请求、跑脚本。MCP是模型上下文协议(Model Context Protocol),它解决的是“工具和资源怎么用统一的方式暴露给模型”,相当于给智能体装了一个标准化的外设接口。记忆则是“跨会话、跨任务的信息沉淀”,让智能体不是每次对话都从零开始。
这四块拼在一起,才是一个真正能用的智能体。只做会话,那是个聊天机器人;只做工具,那是个自动化脚本;只做记忆,那是个向量数据库;只有把四者串起来,智能体才具备“持续工作”的能力。这篇文章我会按这个逻辑,把每一块的设计思路、实操要点、踩坑经验都摊开讲,适合正在做智能体落地的开发者,也适合想理解智能体内部机制的产品和技术负责人。
2. 会话管理:多轮对话的骨架与上下文裁剪策略
2.1 会话的本质是一条有状态的消息流
很多人一开始会把会话理解成“把历史消息拼起来发给模型”,这个理解不算错,但太粗糙。真正工程化的会话管理,核心要解决三个问题:消息怎么存、上下文怎么裁、状态怎么恢复。
先说消息存储。一次会话本质上是一条有序的消息流,每条消息至少包含角色(system、user、assistant、tool)、内容、时间戳、以及可选的元数据(比如工具调用ID)。我见过不少项目直接用一个大数组在内存里存,跑单机demo没问题,一旦要支持多用户、多设备、断线重连就崩了。合理的做法是把会话持久化,每条消息一行记录,会话本身一个主记录,消息通过会话ID关联。这样既能做分页加载,也能做审计和回放。
再说上下文裁剪。模型的上下文窗口是有限的,哪怕现在动辄几十万token的窗口,成本也是实打实的。你不能把整条会话无脑塞进去。常见的裁剪策略有这么几种:滑动窗口(只保留最近N轮)、摘要压缩(把早期对话总结成一段话)、关键信息提取(只保留实体和结论)、以及混合策略。我实测下来,纯滑动窗口最容易丢关键信息,纯摘要又容易丢细节,混合策略最稳——最近几轮保留原文,更早的做摘要,同时把用户明确说过的偏好、约束单独拎出来常驻。
2.2 上下文裁剪的实操参数与计算
这里给一个我常用的裁剪参数配置,你可以直接抄:
- 保留最近6 轮完整对话(user+assistant算一轮)
- 更早的对话压缩成不超过500 token的摘要
- 常驻区(system prompt + 用户偏好 + 当前任务目标)控制在800 token以内
- 工具调用结果超过2000 token的,只保留前500和后500,中间用省略标记
为什么是6轮?因为大多数任务型对话的有效上下文就在最近几轮,再往前基本是寒暄或者已经被摘要覆盖的信息。为什么工具结果要截断?因为很多工具返回的是JSON或者长文本,全塞进去会瞬间吃满窗口,而模型真正需要的往往只是其中几个字段。
注意:裁剪不是简单的截断,截断会破坏消息的角色结构,导致模型理解错乱。一定要按“轮”为单位裁剪,保证user和assistant成对出现。
还有一个容易被忽略的点:会话的恢复。用户关掉页面再回来,你得能把会话状态还原。这要求你不只存消息,还要存“当前进行到哪一步”。比如一个多步任务,用户填了第一步,第二步填了一半就走了,回来时你得知道该从哪继续。我的做法是在会话记录里加一个state字段,存一个轻量的JSON,记录当前任务阶段和已收集的参数。这个字段不发给模型,只在服务端用来做流程控制。
2.3 会话隔离与并发处理
多用户场景下,会话隔离是底线。每个会话必须有独立的ID,且服务端要校验请求方是否有权访问该会话。我见过有项目直接用自增ID做会话标识,结果被人遍历ID读到了别人的对话,这是很低级的错误。用UUID或者带签名的token做会话标识,成本几乎为零,安全性提升一大截。
并发方面,同一个会话如果同时有两个请求进来(比如用户快速连点两次),要加锁或者做请求排队,否则会出现消息顺序错乱。我的做法是在会话级别加一个轻量的分布式锁,锁的粒度就是会话ID,超时时间设短一点(比如5秒),避免死锁。
3. 工具调用:让智能体从“会说”到“会做”
3.1 工具的定义与注册机制
工具是智能体的手脚。没有工具,智能体只能输出文字;有了工具,它才能查数据、发消息、改文件、调服务。工具的本质是一个“函数描述 + 执行逻辑”的组合。函数描述告诉模型这个工具叫什么、干什么、需要什么参数;执行逻辑是真正跑代码的部分。
一个规范的工具定义通常包含这几个字段:名称(唯一标识)、描述(给模型看的自然语言说明)、参数schema(JSON Schema格式,描述每个参数的类型和含义)、以及执行函数。描述写得好不好,直接决定模型会不会在正确的时机调用正确的工具。我踩过的坑是:描述写得太简略,模型经常该调不调,或者调错工具。后来我把描述写成“什么时候用这个工具 + 这个工具做什么 + 参数怎么填”三段式,调用准确率明显提升。
工具注册一般有两种方式:静态注册(启动时把所有工具注册进去)和动态注册(按需加载)。静态注册简单,但工具多了之后,光是工具描述就占掉大量上下文。动态注册更省上下文,但需要一套检索机制来决定当前该暴露哪些工具。我的建议是:工具数量在20个以内用静态注册,超过20个就上动态检索,用向量相似度匹配当前任务和工具描述。
3.2 工具调用的完整链路
一次工具调用的完整链路是这样的:模型输出一个工具调用请求(包含工具名和参数)→ 服务端解析请求 → 校验参数 → 执行工具 → 把结果作为一条tool角色的消息追加到会话 → 再次调用模型 → 模型基于工具结果生成最终回复。
这个链路里最容易出问题的是参数校验和错误处理。模型生成的参数不一定符合schema,比如该传数字的传了字符串,该传数组的传了单个值。服务端必须做严格校验,校验失败要返回明确的错误信息给模型,让它重新生成。我见过有项目直接信任模型输出,结果工具执行时报错,整个会话就卡死了。
错误处理也很关键。工具执行失败时,不要把原始堆栈直接丢给模型,那会污染上下文。应该返回一个结构化的错误信息,比如{"error": "参数格式错误", "detail": "age字段应为数字"},让模型知道怎么修正。
3.3 工具调用的安全边界
工具调用是智能体最危险的部分,因为它真的会执行代码、访问外部资源。安全边界必须提前划好。我的做法是三层防护:第一层是白名单,只有注册过的工具才能被调用;第二层是参数校验,所有参数必须符合schema;第三层是执行沙箱,工具执行在受限环境里跑,限制网络访问、文件访问和执行时间。
注意:绝对不要让模型直接生成并执行任意代码,除非你有非常完善的沙箱。我见过有项目让模型生成shell命令直接执行,这是极其危险的。
还有一个实操技巧:给工具调用加超时。有些工具(比如外部API)可能很慢,如果不设超时,整个会话就挂在那里。我一般设10到30秒的超时,超时后返回一个“工具执行超时”的结果给模型,让它决定是重试还是换方案。
4. MCP:工具与资源的标准接口层
4.1 MCP解决了什么问题
MCP这个词在这两年出现的频率越来越高,但很多人对它的理解还停留在“又一个协议”。我用一句话概括:MCP是让模型和外部能力之间用统一方式对话的协议。在没有MCP之前,每接一个工具就要写一套适配代码,工具A用这套参数格式,工具B用那套,模型每次都要重新学。MCP把这些差异抹平了,工具和资源都按统一的schema暴露,模型只需要学一次。
MCP的核心概念有三个:服务器(提供能力的一方)、客户端(消费能力的一方,通常是智能体运行时)、原语(服务器暴露的具体能力,包括工具、资源、提示模板)。服务器可以是本地的,也可以是远程的;客户端负责连接服务器、发现能力、调用能力。
为什么这个设计重要?因为它把“能力提供”和“能力消费”解耦了。你写一个MCP服务器,暴露你的数据库查询能力,任何支持MCP的智能体都能直接用,不需要为每个智能体单独适配。这对生态的意义很大。
4.2 MCP服务器的实现要点
实现一个MCP服务器,核心是定义好你暴露的能力。工具的定义和前面讲的工具注册类似,但MCP有更严格的schema要求。资源(resource)是MCP特有的概念,它代表可以被读取的数据,比如一个文件、一条数据库记录、一个API返回。资源用URI标识,客户端可以通过URI读取资源内容。
我实操下来的经验是:工具适合“动作”,资源适合“数据”。比如“发送邮件”是工具,“收件箱列表”是资源。分清楚这两者,schema设计会清晰很多。
MCP的通信方式常见的有两种:标准输入输出(stdio)和HTTP。stdio适合本地进程间通信,简单直接;HTTP适合远程服务,但需要处理认证和连接管理。我的建议是本地工具用stdio,远程能力用HTTP,不要混用。
注意:MCP服务器暴露的能力要有明确的权限控制。不是所有客户端都该访问所有资源。我在实现时会加一层鉴权,客户端连接时带上凭证,服务器根据凭证决定暴露哪些能力。
4.3 MCP与工具调用的协同
MCP不是替代工具调用,而是工具调用的标准化层。智能体运行时作为MCP客户端,连接到各个MCP服务器,发现可用工具,然后在需要时调用。对模型来说,它看到的还是工具列表,只是这些工具是通过MCP动态发现的,而不是硬编码在代码里。
这个协同带来的好处是热插拔。你可以随时加一个新的MCP服务器,智能体下次启动时就能发现新能力,不需要改代码。我在一个项目里用这个机制,把数据库、文件系统、外部API分别做成三个MCP服务器,智能体启动时自动发现,运行中按需调用,扩展性非常好。
5. 记忆系统:让智能体不再“失忆”
5.1 记忆的分层设计
记忆是智能体最容易被低估的能力。很多人觉得“把历史对话存下来就是记忆”,这远远不够。真正的记忆系统至少要分三层:短期记忆(当前会话的上下文)、长期记忆(跨会话的事实和偏好)、工作记忆(当前任务的中间状态)。
短期记忆就是前面讲的会话上下文,生命周期是一次会话。长期记忆是跨会话的,比如用户告诉过你“我喜欢简洁的回复”,这个偏好应该被记住,下次对话直接生效。工作记忆是任务级的,比如一个多步任务进行到第三步,前两步的结果要暂存,任务结束就清掉。
这三层的存储介质和检索方式都不一样。短期记忆在内存或缓存里,长期记忆在持久化存储里(向量库或关系库),工作记忆在会话状态里。分清楚这三层,记忆系统才不会乱。
5.2 长期记忆的写入与检索
长期记忆的核心是“什么时候写”和“什么时候读”。写入时机很关键,不能每句话都写,那样会存一堆噪音。我的做法是:只在检测到“值得记住的信息”时才写,比如用户明确表达的偏好、重要的实体信息、任务的关键结论。检测可以用规则(关键词匹配)也可以用模型判断(让模型输出“是否值得记忆”)。
检索时机是每次会话开始时,根据当前输入去长期记忆里捞相关的内容,拼到system prompt里。检索用向量相似度,但纯向量检索有时候会捞到不相关的,我一般会加一层重排序,用一个小模型对候选记忆做相关性打分,取top 3到5条。
注意:长期记忆要有过期和更新机制。用户三年前说喜欢某个东西,现在可能已经变了。我的做法是给每条记忆加时间戳和置信度,检索时优先取新的、置信度高的,旧的记忆定期清理或降权。
5.3 记忆与隐私的平衡
记忆系统天然涉及隐私问题。你记住的用户信息越多,隐私风险越大。我的原则是:只记必要的,不记敏感的。用户明确说“记住这个”才记,用户说“别记”就立刻删。存储时做加密,检索时做权限校验,确保一个用户的数据不会被另一个用户捞到。
还有一个实操细节:记忆的删除要彻底。用户要求删除某条记忆时,不能只是标记删除,要从向量库里真正移除,否则检索时还可能被捞出来。这个坑我踩过,后来改成软删除加定期物理清理,双保险。
6. 四大模块的协同与常见问题排查
6.1 协同工作流
把这四块串起来,一个完整的智能体工作流是这样的:用户发消息 → 会话模块加载上下文 → 记忆模块检索相关长期记忆 → 拼成完整prompt → 模型判断是否需要调用工具 → 如果需要,通过MCP发现并调用工具 → 工具结果回填 → 模型生成回复 → 会话模块保存消息 → 记忆模块判断是否写入长期记忆。
这个流程里,每个模块都有明确的职责,模块之间通过标准接口通信。我建议在实现时把每个模块做成独立的服务或类,通过接口调用,而不是全部揉在一个大函数里。这样调试和替换都方便。
6.2 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方法 |
|---|---|---|---|
| 模型不调用工具 | 工具描述不清或上下文太长 | 检查工具描述是否明确,检查上下文是否超限 | 优化描述为三段式,裁剪上下文 |
| 工具调用参数错误 | schema定义不严或模型理解偏差 | 检查schema是否完整,检查参数示例 | 补充参数示例,加服务端校验 |
| 会话上下文丢失 | 裁剪策略过于激进 | 检查裁剪参数,检查消息存储 | 调整裁剪窗口,确保成对保留 |
| 记忆检索不准 | 向量质量差或重排序缺失 | 检查embedding模型,检查检索结果 | 换更好的embedding,加重排序 |
| MCP连接失败 | 通信方式不匹配或鉴权失败 | 检查stdio/HTTP配置,检查凭证 | 统一通信方式,校验凭证 |
| 响应变慢 | 工具超时或上下文过大 | 检查工具执行时间,检查token数 | 加超时,裁剪上下文 |
6.3 我踩过的几个坑
第一个坑是上下文顺序错乱。早期我没注意消息顺序,工具结果和模型回复穿插错了,导致模型理解混乱。后来严格按时间戳排序,并且保证tool消息紧跟在对应的assistant消息后面。
第二个坑是记忆污染。有次我把工具返回的原始数据也写进了长期记忆,结果检索时捞出一堆无关的JSON。后来改成只写“结论性”的信息,原始数据不写。
第三个坑是MCP服务器崩溃导致整个智能体挂掉。MCP服务器是独立进程,可能因为各种原因挂掉。我的做法是加健康检查和自动重启,同时客户端要有降级策略,某个MCP服务器不可用时,相关工具标记为不可用,而不是整个会话失败。
第四个坑是会话锁死。有次一个工具执行卡住,锁没释放,导致该会话后续请求全部超时。后来改成锁带自动过期,并且工具执行有硬超时,双保险。
7. 从零搭建一个最小可用智能体的实操路径
7.1 技术选型建议
如果你要从零搭一个,我的选型建议是:会话存储用关系库(PostgreSQL或SQLite都行),消息表加会话表;工具调用用函数注册表加JSON Schema校验;MCP用官方SDK,本地工具走stdio,远程走HTTP;记忆用向量库(轻量场景用本地文件加FAISS,重场景用专门的向量数据库)。
模型方面,选一个支持工具调用的就行,现在主流模型基本都支持。关键是模型的工具调用格式要和你服务端的解析逻辑对齐,这个在接入时一定要测。
7.2 分阶段实现路径
第一阶段:只做会话和基础工具调用。目标是能多轮对话,能调用一两个简单工具。这个阶段把消息存储、上下文裁剪、工具注册和调用链路跑通。
第二阶段:接入MCP。把工具改造成MCP服务器,客户端通过MCP发现和调用。这个阶段验证标准化接口的可行性。
第三阶段:加记忆。先做短期记忆(其实就是会话),再做长期记忆的写入和检索。这个阶段重点是记忆的写入时机和检索质量。
第四阶段:优化和加固。加超时、加锁、加鉴权、加监控。这个阶段是把demo变成能用的系统的关键。
7.3 监控与可观测性
智能体系统比普通服务更难调试,因为它的行为有随机性。我的做法是记录完整的调用链路:每次请求的输入、上下文、模型输出、工具调用、工具结果、最终回复,全部落盘。出问题时可以完整回放。另外加几个关键指标:工具调用成功率、平均响应时间、上下文token数、记忆命中率。这些指标能帮你快速定位问题。
注意:日志里可能包含用户隐私数据,落盘前要做脱敏,存储要加密,访问要审计。
8. 一些个人体会和后续扩展方向
这套东西我断断续续搞了大半年,最大的体会是:智能体的难点不在模型,而在工程。模型能力已经足够强了,真正决定一个智能体好不好用的,是会话管理是否稳健、工具调用是否可靠、记忆是否精准、MCP集成是否顺畅。这些全是工程活,没有捷径,只能一个个坑踩过去。
后续可以扩展的方向有几个。一是多智能体协作,让多个智能体通过MCP互相调用,各司其职。二是记忆的主动学习,让智能体自己判断哪些信息值得记,而不是靠规则。三是工具的动态生成,根据任务临时组合已有工具生成新工具。这几个方向我都在试,有进展再分享。
最后分享一个小技巧:调试智能体时,把模型的思考过程(如果有)和工具调用链路都打出来,比只看最终回复有用得多。很多时候问题出在中间步骤,最终回复看起来正常,但中间已经跑偏了。