你可以用400行代码构建一个行为类似于OpenClaw的智能体。只需使用TypeScript、Anthropic SDK、Slack SDK和一个YAML解析库。无需框架,也无需复杂的抽象——只需在一个脚本中包含几个函数。
截至2026年2月19日,OpenClaw的代码库拥有超过50万行TypeScript代码。但其核心可以简化为一个非常短小精悍的智能体:它在Slack中响应,使用技能,跨对话记住事实,浏览计算机上的文件,执行命令,访问互联网,并自主行动——无需人工干预。就像OpenClaw一样。
在这篇文章中,你将了解它的内部工作原理。我将引导你从零开始构建一个类似OpenClaw的智能体,这样你就能更好地理解你可能已经在使用的工具——并将这些想法应用到你自己的智能体系统中。
我们不会重现完整的OpenClaw体验。这篇博客文章的目标是阐明OpenClaw背后的核心原则,而不是精确地重现它。我们不会构建Web界面、Telegram和WhatsApp集成、语音支持或其他生活质量功能。
然而,我们将构建一个功能齐全的智能体,它能够:
- 响应来自授权用户的Slack私信
- 使用计算机
- 访问互联网
- 跨对话保持记忆
- 使用智能体技能
- 学习用户的偏好
- 无需明确提示即可主动行动
这篇博客文章旨在让你能够跟着一起构建智能体。每个部分都会添加一个新功能,并在前一个功能的基础上进行构建,这样你就可以逐步看到和使用智能体的演变。
让我们从创建一个简单的脚本开始,它接收来自Slack的消息并进行回复。
初始化一个新的TypeScript项目。这里,我们将使用Bun:
GPT plus 代充 只需 145bun init
安装Slack SDK:
bun add @slack/bolt
这是完整的逻辑。将其保存到一个名为index.ts的文件中。
GPT plus 代充 只需 145import { App } from "@slack/bolt";
const app = new App({ token: process.env.SLACK_BOT_TOKEN, appToken: process.env.SLACK_APP_TOKEN, socketMode: true, });
app.event("message", async ({ event, client }) => await client.chat.postMessage({
channel: event.channel, thread_ts: event.thread_ts ?? event.ts, text: `Hey! Your Slack user id is `${event.user}` - you'll need it later.`,
}); });
console.log("Slack agent running"); app.start();
GPT plus 代充 只需 145 你需要创建一个Slack应用来运行它:
- 最简单的方法是访问这个链接:它预先填写了创建新应用所需的所有权限,以便与机器人交互。该链接是使用这个脚本生成的。
- 创建后,在"基本信息"页面上生成一个具有
connections:write范围的应用级令牌。Slack会要求你为其命名——任何名称都可以。该令牌将是SLACK_APP_TOKEN环境变量的值。 - 然后,通过访问"安装应用"页面并将应用安装到工作区来生成
SLACK_BOT_TOKEN环境变量。
现在使用以下命令运行机器人:
GPT plus 代充 只需 145SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... bun run index.ts
在Slack中找到它。它会在搜索栏中显示为"picobot"。向它发送一条私信,它应该会回复。
现在让我们使用LLM生成回复。我们将使用Anthropic的SDK:
GPT plus 代充 只需 145bun add @anthropic-ai/sdk
LLM将能够通过工具调用发送Slack消息:
import Anthropic from "@anthropic-ai/sdk";
type ToolWithExecute = Anthropic.Tool & { execute: (input: any) => Promise
; };
function createTools(channel: string, threadTs: string): ToolWithExecute[] { return [
GPT plus 代充 只需 145{ name: "send_slack_message", description: "Send a message to the user in Slack. This is the only way to communicate with the user.", input_schema: { type: "object", properties: { text: { type: "string", description: "The message text (supports Slack mrkdwn formatting)", }, }, required: ["text"], }, execute: async (input: { text: string }) => { await app.client.chat.postMessage({ channel, thread_ts: threadTs, text: input.text, blocks: [ { type: "markdown", text: input.text, }, ], }); return "Message sent."; }, },
]; }
我们将创建一个函数,该函数调用Anthropic的API来生成响应,如果响应包含工具调用,则执行工具调用。
GPT plus 代充 只需 145async function generateMessages(args: {
channel: string; threadTs: string; system: string; messages: Anthropic.MessageParam[]; }): Promise
{ const { channel, threadTs, system, messages } = args; const tools = createTools(channel, threadTs);
console.log("Generating messages for thread", threadTs); const response = await anthropic.messages.create({
model: "claude-opus-4-6", max_tokens: 8096, system, messages, tools,
}); console.log(
GPT plus 代充 只需 145`Response generated for thread ${threadTs}: ${response.usage.output_tokens} tokens`,
);
const toolsByName = new Map(tools.map((t) => [t.name, t])); const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const block of response.content)
try { console.log(`Agent used tool ${block.name}`); const tool = toolsByName.get(block.name); if (!tool) { throw new Error(`tool "${block.name}" not found`); } const result = await tool.execute(block.input); toolResults.push(); } catch (e: any) { console.warn(`Agent tried to use tool ${block.name} but failed`, e); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: `Error: ${e.message}`, is_error: true, }); }
}
messages.push({ role: "assistant", content: response.content }); if (toolResults.length > 0) {
GPT plus 代充 只需 145messages.push({ role: "user", content: toolResults, });
}
return messages; }
最后,我们将在消息事件处理程序中调用generateMessages:
GPT plus 代充 只需 145app.event("message", async ({ event }) =>
const threadTs = event.thread_ts ?? event.ts; const channel = event.channel;
// Only allow authorized users to interact with the bot if (event.user !== process.env.SLACK_USER_ID) {
await app.client.chat.postMessage({ channel, thread_ts: threadTs, text: `I'm sorry, I'm not authorized to respond to messages from you. Set the `SLACK_USER_ID` environment variable to `${event.user}` to allow me to respond to your messages.`, }); return;
}
// Show a typing indicator to the user while we generate the response // It’ll be auto-cleared once the agent sends a Slack message await app.client.assistant.threads.setStatus({
GPT plus 代充 只需 145channel_id: channel, thread_ts: threadTs, status: "is typing...",
});
await generateMessages({
channel: event.channel, threadTs, system: "You are a helpful Slack assistant.", messages: [ { role: "user", content: `User <@${event.user}> sent this message (timestamp: ${event.ts}) in Slack:
GPT plus 代充 只需 145${event.text}
You must respond using the send_slack_message tool.`,
}, ],
}); });
GPT plus 代充 只需 145 这是目前为止的完整代码------你可以将其保存到index.ts中。
LLM现在可以响应来自授权用户的Slack消息。请记住在运行机器人之前设置SLACK_USER_ID环境变量。
GPT plus 代充 只需 145SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... SLACK_USER_ID=U... bun run index.ts
机器人会响应消息,但它不记得对话内容。
让我们改变这一点。我们将对话历史记录持久化到~/.picobot/threads/中的JSON文件中。每个文件都将以线程时间戳命名,并包含线程的消息。当收到新的Slack消息时,我们将加载线程并将新消息添加到其中。
GPT plus 代充 只需 145import fs from "node:fs";
import path from "node:path"; import os from "node:os";
const configDir = path.resolve(os.homedir(), ".picobot"); const threadsDir = path.resolve(configDir, "threads");
interface Thread { threadTs: string; channel: string; messages: Anthropic.MessageParam[]; }
function saveThread(threadTs: string, thread: Thread): void { fs.mkdirSync(threadsDir, { recursive: true }); return fs.writeFileSync(
path.resolve(threadsDir, `${threadTs}.json`), JSON.stringify(thread, null, 2),
); }
function loadThread(threadTs: string): Thread | undefined { try {
GPT plus 代充 只需 145return JSON.parse( fs.readFileSync(path.resolve(threadsDir, `${threadTs}.json`), "utf-8"), );
} catch (e) {
return undefined;
} }
app.event("message", async ({ event }) => { // … existing code up to the status indicator …
const thread: Thread = loadThread(threadTs) ?? {
GPT plus 代充 只需 145threadTs, channel, messages: [],
}; const messages = await generateMessages({
channel: event.channel, threadTs, system: "You are a helpful Slack assistant.", messages: [ ...thread.messages, { role: "user", content: `User <@${event.user}> sent this message (timestamp: ${event.ts}) in Slack:
GPT plus 代充 只需 145${event.text}
You must respond using the send_slack_message tool.`,
}, ],
}); saveThread(threadTs, {
GPT plus 代充 只需 145...thread, messages,
}); });
这是目前为止的完整代码。
机器人现在会记住对话内容:
现在,我们的机器人会记住对话内容,但它会记住所有内容。如果对话持续足够长的时间,它最终会超出LLM的上下文窗口。当这种情况发生时,LLM会开始"失忆",并且无法再引用旧的对话内容。
为了解决这个问题,我们将实现记忆压缩。当对话历史记录变得太长时,我们将要求LLM将其压缩成一个简短的摘要。然后,我们将用摘要替换旧的对话内容,从而为新的对话腾出空间。
GPT plus 代充 只需 145
// ... existing imports ...
import { dump } from "js-yaml";
// … existing Thread interface …
interface Thread { threadTs: string; channel: string; messages: Anthropic.MessageParam[]; summary?: string; }
// … existing saveThread and loadThread functions …
async function compactThread(thread: Thread): Promise
,
GPT plus 代充 只需 145],
});
const summary = response.content.map((block) => block.text).join(" ");
return {
...thread, messages: [ { role: "assistant", content: summary }, // Replace old messages with summary ], summary,
}; }
app.event("message", async ({ event }) => { // … existing code up to the status indicator …
let thread: Thread = loadThread(threadTs) ?? {
GPT plus 代充 只需 145threadTs, channel, messages: [],
};
// Check if compaction is needed const totalTokens = await anthropic.countTokens({
model: "claude-opus-4-6", messages: thread.messages,
});
if (totalTokens > 4000) { // Arbitrary threshold for compaction
GPT plus 代充 只需 145thread = await compactThread(thread);
}
const messages = await generateMessages({
channel: event.channel, threadTs, system: "You are a helpful Slack assistant.", messages: [ ...thread.messages, { role: "user", content: `User <@${event.user}> sent this message (timestamp: ${event.ts}) in Slack:
GPT plus 代充 只需 145${event.text}
You must respond using the send_slack_message tool.`,
}, ],
}); saveThread(threadTs, {
GPT plus 代充 只需 145...thread, messages,
}); });
这是目前为止的完整代码。
机器人现在会压缩对话历史记录,以避免超出LLM的上下文窗口。
OpenClaw最强大的功能之一是它能够使用技能。技能是LLM可以调用的函数,以执行特定任务。例如,一个技能可以用来搜索网络,另一个技能可以用来生成图像。
我们将添加一个简单的技能,允许LLM搜索网络。我们将使用axios库来发出HTTP请求。
GPT plus 代充 只需 145
bun add axios
我们将创建一个skills目录,并在其中添加一个web_search.ts文件:
GPT plus 代充 只需 145// skills/web_search.ts
import axios from "axios";
export async function webSearch(query: string): Promise
, }); // Parse the HTML response to extract relevant information // This is a simplified example, a real implementation would use a more robust HTML parser return response.data.match(/
/)?.[1" target="_blank">https://jishuzhan.net/article/"(.*?)">/)?.[1] || "No results found."; }
现在,我们将修改createTools函数以包含web_search技能:
GPT plus 代充 只需 145// ... existing imports ...
import { webSearch } from "./skills/web_search";
function createTools(channel: string, threadTs: string): ToolWithExecute[] { return [
// ... existing send_slack_message tool ... { name: "web_search", description: "Search the web for a given query.", input_schema: { type: "object", properties: { query: { type: "string", description: "The search query.", }, }, required: ["query"], }, execute: async (input: { query: string }) => { return await webSearch(input.query); }, },
]; }
GPT plus 代充 只需 145 现在,LLM将能够使用web_search技能来搜索网络。例如,如果你问它"OpenClaw是什么?",它可能会使用web_search技能来查找相关信息。
OpenClaw最强大的功能之一是它能够主动行动,而无需明确提示。例如,它可以监视GitHub仓库的更新,并在有新提交时通知你。
我们将添加一个简单的机制,允许LLM主动行动。我们将使用一个cron作业来定期触发LLM,并让它决定是否需要采取行动。
GPT plus 代充 只需 145// ... existing imports ...
import { CronJob } from "cron";
// … existing app.event("message") handler …
new CronJob( "0 * * * * *", // Run every minute async () => ,
], }); // If the LLM responded with NO_REPLY, do nothing if (messages.length === thread.messages.length + 1 && messages[messages.length - 1].content === "NO_REPLY") { continue; } saveThread(threadTs, { ...thread, messages, }); }
}, null, // onComplete true, // start "America/Los_Angeles" // timeZone );
GPT plus 代充 只需 145 这是目前为止的完整代码。
机器人现在会主动行动,而无需明确提示。例如,它可以监视GitHub仓库的更新,并在有新提交时通知你。
在这篇文章中,我们从零开始构建了一个类似OpenClaw的智能体,只用了不到400行代码。我们涵盖了以下功能:
- 响应Slack消息
- 作为LLM回复
- 跟踪对话
- 记忆压缩
- 使用技能
- 主动行动
我希望这篇博客文章能帮助你更好地理解OpenClaw的内部工作原理,并将这些想法应用到你自己的智能体系统中。
你可以在这里找到完整的代码。
- Anthropic SDK
- Slack SDK
- Bun
- js-yaml
- axios
- cron
- OpenClaw Compaction
- Blink
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/245033.html