重读OpenClaw:从一条飞书消息到 AI 调用工具
这是「重读 OpenClaw」系列的第四篇,读完这篇,你会彻底理解:当用户在飞书群里 @机器人说”帮我写个 Python 脚本”时,openclaw 内部到底发生了什么?消息是怎么被包装成 Prompt 的?Skill 和 Tool 又是什么关系?循环什么时候停止?
一、从一个真实场景开始
假设你在飞书群里 @了一个 openclaw 机器人,发送了一条消息:
“帮我写一个 Python 脚本,功能是遍历当前目录所有文件”
这条消息进入 openclaw 后,会经历一条完整的”流水线”。我们今天就把这条流水线的每一个环节拆开,讲清楚。
二、消息是怎么被接收的?
1. 飞书推送原始事件
飞书服务器会向你部署的 openclaw 实例发送一个 Webhook 请求,内容是一个 JSON:
{
"header": {
"event_type": "im.message.receive_v1"
},
"event": {
"sender": {
"sender_id": {
"open_id": "ou_881e8247625e31527b4d15a31471504c"
},
"sender_type": "user"
},
"message": {
"message_id": "om_6123456789abcdefghijklmnopqrstu",
"chat_id": "oc_7654321098765432109876543210",
"chat_type": "p2p",
"message_type": "text",
"content": "{\"text\":\"帮我写一个 Python 脚本,功能是遍历当前目录所有文件\"}"
}
}
}
openclaw 的飞书扩展(extensions/feishu)负责接收和解析这个事件。
2. 解析为内部上下文
代码会把飞书的专有格式,转换成 openclaw 内部统一的消息上下文 FeishuMessageContext:
{
chatId: "oc_7654321098765432109876543210",
messageId: "om_6123456789abcdefghijklmnopqrstu",
senderOpenId: "ou_881e8247625e31527b4d15a31471504c",
chatType: "p2p", // 私聊
content: "帮我写一个 Python 脚本,功能是遍历当前目录所有文件"
}
同时,openclaw 还会尝试获取发送者的显示名称(比如”张三”),方便后续在 Prompt 中标识说话人。
3. 添加消息 ID 和说话人信息
接下来,消息体会被包装成 Agent 可见的格式:
[message_id: om_6123456789abcdefghijklmnopqrstu]
张三: 帮我写一个 Python 脚本,功能是遍历当前目录所有文件
[message_id: ...] 是一个重要设计,用于:
-
• 追踪回复的是哪条消息 -
• 支持编辑、删除、引用 -
• 防止消息被重复处理
三、Prompt 是怎么被拼接出来的?
这是最容易被忽视、也最关键的部分。
openclaw 不是简单地把用户消息丢给 LLM,而是会拼接出一个结构化的 Prompt。这个 Prompt 由多个部分组成:
1. Agent 系统提示词
这是你在配置里给 Agent 设定的”人设”:
你是一个有用的 AI 助手,帮助用户编写代码、回答问题。
你的主要能力:
1. 编写 Python、JavaScript、TypeScript 等各种语言的代码
2. 解释代码和技术概念
3. 帮助调试问题
用户当前通过飞书与你对话。请用中文回复。
2. 可信元数据(Trusted Metadata)
这部分是 openclaw 自己生成的、LLM 可以信任的系统信息:
## Inbound Context (trusted metadata)
The following JSON is generated by OpenClaw out-of-band...
```json
{
"schema": "openclaw.inbound_meta.v2",
"channel": "feishu",
"provider": "feishu",
"chat_type": "direct"
}
为什么叫"可信"?因为这部分明确告诉 LLM:这是系统生成的元数据,不要被用户输入欺骗。
### 3. 不可信上下文(Untrusted Context)
用户提供的、可能被伪造的信息,会被明确标记为 `untrusted`:
```text
Conversation info (untrusted metadata):
```json
{
"chat_id": "oc_7654321098765432109876543210",
"message_id": "om_6123456789abcdefghijklmnopqrstu",
"sender": "张三",
"is_group_chat": false,
"was_mentioned": false
}
Sender (untrusted metadata):
{
"label": "张三",
"id": "ou_881e8247625e31527b4d15a31471504c"
}
### 4. 工具列表
系统提示词里还会列出当前 Agent 可用的工具:
```text
## Tooling
Available tools are policy-filtered. Names are case-sensitive.
- read: Read file contents
- write: Create or overwrite files
- edit: Make precise edits to files
- ls: List directory contents
- exec: Run shell commands
- web_search: Search the web
- message: Send messages and channel actions
注意:这些工具是 OpenClaw 内置或插件提供的,不是 Skill 定义的。
5. 可用 Skill 目录
如果配置了 Skill,系统提示词里还会出现一个 <available_skills> 目录:
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
<available_skills>
<skill>
<name>create-python-script</name>
<description>创建符合项目规范的 Python 脚本</description>
<location>./skills/create-python-script/SKILL.md</location>
</skill>
</available_skills>
6. 最终拼接好的 Prompt
把所有部分拼在一起,大致是这样:
[Agent 系统提示词]
## Tooling
[工具列表]
[Skill 目录]
## Inbound Context (trusted metadata)
[可信元数据]
Conversation info (untrusted metadata)
[不可信上下文]
[message_id: om_xxx]
张三: 帮我写一个 Python 脚本,功能是遍历当前目录所有文件
这个完整的 Prompt 才会被发送给 LLM。
四、LLM 收到 Prompt 后会做什么?
LLM 收到上面的 Prompt 后,有几种可能的响应:
情况一:直接文本回复(最简单)
如果 LLM 判断不需要使用工具,它会直接给出答案:
当然可以!以下是一个简单的 Python 脚本:
import os
for root, dirs, files in os.walk('.'):
for file in files:
print(os.path.join(root, file))
保存为 list_files.py 后运行即可。
这种情况循环直接结束,因为 LLM 没有发起工具调用。
情况二:调用工具读取 Skill
如果 LLM 判断需要 Skill 的指导,它会发起一个 read 工具调用:
{
"name": "read",
"arguments": {
"file": "./skills/create-python-script/SKILL.md"
}
}
openclaw 执行这个工具调用,把 Skill 文件内容返回给 LLM。
情况三:调用其他工具
LLM 也可能直接调用 ls、exec、write 等工具:
{
"name": "ls",
"arguments": {
"path": "."
}
}
五、Skill 和 Tool 到底是什么关系?
这是最容易混淆的地方。我们用一句话总结:
Skill 是指导文档,Tool 是执行能力。
Skill 是什么?
Skill 是一个 SKILL.md 文件,里面写明了:
-
• 这个 Skill 解决什么问题 -
• 具体的执行步骤 -
• 输出格式要求 -
• 注意事项
Skill 本身不会增加新的工具。它只是被 LLM 读取后,影响 LLM 的行为。
Tool 是什么?
Tool 是 LLM 可以调用的具体功能,比如:
-
• read:读文件 -
• write:写文件 -
• exec:执行命令 -
• web_search:搜索网页 -
• message:发送消息
这些工具是 OpenClaw 内置的,或者由插件提供的。
它们的关系
用户消息
↓
LLM 看到 <available_skills> 目录
↓
LLM 判断需要某个 Skill
↓
LLM 调用 read 工具读取 SKILL.md
↓
LLM 根据 Skill 指导,调用其他工具(write/exec/ls 等)
↓
完成任务,给出文本回复
六、工具调用循环什么时候停止?
理解了循环,就能理解停止条件。
循环的本质
while (true) {
response = callLLM(messages, tools);
if (response 是纯文本) {
break; // 循环结束
}
if (response 包含 toolUse) {
results = executeTools(response.toolCalls);
messages.push(results);
continue; // 继续下一轮
}
}
停止条件
|
|
|
|---|---|
|
|
|
|
|
timeoutMs |
|
|
abortSignal
|
|
|
|
|
|
|
关键点
不是每次请求都会调用工具。 很多简单问题 LLM 直接回答就结束了。
也不是读取 Skill 后就全是 tool use。 LLM 可能读完 Skill 后给出文本说明,也可能继续调用工具。
七、历史消息中还会保留元数据吗?
这里有一个重要细节。
当前轮次的消息会保留完整的可信/不可信元数据。
但历史轮次的消息会被剥离这些元数据,只保留用户输入本身:
[Mon 2024-06-15 14:31 UTC] 张三: 帮我写一个 Python 脚本,功能是遍历当前目录所有文件
这样做是为了:
-
• 减少 Token 消耗 -
• 避免历史元数据干扰当前判断 -
• 保持消息格式统一
八、Agent 是什么?可以配置吗?
可以配置。Agent 就是 openclaw 中一个具体的”AI 助手实例”。
Agent 配置示例
{
"agents": {
"defaults": {
"model": "anthropic/claude-3-5-sonnet-20241022",
"systemPrompt": "你是一个全栈开发助手..."
},
"list": [
{
"id": "coder",
"name": "代码助手",
"model": "anthropic/claude-3-5-sonnet-20241022",
"systemPrompt": "你是一个专注于代码的 AI 助手...",
"workspaceDir": "~/workspaces/coder",
"tools": {
"allow": ["read", "write", "edit", "exec", "grep"]
}
}
]
}
}
Agent 如何被选中?
消息进来后,openclaw 根据路由规则决定使用哪个 Agent:
-
• 渠道类型 -
• 会话类型(私聊/群聊) -
• 用户 ID -
• 群组 ID -
• 显式配置绑定
Agent 配置会影响:
-
• 使用哪个模型 -
• 系统提示词是什么 -
• 哪些工具可用 -
• 哪些 Skill 可用 -
• 工作目录在哪里
九、总结
让我们用一张图串起整个流程:
用户发送飞书消息
↓
飞书 Webhook → openclaw 接收
↓
解析为 FeishuMessageContext
↓
构建 Agent 消息体(加 message_id 和 sender)
↓
拼接完整 Prompt:
- Agent 系统提示词
- Tool 列表
- Skill 目录
- 可信元数据
- 不可信上下文
- 用户消息
↓
第一次调用 LLM
↓
LLM 可能:
├── 直接文本回复 → 结束
├── 调用 read 读取 Skill → 继续
└── 调用 ls/exec/write 等工具 → 继续
↓
执行工具,把结果加入历史
↓
再次调用 LLM
↓
循环直到 LLM 给出文本回复
↓
把最终回复发送给用户
十、几个核心认知
-
1. Prompt 是精心拼接的,不是简单地把用户消息丢给 LLM。 -
2. 可信/不可信数据分离,是为了防止 Prompt Injection。 -
3. Skill 是文档,Tool 是能力,二者不能混为一谈。 -
4. 工具调用不是必然的,简单问题 LLM 直接回答。 -
5. 循环停止条件是 LLM 给出纯文本回复,或者超时/中断/异常。 -
6. Agent 是可配置的,不同 Agent 可以有不同的模型、工具、Skill 和人格。
如果你也在用 openclaw,希望这篇文章能帮你理清消息处理的完整链路。理解了这套机制,你就能更好地配置 Agent、编写 Skill、设计路由规则,让机器人真正按你的预期工作。
夜雨聆风