Agent能力体系拆解:会话、工具、MCP与记忆机制的设计与实现

发布时间:2026/10/22 4:45:59
Agent能力体系拆解:会话、工具、MCP与记忆机制的设计与实现
1. 从一次翻车现场说起为什么我要拆解这套Agent能力体系去年年底我接手了一个内部知识助手项目需求听起来很朴素让一个智能体能够记住用户之前说过的偏好能调用几个内部工具查数据还要能接入团队已有的几个服务端点。我当时觉得这有什么难的选个框架、挂几个工具、加个向量库就完事了。结果上线第一周就被打脸——用户说帮我查一下上周那个报表智能体一脸茫然因为它根本不知道上周那个指的是什么用户切换话题聊了三轮之后又绕回来问刚才那个数据再算一遍它直接把前面的上下文全丢了更离谱的是工具调用偶尔会串台查A系统的接口返回了B系统的字段。那次翻车让我意识到一个能用的Agent和一堆能跑的组件之间隔着一整套能力体系的设计。后来我花了大概两个月时间把会话管理、工具调用、MCP协议接入、记忆机制这四块从头捋了一遍重新设计了一版架构才算把问题压下去。这篇内容就是那次重构的完整复盘我会把每一块的设计思路、踩过的坑、参数怎么定、代码怎么写全部摊开讲。Hermes Agent这套能力体系核心就是四件事会话Session负责一次交互的生命周期管理工具Tool负责让智能体有手有脚能干活MCP负责把外部能力标准化地接进来记忆Memory负责让智能体不是金鱼脑。这四块单独看都不复杂难的是它们之间的边界怎么划、状态怎么流转、数据怎么共享。如果你正在做智能体相关的开发或者准备把大模型能力落到具体业务里这篇内容应该能帮你少走不少弯路。哪怕你只是刚接触这块我也会尽量用生活化的类比把原理讲清楚保证你能看懂。2. 整体架构设计四块能力怎么拼成一台能用的机器2.1 为什么是这四块而不是别的组合先说说我为什么把能力拆成这四块。市面上很多框架喜欢把东西揉在一起会话里塞记忆工具里塞协议看起来省事实际上后期维护是灾难。我拆分的逻辑很简单按状态的生命周期长短来分。会话是短生命周期的一次对话结束会话状态基本就该释放了记忆是长生命周期的跨会话、跨天甚至跨月都要保留工具是无状态的能力单元调用完就结束不应该持有上下文MCP是工具和外部系统之间的接线板负责协议转换和连接管理。这四者的生命周期完全不同混在一起就会出现该释放的没释放、该保留的被清掉的经典问题。打个比方会话像是你和朋友的一次当面聊天聊完就散了记忆像是你对这个朋友的长期印象下次见面还记得他喜欢喝什么工具像是你手边的计算器、地图、通讯录用的时候拿起来用完放下MCP像是你家里那排插座和转接头让各种电器都能接上电。这四样东西你肯定不会塞进同一个抽屉里对吧。2.2 数据流向与状态边界理解了生命周期再来看数据怎么流。一次完整的交互大概是这样的用户输入进来会话层先接住给它分配一个会话ID加载这个会话的历史消息然后记忆层根据当前输入去检索相关的长期记忆把召回的内容拼进上下文接着工具层根据意图判断要不要调工具如果要调通过MCP层去连接外部服务工具返回结果后再交回会话层组织成回复最后会话层判断这次交互里有没有值得沉淀的信息如果有写入记忆层。这里有个关键设计点记忆的写入是异步的读取是同步的。为什么因为写入记忆往往涉及向量化、去重、冲突检测这些操作耗时且不应该阻塞用户等待回复。而读取记忆必须在生成回复之前完成否则上下文就不完整。我一开始把写入也做成同步的结果每次回复都要多等一两秒用户体验很差。改成异步之后回复速度立刻上来了。状态边界上我定了一条铁律会话层不直接碰外部服务工具层不直接持有会话状态记忆层不感知工具调用细节。每一层只跟相邻层打交道跨层调用一律通过明确定义的接口。这条规矩看起来死板但它救了我好几次——有一次工具层出了bug疯狂重试因为边界清晰会话层完全没受影响用户那边只是感觉这次工具调用慢了点对话本身没崩。2.3 选型背后的取舍逻辑具体到技术选型我做了几个关键取舍这里展开说说理由。会话存储用Redis而不是纯内存。纯内存最快但服务一重启会话全丢用户正在聊到一半突然断线体验是灾难性的。Redis兼顾了速度和持久化而且支持设置TTL会话过期自动清理不用自己写定时任务。我用的是Redis的Hash结构存会话元数据List结构存消息序列实测下来单会话读写都在毫秒级。记忆存储用向量库加关系库的组合。纯向量库做语义检索很强但做精确的条件过滤比如只召回某个用户上周的记忆就很别扭。我的做法是向量库存语义索引关系库存结构化元数据时间戳、用户ID、记忆类型、重要度评分检索时先用关系库做粗筛再用向量库做精排。这个组合方案比纯向量库的召回准确率高了大概三成。MCP作为独立进程而不是内嵌库。内嵌库调用方便但一旦某个外部服务卡住整个Agent进程都可能被拖死。独立进程虽然多了进程间通信的开销但隔离性好某个MCP连接挂了不影响主进程而且可以独立重启、独立扩容。这个取舍在初期会显得过度设计但等到你接入的服务多起来就会庆幸当初的决定。3. 会话管理一次对话的生命周期到底该怎么管3.1 会话ID的设计与分配策略会话管理的第一件事就是会话ID怎么设计。我见过有人用自增数字有人用UUID有人用时间戳。这几种我都试过最后选的是**用户标识前缀 随机串 时间戳后缀**的混合方案。为什么不用纯UUID因为纯UUID没法从ID本身看出任何信息排查问题时你得先去数据库查这个ID属于谁、什么时候创建的多一步操作。为什么不用纯时间戳因为高并发下同一毫秒可能创建多个会话会冲突。混合方案的好处是前缀能快速定位用户随机串保证唯一性时间戳后缀方便按时间排序和排查。具体格式我用的是u{用户ID后6位}_{8位随机串}_{13位毫秒时间戳}比如u8a3f2c_k9m2p4x7_1735689600000。这个格式在日志里一眼就能看出是谁的会话、大概什么时候创建的排查效率提升很明显。会话ID分配之后还要考虑会话的创建时机。我的策略是懒创建用户第一次发消息时才创建会话而不是用户一进页面就创建。因为很多用户进来只是看看并不一定真的对话提前创建会浪费存储。懒创建配合一个预分配优化——当用户开始输入时前端可以监听输入事件后台悄悄预创建一个会话等用户真正发送时直接复用这样既省资源又不影响首次响应速度。3.2 消息序列的存储结构与截断策略会话里的消息怎么存直接决定了上下文怎么拼。我一开始用的是一个大的JSON数组每次追加消息就整个读出来、改、写回去。这个方案在消息少的时候没问题消息一多比如超过50轮每次读写都是几百KB性能急剧下降。后来改成Redis List结构每条消息一个元素用RPUSH追加用LRANGE读取最近N条。这样追加是O(1)读取最近N条也是O(N)性能稳定。每条消息存成一个JSON字符串包含角色user/assistant/system/tool、内容、时间戳、token数这几个字段。截断策略是另一个坑。大模型的上下文窗口是有限的消息不能无限往里塞。我试过三种策略按条数截断只保留最近20条、按token截断保留最近N个token、滑动窗口加摘要保留最近N条更早的压缩成摘要。实测下来纯按条数截断最简单但容易丢关键信息纯按token截断更精确但实现复杂滑动窗口加摘要效果最好但成本最高。我最终采用的是分层截断最近10条消息完整保留第11到30条只保留用户消息和助手的关键回复去掉工具调用的中间过程30条以前的压缩成一段摘要。这个方案在保证关键信息不丢的前提下把上下文长度控制在了合理范围。摘要的生成我用的是异步任务不阻塞主流程。提示截断策略一定要可配置不同业务场景对上下文的需求差别很大。客服场景可能只需要最近5条而代码助手场景可能需要保留很长的历史。把阈值做成配置项后期调整不用改代码。3.3 多轮对话中的状态同步问题多轮对话最头疼的是状态同步。用户可能在手机上说了一半换到电脑上继续也可能同时开了两个窗口在两边交替发消息。这时候会话状态怎么同步我的方案是会话状态集中存储客户端只持有会话ID。所有状态变更都走服务端客户端每次发消息都带上会话ID服务端根据ID加载最新状态。这样无论用户从哪个设备进来看到的都是一致的状态。代价是每次交互都要读一次Redis但这个开销可以接受。但这里有个并发问题如果用户在两个窗口同时发消息两个请求几乎同时到达都去读状态、都去改状态就可能出现覆盖。我的解决办法是给会话加一个版本号更新时做乐观锁校验。读的时候拿到版本号V1写的时候检查当前版本号还是不是V1如果是就写入并升级到V2如果不是就说明有并发修改重新读取再处理。这个机制听起来简单但确实解决了我遇到的大部分并发问题。还有一个细节是消息的顺序保证。用户快速连发两条消息服务端可能因为网络原因先收到第二条。我的做法是在客户端给每条消息打一个本地递增的序号服务端按序号排序后再处理。如果发现序号有跳跃比如收到1和3没收到2就等一小段时间看2会不会到超时了再按现有顺序处理。这个机制避免了消息乱序导致的上下文错乱。4. 工具调用让智能体真正能干活的关键一环4.1 工具描述的设计模型能不能调对全看这里工具调用能不能成功八成取决于工具描述写得好不好。我见过太多人把工具描述写成查询数据这种模糊的话然后抱怨模型老是调错。模型不是人它只能根据你给的描述来判断这个工具是干什么的、什么时候该用、参数怎么填。一个好的工具描述应该包含四个部分功能说明、使用场景、参数说明、返回说明。功能说明要一句话讲清楚这个工具做什么使用场景要说明什么情况下该用这个工具什么情况下不该用参数说明要写清楚每个参数的类型、含义、是否必填、取值范围返回说明要描述返回的数据结构方便模型理解结果。举个例子一个查询订单的工具描述不该是查询订单而应该是这样的{ name: query_order, description: 根据订单号或用户ID查询订单详情。当用户询问订单状态、物流信息、订单金额时使用此工具。注意此工具只能查询不能修改订单。, parameters: { order_id: { type: string, description: 订单号18位数字字符串。与user_id二选一优先使用order_id。, required: false }, user_id: { type: string, description: 用户ID。当用户没有提供订单号时使用返回该用户最近的订单列表。, required: false } } }这个描述里只能查询不能修改这句话很关键它防止了模型在用户说帮我改一下订单时错误地调用这个工具。参数说明里的二选一优先使用order_id也是实战中总结出来的——不写清楚模型有时候会两个都填有时候该填一个却填了另一个。4.2 参数校验与容错处理模型生成的参数不一定靠谱类型错了、格式不对、必填项漏了这些都很常见。所以工具调用之前必须做参数校验而且校验失败时的处理方式很讲究。我的做法是三层校验第一层是结构校验检查必填项在不在、类型对不对第二层是格式校验比如订单号是不是18位数字、日期是不是合法格式第三层是业务校验比如这个用户有没有权限查这个订单。三层校验都过了才真正执行工具。校验失败时不要直接报错给用户而是把错误信息返回给模型让它重新生成参数。比如模型传了个2024-13-45这种非法日期我就返回日期格式错误应为YYYY-MM-DD格式且月份在1-12之间模型看到这个提示通常会重新生成一个合法的日期。这个重试机制我设置了最多3次3次还不行就放弃并告知用户。这里有个经验错误信息要具体不要笼统。说参数错误模型不知道错哪了说order_id应为18位数字字符串当前传入的是15位模型就知道怎么改了。我统计过错误信息写得具体之后模型一次重试成功率从大概六成提升到了九成。4.3 工具调用的超时与重试策略工具调用最怕的就是卡住。外部服务响应慢、网络抖动、对方服务挂了这些都会导致工具调用迟迟不返回。如果不设超时整个Agent就卡在那里用户等半天没反应。我的超时策略是分级超时查询类工具超时设3秒写入类工具设5秒批量操作类设10秒。为什么写入类要比查询类长因为写入操作往往涉及事务中途中断可能导致数据不一致多给点时间让它完成更安全。批量操作更不用说了处理的数据量大时间自然长。超时之后要不要重试我的原则是查询类可以重试写入类谨慎重试。查询类工具是幂等的重试不会产生副作用所以超时后自动重试一次还不行就返回失败。写入类工具不幂等重试可能导致重复写入所以超时后不自动重试而是返回一个结果未知的状态让上层决定怎么处理。重试的间隔我用的是指数退避第一次等200毫秒第二次等400毫秒第三次等800毫秒。为什么不用固定间隔因为如果对方服务是真的挂了固定间隔重试只是徒增压力指数退避给了对方恢复的时间也避免了短时间内大量重试打垮对方。注意重试一定要设上限我见过有人写了个无限重试的逻辑结果对方服务挂了之后自己的Agent疯狂重试把线程池占满了整个服务雪崩。上限我一般设3次超过就放弃。5. MCP接入把外部能力标准化接进来的正确姿势5.1 MCP到底解决了什么问题在讲MCP之前先说说没有MCP的时候我们是怎么接外部能力的。每个外部服务都要写一套适配代码A服务用RESTB服务用gRPCC服务用WebSocket每个都要单独处理连接、认证、序列化、错误码。接十个服务就是十套代码维护成本极高而且每接一个新服务都要重新踩一遍坑。MCP的价值就是把这些差异标准化。它定义了一套统一的协议不管外部服务原本是什么形态只要包一层MCP适配对Agent来说就是统一的接口。这就像USB接口统一了各种外设的连接方式你不需要为每个设备准备不同的插口。具体来说MCP规定了工具发现、工具调用、资源访问、提示模板这几类标准能力。Agent通过MCP客户端连接MCP服务端服务端告诉客户端我有哪些工具、每个工具什么参数客户端调用时按标准格式发请求服务端按标准格式返回结果。整个过程Agent不需要知道底层是什么技术。5.2 MCP服务端的连接管理MCP服务端的连接管理是个容易被忽视但很重要的点。我一开始是每次调用都新建连接、用完就关简单是简单但连接建立的开销很大尤其是跨网络的连接一次握手可能就要几百毫秒。后来改成连接池维护一组长连接调用时从池里取一个用完还回去。连接池的大小根据并发量来定我一般设成预期并发数 × 1.5留点余量。连接空闲超过一定时间我设的是5分钟就关闭避免占用资源。连接的健康检查也很重要。长连接可能因为网络原因悄悄断开如果不检查调用时才发现断了就晚了。我的做法是定期发心跳每隔30秒发一个轻量的ping收不到pong就标记这个连接不可用从池里移除并新建一个。心跳间隔不能太短太短浪费资源也不能太长太长断开了发现不及时。30秒是我实测下来比较平衡的值。还有一个细节是连接的重连策略。连接断了之后不要立刻疯狂重连而是等一小段时间再试避免对方服务刚重启就被大量重连请求打垮。我用的是指数退避第一次等1秒第二次等2秒最多等到30秒。5.3 工具发现与动态注册机制MCP的一个强大之处是工具可以动态发现。Agent启动时不需要硬编码有哪些工具而是连接上MCP服务端后通过标准的list_tools请求获取工具列表。这样服务端新增工具时Agent不需要改代码、不需要重启下次请求就能发现新工具。这个机制听起来很美但实际用起来有个坑工具列表缓存。如果每次调用都去问服务端有哪些工具开销太大但如果缓存了不更新新增的工具就发现不了。我的做法是缓存加定期刷新启动时拉一次全量工具列表缓存起来之后每隔5分钟刷新一次。刷新时对比新旧列表有变化就更新缓存并记录日志。动态注册还有个安全考虑不是所有工具都该暴露给所有用户。有些工具涉及敏感操作只应该对特定权限的用户开放。我的做法是在MCP服务端做权限过滤根据连接时携带的身份信息只返回该身份有权限使用的工具。这样Agent拿到的工具列表本身就是过滤过的不需要在Agent侧再做权限判断。工具发现之后还要做工具名的冲突处理。不同MCP服务端可能有同名工具比如两个服务端都提供了search工具。我的做法是给工具名加服务端前缀变成serverA.search和serverB.search调用时按完整名调用。这个前缀在展示给模型时可以去掉但内部调用必须用完整名避免歧义。6. 记忆机制让智能体不再是金鱼脑6.1 记忆的分类短期、长期、工作记忆记忆不是单一的东西我把它分成三类每类的存储方式、生命周期、检索策略都不一样。短期记忆就是当前会话的上下文存在会话层里会话结束就释放。它的特点是读写频繁、生命周期短、容量有限。长期记忆是跨会话保留的信息比如用户的偏好、历史事实、重要结论存在向量库和关系库里生命周期长。工作记忆是当前任务执行过程中的临时状态比如正在处理一个退款请求已经查了订单下一步要查支付记录它介于短期和长期之间任务结束就清理。为什么要分这么细因为混在一起会导致检索效率低下。如果所有记忆都塞进一个库检索时既要匹配语义又要过滤时间还要判断类型查询会变得很慢。分开之后每类记忆用最适合的存储和检索方式效率高很多。举个例子用户说我上次说的那个偏好这明显是要检索长期记忆用户说刚才那个数据这是短期记忆用户说这个任务做到哪了这是工作记忆。三类记忆的检索路径完全不同分开处理才能准确命中。6.2 记忆的写入时机与去重策略记忆写入的时机很关键。写太频繁存储爆炸且检索噪声大写太少该记的没记住。我的策略是事件驱动加定期扫描。事件驱动是指当检测到值得记忆的事件时立即写入。什么算值得记忆我定义了几类用户明确表达的偏好我喜欢用表格展示、用户纠正Agent的错误不对应该是XX、任务的关键结论最终方案确定为A、用户的身份信息我是XX角色的用户。这几类信息一旦出现立即异步写入长期记忆。定期扫描是指每隔一段时间我设的是每小时扫描最近的会话记录用模型判断有没有遗漏的值得记忆的信息。这个机制是兜底防止事件驱动漏掉一些不那么明显但实际有用的信息。去重是写入时的另一个重点。用户可能反复说同一件事如果每次都写一条记忆库很快就充满了重复内容。我的去重策略是语义相似度加时间窗口新记忆写入前先检索最近一段时间比如7天内语义相似的记忆如果相似度超过阈值我设的是0.92就不新建而是更新已有记忆的时间戳和重要度。这个阈值不能设太低太低会把相关但不同的记忆误判为重复也不能太高太高去重效果差。0.92是我调了几次之后觉得比较合适的值。6.3 记忆检索的召回与重排记忆检索是决定Agent记性好不好的关键。检索做不好要么召回一堆无关的要么该召回的没召回。我的检索流程是两阶段召回加重排。召回阶段用向量相似度快速筛出一批候选比如top 50重排阶段用更精细的模型对候选打分选出最相关的几条比如top 5拼进上下文。召回阶段的关键是查询改写。用户的当前输入不一定直接适合做检索比如用户说还是按老规矩来这句话本身没有明确的检索关键词。我的做法是先用模型把当前输入改写成适合检索的查询比如改写成用户的偏好设置 默认选项 历史选择再用这个改写后的查询去检索。这个改写步骤让召回准确率提升了不少。重排阶段的打分维度我用了四个语义相关性、时间新鲜度、重要度、使用频率。语义相关性是基础时间新鲜度让近期记忆优先重要度是写入时打的标签使用频率是这条记忆被召回过的次数经常被用到的记忆说明它重要。四个维度加权求和权重我设的是0.5、0.2、0.2、0.1。这个权重不是拍脑袋定的是我用一批标注数据调出来的不同业务场景可能需要调整。提示记忆检索一定要设上限不能把所有相关记忆都塞进上下文。我一般最多召回5条每条限制在200字以内。塞太多不仅浪费token还会稀释真正重要的信息反而让模型抓不住重点。7. 四块能力的协同那些只有跑起来才会暴露的问题7.1 会话与记忆的边界什么该记什么不该记会话和记忆最容易混淆的地方是会话里的内容哪些该沉淀到记忆哪些聊完就忘。我一开始没想清楚这个问题把所有会话内容都往记忆里写结果记忆库迅速膨胀检索质量急剧下降。后来我定了几条规则。该记的用户明确表达的偏好和事实、用户对Agent的纠正、任务的最终结论、跨会话需要复用的信息。不该记的寒暄和闲聊、一次性的查询请求、工具调用的中间过程、已经被后续对话推翻的临时结论。判断标准其实就一条这条信息在下次会话里还有用吗。如果下次用户来这条信息能帮他更快得到答案那就该记如果只是这次对话的上下文下次完全用不上那就不该记。这个判断我一部分用规则做比如工具调用结果默认不记一部分用模型做让模型判断这条信息是否值得长期保留。还有个细节是记忆的时效性。有些记忆是有有效期的比如用户当前正在处理一个退款这个状态过几天就失效了。我的做法是给记忆加一个过期时间字段检索时过滤掉已过期的。过期时间可以显式设置也可以根据记忆类型给默认值比如偏好类默认永不过期状态类默认7天过期。7.2 工具调用结果要不要进记忆工具调用的结果要不要写进记忆这个问题我纠结了很久。写进去吧记忆库会被大量查询结果占满不写吧用户下次问上次查的那个数据就找不到了。最后的方案是分级处理。查询类工具的结果默认不写长期记忆但写工作记忆在当前任务内可复用。如果用户明确说记住这个结果或者这个结果被后续对话反复引用才升级写入长期记忆。写入类工具的结果比如订单已创建订单号XXX默认写入长期记忆因为这类结果通常有长期价值。这个分级策略的关键是升级机制。工作记忆里的内容不是永远待在工作记忆里如果它被多次引用或者被标记为重要就升级到长期记忆。我实现的方式是给工作记忆加一个引用计数器每次被召回就加一超过阈值我设的是3次就触发升级。这个机制让真正有用的信息自然沉淀下来没用的自然淘汰。7.3 MCP工具与本地工具的优先级当MCP工具和本地工具功能重叠时该优先用哪个比如本地有个查询工具MCP也提供了一个功能类似的查询工具。我的优先级规则是本地工具优先MCP工具兜底。为什么因为本地工具通常延迟更低、更可控、出问题好排查。MCP工具虽然标准化但多了一层网络通信延迟和不确定性都更高。只有当本地工具不可用或者MCP工具提供了本地没有的能力时才用MCP工具。但这个规则不是绝对的。有些场景下MCP工具反而更合适比如需要访问外部系统的数据本地工具根本拿不到那就只能用MCP。还有些场景下MCP工具更新更及时本地工具可能版本落后这时候也该用MCP。所以我在配置里留了开关可以针对每个工具单独设置优先级默认本地优先特殊情况手动调整。工具选择还有个降级策略。如果首选工具调用失败超时、报错自动尝试备选工具。比如本地查询工具超时了自动切到MCP的同类工具。这个降级让系统的鲁棒性提升了不少用户基本感知不到某个工具出了问题。8. 常见问题排查速查表实际跑起来之后问题基本都集中在这几类。我把它们整理成表格方便对照排查。问题现象可能原因排查方法解决方案智能体失忆不记得之前说过的话会话ID变了或会话过期检查请求里的会话ID是否一致检查Redis里会话是否还在确认客户端正确传递会话ID调整会话TTL工具调用参数总是错的工具描述不清晰检查工具描述是否包含参数类型、格式、示例补充详细的参数说明和示例记忆检索召回不相关的内容查询改写没做好或相似度阈值太低打印改写后的查询和召回结果人工判断相关性优化查询改写提示词调高相似度阈值MCP工具调用超时连接池耗尽或对方服务慢检查连接池使用率检查对方服务响应时间扩大连接池设置合理超时加降级上下文太长导致回复变慢截断策略没生效或阈值太大统计每次请求的上下文token数调整截断阈值启用摘要压缩并发请求导致状态覆盖缺少乐观锁检查是否有版本号校验加版本号乐观锁冲突时重试记忆库膨胀太快写入太频繁或去重没生效统计记忆增长速度检查去重逻辑收紧写入规则调低去重相似度阈值除了表格里的还有几个我踩过的坑值得单独说。第一个是时间戳的时区问题。我一开始用本地时间存时间戳结果服务器迁移到不同时区后记忆的时间排序全乱了。后来统一改成UTC时间戳显示时再转本地时区问题解决。第二个是向量维度的兼容性。换了嵌入模型之后向量维度变了旧记忆检索直接报错。解决办法是记忆里存模型标识检索时按模型分组换模型时旧记忆要么重新向量化要么标记为不可检索。第三个是工具名的特殊字符。有些MCP服务端的工具名带点号或横线直接拼进提示词里模型会解析错。我的做法是内部用完整名展示给模型时替换成下划线调用时再换回来。9. 一些参数调优的实战经验参数调优这块我踩的坑最多也最有心得。这里挑几个关键参数说说。会话TTL设多久合适。设太短用户聊到一半会话过期了设太长Redis里堆满僵尸会话。我的经验值是30分钟。为什么是30分钟因为大部分用户的对话间隔不会超过这个时间超过30分钟还没动静的基本就是走了。但这个值要根据业务调客服场景可能15分钟就够而需要长时间思考的场景可能要1小时。我一般会看会话的最后活跃时间分布找到那个大部分会话在这个时间后不再活跃的点。记忆检索的相似度阈值。这个值直接决定召回的记忆是精准但少还是宽泛但多。我试过0.7、0.8、0.85、0.9几个值最后定在0.82。0.7太宽召回一堆不相关的0.9太严该召回的召不回来。0.82是在一批测试数据上跑出来的平衡点。但这个值跟嵌入模型强相关换模型必须重新调。工具调用的最大重试次数。设1次容错不够设5次失败时用户等太久。我定的是3次配合指数退避总耗时控制在2秒以内。超过3次还失败说明问题不是偶发的重试也没用直接返回失败让上层处理。上下文的最大token数。这个值要留出足够的空间给模型生成回复。我的做法是模型上下文窗口的70%用于输入30%留给输出。比如模型窗口是8K那输入最多5.6K输出预留2.4K。这个比例不是死的如果任务需要长输出比如生成长文档就要调整比例。记忆写入的批量大小。异步写入记忆时是一条一条写还是攒一批一起写一条一条写实时性好但开销大攒批写开销小但延迟高。我用的是攒批加超时攒够10条或者超过5秒就写一次兼顾了效率和实时性。这些参数没有标准答案都是根据具体场景调出来的。我的建议是先把参数做成配置项跑一段时间收集数据再根据数据调。拍脑袋定的参数十有八九不合适。10. 写在最后这套体系后续还能怎么扩展这套四层能力体系跑了大半年整体是稳的但也不是没有可以继续优化的地方。我自己在琢磨的几个方向顺便分享一下。记忆的主动遗忘机制。现在的记忆只增不减虽然有过期时间但很多记忆其实早就没用了却还占着位置。我在想做一个主动遗忘机制根据记忆的被召回频率和最后使用时间来判断它是不是该淘汰了。长期不被召回的记忆要么降权要么直接清理。这个机制能让记忆库保持精简高效而不是越滚越大。工具调用的预测性预加载。现在工具调用是用户问了才调有延迟。如果能根据对话上下文预测用户接下来可能要调什么工具提前把结果准备好响应速度能再上一个台阶。比如用户问订单状态系统预测接下来可能要查物流提前把物流信息拉好。这个预测用模型做意图预判准确率够高的话体验提升很明显。MCP的多路复用。现在每个MCP服务端一个连接池服务端多了之后连接数很可观。如果能做多路复用一个连接跑多个服务端的请求资源占用能降不少。这个需要MCP协议层面的支持目前还在观望。会话的跨设备无缝迁移。现在虽然状态集中存储但设备切换时还是有点割裂感比如手机上没看完的内容电脑上要重新滚动。如果能做到打开电脑自动定位到手机上的位置体验会更好。这个更多是前端的事但后端要提供足够的同步支持。这套东西说到底核心就一句话把状态管好把边界划清把参数调对。听起来简单做起来全是细节。我这两个月踩的坑基本都在这篇里了。你要是也在做类似的东西希望这些经验能帮你省点时间。有说得不对的地方也欢迎一起讨论毕竟这块东西变化快谁也不敢说自己就是标准答案。