Day 7 | OpenClaw 工程反思:构建 AI Agent 框架的难点与取舍
本文总结了构建AI Agent框架OpenClaw的核心挑战与经验教训。主要难点包括:LLM输出的不确定性导致传统测试方法失效,需采用行为约束测试;上下文管理需要分层设计而非简单堆积;网络不可靠性和工具执行的幂等性对可靠性提出高要求。设计上需要在简单与功能完备间平衡,采用"约定优于配置"原则。Prompt应被视为代码进行版本控制和DRY管理。作者反思应更早建立测试框架、减少配置
Day 7 | OpenClaw 工程反思:构建 AI Agent 框架的难点与取舍
系列:《从 0 到 1 拆解 AI Agent 框架:OpenClaw 技术深度解析》
前言
这是系列的最后一篇。
前六篇都在讲"是什么"和"怎么做",这篇想聊聊"为什么这样做"以及"做过后悔了什么"。
构建 AI Agent 框架是一件奇特的事——它比传统后端复杂,因为你的核心逻辑是一个不可预测的黑盒(LLM);它又比 ML 基础设施简单,因为你不需要训练模型,只需要调度和路由。
这种独特性带来了一些独特的工程挑战。本文是我对整个系列的总结与反思。
一、最难解决的问题:不确定性
传统软件工程有一个假设:给定相同的输入,系统产生相同的输出。
AI Agent 打破了这个假设。
1.1 LLM 的不确定性
即使 temperature=0,LLM 的输出也可能因为:
- 模型版本更新
- API 负载不同导致的精度差异
- 上下文微小变化(比如时间戳)
……而产生不同的结果。这意味着:
- 无法写传统的单元测试:“给定输入 X,断言输出 Y”
- Bug 难以复现:同样的步骤可能得到不同结果
- 回归测试代价极高:需要大量样本才能判断行为是否"变差"
我的应对方式:放弃对 LLM 输出的精确断言,改为测试行为约束:
- 不应该调用某些工具(安全约束)
- 应该在 N 步内完成任务(效率约束)
- 输出应该包含某些关键词(内容约束)
这不是完美的测试,但比没有测试好。
1.2 工具执行的副作用
工具调用会产生真实的副作用:文件被修改、消息被发送、代码被执行。这让测试和调试格外棘手。
我的应对方式:引入干运行(Dry Run)模式——工具调用只记录"打算做什么",而不真正执行。这让我可以在不破坏生产环境的情况下,验证 Agent 的行为轨迹。
二、最反直觉的设计:上下文管理
在开始构建之前,我以为上下文管理很简单:维护一个消息列表,每次请求都带上去就行。
现实证明这是最复杂的部分之一。
2.1 上下文窗口不是越大越好
理论上,上下文越长,模型能"记住"的信息越多。但实践中:
- 注意力稀释:上下文越长,模型越难关注到早期的重要信息
- 性能下降:超过某个长度,模型的指令遵循能力会变差
- 成本线性增长:token 越多,费用越高,延迟越大
我见过一个反直觉的现象:给模型 200K token 的上下文,它的表现反而比 20K token 时更差——它被淹没在信息海洋里,找不到重点。
教训:上下文管理的核心不是"装更多信息",而是"装最相关的信息"。
2.2 Memory 的层次设计
解决方案是分层记忆:
工作记忆(当前对话)
↓ 定期压缩
短期记忆(Session 历史摘要)
↓ 重要事项提炼
长期记忆(MEMORY.md 等持久化文件)
每一层有不同的容量、访问成本和衰减速度——这不是程序设计,这更像认知科学。
三、最易被忽视的挑战:可靠性
开发阶段,偶尔的错误可以接受。生产环境里,可靠性是头等大事。
3.1 网络是不可靠的
LLM API 调用通过网络,网络会:
- 超时
- 丢包
- 返回格式错误的响应
- 被速率限制(429)
每一种情况都需要妥善处理。
我的策略:
async function callLLMWithRetry(request: LLMRequest): Promise<LLMResponse> {
const maxAttempts = 3;
for (let i = 0; i < maxAttempts; i++) {
try {
return await llm.call(request);
} catch (err) {
if (err.status === 429) {
// 速率限制:等待后重试
await sleep(Math.pow(2, i) * 1000);
continue;
}
if (err.status >= 500) {
// 服务端错误:重试
await sleep(1000);
continue;
}
// 客户端错误(4xx):不重试,直接抛出
throw err;
}
}
throw new Error('Max retry attempts exceeded');
}
指数退避(Exponential Backoff)是处理速率限制的标准方案,很有效。
3.2 工具执行的幂等性
当工具执行结果还没返回给模型时,进程崩溃了怎么办?
重启后重试,但工具可能已经执行了一半——文件写了一半,消息发了一条……
我的策略:
- 纯读操作(read、web_search):天然幂等,随意重试
- 写操作:尽可能设计为幂等(写同一个文件多次,最终状态一样)
- 发消息:维护幂等键,防止重复发送
- 高风险操作(exec):执行前记录意图,执行后确认结果
幂等性是分布式系统的经典话题,在 AI Agent 里同样重要。
3.3 会话恢复
进程重启后,正在进行中的对话怎么办?
我的策略:
- 消息写入 JSONL 后才算"已处理"
- 未完成的工具调用通过状态机恢复
- 对用户透明:重启后发送"上次对话已恢复"的提示
这类似于数据库的 WAL(Write-Ahead Log)机制——先记录意图,再执行操作。
四、最难做对的取舍:简单 vs 功能完备
框架设计中最难的问题不是技术,而是范围。
4.1 每个功能都有理由存在
用户提了需求 A,于是加了功能 A。
用户提了需求 B,于是加了功能 B。
两个月后,框架变成了一个复杂的怪物,没有人能全部理解。
这是所有框架的宿命,但 AI Agent 框架尤其危险——因为 AI 能力的边界不清晰,"能加的功能"几乎是无限的。
我的原则:每个新功能都必须回答这个问题:如果没有这个功能,用户能否用现有机制实现同样的效果?
如果答案是"能,但麻烦一点"——不加。
如果答案是"不能"——才考虑加。
4.2 零依赖 vs 功能丰富
JSONL 文件存储 vs SQLite vs PostgreSQL——这个选择贯穿了整个架构。
我选择了最简单的方案:JSONL 文件。代价是:
- 不能做复杂查询
- 大文件读写性能差
- 没有事务支持
收益是:
- 零配置,开箱即用
- 文件可以直接
cat查看,极易调试 - 任何平台都能运行,包括树莓派
这个选择对个人开发者友好,对企业级部署不够好。这是一个明确的目标用户取舍,不是技术能力的限制。
4.3 配置 vs 约定
每个功能都面临这个取舍:让用户配置?还是约定默认行为?
比如 Session JSONL 文件的存储路径:
- 配置:用户可以指定任意路径,灵活但需要配置
- 约定:固定存在
~/.openclaw/sessions/,无需配置但不灵活
我倾向于约定优先,配置为辅:先提供合理的默认值,让 80% 的用户不需要任何配置;对需要自定义的 20% 用户,提供配置选项。
Rails 的"约定优于配置"哲学在 Agent 框架里同样适用。
五、最意外的发现:Prompt 是代码
我最初把系统提示词(System Prompt)当成"文档"——用自然语言描述 Agent 的行为。
随着开发深入,我意识到:Prompt 更像代码。
5.1 Prompt 的"语法错误"
传统代码有明确的语法错误。Prompt 的"语法错误"是:Agent 始终无法完成某类任务,但你不确定是 Prompt 写得不好,还是模型能力的边界。
调试 Prompt 比调试代码难得多——你无法设断点,无法追踪变量,只能通过观察输出猜测原因。
5.2 Prompt 需要版本控制
每次修改系统提示词,Agent 的行为都会变化——有时候变好,有时候某些原本能用的功能突然失效了。
教训:Prompt 改动要像代码改动一样:
- 用 Git 追踪每次变更
- 写清楚每次修改的原因
- 有重大改动时,在旧版本上跑一遍关键场景,再切换
5.3 Prompt 的 DRY 原则
系统提示词经常有重复内容:安全规则在 Agent 里写了一遍,又在工具描述里写了一遍,又在 SKILL.md 里写了一遍。
这违反了 DRY(Don’t Repeat Yourself)原则。重复的内容一旦需要修改,要同步修改多处,很容易遗漏。
我的解法:把通用规则提取到基础系统提示词,各 Agent 继承基础提示词,只覆盖差异部分——类似面向对象的继承机制。
六、对"AI Native"应用的思考
OpenClaw 的架构设计,本质上是在探索一个问题:怎么让 AI 真正融入软件系统,而不只是作为一个 API 调用?
6.1 AI 不是功能,是执行引擎
传统软件:业务逻辑 → 函数调用 → 结果
AI-Native 软件:目标描述 → AI 规划 → 工具调用 → 结果验证 → 迭代
这个范式转变的核心是:控制流从代码转移到了 AI。代码定义工具(能做什么),AI 决定流程(怎么做)。
这带来了前所未有的灵活性——AI 可以自己决定用几个工具、按什么顺序——但也带来了前所未有的不确定性。
6.2 观测性变得更重要
传统系统出了问题,看日志和 Stack Trace 就能定位。
AI Agent 出了问题,你需要知道:
- 模型看到了什么上下文
- 模型做了什么决策
- 决策依据是什么
- 哪一步出了问题
这需要专门的观测基础设施:记录每次 LLM 调用的完整输入输出、工具调用序列、Token 消耗……
OpenClaw 目前的观测能力还不够好。这是最需要改进的地方之一。
6.3 人机协作的边界
“完全自主的 AI Agent” 是个迷人但危险的目标。
完全自主意味着没有人类在关键决策点的介入,一旦 AI 走偏(hallucination、误解用户意图、遭遇注入攻击),后果可能很严重。
更务实的设计是可调节的自主程度:
- 低风险任务(读文件、搜索):完全自主
- 中风险任务(修改文件、执行命令):执行后汇报
- 高风险任务(发外部消息、删除数据):执行前确认
这不是技术限制,而是对人类控制权的主动保留。
七、给自己的备忘录
如果重来一次,我会:
更早建立测试框架。AI 系统的测试很难,但不做测试会让后期维护变成噩梦。哪怕是简单的"回放测试"(录制一组对话,验证 Agent 在关键节点的决策是否符合预期)也比没有好。
更少的配置项,更好的默认值。每增加一个配置项,就是在把认知负担转移给用户。只有真正必要的配置才值得暴露。
更重视文档。Skill 系统的成功验证了一点:AI 能读懂良好的文档并按照它行动。这意味着写好文档不只是为了人类开发者,也是在"编程"AI 的行为。
更早考虑可观测性。看不见的系统是无法信任的系统。
系列总结
七篇文章,从架构概览到工程反思,我们走完了 OpenClaw 的核心实现:
| Day | 主题 | 核心收获 |
|---|---|---|
| 1 | 整体架构 | Gateway + Agent + Channel 的三层模型 |
| 2 | WebSocket Gateway | 长连接、设备配对、心跳机制 |
| 3 | Agent 运行时 | JSONL 持久化、Session 管理、上下文压缩 |
| 4 | 流式输出 | Block 设计、Chunk 算法、平台适配 |
| 5 | 多 Agent 路由 | Binding 规则、Session 隔离、Sub-Agent |
| 6 | 工具与 Skills | 工具生命周期、安全防御、文档即技能 |
| 7 | 工程反思 | 不确定性、可靠性、取舍哲学 |
AI Agent 框架是个还在快速演化的领域。今天的"最佳实践",也许明年就会被更好的方案取代。但有些底层原则是稳定的:简单、可靠、可观测、安全——这些不会过时。
感谢你看完这个系列。如果某一篇触动了你,欢迎在评论区聊聊你的想法。
作者:一个在折腾 AI Agent 框架的工程师
系列索引:[Day 1 架构概览] | [Day 2 Gateway] | [Day 3 Agent 运行时] | [Day 4 流式输出] | [Day 5 多 Agent 路由] | [Day 6 工具系统] | Day 7 工程反思
如果这个系列对你有帮助,欢迎点赞收藏 🎯 期待和你继续交流!
更多推荐



所有评论(0)