一、OpenClaw 整体架构
OpenClaw 采用经典的分层架构,从下到上分为:
┌─────────────────────────────────────┐
│ 应用层(Web UI / API) │
├─────────────────────────────────────┤
│ 业务层(工作流引擎 / 对话管理) │
├─────────────────────────────────────┤
│ 服务层(模型调度 / 工具执行) │
├─────────────────────────────────────┤
│ 数据层(PostgreSQL / Redis) │
└─────────────────────────────────────┘
核心模块:
- 工作流引擎 :编排节点、执行流程、状态管理 - 模型调度器 :统一多模型接口、负载均衡、重试容错 - 工具系统 :函数注册、参数校验、沙箱执行 - 对话管理 :历史存储、上下文窗口、多轮对话 - 集成层:飞书、钉钉、企微等第三方平台接入
二、工作流引擎
2.1 节点类型
OpenClaw 的工作流由节点(Node)组成,每个节点是一个独立的处理单元:
| 节点类型 | 作用 | 示例 |
|---|---|---|
| Start | 流程入口 | 接收用户输入 |
| LLM | 调用大模型 | GPT-4 生成回复 |
| Tool | 执行工具函数 | 搜索、计算、查数据库 |
| Condition | 条件分支 | 根据结果走不同路径 |
| Loop | 循环执行 | 批量处理数据 |
| End | 流程结束 | 返回最终结果 |
2.2 节点定义
每个节点是一个 JSON 对象:
json
{
"id": "node_1",
"type": "llm",
"config": {
"model": "gpt-4",
"prompt": "你是一个助手,回答用户问题:{{input}}",
"temperature": 0.7,
"max_tokens": 1000
},
"inputs": ["start"],
"outputs": ["node_2", "node_3"]
}
关键字段:
id:唯一标识 -type:节点类型 -config:配置参数(模型、提示词等) -inputs:上游节点 ID 列表 -outputs:下游节点 ID 列表
2.3 执行流程
工作流引擎的执行逻辑:
javascript
class WorkflowEngine {
async execute(workflow, input) {
const context = { input, variables: {} };
const queue = [workflow.startNode];
const visited = new Set();
while (queue.length > 0) {
const nodeId = queue.shift();
if (visited.has(nodeId)) continue;
visited.add(nodeId);
const node = workflow.nodes[nodeId];
// 执行节点
const result = await this.executeNode(node, context);
context.variables[nodeId] = result;
// 根据结果决定下游节点
const nextNodes = this.getNextNodes(node, result);
queue.push(...nextNodes);
}
return context.variables[workflow.endNode];
}
async executeNode(node, context) {
switch (node.type) {
case 'llm':
return this.executeLLM(node, context);
case 'tool':
return this.executeTool(node, context);
case 'condition':
return this.executeCondition(node, context);
default:
throw new Error(`Unknown node type: ${node.type}`);
}
}
}
核心机制:
- 队列调度 :用队列管理待执行节点,支持并行 2. 上下文传递 :
context对象存储中间结果,节点间共享 3. 变量替换 :提示词中的{{variable}}会被替换为实际值 4. 循环检测 :用visited集合防止死循环
2.4 并行执行
当多个节点没有依赖关系时,可以并行执行:
javascript
async executeParallel(nodes, context) {
const promises = nodes.map(node => this.executeNode(node, context));
const results = await Promise.all(promises);
return results;
}
示例场景:同时调用多个模型,取最优结果。
三、模型调度器
3.1 统一接口
OpenClaw 抽象了一个统一的模型接口,屏蔽不同厂商的差异:
javascript
class ModelProvider {
async chat(messages, options) {
throw new Error('Not implemented');
}
}
class OpenAIProvider extends ModelProvider {
async chat(messages, options) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: options.model || 'gpt-3.5-turbo',
messages,
temperature: options.temperature || 0.7,
max_tokens: options.max_tokens
})
});
const data = await response.json();
return data.choices[0].message.content;
}
}
class ZhipuProvider extends ModelProvider {
async chat(messages, options) {
// 智谱 API 调用逻辑
// ...
}
}
3.2 模型注册
在配置文件中注册多个模型:
javascript
const modelRegistry = {
'gpt-4': new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }),
'gpt-3.5-turbo': new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }),
'glm-4': new ZhipuProvider({ apiKey: process.env.ZHIPU_API_KEY }),
'qwen-max': new QwenProvider({ apiKey: process.env.QWEN_API_KEY })
};
3.3 负载均衡
当同一模型有多个 API Key 时,轮询或随机选择:
javascript
class LoadBalancer {
constructor(providers) {
this.providers = providers;
this.index = 0;
}
getProvider() {
const provider = this.providers[this.index];
this.index = (this.index + 1) % this.providers.length;
return provider;
}
}
// 使用
const balancer = new LoadBalancer([
new OpenAIProvider({ apiKey: 'key1' }),
new OpenAIProvider({ apiKey: 'key2' })
]);
const provider = balancer.getProvider();
const result = await provider.chat(messages, options);
3.4 重试与容错
模型调用可能失败(限流、超时等),需要重试机制:
javascript
async function callWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// 指数退避
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用
const result = await callWithRetry(() =>
provider.chat(messages, options)
);
3.5 流式输出
支持 SSE(Server-Sent Events)实现打字机效果:
javascript
async *chatStream(messages, options) {
const response = await fetch(url, {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({ ...options, stream: true })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
yield data.choices[0].delta.content;
}
}
}
}
// 使用
for await (const chunk of provider.chatStream(messages, options)) {
process.stdout.write(chunk);
}
四、工具系统
4.1 工具定义
工具是一个带元数据的函数:
javascript
const weatherTool = {
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称,如"北京"'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
default: 'celsius'
}
},
required: ['city']
},
async execute({ city, unit }) {
const response = await fetch(`https://api.weather.com?city=${city}&unit=${unit}`);
return response.json();
}
};
4.2 工具注册
全局注册工具,供工作流调用:
javascript
class ToolRegistry {
constructor() {
this.tools = new Map();
}
register(tool) {
this.tools.set(tool.name, tool);
}
get(name) {
return this.tools.get(name);
}
list() {
return Array.from(this.tools.values()).map(tool => ({
name: tool.name,
description: tool.description,
parameters: tool.parameters
}));
}
}
const registry = new ToolRegistry();
registry.register(weatherTool);
registry.register(searchTool);
4.3 Function Calling
模型决定调用哪个工具:
javascript
async function chatWithTools(messages, tools) {
// 第一次调用:模型决定是否用工具
const response1 = await provider.chat(messages, {
tools: tools.map(t => ({
type: 'function',
function: {
name: t.name,
description: t.description,
parameters: t.parameters
}
}))
});
// 如果模型要求调用工具
if (response1.tool_calls) {
const toolResults = [];
for (const call of response1.tool_calls) {
const tool = registry.get(call.function.name);
const args = JSON.parse(call.function.arguments);
const result = await tool.execute(args);
toolResults.push({
tool_call_id: call.id,
role: 'tool',
content: JSON.stringify(result)
});
}
// 第二次调用:把工具结果给模型
messages.push(response1);
messages.push(...toolResults);
const response2 = await provider.chat(messages);
return response2.content;
}
return response1.content;
}
4.4 沙箱执行
用 VM 或 Worker 隔离工具代码,防止恶意操作:
javascript
const { VM } = require('vm2');
function executeTool(code, args) {
const vm = new VM({
timeout: 5000,
sandbox: {
args,
fetch: safeFetch, // 限制网络访问
console: safeConsole
}
});
return vm.run(`
(async function() {
${code}
return execute(args);
})()
`);
}
五、对话管理
5.1 历史存储
每轮对话存入数据库:
sql
CREATE TABLE conversations (
id UUID PRIMARY KEY,
user_id VARCHAR(255),
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CREATE TABLE messages (
id UUID PRIMARY KEY,
conversation_id UUID REFERENCES conversations(id),
role VARCHAR(20), -- user / assistant / system
content TEXT,
created_at TIMESTAMP
);
5.2 上下文窗口
限制历史消息数量,避免超出模型 token 限制:
javascript
function getContextMessages(conversationId, maxTokens = 4000) {
const messages = db.query(
'SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at DESC',
[conversationId]
);
const result = [];
let totalTokens = 0;
for (const msg of messages) {
const tokens = estimateTokens(msg.content);
if (totalTokens + tokens > maxTokens) break;
result.unshift(msg);
totalTokens += tokens;
}
return result;
}
function estimateTokens(text) {
// 简单估算:1 token ≈ 4 字符
return Math.ceil(text.length / 4);
}
5.3 多轮对话
保持上下文连贯:
javascript
async function chat(conversationId, userMessage) {
// 获取历史消息
const history = getContextMessages(conversationId);
// 添加用户消息
const messages = [
{ role: 'system', content: '你是一个助手' },
...history,
{ role: 'user', content: userMessage }
];
// 调用模型
const reply = await provider.chat(messages);
// 存储对话
db.insert('messages', {
conversation_id: conversationId,
role: 'user',
content: userMessage
});
db.insert('messages', {
conversation_id: conversationId,
role: 'assistant',
content: reply
});
return reply;
}
六、集成层
6.1 飞书机器人
接收飞书消息,转发给工作流:
javascript
app.post('/api/feishu/webhook', async (req, res) => {
const { header, event } = req.body;
// 验证签名
if (!verifySignature(req.headers, req.body)) {
return res.status(401).send('Invalid signature');
}
// 处理消息事件
if (event.type === 'im.message.receive_v1') {
const { message, sender } = event;
const userId = sender.sender_id.user_id;
const text = JSON.parse(message.content).text;
// 调用工作流
const reply = await workflowEngine.execute('default', { input: text, userId });
// 回复消息
await feishuClient.sendMessage({
receive_id: userId,
msg_type: 'text',
content: JSON.stringify({ text: reply })
});
}
res.send({ code: 0 });
});
6.2 钉钉机器人
类似逻辑,适配钉钉 API:
javascript
app.post('/api/dingtalk/webhook', async (req, res) => {
const { text, senderStaffId } = req.body;
const reply = await workflowEngine.execute('default', { input: text.content });
await dingtalkClient.send({
chatId: req.body.conversationId,
msgtype: 'text',
text: { content: reply }
});
res.send({ success: true });
});
七、数据流转
一次完整的对话流程:
sql
用户输入
↓
飞书 Webhook
↓
集成层解析消息
↓
对话管理加载历史
↓
工作流引擎执行
├─ LLM 节点调用模型
├─ Tool 节点执行工具
└─ Condition 节点判断分支
↓
返回结果
↓
对话管理存储历史
↓
集成层发送回复
↓
用户收到消息
八、性能优化
8.1 缓存
对相同输入缓存结果:
javascript
const cache = new Map();
async function cachedChat(messages, options) {
const key = JSON.stringify({ messages, options });
if (cache.has(key)) {
return cache.get(key);
}
const result = await provider.chat(messages, options);
cache.set(key, result);
return result;
}
8.2 队列
用 Redis 队列处理高并发:
javascript
const queue = new Bull('chat', { redis: redisConfig });
queue.process(async (job) => {
const { conversationId, message } = job.data;
const reply = await chat(conversationId, message);
return reply;
});
// 添加任务
await queue.add({ conversationId, message });
8.3 数据库索引
sql
CREATE INDEX idx_messages_conversation ON messages(conversation_id, created_at);
CREATE INDEX idx_conversations_user ON conversations(user_id, updated_at);
九、安全机制
9.1 权限控制
基于角色的访问控制(RBAC):
javascript
const permissions = {
admin: ['read', 'write', 'delete', 'manage'],
user: ['read', 'write'],
guest: ['read']
};
function checkPermission(userId, action) {
const role = getUserRole(userId);
return permissions[role].includes(action);
}
9.2 输入校验
防止注入攻击:
javascript
function sanitizeInput(text) {
// 移除危险字符
return text.replace(/<script>/gi, '')
.replace(/javascript:/gi, '')
.trim();
}
9.3 速率限制
防止滥用:
javascript
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟
max: 10, // 最多 10 次请求
message: '请求过于频繁,请稍后再试'
});
app.use('/api/chat', limiter);
十、监控与日志
10.1 日志记录
javascript
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
logger.info('Workflow executed', { workflowId, duration, result });
10.2 性能监控
javascript
async function executeWithMetrics(fn, name) {
const start = Date.now();
try {
const result = await fn();
const duration = Date.now() - start;
metrics.record(name, { duration, status: 'success' });
return result;
} catch (error) {
metrics.record(name, { duration: Date.now() - start, status: 'error' });
throw error;
}
}
总结
OpenClaw 的核心架构:
- 工作流引擎 :节点编排、队列调度、上下文传递 - 模型调度器 :统一接口、负载均衡、重试容错、流式输出 - 工具系统 :函数注册、参数校验、Function Calling、沙箱执行 - 对话管理 :历史存储、上下文窗口、多轮对话 - 集成层:飞书/钉钉等平台适配
关键设计:
- 分层架构,职责清晰 - 插件化工具系统,易扩展 - 统一模型接口,支持多厂商 - 队列 + 缓存优化性能 - RBAC + 速率限制保障安全
理解这些原理,你就能定制自己的 AI 工作流平台。