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 工程反思

如果这个系列对你有帮助,欢迎点赞收藏 🎯 期待和你继续交流!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐