资讯详情

资讯详情

大模型Agent四层能力拆解:会话、工具、MCP与长期记忆实战指南

前两年我做Agent框架选型的时候踩过一个很典型的坑模型接上了、对话能跑了但一让它调用工具就乱套换个话题再回来就失忆接第三方服务更是每个接口单独写适配代码。后来我才想明白一件事——Agent不是“模型API的壳”它的核心是四个能力层会话、工具、MCP、记忆。你把这四层理顺了模型才能从“能聊天”变成“能干活”。这篇文章就以Hermes Agent的能力体系为主线拆开讲每一层解决什么问题、底层原理是什么、实操时有哪些坑。适合正在选型Agent框架、或者想把现有对话服务升级成真正可执行任务的开发者不管你用的哪家模型思路都通用。1. 先理清一件事Agent的能力地图1.1 四个能力分别解决什么问题把Agent拆开看任何一个完整可用的Agent都绕不开这四件事会话管理、工具调用、外部协议对接、长期记忆。它们不是锦上添花的功能模块而是决定Agent能不能在真实业务里站稳的地基。会话解决的是模型“记不住上下文”的问题。大模型本身是无状态的每次请求都像第一次见面上一个问题说了什么、用户提供了哪些信息模型完全不知道。会话层负责把一次连续的对话组织成模型能理解的上下文序列同时维持任务状态。工具解决的是模型“只能动嘴不能动手”的问题。模型能生成文本却不能直接查数据库、发请求、操作文件。工具层让模型可以输出结构化的“调用意图”由外部代码真正执行再把结果喂回模型。MCP解决的是“接口碎片化”的问题。每个工具各有各的鉴权方式、参数格式、调用协议Agent要接十个工具就得写十个适配器。MCP用一套标准协议统一外部工具和数据的接入让Agent和工具提供方不需要再两两对接。记忆解决的是模型“每次见面都像陌生人”的问题。长期记忆让Agent跨会话记住用户偏好、历史事实、任务状态避免每次对话都从零开始铺垫背景。一句话总结会话管状态工具管执行MCP管连接记忆管持久。四个能力互相配合才构成一个真正可用的Agent。1.2 为什么“会话”是地基很多人觉得会话不就是把聊天记录存下来再传给模型吗有什么好讲的。但实际做下来你会发现工具调用、MCP、记忆最终都发生在一次会话的过程中会话层是整个Agent运行时的骨架。举一个很常见的例子。用户说“帮我查一下刚才那个订单”如果会话层没有记录“刚才那个订单”指的是哪一单工具层根本不知道参数从哪来。再比如用户在多轮对话中分几次提供了地址、日期、商品信息最终下单工具需要把这些散落的信息拼成完整参数拼不齐就下单失败。会话层如果只做“消息存取”这一件事那它确实简单。但真正的会话层还要承担状态跟踪、关键信息提取、上下文裁剪、摘要生成这些活。这些工作做得好不好直接决定后续工具调用的准确率和记忆写入的质量。所以我把会话排在第一个讲它不是附属品而是地基。1.3 四个能力的协作流程把四个能力的协作流程串起来看会更清楚每一层的位置用户输入进入会话层会话层把系统提示、历史消息、相关记忆、可选工具定义组装成一份完整上下文。模型根据上下文决定是直接回答还是产生工具调用请求。若请求的是原生工具由工具层执行若请求的是MCP工具则由MCP Client转发给远端MCP Server。执行结果按调用ID回填消息模型拿到结果后生成最终回答。在会话推进过程中记忆层根据触发策略抽取值得保存的信息写入长期存储。这个流程看起来不长但每一层都有细节。接下来的四个章节就按这个顺序逐个展开。2. 会话让模型记住“说到哪了”2.1 LLM天生无状态会话层帮你补状态大模型的核心机制是“根据输入生成输出”它本身不保存任何状态。你把同样的问题问两遍它给出同样的回答不会意识到自己刚才回答过一次。这和人的对话模式完全不同所以必须有一个外部组件替模型补上“记忆”。最简单的做法是为每个对话分配一个会话ID服务端保存消息历史每次请求时把历史全量拼进上下文。这个方案在对话轮次少的时候很有效简单直接效果也好。但当对话拉长到几十轮、上百轮时消息历史会迅速膨胀很快撞上上下文窗口的上限。所以会话层不能只做“存取”还得做“管理”。管理什么管理上下文的组成方式、管理关键信息的留存、管理token的预算分配。2.2 多轮上下文的三种组织方式我实际用过的上下文组织方式主要有三种它们各有取舍。第一种是全量拼接。把全部消息按时间顺序塞进上下文模型能看到完整的对话过程效果最好但token消耗也最大。短对话用没问题长对话基本跑不动。第二种是摘要压缩。把早期消息用模型总结成一段摘要只保留关键信息。token占用大幅下降但细节容易丢失有时候用户之前提过的一个关键参数摘要里没写进去后面就找不回来了。第三种是混合策略也是我现在最常用的方式最近几轮完整保留更早的消息压缩成摘要同时把关键字段单独抽取到会话元数据里。这样既保留了近期对话的细节又控制了token成本。conversation { id: conv_123, recent_messages: [], # 最近5轮完整保留 summary: 用户咨询订单状态订单号OD2024001期望了解物流进度, extracted_fields: {order_id: OD2024001} }这个结构的核心是extracted_fields。不要把关键信息只放在摘要里因为摘要毕竟是由模型生成的存在漏掉的风险。单独抽出来放结构化字段里程序可以精确读取供工具层使用。2.3 会话状态机与会话ID设计消息历史只是会话的一部分会话还应该包含“任务状态”。比如在订单查询流程中用户还没提供订单号时会话处于awaiting_order_id状态拿到订单号后进入querying状态查出结果后进入done状态。状态机帮助Agent判断下一步该问什么、该调哪个工具避免模型漫无目的地发挥。会话ID我建议直接用UUID不要用自增数字。自增ID容易被遍历会带来会话数据泄露的风险。UUID足够随机适合作为会话的唯一标识。这里有个实操细节会话元数据创建时间、所属用户、状态字段、提取出的关键参数和消息历史要分开存储。会话元数据用结构化字段方便程序直接读取消息历史保持原始时间线供需要时拼进上下文。两者不要混在一起否则每次读取元数据都要解析长串消息性能很难看。2.4 上下文窗口管理与Token预算上下文窗口是有限的所以token预算必须提前规划。我常用的分配比例是系统提示15%到20%消息历史40%到50%工具定义20%到25%生成预留10%到15%当消息历史接近预算上限时触发压缩或裁剪。裁剪策略上优先保留用户提供的事实性信息比如订单号、日期、地址优先保留尚未完成的子任务信息可以丢弃寒暄、重复表达、过时的中间步骤。我试过按时间戳简单截断旧消息效果很一般。因为简单截断可能把关键参数切掉比如用户第二轮提到订单号到第十轮你把它截掉了后面所有工具调用都缺参。后来改成“关键信息提取摘要”的混合方案明显稳定。2.5 实操细节系统提示词与消息排列会话层有几个容易忽略的细节列出来给你参考系统提示词放在消息序列最前面用户最新输入放在最后面这是主流模型都能识别的标准布局。工具定义如果比较多放在系统提示之后、消息历史之前让模型在阅读历史前先知道有哪些工具可用。流式输出时如果做了多路并发要确保每路请求的消息顺序一致顺序乱了模型很容易答非所问。摘要不是一次性做完的可以在每一轮结束时增量更新分摊开销。所有关键字段同时写入会话元数据不要只依赖摘要这是我在生产环境里吃过大亏后总结出来的。3. 工具把“嘴”变成“手”3.1 Function calling的本质Function calling这个能力名字容易误导人。它不是让模型直接执行代码而是让模型输出一个结构化的“调用请求”。比如用户问“北京今天多少度”模型输出工具名get_weather、参数{city: 北京}。真正去发起HTTP请求的是Agent的代码模型不负责执行。这个分层是工具调用的核心模型只负责决策“该用什么工具、参数填什么”执行和校验是外围代码的责任。也正因为如此模型输出的参数不能直接信任。模型可能把“北京”识别成“背景”可能把参数类型从string写成object这些都要靠外围代码兜底。我用一个生活类比来说模型是大脑工具执行层是手。大脑决定要拿杯子但真正伸出手去拿、拿稳、拿住的是手。如果手没有控制力大脑再聪明也白搭。3.2 工具注册与Schema定义要点工具调用的第一步是注册工具。注册的本质是向模型描述“你有什么工具、工具是干什么用的、参数怎么填”。模型的选型准确率很大程度取决于这份描述写得好不好。一个典型的工具定义长这样{ name: query_order, description: 根据订单号查询订单状态与物流信息适用于用户询问发货、配送、签收场景, parameters: { type: object, properties: { order_id: { type: string, description: 完整的订单号例如OD2024001 }, include_logistics: { type: boolean, description: 是否同时返回物流轨迹默认false } }, required: [order_id] } }description字段写得越具体模型选对工具的概率越高。模糊的描述比如“查询订单”会让模型在多个相似工具之间犹豫。我踩过的一个坑是工具名太像比如query_order和get_order_detail模型经常选错。后来把两个合并成一个加了一个mode参数准确率明显上升。工具命名的原则应该是“一看就懂、一描述就唯一”。3.3 工具选择与并行调用策略工具数量多了以后模型更容易挑错。我的几个现实建议高频工具保持在10个以内低频工具考虑合并或用MCP按需加载。相似工具合并成一个用参数区分减少模型的决策空间。工具描述开头写清适用场景比如“当用户询问物流轨迹时使用本工具”。不要一次性把所有工具都注册进去按会话场景动态加载子集。并行调用方面支持多工具调用的模型可以一次返回多个工具请求但要注意依赖关系。比如“查一下今天天气并提醒我适不适合跑步”需要先拿到地理位置再查天气这种就不能并行要串行执行。可以先调用get_location拿到城市再把这个城市作为参数传给get_weather。3.4 工具结果回填与错误恢复工具执行完成后结果需要用tool消息按调用ID回填给模型。这是一个常见的结构{role: tool, tool_call_id: call_001, content: 订单状态已发货物流顺丰单号SF123456}模型拿到回填结果后会结合原始对话生成最终回复。这里有一个动作容易被忽略tool_call_id必须与模型生成的调用ID一一对应对不上模型会找不到结果来源可能重新发起一次同样的调用。工具执行失败时不要把异常堆栈直接丢给模型。你应该先加工错误信息比如把“java.lang.NullPointerException”改成“查询失败订单号不存在”再回填给模型让模型以自然语言向用户解释。这样做用户体验好很多排查日志也更清晰。3.5 一个天气查询工具的完整示例走一个完整流程看看工具从注册到执行的闭环。第一步注册工具定义tools [ { name: get_weather, description: 查询指定城市当前的天气情况包括温度、湿度、风力, parameters: { type: object, properties: { city: {type: string, description: 城市名如北京、上海} }, required: [city] } } ]第二步用户问“北京今天多少度”。模型可能返回{ name: get_weather, arguments: {city: 北京} }第三步代码执行真正的天气查询这里假设调用的是某个天气服务API。第四步回填结果{role: tool, tool_call_id: call_001, content: 北京晴25℃湿度40%风力2级}第五步模型生成最终回复“北京今天晴25度湿度40%风力2级体感挺舒服的。”整个闭环里模型只做了两件事决定查天气和基于结果组织回复。真正的API调用、参数校验、错误处理全部在代码层完成。3.6 注意事项参数校验、token开销、权限隔离工具层还有三个必须注意的工程问题。第一参数校验。模型输出的参数必须程序化校验类型、枚举、范围都要核验。比如模型给city传了一个空字符串你要么纠正为默认值要么返回一个“参数缺失”的错误不能直接拿空参数去请求天气API。第二token开销。每个工具定义大约消耗100到200个token20个工具就是几千token。这会挤压消息历史的预算空间所以工具数量要控制工具描述要精简。第三权限隔离。工具执行环境要最小权限。模型生成了某个action不代表这个action就应该无限制地执行。写文件、发消息、删数据这种高风险操作都要在代码层做二次校验和审计。我在一个项目里就遇到过模型把删除命令的参数填错差点酿成大祸从那以后所有高风险工具都加了人工确认机制。4. MCPAgent的通用插座4.1 MCP是什么MCP的全称是Model Context Protocol是一个面向模型上下文对接的开放协议标准。它的定位很像USB-C标准以前接一个设备需要一根专属线缆现在用统一接口解决。在Agent生态里MCP希望解决的问题是你不再需要为每个外部工具写专属适配器只要工具提供方实现了MCP Server任何支持MCP的Agent都可以直接使用它。MCP的价值在工具数量多、生态共享的背景下尤其明显。如果只接一两个内部工具直接用Function Calling可能更快但一旦工具数量上来了每个工具都要单独写鉴权、参数转换、错误处理适配成本就会爆炸式增长。4.2 MCP的三大原语MCP协议里定义了三种原语Tools、Resources、Prompts。它们各自的语义差别很大。Tools是可执行操作会改变状态比如发消息、创建订单、删除文件。Resources是只读数据源比如读取文档内容、查询只读数据库不改变任何状态。Prompts是可复用的提示词模板用于预设工作流或分析流程比如“周报生成模板”“订单分析模板”。这里容易混的是Tools和Resources。简单判断标准是要不要改状态。发一条消息就是Tool读一个文件内容就是Resource。这个区分在Agent调度时很重要因为Tool调用通常需要更严格的权限控制而Resource读取可以相对放开。我用一个表格对比原语语义是否改变状态典型用途Tools执行操作是发送消息、创建订单、调用APIResources读取数据否读取文档、查询只读数据Prompts模板提示否预设分析流程、复用固定话术4.3 Client-Server架构与Transport选择MCP分Client和Server两层。Agent内嵌MCP Client工具提供方运行MCP Server两者通过传输层通信。目前主流的传输方式有两种stdio和HTTP/SSE。stdio方式是在本地启动一个子进程通过标准输入输出通信。优点是部署简单延迟低适合跑在Agent同机的本地工具。缺点是要自己管理子进程的生命周期Agent退出时如果没清理干净会留下僵尸进程。HTTP方式是通过网络协议通信适合远程部署的工具服务天然支持鉴权和跨机器访问。缺点是引入了网络延迟还要处理连接保持和鉴权逻辑。选择标准其实不复杂工具和Agent在同一台机器上用stdio工具是远程服务用HTTP。混合场景下两种可以并存。4.4 为什么值得用MCP我自己的项目是用Ph tag后来意识到没有其他可选背景就不再节外生枝了回到话题本身。根本上说MCP值得用的原因有四个第一一次接入多处复用。一个实现了MCP Server的工具可以被任何支持MCP的Agent复用不需要为每个Agent写适配代码。第二生态红利。社区里已经积累了大量的现成MCP Server覆盖文件系统、数据库、各类常用服务接入成本大幅降低。第三权限与审计集中。鉴权、限流、操作日志都可以在Server这一层统一完成Agent侧就不需要重复实现。第四协议升级不用改业务代码。只要Server端遵循协议演进Agent侧几乎不用动。但我也得说句实在话如果你的项目只接一两个内部工具直接用Function Calling可能更快MCP的优势要在“工具数量多、需要共享复用、外部生态丰富”时才真正体现。4.5 实操接入一个MCP服务的配置过程MCP接入的配置过程通常是在Agent的配置里声明要连接的Server。下面是一个典型的配置片段{ mcpServers: { filesystem: { command: npx, args: [-y, mcp/fs-server, /tmp/data], transport: stdio }, remote_orders: { url: https://mcp.example.com/orders, headers: { Authorization: Bearer ${API_KEY} }, transport: http } } }Agent启动时会读取这份配置对于stdio类型的Server启动对应子进程对于HTTP类型的建立连接。连接建立后Agent会拉取Server暴露的工具列表注册到工具层。至此MCP工具就和原生工具一样可以被模型调用了。有一个细节多个Server可能暴露同名工具比如filesystem里有个read_fileremote_orders里也有个read_file。这时候要给工具名加前缀变成filesystem_read_file、remote_orders_read_file避免冲突。4.6 踩坑记录MCP接入过程中我踩过不少坑挑几个典型的说stdio子进程残留。Agent异常退出时MCP Server子进程可能没被杀掉时间久了会积累一堆僵尸进程。解决办法是在Agent生命周期结束时统一清理给子进程设置退出钩子。密钥管理。配置里的API Key不要明文写死用环境变量注入。我见过有人把密钥写进工具描述里模型输出结果时偶尔会把密钥也带出来这是很严重的安全隐患。超时与重试。远程MCP Server可能因为网络抖动失败工具调用层要有超时和重试机制不能无限等下去。参数透传。有些Server返回的结果不是纯文本是JSON结构回填模型之前建议先做预处理比如只提取关键字段减少token占用。工具白名单。如果Server暴露了20个工具全注册进去会占大量token。更合理的做法是做白名单过滤按当前会话场景只注册需要的子集。5. 记忆短期靠上下文长期靠存储5.1 记忆的分层记忆绝不是简单的“把对话记录存下来”。面向Agent的记忆设计我会分三层来看。工作记忆对应当前会话内模型看到的上下文容量小、速度快会话结束就清空。情景记忆是跨会话的事实记录比如“用户上周说家里养了一只猫”“用户的老家在杭州”这些信息在下次对话时可能会用到。语义记忆是从大量对话中提炼的用户画像和偏好比如“用户偏好简洁的回答风格”“用户经常在晚上购物”。三层记忆的存储位置和容量差异很大记忆类型存储位置容量典型示例工作记忆上下文窗口小当前对话的最新几轮消息情景记忆结构化数据库中用户养猫、用户老家在杭州语义记忆向量库/知识库大用户偏好、行为模式画像5.2 记忆写入时机与触发策略不是所有对话内容都值得写入记忆。如果每句话都记记忆库很快就会变成垃圾场检索质量也会急剧下降。我的经验是至少满足以下一个条件再写入用户明确表达身份、偏好、长期目标比如“我不吃辣”“我住在朝阳区”。任务中出现关键参数且后续可能复用比如订单号、收件地址、会议时间。用户决策发生变化且和旧记忆冲突比如“我不用顺丰了以后都用圆通”。信息相对稳定不是一次性闲聊比如“我周末想去爬山”就不太值得记。可以用一个伪代码函数来表达判断逻辑def should_remember(message): if contains_personal_info(message): return True if contains_preference(message): return True if message.topic in [orders, address, schedule]: return True return False写入时机选择在对话回合结束后做不要在线程里同步阻塞主流程。可以放在后台上任务里异步执行避免影响对话响应速度。5.3 记忆检索向量化、重排、阈值记忆写入之后检索质量决定记忆好不好用。基础流程是把用户当前输入向量化。在向量库中做相似度检索取top-k。用重排模型或规则对结果排序。计算相似度分数低于阈值的记忆不注入上下文。阈值的经验值在0.7到0.85之间浮动具体取决于你用的embedding模型的分布特征。阈值太低会把很多无关记忆塞进去模型回答会被带偏阈值太高则什么都记不住。建议先在测试集上调好再上线。时间衰减也是一个提升检索质量的技巧。最近被访问过的记忆在排序时加权提高长期未访问的权重降低。这样能保证高频使用的信息优先被检索出来避免模型总是被一些陈年旧事干扰。5.4 记忆的更新与遗忘机制记忆不是只增不改的。用户说“我不养猫了”之前那条“用户养猫”的记忆就得标记为失效或更新。如果不处理Agent可能会在某个场合跟用户说“你的猫还好吗”这个体验就非常尴尬。简单实现每条记忆带created_at、updated_at、hit_count、status字段。当新信息和旧记忆冲突时优先信任新信息把旧记忆标记为deprecated同时保留一条冲突记录供追溯。另外还要做定期清理。长时间未命中且低价值的记忆可以自动降权或删除控制存储增长。我习惯每周跑一次离线任务把三个月以上未命中、且非关键类型的记忆归档。5.5 记忆安全与隐私边界记忆能力越强隐私风险越大。几个红线必须守住敏感信息密码、证件号、支付信息绝不进长期记忆即使提取到了也要立即丢弃。用户应能查看和删除自己的记忆产品层面必须提供入口不能只做内部功能。从记忆中检索出的内容在回复时不要原样兜底展示避免信息泄露到无关会话。记忆的写入和读取都要有审计日志出了问题能追溯到具体操作。记忆模块看起来是技术问题往深了做其实是信任问题。用户敢不敢跟你多说话取决于你的记忆策略是否安全透明。5.6 一个最小记忆模块的设计一个最小可用的记忆模块至少包含存储和检索两部分。存储用SQLite起步就够等数据量大了再上向量库。这里给出一个表结构示例CREATE TABLE memories ( id TEXT PRIMARY KEY, user_id TEXT, content TEXT, memory_type TEXT, tags TEXT, status TEXT DEFAULT active, created_at INTEGER, updated_at INTEGER, hit_count INTEGER DEFAULT 0 );写入流程判断触发条件 → 提取关键内容 → 向量化 → 向量索引插入 元数据写入SQLite → 建立索引。检索流程向量化query → 向量库top-k → 过滤status为active的记录 → 按时间衰减排序 → 注入上下文。这个设计虽然简单但已经把记忆的基本模型跑通了。后续要扩展无非是加重排、加标签过滤、加知识图谱底层的存取框架是不变的。6. 常见问题与排查实录6.1 模型一直不调用工具怎么办这是工具层最常遇到的问题。模型明明有工具可用却总是直接给出文字回答不生成调用请求。原因通常是三类工具描述不够清晰模型没理解这个工具什么时候用工具列表太大模型在决策空间里乱了系统提示词里没有明确告诉模型可以使用工具。解决方法是在系统提示词里加一句“当需要获取实时数据或执行操作时使用提供的工具不要直接编造结果”精简工具数量按场景动态加载给高频工具加一个few-shot示例告诉模型“遇到这类问题就调用它”。我实测过加few-shot对选型准确率的提升非常明显。6.2 多轮对话越聊越偏会话层常见问题是对话超过十轮以后模型开始答非所问或者重复操作。根本原因是历史被截断时关键参数被丢掉或者摘要信息不足以支撑当前上下文。解决办法是会话元数据单独保存关键字段不依赖模型对历史的理解每次工具调用前从会话状态重新组装当前目标把“当前在做什么、还差什么信息”显式放进上下文对摘要做增量更新而不是等窗口满了才一次性压缩。6.3 MCP连接不上、超时排查顺序有讲究我一般按这个顺序去做先用测试工具直接访问MCP Server排除网络和服务端本身的问题。检查配置里的command、args、url字段是否正确拼写错误和路径错误是最常见的。检查环境变量是否注入成功密钥没配上会导致鉴权失败。检查stdio子进程是否被系统回收超时或者OOM都可能导致进程消失。查看Agent日志里的握手和工具列表拉取记录确认连接建立到了哪一步。这个排查顺序能帮你快速定位是配置问题、环境问题还是代码问题不用到处瞎猜。6.4 记忆检索结果“八竿子打不着”记忆检索质量差最典型的表现是模型把完全不相干的历史信息当作上下文回答出来的内容很离谱。原因一般是embedding模型对短文本区分度不足、检索阈值设太低、记忆写入太碎。解决方法是把阈值提上去比如从0.7提到0.8写入记忆时加类型标签偏好、事实、身份检索时按标签过滤对碎片化记忆做合并不要一句话一存尽量把相关信息归拢成一条完整记录。我见过一个案例用户和Agent聊了一次“最近在学游泳”结果之后每次对话的检索都会把这个信息拉出来模型在聊代码时突然来一句“游泳进展如何”非常出戏。加了类型标签和阈值之后这类问题就消失了。6.5 一张问题速查表把上面这些常见问题整理成一张表方便你快速定位问题常见原因快速定位预防建议模型不调用工具描述不清晰、工具过多、缺提示检查工具定义和系统提示词精简工具、加few-shot多轮对话跑偏关键字段丢失、摘要不全查看会话元数据中的关键参数独立存字段、增量摘要MCP连不上配置错误、密钥失效、进程退出依次检查配置、环境变量、日志统一配置管理、加超时重试记忆检索不准阈值太低、标签缺失、碎片化查看检索分数和记忆标签提高阈值、加标签过滤最后说几句实在话会话、工具、MCP、记忆这四块单独看都不算难真正难的是它们的配合。我个人的体会是你要先把会话状态和工具调用结果的数据格式定稳后面接MCP和记忆层才不会返工。如果刚入门建议顺序是先做会话和工具再上记忆最后再碰MCP协议。一个小技巧所有跨模块数据统一用JSON结构哪怕多写两行代码也值得后面调试的时候你会感谢这个决定。Hermes Agent这套能力体系本质上就是把“模型会说话”变成“Agent会办事”希望这篇拆解能帮你少踩几个坑。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →