本文基于 OpenClaw 开源项目的源码,深入剖析 AI Agent 的完整工作流程。
前言
当你在对话框输入“帮我搜索 OpenClaw 的架构文档并总结”,这行文字经历了怎样的过程?
本文将从源码角度,完整剖析一个 AI Agent 如何接收消息、调度工具、调用大模型,最终返回结果。
一、整体架构总览
先来看一张全局图:
┌─────────────────────────────────────────────────────────────────┐
│ 用户界面层 │
│ (Feishu / Web / 终端) │
└────────────────────────────┬────────────────────────────────────┘
│ 消息入口
▼
┌─────────────────────────────────────────────────────────────────┐
│ Gateway 网关层 │
│ dispatchInboundMessage → chat.send handler │
└────────────────────────────┬────────────────────────────────────┘
│ Session 加载/保存
▼
┌─────────────────────────────────────────────────────────────────┐
│ Agent 核心层 │
│ runEmbeddedPiAgent → runEmbeddedAttempt → session.prompt │
└────────────────────────────┬────────────────────────────────────┘
│ LLM 调用 + 工具执行
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 工具系统 │ │ LLM 处理 │ │ 流式响应 │
│ pi-tools │ │ 思考循环 │ │ subscribe │
└─────────────────┘ └──────────────┘ └─────────────────┘
二、消息入口:Gateway 层
2.1 消息接收流程
所有消息都经过同一个入口:
// src/gateway/server-methods/chat.ts
export const chatHandlers: GatewayRequestHandlers = {
"chat.send": async ({ params, respond, context }) => {
// 1. 验证参数
// 2. 加载或创建 Session
// 3. 保存用户消息
// 4. 调用 runEmbeddedPiAgent() 触发 Agent
// 5. 流式返回响应
}
}
2.2 关键代码片段
// 加载 Session
const { sessionKey, sessionFile } = await loadSessionEntry(params, context);
// 追加用户消息到历史
await appendMessage({ sessionKey, sessionFile, message: params.message });
// 触发 Agent 运行
const result = await runEmbeddedPiAgent({
sessionKey,
sessionFile,
prompt: params.message.content.text,
// ... 其他参数
});
// 流式返回
for await (const chunk of result.stream) {
await respond({ data: chunk });
}
三、Agent 核心:消息如何变成思考
3.1 入口函数
// src/agents/pi-embedded-runner/run.ts
export async function runEmbeddedPiAgent(params: RunEmbeddedPiAgentParams) {
// 1. 解析 workspace 和 agent 配置
// 2. 构建系统提示词 (System Prompt)
// 3. 创建 Agent Session
// 4. 调用 runEmbeddedAttempt() 执行 LLM 调用
// 5. 返回流式响应
}
3.2 系统提示词工程
System Prompt 是 Agent 的"灵魂",它决定了 Agent 的行为模式:
// src/agents/system-prompt.ts
const lines = [
"You are a personal assistant running inside OpenClaw.",
"",
"## Tooling",
"Tool availability (filtered by policy):",
"- web_search: Search the web",
"- web_fetch: Fetch and extract content from URL",
"- read: Read file contents",
"- write: Create or overwrite files",
// ... 更多工具
"",
"## Tool Call Style",
"Default: do not narrate routine, low-risk tool calls (just call the tool).",
"Narrate only when it helps: multi-step work, complex problems...",
"",
"## Safety",
"Prioritize safety and human oversight over completion...",
];
3.3 思考标签机制
Agent 使用特殊的 XML 标签来区分"内部思考"和"用户可见输出":
<think>
用户问的是架构文档,我需要先搜索相关内容。
根据搜索结果,OpenClaw 的架构主要包括...
让我组织一下回答的结构。
</think>
<final>
根据搜索结果,OpenClaw 的 Agent 架构主要包含以下几个核心模块...
</final>
代码层面是这样处理的:
// src/agents/pi-embedded-subscribe.ts
const THINKING_TAG_SCAN_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought)\s*>/gi;
const FINAL_TAG_SCAN_RE = /<\s*(\/?)\s*final\s*>/gi;
const stripBlockTags = (text) => {
// 1. 剥离 <think> 块,保留内部内容用于思考流展示
// 2. 识别 <final> 块,只返回最终输出给用户
// 3. 代码块内的标签不会被误处理
};
四、工具系统:Agent 的能力边界
4.1 工具注册
// src/agents/pi-tools.ts
export function createOpenClawCodingTools() {
return [
...baseTools, // read, write, edit
...execTools, // exec, process
...openClawTools, // message, cron
...channelTools, // 渠道特定工具
...pluginTools, // 插件工具
];
}
4.2 工具调用生命周期
当 LLM 决定调用工具时,整个过程如下:
| 阶段 | 事件 | 说明 |
|---|---|---|
| 开始 | tool_execution_start | 记录开始时间,发送摘要 |
| 执行 | 工具逻辑运行 | 如调用搜索 API |
| 结束 | tool_execution_end | 清理结果,返回给 LLM |
// src/agents/pi-embedded-subscribe.handlers.tools.ts
export async function handleToolExecutionStart(ctx, evt) {
const { toolName, toolCallId, args } = evt;
// 记录工具调用
ctx.state.toolMetaById.set(toolCallId, {
name: toolName,
args,
startTime: Date.now()
});
// 发送工具事件
emitAgentEvent({ stream: "tool", data: { phase: "start", ... }});
}
export async function handleToolExecutionEnd(ctx, evt) {
const { toolName, toolCallId, result } = evt;
// 清理结果
const sanitized = sanitizeToolResult(result);
// 写回 Session
ctx.state.toolMetas.push({ toolName, result: sanitized });
}
4.3 Web Search 执行示例
sequenceDiagram
participant LLM
participant Tool
participant Runtime
participant API
LLM->>Tool: toolUse {web_search, args: {query: "..."}}
Tool->>Runtime: resolveWebSearchDefinition()
Runtime->>API: POST /search
API-->>Runtime: JSON 结果
Runtime-->>Tool: 搜索结果
Tool-->>LLM: tool_result
五、Session 管理:记忆的持久化
5.1 为什么用文件系统而非数据库?
| 考量维度 | 文件系统方案 | 数据库方案 |
|---|---|---|
| 简单性 | 无需额外依赖 | 需要安装配置 |
| 追加友好 | JSONL 天生适合 | 需要特殊配置 |
| 可调试 | 直接查看文件 | 需客户端查询 |
| 可移植 | 复制文件即可 | 需要导出导入 |
核心洞察:消息系统的特点是写多读少、顺序追加,JSONL 的追加模式完美契合这个场景。
5.2 存储结构
~/.openclaw/
└── agents/
└── main/
├── sessions/
│ ├── sessions.json # Session 索引
│ ├── sess-abc123.jsonl # 具体消息历史
│ └── sess-def456.jsonl
└── sessions.json # 主索引文件
5.3 消息文件内容示例
{"type":"session","version":3,"id":"sess-abc123","timestamp":"2025-03-23T10:00:00.000Z"}
{"type":"message","id":"msg-001","role":"user","content":[{"type":"text","text":"帮我搜索架构文档"}]}
{"type":"message","id":"msg-002","role":"assistant","content":[{"type":"toolUse","name":"web_search","input":{"query":"OpenClaw architecture"}}]}
{"type":"message","id":"msg-003","role":"assistant","content":[{"type":"text","text":"根据搜索结果..."}]}
5.4 数据量限制
| 配置项 | 默认值 |
|---|---|
| 单文件大小 | 10 MB(自动轮转) |
| Session 数量 | 500 个(自动清理) |
| 保留期限 | 30 天未使用 |
| 磁盘预算 | 可配置上限 |
六、完整请求流程
用时序图来看整个过程:
sequenceDiagram
participant User as 👤 用户
participant Gateway as 网关
participant Session as Session
participant Agent as Agent
participant LLM as 大模型
participant Tool as 工具
participant UI as 界面
User->>Gateway: "帮我搜索架构文档"
Gateway->>Session: 加载历史
Session-->>Gateway: 历史消息
Gateway->>Agent: 触发运行
Agent->>Agent: 构建 System Prompt
Agent->>LLM: 发送请求
rect rgb(200, 230, 200)
Note over LLM: 第一轮:决策
LLM->>Tool: 调用 web_search
Tool->>Tool: 执行搜索
Tool-->>LLM: 返回结果
end
rect rgb(200, 220, 250)
Note over LLM: 第二轮:总结
LLM-->>Agent: 最终回复
end
Agent->>Session: 保存历史
Agent-->>UI: 流式输出
UI-->>User: 实时显示
七、设计哲学与思考本质
7.1 Session 优先:长期记忆的代价
选择持久化而非每次清零:
- 优势:记得三个月前的对话,多轮交互连贯
- 代价:需要上下文压缩(Compaction)防止 token 溢出
7.2 工具即能力:安全的边界
每个功能封装为工具,LLM 像搭积木一样组合使用:
搜索 → 读取文档 → 编辑文件 → 发送消息
比直接操作外部世界更安全、更可控。
7.3 流式即体验:透明建立信任
用户实时看到思考过程,而非等待漫长加载——这不仅是体验,更是信任。
7.4 思考的本质
| 阶段 | 做了什么 |
|---|---|
| 路由 | 消息从指尖到网关 |
| 加载 | 恢复历史上下文 |
| 构建 | 精心设计的提示词 |
| 推理 | 模型决定调用工具 |
| 执行 | 搜索 API 调用 |
| 整合 | 基于结果生成回答 |
| 输出 | 流式响应呈现 |
八、深入探索
如果你想进一步研究源码:
| 模块 | 路径 |
|---|---|
| Agent 核心 | src/agents/pi-embedded-runner/ |
| 流式处理 | src/agents/pi-embedded-subscribe.ts |
| Session 管理 | src/config/sessions/ |
| 消息入口 | src/gateway/server-methods/chat.ts |
结语
AI Agent 的架构,本质上是一套精密的消息处理管道:输入是用户的文字,输出是智能的响应,中间穿插着工具调用、模型推理、记忆管理。
希望这篇文章能帮助你更好地理解 AI Agent 的工作原理。如果你有任何问题,欢迎在评论区交流。
本文涉及的源码文件:
src/agents/pi-embedded-runner/run.ts- Agent 主入口src/agents/pi-embedded-runner/run/attempt.ts- 核心运行循环src/agents/pi-embedded-subscribe.ts- 流式响应处理src/agents/system-prompt.ts- 系统提示词构建src/agents/pi-tools.ts- 工具系统src/config/sessions/- Session 持久化