从聊天助手到Agent:架构转型的关键决策与踩坑实践 最近两个月我一直在重构 OpenClaw旧代号 Clawdbot/Moltbot这套系统越改越清楚地意识到一个不太舒服的事实从聊天助手到自主智能体Agent的转型真正难的地方不在于把底座模型从 A 换成 B而在于整套系统要从一个回答问题的闭环改造成一个完成任务的闭环。用户问一句帮我把下周的会都安排一下顺便订个会议室聊天助手只能回一句好的我帮你记录下来了而 Agent 得能自己去翻日历、找空闲时间、发起预订、处理冲突最后把结果汇报给用户。这中间的架构差距不是调一调 prompt 就能抹平的。这篇文章就把我这几个月在 OpenClaw 转型过程中的关键架构决策、工具层设计思路、记忆系统取舍以及若干踩得比较深的坑原原本本记录下来。如果你也在做类似的 Agent 化改造或者准备把一个已有的对话机器人升级成会干活的智能体这些经验应该能帮你少走不少弯路。1. 从对话闭环到任务闭环转型的第一道坎1.1 聊天助手的能力天花板聊天助手的运行逻辑在架构上其实非常简单用户发一段文本进来系统把它加上历史上下文拼成 prompt喂给模型模型返回一段文本系统把文本展示出去。这个循环从第一代聊天机器人到现在的大型语言模型产品本质没有变过只是在输入输出的质量和上下文长度上不断优化。问题恰恰出在这个本质没变过上。在这个架构里模型是唯一的信息入口也是唯一的信息出口它既看不到执行结果也无法验证自己说的内容是否真实生效。用户问我的支付配置哪里有问题聊天助手只能基于训练时的记忆给出一个泛泛的排查建议它没法真的去拉取配置、试跑一遍流程、把异常日志读出来。当任务复杂度上升到需要先调查、再执行、后汇报的层次时这种单轮问答的天花板就会非常明显。我在给 OpenClaw 做转型前评估时列过一个很粗的用户任务清单发现大约有超过一半的常见需求本质上不是需要一句话回答而是需要一串动作最终产出一个结果。比如订机票、对比两份合同的差异、监控某个服务的日志并按条件报警、批量整理某一批文件。这些任务的共同特征是需要调用外部系统需要根据中间结果做决策并且执行链路可能很长。1.2 Agent 至少要多出四样东西把聊天助手升级成 Agent我理解下来至少要补四样架构组件一是工具调用能力让模型能触发外部操作二是多步决策循环让系统能根据执行结果决定下一步动作三是记忆与状态管理让任务在执行过程中和跨会话后都能保持上下文四是安全与熔断机制防止模型在无人监管的情况下做出不可撤销的操作。这四样东西不是可选项而是缺一不可。工具调用让 Agent 长出手多步决策循环让 Agent 长出脑子记忆与状态让 Agent 长出工作台安全熔断让 Agent 长出缰绳。聊天助手阶段可能只需要把手部能力做成一个粗糙的插件形态到了 Agent 阶段它们必须成为系统的主干。下面这张对比表可以比较直观地看出差距维度聊天助手架构Agent 架构核心控制流单次请求-响应多步循环思考-行动-观察信息获取完全依赖模型内置知识可通过工具实时获取外部数据状态管理无状态或简单会话缓存需要持久化任务状态与会话记忆失败处理重新生成一句话应答重试、切换策略、上报异常评估标准回答内容是否准确、连贯任务是否完成、步骤是否高效这张表也解释了为什么很多人直接给聊天助手接入了一个工具调用的 API就以为完成了 Agent 化转身结果跑起来发现效果非常差工具调用只是手循环、记忆和熔断这些骨架还缺着呢。2. 重构决策回路让模型从一次性回答变成多步行动2.1 把思考-行动-观察循环放到模型外面OpenClaw 在改造过程中最先动刀的就是这个决策回路。原来的代码逻辑是拿到用户输入后拼好 prompt 丢给模型模型生成好就直接返回。改造后的逻辑是把模型的单次输出当成多步循环中的一个中间决策而不是最后回答。我现在实现的循环骨架简化后大概是这样的state { task: user_task, history: [], step: 0, max_steps: 12, } while state[step] max_steps: state[step] 1 response llm_agent_step(state) if response[type] final_answer: return response[content] if response[type] tool_call: observation execute_tool(response[tool_name], response[arguments]) state[history].append({ action: response, observation: observation, }) continue if response[type] need_more_info: return ask_user(clarification_prompt) raise MaxStepsExceeded(state)有一个非常重要的设计决策这个循环必须放在框架层而不是让模型自己循环。我的意思是不要让模型在单次生成里自己调用自己或者试图让它用一个很大的上下文把多步推理一口气完成。那样做一方面消耗的 token 量非常惊人另一方面模型很容易在超过一定轮次后开始逻辑混乱甚至自顾自地编造中间结论。放在框架层的好处是可以单步调试、可以设置硬性的步数上限也可以在每一步之间插入安全检查、日志记录和人工干预钩子。我在本地调试 OpenClaw 时经常会把循环的每一步输出都打印出来看模型在哪个环节开始走偏。如果这个循环被塞进模型内部这种可观测性就完全谈不上了。2.2 步数预算、超时与收敛判断引入循环之后第一个要面对的问题就是什么时候算完模型可能给出 final answer 结束也可能一直调工具、一直观察结果陷入一种假性忙碌状态。所以在循环设计中必须有硬性的预算机制。我目前的参数组合是这样的单任务最大步数max_steps 12单次工具调用超时tool_timeout 30s整条任务链路超时task_timeout 180s同时要求模型在每次工具结果返回后必须更新当前进度小结。步数上限不能设得太大。最开始我把max_steps设为 30想着多给模型一些发挥空间结果副作用非常明显模型会下意识地把一个简单任务拆成大量无意义的中间动作比如查三次天气、把同一个文件反复读取两遍。步数上限收紧到 12 之后模型反而学会了在有限的行动次数内规划更紧凑的执行路径。收敛判断也要做得多层。一个是模型显式输出 final answer另一个是连续 N 步内没有产生新的有效信息我的做法是检测同一工具 相同参数是否连续出现两次以上如果是直接判定为无效循环并触发中断。这个规则在早期救了我很多次模型的类人化重复行为比想象中更常见。2.3 循环的三种退出路径循环不能只有成功完成这一条路径至少要设计三种退出方式正常收敛、异常中断、需要用户澄清。正常收敛就是模型给出 final answer并且这个 answer 引用了足够的执行依据。异常中断包含超过步数上限、工具连续报错、检测到疑似死循环等这时候系统不能把半截结果直接丢给用户而是要做一次阶段性汇报告诉用户任务进行了哪几步、卡在哪一步、可能需要什么帮助。需要用户澄清这条路径很容易被忽略。Agent 在任务早期经常会遇到指令不明确的情况比如帮我订个会议室——订哪个时间的多少人在哪层与其让模型猜一个然后执行不如让它及时停下来问一句。我给 OpenClaw 设计了一个特殊出口need_more_info当模型对关键执行参数置信度不足时可以中断循环向用户提问而不是强行继续。这个设计看似简单实际对用户体验的提升非常明显。3. 工具层升级Agent 的手和边界3.1 工具契约描述、参数与幂等性聊天助手阶段我见过很多粗放的工具接入方式写一个函数在 prompt 里用一两句话描述然后让模型自己猜参数。这种玩法在演示时很酷但一旦进入 Agent 的严肃任务场景就会频繁出问题——模型调工具的参数格式不稳定有时缺字段有时类型错误有时传了根本不存在的参数名。OpenClaw 的做法是把每个工具定义成严格的契约。每个工具包含清晰的行为描述、JSON Schema 参数定义、必要的幂等性说明。以会议室预订工具为例Schema 大概是这样的{ name: book_meeting_room, description: 预订一间会议室。仅在用户明确需要线下会议室时使用如果用户只是提到线上会议不要调用此工具。, parameters: { type: object, properties: { room_id: { type: string, description: 会议室ID }, start_time: { type: string, format: date-time }, end_time: { type: string, format: date-time }, attendees: { type: array, items: { type: string }, description: 参会人邮箱列表可为空 } }, required: [room_id, start_time, end_time] } }这里有一个细节非常关键工具的 description 不只是给人类看的说明更是给模型看的分类器。我在book_meeting_room的描述里专门写了仅在用户明确需要线下会议室时使用如果只是提到线上会议不要调用这个看似多余的半句话直接把误调用率降低了一个数量级。模型在判断该不该用这个工具时非常依赖 description 里的边界信息写得越精确越不容易乱来。幂等性也是容易踩坑的点。比如发送邮件这个工具如果模型在超时重试时把同一封邮件发出去了两次用户就会收到两封完全相同的邮件体验极差。所以我会在工具设计阶段要求凡是会造成外部副作用的工具要么实现幂等键要么把重复调用提醒暴露在返回结果中让模型感知到重复执行的风险。3.2 工具结果回传截断、摘要与错误传递工具执行完结果如何回到模型手里是一个看起来普通但影响极大的问题。我在早期直接把工具返回的原始文本全量塞进上下文很快就发现两个毛病第一工具结果太长分分钟把上下文窗口撑爆第二模型看到过长文本时注意力会被无关细节带跑反而忽略了关键字段。现在的处理流程是工具结果先经一层标准化抽取关键信息按长度阈值做截断或摘要。比如日志查询工具返回 200 行日志我不会全量送回给模型而是先由框架做一个日志摘要提取出 ERROR、WARNING 级别的事件、时间分布、异常关键字再把摘要送给模型。这个摘要工作看起来简单但等于替模型做了一轮预处理模型后续的决策质量会有明显提升。错误传递同样重要。工具执行失败时返回的错误信息不能是干巴巴的一句调用失败而要尽量带上失败原因和可操作的建议。比如会议室预订失败该时段已被占用建议查询同楼层其他可用会议室。模型拿到这样的信息后可以自主切换策略去查另外的会议室而不是直接向用户汇报失败。3.3 危险操作熔断与双轨权限Agent 一旦真的开始操作外部系统安全就不仅仅是模型问题了而是工程问题。OpenClaw 在工具层做了一个简单的三级权限分级只读类工具查询日历、查日志、搜索文档直接放行普通写操作创建草稿、发送普通消息需要用户一次性确认高危操作删除资源、对外群发、修改配置不仅需要确认还必须在工具内再做一次参数复核。这些确认不是靠模型自己去判断的而是框架层硬编码的。模型可以提出我想删除这个文件的行动计划但执行器会拦截它返回需要用户确认的结果由用户在界面上点确认后再真正执行。遇到对话自主循环时这个机制能够有效避免模型在无人值守状态下做出不可逆操作。还没做但在规划中的是工具调用频率限制比如单个任务中调用邮件发送工具的上限、单次请求内查询外部 API 的次数。对 Agent 这种自动行动系统来说没有频率限制意味着任何一次模型误判都可能造成批量副作用熔断机制越早设计越好。4. 记忆分层与状态持久化从无状态走向可恢复4.1 上下文窗口不是记忆这是我在跟不少同行交流时发现的一个普遍误区很多人觉得上下文窗口已经这么大了记忆问题应该不存在了吧。实际上上下文窗口只是给模型看的临时工作区任务一结束、窗口一清空所有中间信息就都没了。用户上个月提到过的偏好模型不会记得昨晚执行到一半的批量任务服务重启后也找不回来。真正的记忆需要分三层来设计会话内工作记忆、长期事实记忆、任务状态持久化。会话内工作记忆就是模型当前回合能看到的东西包括对话历史、工具调用记录和结果摘要这部分在上下文窗口里长期事实记忆则要落到外部存储中比如用户偏好、项目背景、历史决策记录任务状态持久化解决的是系统崩溃、进程重启后的恢复问题。OpenClaw 目前使用的是三层分离的方案上下文窗口里只放当前任务必需的信息用户的长期偏好写入结构化存储任务执行中间态定期快照到本地数据库。这套设计的核心原则是记得住的不一定都要带到上下文里只有对当前决策有直接帮助的信息才值得占用窗口资源。4.2 会话内压缩与长期记忆的取舍会话内压缩是每一轮对话都在发生的隐性操作。当对话轮次多起来后我会定期触发一个摘要回写流程把较早轮次的对话内容压缩成一段结构化摘要替换掉原始内容只保留最新的几轮完整对话。这样既能让模型保持对上下文的整体把握又不会让窗口容量被旧对话占满。这个摘要不是一次性做就完了而是每达到一个阈值就重新压缩一轮。比如每超过 8 轮对话就把前 6 轮压缩成概括文本保留最近 2 轮完整内容。压出来的摘要会继续参与后续决策后续新产生的对话再追加到它后面。这个思路类似给模型递上一页不断更新的会议纪要而不是让它每次从头读一遍全部聊天记录。长期事实记忆这块我试过向量数据库方案也试过结构化字段方案最后对于大多数场景选的是结构化存储加全文检索而不是纯向量检索。原因是用户偏好这种东西通常是明确的、低维度的比如不喜欢收到促销邮件会议纪要抄送给经理这种信息最佳实践是抽成结构化标签存下来而不是扔进向量库里靠相似度碰运气。向量检索适合语义模糊、无法枚举的开放信息比如用户上个月提过的一个重要需求是什么这时候全文检索配合关键词可能还不够需要向量召回。但作为第一版我优先保证确定性结构化管理用户显式表达的偏好向量库留给开放信息检索引擎。4.3 任务状态的持久化与恢复聊天助手阶段的服务进程重启就直接丢会话这在 Agent 阶段是不可接受的。OpenClaw 现在的做法是把每个任务的状态定义为一个可序列化的数据结构包括当前意图、已完成步骤、剩余计划、已获得的工具结果摘要、当前阻塞点。每一步循环后框架会自动保存这个状态到本地存储类似一个简易的断点续传机制。状态恢复的代码思路并不是直接把旧上下文塞回去而是重新构造一个新会话把目标任务、进度小结、下一步计划交给模型继续执行。因为模型每次启动时可以看到这是一个已经进行到 X 步骤的任务已完成如下操作下一步需要去做 Y这就足够了。从这一点来看任务状态持久化不是简单的快照而是一种日志回放思路的重建可以让 Agent 在任意一次中断后重新获得执行上下文。5. 转型期踩过的坑排查链路与解决方式5.1 工具误调用模型乱伸手怎么治第一个常见的坑是模型在没有必要的情况下调用工具。我遇到过用户只是在闲聊今天天气感觉不错模型直接跑去查了天气 API 并返回结果还有一种更隐蔽的误调用是模型把一个不太相关的工具强行绑定到任务上比如用户说帮我整理一下项目文档模型却去调了邮件发送工具试图顺便把整理的文档发出去。排查这类问题时我会去翻阅当次任务的完整日志重点看模型在调用工具前的内部推理片段。总结下来误调用通常有两种来源工具描述边界不清或者系统 prompt 中缺少约束。对策也分两层工具描述里加上不要用于...的负向约束同时在系统 prompt 里写明只有确认用户明确表达相关意图时才允许调用工具执行不确定时可以提问澄清而不是猜测执行。这两条叠加后误调用比例确实降了不少。5.2 死循环与重复动作如何打断僵局另一个让我相当头疼的坑是模型陷入重复动作的僵局。比较典型的场景是模型想查某个关键词的搜索结果第一次返回不够满意它就开始同样关键词换着角度反复搜索有时候连续五六步都在做同一件事既没有进展也没有换策略。这种情况在长链路任务中尤为常见。解决思路有三层检测层、干预层、预防层。检测层就是我在前面提到的同一工具 相同参数重复识别出现两次就判定异常。干预层是中断循环把你已经尝试过两次同样的搜索请基于已有信息换一种方式推进作为反馈塞回给模型。预防层是在工具调用时让模型附带本次调用预期达到的目标这相当于逼着模型在每个行动前先想清楚为什么做这一步能明显减少无意识的重复动作。5.3 上下文爆炸工具结果把窗口塞爆上下文爆炸这个问题在各方向都做对的情况下也依然可能发生。核心原因是工具结果的体量完全不受模型控制。一次数据库查询、一次接口拉取返回的数据量可能远超一次正常对话的长度。尤其是某些接口返回 JSON 嵌套非常深模型看这种内容特别吃力还会把它误认为代码然后开始分析括号平衡问题完全偏离了任务主线。后来的排查发现问题不在模型而是我把工具结果原封不动塞回了上下文。现在所有工具结果都要经过一个统一的处理管道标准化成纯文本、截断超长部分、抽取关键字段、必要时调用小结模型做摘要。另外还有一个非常实用的策略给中间结果和最终结果分开设置长度预算中间结果即使被截断也只影响单步决策不会污染整条任务。5.4 回归评估与灰度切换怎么证明它真的变强了转型期间最难回答的问题之一是我怎么知道新的 Agent 架构真的比旧的聊天助手更好我在项目里搭了一个非常简陋但足够反映问题的评估集包含三类任务能一句话回答的简单问答、需要一次工具调用的标准操作、需要多步决策的复合任务。每类任务准备几十条样本评分维度不只看最终答案还看步骤数、工具调用是否合理、错误恢复是否顺畅。这个评估集一开始很不完善后来每踩一个坑就往里加一类样本慢慢就比较能反映真实水平了。比如用户表达不完整时需要澄清这一条就是因为在测试中连续出现模型瞎猜参数的问题才加进去的。灰度切换我采用的是双轨并行策略简单问答继续走旧链路复合任务逐步切到新 Agent 链路用户侧无感知内部可以做 AB 对比。等新链路的任务完成率达到一个稳定水平后再扩大流量范围。我还发现一个很有意思的关联聊天助手阶段常用的流式输出体验到了 Agent 阶段反而要弱化。用户在 Agent 模式下更关心的是任务进行到哪一步了还需要等多久而不是字一个个蹦出来的感觉。所以 OpenClaw 的 Agent 界面把重点换成了步骤进度展示、工具调用卡片和阶段性小结产品形态也随之有了很多可以调整的空间。写到这里突然想到一个比较朴素的建议送给正在做同类改造的人转型过程中一定要给每次工具调用加一个 trace_id把模型决策、工具执行、结果回传串成一条完整的调用链。排查死循环也好复盘误调用也好有了这条链你会省掉大半的猜谜时间。我就是在把 trace_id 加上去之后很多原本玄学性质的问题第一次真正变得可追溯了。这一步的下一阶段我会继续补两个方向一是把工具调度的并发控制做起来让无依赖的多工具调用可以在单轮循环内并行执行缩短任务耗时另一个是完善用户主动中断 Agent 执行的交互让随时可停成为 Agent 系统的默认能力而不是事后补救。