markdown复制## 1. 项目概述:手写简化版OpenClaw框架
最近在AI领域,OpenClaw框架因其强大的Agent能力成为开发者热议的话题。作为从业者,我决定通过500行代码实现一个简化版本,帮助大家理解其核心机制。这个项目不是简单的功能复刻,而是对AI Agent本质的探索——如何让语言模型具备"思考-行动-观察"的闭环能力。
### 1.1 核心需求解析
传统语言模型只能进行文本对话,而AI Agent需要:
- 自主决策何时调用工具
- 正确处理工具返回结果
- 维护对话上下文记忆
- 控制执行流程避免无限循环
我们的简化版将聚焦四个核心模块:
1. **Agent Core**:处理主循环逻辑
2. **Tool Executor**:安全执行外部操作
3. **Memory**:维护对话历史
4. **LLM Client**:与语言模型API交互
> 关键设计原则:每个模块保持独立职责,通过清晰定义的接口通信。例如工具调用统一使用`ToolCall`类型,避免模块间直接依赖。
## 2. 架构设计与实现细节
### 2.1 类型系统设计
首先定义基础类型,这是框架的"宪法":
```typescript
// 消息类型
interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_call_id?: string; // 工具调用的ID
tool_calls?: ToolCall[]; // 助手请求的工具调用
}
// 工具调用规范
interface ToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string; // JSON字符串
};
}
这种设计实现了:
- 角色分离:区分系统指令、用户输入、AI回复和工具结果
- 工具调用标准化:所有工具遵循相同调用规范
- 可追溯性:通过
tool_call_id关联调用与结果
2.2 工具系统实现
工具是Agent与真实世界交互的桥梁。我们实现了五种基础工具:
- Shell命令执行:
typescript复制const shellTool: Tool = {
name: 'execute_shell',
description: '执行安全的shell命令',
async execute(args) {
if (!isSafeCommand(args.command)) {
throw new Error('命令不在白名单中');
}
const { stdout } = await execAsync(args.command);
return stdout;
}
};
安全机制包括:
- 命令白名单(仅允许ls/cat等只读命令)
- 危险模式检测(阻止rm -rf等操作)
- 执行超时(默认30秒)
- 文件读写工具:
typescript复制const readFileTool: Tool = {
async execute({ path, maxLines }) {
const content = await fs.readFile(path, 'utf-8');
return content.split('\n').slice(0, maxLines).join('\n');
}
};
关键安全措施:
- 路径规范化防止目录遍历攻击
- 行数限制避免返回过大内容
- 禁止访问系统目录(/etc, /usr等)
2.3 Agent Loop核心逻辑
这是框架最精妙的部分——模拟人类"思考-行动-观察"的循环:
typescript复制async function run(userInput: string) {
let iteration = 0;
while (iteration < MAX_ITERATIONS) {
// 1. 思考阶段
const llmResponse = await llm.chat([
...memory,
{ role: 'user', content: userInput }
]);
// 2. 行动阶段
if (llmResponse.toolCalls) {
const toolResults = await executeTools(llmResponse.toolCalls);
memory.push(...toolResults);
continue;
}
// 3. 返回阶段
return llmResponse.content;
}
}
循环终止条件:
- LLM返回自然语言回复(finishReason='stop')
- 达到最大迭代次数(防无限循环)
- 工具执行失败
3. 关键技术实现
3.1 流式响应处理
为提升用户体验,我们实现逐字输出效果:
typescript复制async function handleStream(response: Response) {
const reader = response.body.getReader();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += new TextDecoder().decode(value);
const lines = buffer.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.event === 'text') {
process.stdout.write(data.text); // 实时输出
}
}
}
}
}
注意事项:
- 正确处理分块边界(一个JSON可能被拆分成多个chunk)
- 及时清空buffer避免内存泄漏
- 错误处理网络中断
3.2 记忆管理策略
对话历史是Agent的"工作记忆",我们采用两种优化:
- 自动截断:
typescript复制function truncateHistory(messages: Message[]) {
if (messages.length < 15) return messages;
return [
messages[0], // 保留系统提示
...messages.slice(-10) // 保留最近10条
];
}
- 工具结果压缩:
typescript复制function compressToolResult(result: string) {
if (result.length < 500) return result;
return result.slice(0, 500) + '...(已截断)';
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
4. 实战问题与解决方案
4.1 工具调用参数解析
问题现象:
- 流式响应中工具参数分多次到达
- 直接解析会导致JSON解析错误
解决方案:
typescript复制let argsBuffer = '';
for await (const chunk of stream) {
if (chunk.type === 'tool_args') {
argsBuffer += chunk.data; // 累积参数片段
}
if (chunk.type === 'tool_end') {
const args = JSON.parse(argsBuffer); // 完整解析
argsBuffer = '';
}
}
4.2 循环失控预防
典型场景:
- Agent反复调用同一工具
- 陷入死循环无法退出
防御措施:
- 系统提示词明确停止条件
- 强制最大迭代次数(默认10次)
- 工具调用频率限制:
typescript复制const lastCalled = new Map<string, number>();
function canCallTool(toolName: string) {
const lastTime = lastCalled.get(toolName) || 0;
return Date.now() - lastTime > 3000; // 3秒冷却
}
5. 部署与扩展
5.1 命令行界面实现
通过Node.js的readline模块构建交互式REPL:
typescript复制const rl = createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', async (input) => {
process.stdout.write('AI: ');
await agent.run(input, (chunk) => {
process.stdout.write(chunk); // 流式输出
});
});
5.2 Web服务扩展
使用Express快速构建HTTP接口:
typescript复制app.post('/chat', async (req, res) => {
const stream = new PassThrough();
agent.run(req.body.message, (chunk) => {
stream.write(`data: ${JSON.stringify(chunk)}\n\n`);
});
stream.pipe(res);
});
6. 经验总结与优化建议
6.1 性能优化点
- 对话历史压缩:
- 移除重复的系统提示
- 合并连续的用户消息
- 工具并行执行:
typescript复制async function executeTools(toolCalls) {
return Promise.all(
toolCalls.map(tc =>
toolRegistry.get(tc.name).execute(tc.args)
)
);
}
6.2 安全增强建议
- 沙箱执行:
typescript复制const vm = require('vm');
const context = {
console: sandboxConsole
};
new vm.Script(code).runInNewContext(context);
- 权限分级:
- 只读工具(文件读取)
- 受限写工具(指定目录)
- 特权工具(需要额外授权)
7. 扩展阅读与资源
- 工具调用规范:Tool Use Documentation
- 流式处理最佳实践:Streaming Patterns
- 安全设计指南:AI Safety
实现过程中最深的体会是:AI Agent的核心不在于复杂的功能堆砌,而在于建立可靠的"认知-行动"循环。这个500行代码的简化版已经包含了最精华的设计思想,建议读者可以在此基础上继续扩展以下方向:
- 添加向量记忆实现长期记忆
- 引入验证机制确保工具调用安全性
- 实现多Agent协作架构
code复制
