news 2026/10/19 2:36:25

大模型智能体五层架构设计:从能跑到稳定跑的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型智能体五层架构设计:从能跑到稳定跑的工程实践

接手OpenClaw这个小项目的时候,团队里已经换过三版设计。第一版什么逻辑都往主循环里塞,聊天、工具调用、状态管理全在一起,跑通demo容易,但一加新工具就崩;第二版把模型调度单独拆了出来,问题好了一些,可记忆和权限还是揉成一团;到了第三版,我们彻底按照五层架构来重新组织,才真正解决了稳定性和可扩展性的问题。这篇文章想把这套拆分思路完整记录下来,重点是每一层为什么存在、边界怎么划、落地时有哪些坑。面向的对象是做智能体应用、想从"能跑"跨到"能稳定跑"的开发者,也适合准备把Agent接入生产环境的架构师。

OpenClaw本质上是一个开源的多模态智能体运行框架,目标是让大模型不光会聊天,还能自主调用工具、查询记忆、执行任务。它用五层架构把整个运行时拆成接口层、记忆层、规划层、执行层和安全层。这个设计和很多Monolithic Agent项目的本质区别在于:每一层都是独立的模块,拥有自己的数据协议和生命周期。你不需要理解全部源码就能替换某一层,比如把默认的文件记忆换成数据库,把本地执行换成容器沙箱,都只需要改对应层的实现。

1. 为什么要把智能体拆成五层来设计

1.1 单一大模型的瓶颈与"分层的必要性"

先回到一个基本问题:为什么不能只靠一个大模型做完所有事?如果你只是写一个问答机器人,直接调模型API就够了,不需要五层架构。但一旦涉及自主操作——读取文件、调用API、写代码、操作浏览器——你就必须面对三个现实问题:上下文窗口有限、模型输出不可控、工具调用需要权限管理。

这三个问题互相纠缠。上下文窗口有限,意味着你不能把全部历史记录和工具说明都塞进Prompt;模型输出不可控,意味着你不能信任它一次性生成正确的工具调用序列;工具调用需要权限管理,意味着你必须在模型和执行环境之间加一道审查关卡。五层架构的每一层,本质上是在解决这三个问题中的一个或几个。分层不是形式主义,而是把复杂度切分成可以单独测试、单独优化的单元。

1.2 每一层的职责边界与依赖方向

OpenClaw五层架构的依赖方向是单向的:接口层在最外面,安全层在底部兜底。请求从接口层进入,经过记忆层补充上下文,到达规划层做决策,然后由执行层调用工具,整个过程被安全层监控和约束。反向的依赖几乎不存在,也就是说,执行层不需要知道用户是通过网页还是命令行发来的请求,规划层也不关心工具实际上跑在本地还是远程容器里。

这种单向依赖带来一个直接好处:层与层之间可以用接口协议取代函数调用。比如规划层给执行层传递的不是"调用某某函数"的命令,而是一份结构化的Action对象——包含工具名、参数、超时时间、重试策略。执行层拿到这个对象后,自己决定怎么调用、怎么处理异常。这样一来,规划层的输出可以被日志系统完整记录,被执行层安全校验,将来还可以接入更复杂的策略引擎。

1.3 五层模型的通用性与扩展点

如果你看过其他Agent框架,会发现很多产品也有类似的"五层"说法,只是叫法不同:有的叫Surface、Memory、Brain、Hands、Guardrail;有的叫入口、记忆、推理、工具、审计。名字不重要,重要的是职责划分是否清晰。OpenClaw的这套分层之所以值得参考,是因为它把最容易膨胀的"记忆"单独拆成了完整的一层,同时把安全从执行层里解放出来,单独做成横向贯穿的治理层。

扩展点也很明确。新增一种交互渠道,只需要在接口层加一个Adapter;换一个向量数据库,只需要改记忆层的存储驱动;接入新的工具集,只需要在工具注册表里声明Schema;调整模型行为,只需要修改规划层的Prompt模板和解析逻辑。每个扩展点都对应一个明确的文件目录和测试入口,这也是我后来向其他团队推荐这套架构的原因——它不是一张纸上的理论图,而是已经把扩展路径画好的工程蓝图。

2. 从 I/O 到记忆:最容易被低估的前两层

2.1 接层:统一事件建模与多端口接入

接口层听起来简单,不就是接收输入返回输出吗?实际远不止。OpenClaw在这层做了一个非常关键的设计:把所有输入统一建模为Event对象,而不是分别处理HTTP请求、命令行参数、消息平台回调。每个Event包含消息内容、来源渠道、用户ID、会话ID、时间戳,以及一组可选的元数据。这样一来,规划层看到的永远是同一套数据格式,不同渠道的差异被全部挡在接口层。

我们接入过一个类似微信的聊天平台,刚开始没有走统一事件建模的路子,直接在业务代码里判断消息来源,结果每条分支都要处理一遍平台特有的字段,代码里满是if else。后来改造成Event模型,新建渠道只需要实现一个转换函数,把平台消息映射成标准Event,剩下的流程完全复用。这个改造大概节省了三分之一的开发量。

接口层的另一个职责是输入校验和预处理。大模型对输入长度和格式有要求,接口层会根据目标模型做截断、转义和压缩。图片、语音这类多模态输入,也会在接口层做统一编码。这里要特别注意:不要把业务逻辑写进接口层。接口层一旦膨胀,后续所有渠道的维护成本都会跟着涨。

2.2 记忆层:短期会话与长期知识库的存取

记忆层是很多Agent项目最容易做砸的地方。一开始大家觉得记忆就是聊天记录,把对话历史往数组里一存,下次拼到Prompt里就完事。但实际跑起来就会遇到三个问题:上下文窗口放不下、重要信息被淹没、不同会话之间的知识无法复用。

OpenClaw的记忆层把记忆分成三档:短期会话记忆、工作记忆、长期记忆。短期会话记忆保存最近几轮完整对话,直接放进Prompt;工作记忆保存当前任务的关键变量,比如用户正在填的表单数据、正在调试的代码片段;长期记忆存储跨会话的知识和偏好,存放在向量数据库中,按需检索。

这三档的区别直接体现在数据生命周期上。短期会话记忆在会话结束后就可以丢弃;工作记忆在任务完成或中断时需要做持久化和恢复;长期记忆则需要做去重、摘要和权限隔离。我见过一些项目把三档混在一张表里,结果检索到的往往是过期状态或者别的用户的信息,非常危险。

2.3 关键参数与配置示例

记忆层有几个参数直接影响效果。以向量检索为例,top_k决定每次取回多少条相关记忆,我们实践下来一般设在3到5之间;score_threshold是相似度阈值,低于这个分数的记忆宁可不用也不要强塞进上下文,因为它们会干扰模型判断。会话过期时间建议设置为30分钟无活跃就归档,工作记忆的保留时间则应该跟着任务状态走。

下面是一份简化版配置,用来说明记忆层的开关和参数:

memory: short_term: max_rounds: 10 max_chars: 8000 working: store: sqlite auto_restore: true key_fields: ["task_id", "session_id"] long_term: store: vector_store top_k: 5 score_threshold: 0.72 embedding_model: "default-embedding-model" ttl_days: 90

这里有个容易被忽略的点:短期会话记忆的max_chars要按编码后的长度估算,而不是按读者看到的字符数。中文在多数Tokenizer里的占用比英文更高,如果按2000个中文字符去设上限,可能实际占掉3000多个Token,导致Prompt溢出。我们第一次上线就吃过这个亏,模型在长对话场景下频繁截断,排查了半天才发现是编码长度估算不准。

3. 规划层:从意图识别到步骤拆解的落地写法

3.1 任务分解与工具路由的核心流程

规划层是整个五层架构里最像"大脑"的部分。它接收经过记忆层增强的上下文,输出一步一步的行动计划。OpenClaw在这层采用了一种类似ReAct模式的循环:先根据上下文判断当前意图,然后决定调用哪个工具、传什么参数,观察结果后再决定下一步。每一步都记录在案,方便后续审计和调试。

任务分解的实现方式通常会经历三个阶段。最初版本直接用JSON Schema约束模型输出,让它一次性返回完整的步骤数组,结果发现步骤一旦超过三步,模型就开始胡编。后来改成每次只输出一步,由运行时循环驱动,稳定性提高不少。再后来加了"计划复用"机制——如果当前任务和之前某个已成功执行的任务结构相似,直接沿用旧计划,省去重复推理。

工具路由的核心不是让模型直接记住每个工具的参数,而是给模型一份工具目录,目录里包含工具名称、用途描述、参数Schema、返回值说明。模型只负责在这份目录里选。这里的关键是工具描述的质量:描述太模糊,模型会选错;描述太啰嗦,占用上下文;描述里有歧义,模型会在两个相似工具之间摇摆。一个实用技巧是给每个工具写三行以内的描述,第一行说清楚功能,第二行给出典型使用场景,第三行标注和其他工具的边界。

3.2 对话历史压缩与上下文窗口管理

上下文管理做得好不好,决定了一个Agent能不能在长时间任务中保持稳定。OpenClaw的规划层设计了一套分层压缩策略:对话超过一定阈值就把早期的完整消息改写成摘要,最近的消息保留原始内容;对工具调用记录也做类似处理,历史调用步骤压缩成"已完成步骤列表",当前步骤保留完整细节。

压缩策略的触发时机需要谨慎。压缩太频繁会丢失细节,太晚又会溢出上下文。我们采用的判断标准是"剩余Token数与当前步骤所需Token数的比例",当剩余Token不足当前任务预估消耗的两倍时,启动压缩。预估方法也很简单:用上一次工具调用的输入输出长度做指数移动平均,得到一个动态估值。

这个策略里还藏着一个很实际的经验:摘要操作最好由同型号模型执行,而不要用小模型。因为摘要的质量直接决定了后续规划的质量,省这点Token成本会得不偿失。我们曾经为了省钱,用了一个轻量模型做摘要,结果它在压缩时把关键指令丢掉了,模型在后续执行中反复追问用户同一件事,体验非常糟糕。

3.3 一个可参考的Prompt模板与后处理逻辑

规划层的Prompt设计要兼顾稳定性和可扩展性。下面是一个简化后的规划提示词结构:

你是一个任务规划引擎。你现在位于一个智能体系统的规划层。 可用工具列表: {{tools_schema}} 当前任务上下文: {{recent_messages}} 检索到的长期记忆: {{retrieved_memories}} 历史完成步骤: {{executed_steps}} 请输出下一步行动,严格使用JSON格式: {"reason": "你的判断依据,50字以内", "action": "工具名或reply", "params": {...}}

POST处理逻辑至少要包含四步:先把模型输出解析成JSON,如果解析失败则做模糊纠错——比如把单引号替换成双引号,把尾部的多余逗号删掉;然后校验action是否在工具目录中,不在则拒绝并让模型重新输出;接着校验params是否符合工具Schema,不符合则按Schema做类型强制转换;最后检查该工具是否在当前会话的权限白名单内。四步走完,才把Action交给执行层。

这套后处理逻辑让我明白了一个道理:规划层输出的质量不仅取决于模型能力,还取决于你如何对待模型输出。把模型当成不可靠的合作者,每步都做校验和纠正,整体稳定性才能上去。相反,如果默认模型输出总是正确,总有一天会栽跟头——比如模型突然在params里塞了一个非预期的字段,如果执行层不校验,轻则报错,重则产生副作用。

4. 执行层与安全层:工具调用的稳定性与边界控制

4.1 工具注册与执行沙箱

执行层负责把规划层给出的Action变成真实的效果。它最核心的机制是工具注册表。每个工具在注册时要声明名称、描述、参数Schema、执行函数、超时时间、幂等策略。这六个信息缺一不可,但很多开发者会在前两个上面认真填写,后面四个就随意了。实际上超时和幂等才是执行层稳定性的关键。

超时设置要分工具类型。读类操作可以宽松一点,比如10秒;写类操作要严格控制,比如3到5秒;长时间运行的异步任务则不走同步等待,而是立即返回一个任务ID,由状态查询工具轮询结果。我们第一次接入浏览器自动化工具体系时,没设超时,导致一个元素等待操作卡了整整两分钟,整个Agent像死掉一样。从那之后,所有执行调用都强制带超时参数。

沙箱机制解决的是工具执行环境的安全隔离问题。OpenClaw默认支持三种沙箱:进程级沙箱、容器沙箱、远程沙箱。进程级沙箱适合信任度高的本地工具;容器沙箱适合运行不可信代码,比如用户上传的脚本;远程沙箱适合需要特定环境的工具,比如跨平台测试。选择策略不是越隔离越好,因为沙箱层级越高、性能损耗越大,可用的系统能力也越少。一个可参考的默认方案:开发环境用进程沙箱以便调试,生产环境用容器沙箱保证安全。

4.2 错误重试与幂等设计

执行层的错误处理直接决定了用户的感官体验。常见的错误模式有四种:网络超时、工具返回异常状态码、输出格式不符合预期、依赖服务未就绪。针对这四种模式,重试策略各不相同。网络超时适合指数退避重试,最多三次;异常状态码需要先查业务含义,有些错误码重试无意义;输出格式不符合预期时,可以尝试对返回结果做一次转换,而不是重跑工具;依赖服务未就绪则要快速失败,通知上层切换备用方案。

幂等设计是所有写操作工具必须要做的功课。所谓幂等,就是同一个请求执行多次,结果和只执行一次完全相同。举一个实际的例子:如果Agent负责创建订单,一次网络超时后重试,可能会导致重复下单。解决办法是在客户端生成一个唯一的request_id,服务端保存已处理过的request_id集合,重复请求直接返回第一次的结果。OpenClaw框架提供了一种统一的幂等拦截机制,不用每个工具单独写一遍,只要在工具声明里把idempotent设置为true,并指定幂等键来源即可。

幂等配置示意 { "tool": "create_order", "idempotent": true, "idempotency_key": "params.order_id", "dedup_window_seconds": 3600 }

配置里有个细节容易被忽略:幂等键的取值不能只依靠模型输出,因为模型可能在重试时改变参数。最好在执行层对参数做一次规范化处理,将业务主键单独抽取出来作为幂等键来源。这个措施能避免大量重试导致的脏数据。

4.3 安全治理:权限收敛、审计日志与熔断机制

安全层不是挂在系统边缘的防火墙,而是贯穿每一条Action的横切关注点。OpenClaw里有一条完整的ActionLifecycle:Action从规划层产生后,先经过权限检查,再经过敏感操作确认,然后才进入执行队列;执行完的结果也要做输出过滤,防止工具返回内容里夹带敏感信息进入模型上下文。

权限收敛的思路参考了最小权限原则。每个Agent实例启动时分配一个角色,角色绑定一组工具权限列表。例如处理日常问答的Agent不需要拥有文件删除权限,涉及资金操作的Agent必须开启双重确认。这里有个很实用的机制——敏感操作确认回调:当规划层发起一个风险等级较高的Action时,安全层会拦截该系统消息,要求用户输入确认码,确认通过后才放行到执行层。

审计日志则要记录完整链路:用户请求、原始输入、处理后上下文、规划层决策、工具调用参数、工具返回结果、重试记录、最终输出。一条日志记录看似简单,但真正做到完整采集并不容易,尤其是工具返回的大体积二进制数据,需要做摘要存储,原始内容放到对象存储。审计日志不仅是事后追溯的依据,也是调试的宝贵素材。我们排查过几次疑难问题,最后都是靠审计日志还原出了模型在规划层产生的幻觉参数。

熔断机制是保护系统的最后一道闸门。当某个工具在短时间内连续失败超过阈值,安全层会自动断开该工具的调用通道,并返回一个优雅的错误信息给规划层,让模型走备用路径。比如网页抓取工具连续超时,熔断后系统自动切换为搜索缓存工具。阈值不是拍脑袋定的,要根据工具的平均耗时和失败率动态调整。记住:熔断的目标不是零错误,而是把故障控制在一个相对稳定的范围,保证Agent整体不出症状。

5. 常见问题与排查实录

5.1 症状一:模型反复调用同一个工具导致死循环

这是Agent跑起来之后最常见的故障。表面现象是日志里全是同一个工具的调用记录,参数一模一样,执行层一次次重试,系统像卡死一样。排查思路第一步不是查模型,而是查工具本身:如果工具返回的是错误且不明确的状态码,模型没有足够信息判断失败原因,就会反复重试同一动作。第二步查配置:死循环往往是因为重试次数设置过大,或者超时设置过短导致快速失败又快速重试。

解决方案通常分三层。第一层,工具的错误信息必须结构化成机器可读格式,让模型能清晰看到失败类型;第二层,规划层要记录每个工具已尝试次数,超过上限时强制切换策略;第三层,安全层要设全局环检测,检测到同一Action连续出现N次且参数相同,直接阻断并通知用户。三层都做了,死循环基本可以根除。

5.2 症状二:记忆文件越来越大,上下文被撑爆

长期记忆存储设计不好的话,词条会越攒越多,检索噪音越来越大。我们遇到过一种情况:向量数据库里的记忆片段大量重复,一个用户的一次对话被拆成了上百个碎片,每次检索都会返回一堆内容几乎一样的条目。这不仅浪费Token,还让模型误以为用户反复强调同一件事。

解决办法是给记忆写入加"合并"步骤:写入新记忆前,先做一次相似度检索,如果找到相似度高于某个阈值的已有条目,就采用合并逻辑而不是新增条目。合并策略可以简单粗暴——两个文件内容相似度超过0.9,则保留时间戳新的,删除旧的;相似度在0.7到0.9之间,则拼接并做一次摘要。另外,建议定期跑一次离线清理任务,将旧记忆做二次摘要和分层归档,保证在线检索量的可控性。

5.3 症状三:工具返回结果格式错乱,无法解析

外部服务的返回格式不会总遵循你的预期。真实项目里碰到过的情况包括:API返回了额外字段、字段类型从string变成了object、编码从UTF-8变成GBK、甚至整个返回体被包装在一层CDN错误页里。很多开发者遇到这类问题,第一反应是改解析代码,但改完一次又发现另一种格式问题,陷入打地鼠模式。

更好的做法是引入两层解析架构。第一层做通用清洗:检测编码、去HTML包裹、去除CDN错误页、提取JSON体;第二层做Schema校验和字段映射,使用JSON Schema校验通过后,再把字段映射到系统内部标准结构。如果校验失败,不要直接报错,而是把原始内容和Schema错误一起丢给规划层,让模型尝试调整参数或换工具。这样等于把格式解析的兜底能力赋予了模型本身,而不是靠写死逻辑维护每一个工具的返回格式。

5.4 症状四:环境变量泄漏导致误操作

执行层的沙箱隔离再好,也架不住配置文件里把密钥写错地方。我们有过一次事故排查经验:Agent执行一个shell工具时意外把用户环境变量打印进了日志,里面包含一组访问密钥。好在是测试环境,否则后果很严重。从那以后我们规定:所有敏感信息必须通过密钥管理服务注入到执行环境中,严禁用明文配置;日志输出前必须经过脱敏过滤器,把密钥匹配正则替换成掩码。

这类问题在排查时往往不会第一时间暴露,它有延迟性。更可怕的是,这些敏感信息一旦进入模型上下文,后续的规划决策都会受到污染。所以安全层的输出过滤不只是指用户可见输出,还包括进入模型上下文的一切内容。建议在接入新Agent实例时做一次敏感信息扫描,把Prompts和工具返回样例都跑一遍脱敏检查。

5.5 避坑清单:五个血泪教训

第一,不要为"看起来简单"就跳过接口层的统一事件建模,后续每加一个渠道都会加倍偿还技术债。第二,记忆层的三档分类要一开始就建好,等数据量大了再重构是痛苦的。第三,规划层的工具描述必须专人维护,让模型自己看文档是不靠谱的。第四,执行层所有工具链路都要做幂等验证,尤其是外呼第三方服务的工具,写操作必须带幂等键。第五,安全层的审计日志要从第一天就开启,而不是等到事故发生后。

这些教训听起来都不深奥,但每一个背后都有真实的线上事故。团队里一个新同事说过一句话让我印象深刻:"这套架构不是设计出来的,是踩坑踩出来的。"确实,五层架构的每一层划分,几乎都对应着之前某个踩坑后的反思。

打开OpenClaw的源码时,我特意先看了它的目录结构,发现模块划分和这套五层架构完全对应——每个层有一个主目录,各自包含README、接口定义、默认实现和测试用例。这种"架构即目录"的设计极大降低了新成员的上手成本。我自己在做另一个Agent项目时,也按这个思路把目录结构和架构文档一一对应起来,效果非常好。如果你正在设计或重构一个智能体应用,建议先从画清楚每一层的边界开始,再动代码,你会发现后续的扩展和维护都会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 2:36:09

SQL中LEFT JOIN IS NULL:反连接实现“查不存在”记录的经典写法

在SQL里写LEFT JOIN ... IS NULL,我第一次看到这个写法时也有点懵:LEFT JOIN不是要把左边表的数据全保留下来吗?后面再加个WHERE 右表字段 IS NULL,这不等于是把刚保留下来的数据又筛掉一部分吗?绕这么大一圈&#xff…

作者头像 李华
网站建设 2026/10/19 2:36:08

知识蒸馏原理与实战:从大模型到轻量模型的压缩指南

1. 项目概述:知识蒸馏到底是什么,为什么值得学做深度学习落地的朋友,这两年应该都有一个共同的感觉:模型越来越大,效果越来越好,但是上线的门槛也越来越高。一张卡塞不下,推理延迟压不下去&…

作者头像 李华
网站建设 2026/10/19 2:36:01

OpenClaw在CentOS 7虚拟机上的编译安装与排坑指南

写这篇记录之前,先交代一个背景:我折腾的是 OpenClaw 这个开源项目。它本质上是上世纪九十年代末那款经典横版动作闯关游戏的独立重实现引擎,玩家圈子里也常有人叫它“龙虾”。简单说,OpenClaw 本身并不包含任何游戏内容&#xff…

作者头像 李华
网站建设 2026/10/19 2:35:54

35:求出e的值

/*** 题目名称&#xff1a;求出e的值 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0105/35/ <p>* 程序功能&#xff1a;输出e的值** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import java.util.Scanner;public class Main {/*** 返回…

作者头像 李华
网站建设 2026/10/19 2:35:27

本地化虚拟人直播技术栈:文本到B站推流全链路实践

简介&#xff1a;VirtualWife是一个开源的虚拟数字人项目&#xff0c;面向AI开发者、直播技术实践者及AIGC应用探索者&#xff0c;旨在降低B站虚拟直播的技术门槛&#xff0c;提供可快速部署、可定制的本地化数字人交互方案。资源包共246个文件&#xff0c;涵盖97个Python核心逻…

作者头像 李华
网站建设 2026/10/19 2:35:00

RV1126B边缘盒子上的微小目标检测与跟踪实战:YOLOv8+DNTR架构解析

去年手头接了个有点折磨人的活&#xff1a;户外监控场景&#xff0c;要盯住画面里经常只有十几甚至几个像素大小的运动目标&#xff0c;还得24小时连续跑。设备端没有GPU可用&#xff0c;散热和功耗卡得死死的&#xff0c;最初尝试用纯检测方案做一帧一帧框选&#xff0c;结果目…

作者头像 李华