如果让你基于 OpenClaw 的设计理念从零搭建一个 Agent 框架,你会先做哪三个模块?为什么?

魏远标 Lv7

参考答案

从零搭一个 Agent 框架,我会先做三件事:Agent Runner(执行引擎)、Context Engine(上下文管理)、Gateway(请求网关)。这三个是最小可运行系统的核心。

Agent Runner 是整个系统的引擎,它负责 LLM 调用 → 工具执行 → 结果回传这个核心循环。

最小版本大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


java

复制代码

public class AgentRunner {
private final LlmClient llm;
private final ToolRegistry tools;

public String run(String userMessage, Context context) {
List<Message> messages = context.assemble(userMessage);
while (true) {
LlmResponse response = llm.chat(messages);
if (!response.hasToolCalls()) {
return response.getText();
}
for (ToolCall call : response.getToolCalls()) {
ToolResult result = tools.execute(call);
messages.add(Message.toolResult(call.getId(), result));
}
}
}
}

先把这个循环跑通,能调模型、能执行工具、能把结果喂回去,一个最小的 Agent 就活了。

第二个做 Context Engine。Agent 能不能持续完成复杂任务,全看上下文管理做得好不好。跑个十几轮对话 token 就爆了,需要上下文裁剪和压缩。OpenClaw 里的 compaction 机制就是干这个的,把历史对话压缩成摘要,腾出 token 窗口给新内容。

第三个做 Gateway。没有网关,Agent 就是一个孤立的函数调用,没法服务真实用户。

Gateway 是整个系统的中枢神经,它的职责远不止”接收 HTTP 请求”,还要管消息渠道的接入(Telegram、Discord、飞书等各平台的连接生命周期)、会话管理(创建/销毁/状态维护)、请求排队和幂等去重、定时任务调度(Cron)、子 Agent 协调、配置热更新,以及对外暴露 WebSocket 实时流。

可以说 Gateway 是连接”外部世界”和”Agent 内核”的桥梁,所有进出系统的流量都从它这里过。

三个模块的协作关系:外部消息(用户在 Telegram 发了一句话、定时任务触发、子 Agent 回传结果)先到 Gateway,Gateway 做渠道适配、路由匹配、排队控制后分发给 Agent Runner,Agent Runner 在执行循环中通过 Context Engine 组装上下文、调用 LLM、执行工具,最终把结果沿原路经 Gateway 返回给对应渠道的用户。

扩展知识

这三个模块的优先级是从 OpenClaw 的架构里提炼出来的规律,每一个都有明确的设计考量。

Agent Runner 的核心设计

对应 OpenClaw 的 src/agents/pi-embedded-runner/,是整个系统最复杂的部分。

先实现最简循环:构建 messages → 调用 LLM API → 解析 tool_use → 执行工具 → 回传 tool_result → 重复。

然后逐步加上错误处理、超时、fallback、流式输出。

关键的设计决策有几个:

1)模型抽象层要做好。今天用 GPT,明天可能换 Claude,后天可能上本地部署的 Llama。接口定义成 LlmClient,底层随便切换,上层代码一行不改。

2)工具注册要用声明式。每个工具通过注解或配置描述自己的名称、参数 schema、执行逻辑,Runner 启动时自动扫描注册。OpenClaw 里工具描述直接喂给 LLM 的 function calling 接口,LLM 自己决定调哪个工具。

3)错误处理和超时不能拖到后面再加。LLM 调用动不动就 30 秒超时,工具执行也可能卡死。从第一版就要把 timeout 和 retry 机制搭好,不然后面补的代价非常大。

Context Engine 的设计要点

对应 OpenClaw 的 src/context-engine/src/agents/compaction.ts

上下文管理要解决的核心矛盾就是:token 窗口有限,但对话历史越来越长。GPT-4o 给你 128K 上下文,听起来很多,实际跑个复杂任务十几轮对话就能吃掉大半。

OpenClaw 的做法是分层管理:

1)最近几轮的对话原样保留,保证 Agent 能记住刚才在干嘛。

2)更早的历史通过 compaction 压缩成摘要。比如前面 20 轮对话压成一段 500 token 的总结,核心信息不丢,但 token 占用从 8000 降到 500。

3)系统 prompt 和工具描述单独管理,这部分每次都要完整带上,不能裁。

接口设计上一定要可插拔。上下文管理这个领域还在快速演进,RAG、长上下文窗口、记忆检索这些方案都在迭代。OpenClaw 把 ContextEngine 定义成接口,策略随时可以换,不会写死在 Runner 里。

Gateway 的职责全景

Gateway 是整个框架里职责最重的模块,远不止”接收 HTTP 请求然后转发”。

从 OpenClaw 的实现来看,它至少承担六大类职责:

1)消息渠道管理。Gateway 负责管理所有外部渠道的连接生命周期:启动 Telegram bot、连接 Discord WebSocket、监听 Slack webhook。每个渠道可能有多个账号(比如同时运营 3 个 Telegram bot),Gateway 要维护每个账号的连接状态、自动重连、优雅关闭。这是 Agent 能”听到”外部消息的前提。

2)会话管理与请求排队。Gateway 管理所有 session 的创建、状态维护和销毁。同一个 session 的请求必须串行处理(不然 Agent 上下文会乱),不同 session 之间可以并行。这是 Lane(并发槽位)机制实现的。

3)幂等去重。网络抖动导致客户端重发、子 Agent 重复回传,Gateway 通过 idempotency key 识别重复请求,同一个请求只执行一次。

4)定时任务(Cron)。Agent 不只是被动响应消息,还需要主动做事:定时检查数据、定期生成报告、心跳监控。Gateway 内置了 Cron 调度器,支持 cron 表达式触发 Agent 执行,也支持 webhook 回调。

5)子 Agent 协调。父 Agent spawn 子 Agent、子 Agent 完成后通过 Announce 机制回传结果,这些 inter-session 通信全部经过 Gateway 中转。Gateway 在这里充当消息总线的角色。

6)配置热更新与运维。Gateway 支持不停机更新配置(config patch/apply)、插件 HTTP 路由、健康检查、Control UI(Web 管理界面)。生产环境里 Agent 不能说停就停,配置变更必须热生效。

面试官追问

提问:Agent Runner 里面如果 LLM 返回了一个不存在的工具名,你怎么处理?

回答:直接构造一条 tool_result 消息告诉 LLM “这个工具不存在,请重新选择”,然后继续循环让 LLM 重新决策。不能直接报错退出,LLM 调工具本质上是概率生成,偶尔幻觉出不存在的工具名很正常。一般加个最大重试次数,比如连续 3 次都调错就终止,防止死循环。

提问:Context Engine 做 compaction 的时候,怎么保证压缩后不会丢掉关键信息?

回答:核心思路是用 LLM 自己来做摘要,因为只有 LLM 才能判断哪些信息对当前任务是关键的。把需要压缩的历史对话喂给 LLM,让它生成一段结构化摘要,保留关键决策、中间结果和未完成的任务。OpenClaw 的 compaction.ts 就是这么干的。缺点是每次压缩本身也要消耗 token 和时间,所以一般不会每轮都压,攒到一定量再触发。

提问:Gateway 做 session 级排队,如果一个用户的请求处理特别慢,后面的请求全堵住了怎么办?

回答:设超时。每个请求给一个最大执行时间,比如 120 秒,超了就强制终止当前 Agent 执行,返回超时错误,放行队列里下一个请求。另外可以做优先级区分,比如用户主动发的新消息优先级高于系统自动触发的后台任务。极端情况下还可以允许用户手动取消正在执行的请求。

提问:你说模型抽象层要做好,那不同模型的 function calling 格式都不一样,怎么统一?

回答:在抽象层里定义一套自己的工具调用协议,包含工具名、参数 JSON、调用 ID 三个字段。然后每个模型适配器负责把自家格式转成统一格式。比如 OpenAI 用的是 tool_calls 数组,Anthropic 用的是 content 里的 tool_use block,格式完全不同,但适配器转换完之后上层看到的都一样。OpenClaw 也是这么做的,Runner 只跟抽象接口打交道,压根不关心底层是哪个模型。

目录
如果让你基于 OpenClaw 的设计理念从零搭建一个 Agent 框架,你会先做哪三个模块?为什么?