本指南将展示如何在 Cloudflare Workers 上构建并部署 AI 驱动的 Slack bot,它可以:
- 响应私信
- 在频道中被 @提及时回复
- 在线程中保持对话上下文
- 使用 AI 生成智能回复
你的 Slack Agent 将是多租户应用,即单次部署可服务多个 Slack 工作区。每个工作区拥有由 Agents SDK 驱动的独立 Agent 实例与专用存储。
完整示例代码见此处 ↗。
开始前,你需要:
- Cloudflare 账户 ↗
- 已安装 Node.js ↗(v18 或更高)
- 拥有安装应用权限的 Slack 工作区 ↗
- OpenAI API key ↗(或其他 LLM 提供商)
首先,创建 Agent 用于与 Slack 交互的新 Slack App:
- 前往 api.slack.com/apps ↗ 并选择 Create New App(创建新应用)。
- 选择 From scratch(从零开始)。
- 为应用命名(例如 "My AI Assistant")并选择工作区。
- 选择 Create App(创建应用)。
在 Slack App 设置中,前往 OAuth & Permissions(OAuth 与权限) 并添加以下 Bot Token Scopes(Bot 令牌范围):
chat:write— 以 bot 身份发送消息chat:write.public— 无需加入频道即可发送消息channels:history— 查看公开频道中的消息app_mentions:read— 接收 @提及im:write— 发送私信im:history— 查看私信历史
部署 Agent 后你将配置 Event Subscriptions URL。现在请先在 Slack App 设置中前往 Event Subscriptions(事件订阅) 并做好启用准备。
订阅以下 bot 事件:
app_mention— bot 被 @提及时message.im— 发给 bot 的私信
暂时不要启用。部署后再启用。
从 Slack App 设置中收集以下值:
- Basic Information(基本信息) > App Credentials(应用凭据):
- Client ID(客户端 ID)
- Client Secret(客户端密钥)
- Signing Secret(签名密钥)
请妥善保存 — 下一步会用到。
- 为 Slack Agent 创建新项目:
npm create cloudflare@latest -- my-slack-agentyarn create cloudflare my-slack-agentpnpm create cloudflare@latest my-slack-agent- 进入项目目录:
cd my-slack-agent- 安装所需依赖:
npm install agents openai- 在项目根目录创建
.env文件用于本地开发密钥:
touch .env- 将凭据添加到
.env:
SLACK_CLIENT_ID="your-slack-client-id"
SLACK_CLIENT_SECRET="your-slack-client-secret"
SLACK_SIGNING_SECRET="your-slack-signing-secret"
OPENAI_API_KEY="your-openai-api-key"
OPENAI_BASE_URL="https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY/openai"- 更新
wrangler.jsonc以配置 Agent:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-slack-agent",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-17",
"compatibility_flags": [
"nodejs_compat"
],
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent",
"script_name": "my-slack-agent"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": [
"MyAgent"
]
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-slack-agent"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "MyAgent"
class_name = "MyAgent"
script_name = "my-slack-agent"
[[migrations]]
tag = "v1"
new_classes = [ "MyAgent" ]-
首先在
src/slack.ts创建基础SlackAgent类。该类处理 OAuth、request 验证与事件路由。完整实现见 GitHub ↗。 -
在
src/index.ts创建 Agent 实现:
import { env } from "cloudflare:workers";
import { SlackAgent } from "./slack";
import { OpenAI } from "openai";
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY,
baseURL: env.OPENAI_BASE_URL,
});
type SlackMsg = {
user?: string;
text?: string;
ts: string;
thread_ts?: string;
subtype?: string;
bot_id?: string;
};
function normalizeForLLM(msgs: SlackMsg[], selfUserId: string) {
return msgs.map((m) => {
const role = m.user && m.user !== selfUserId ? "user" : "assistant";
const text = (m.text ?? "").replace(/<@([A-Z0-9]+)>/g, "@$1");
return { role, content: text };
});
}
export class MyAgent extends SlackAgent {
async generateAIReply(conversation: SlackMsg[]) {
const selfId = await this.ensureAppUserId();
const messages = normalizeForLLM(conversation, selfId);
const system = `You are a helpful AI assistant in Slack.
Be brief, specific, and actionable. If you're unsure, ask a single clarifying question.`;
const input = [{ role: "system", content: system }, ...messages];
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: input,
});
const msg = response.choices[0].message.content;
if (!msg) throw new Error("No message from AI");
return msg;
}
async onSlackEvent(event: { type: string } & Record<string, unknown>) {
// Ignore bot messages and subtypes (edits, joins, etc.)
if (event.bot_id || event.subtype) return;
// Handle direct messages
if (event.type === "message") {
const e = event as unknown as SlackMsg & { channel: string };
const isDM = (e.channel || "").startsWith("D");
const mentioned = (e.text || "").includes(
`<@${await this.ensureAppUserId()}>`,
);
if (!isDM && !mentioned) return;
const conversation = await this.fetchConversation(e.channel);
const content = await this.generateAIReply(conversation);
await this.sendMessage(content, { channel: e.channel });
return;
}
// Handle @mentions in channels
if (event.type === "app_mention") {
const e = event as unknown as SlackMsg & {
channel: string;
text?: string;
};
const thread = await this.fetchThread(e.channel, e.thread_ts || e.ts);
const content = await this.generateAIReply(thread);
await this.sendMessage(content, {
channel: e.channel,
thread_ts: e.thread_ts || e.ts,
});
return;
}
}
}
export default MyAgent.listen({
clientId: env.SLACK_CLIENT_ID,
clientSecret: env.SLACK_CLIENT_SECRET,
slackSigningSecret: env.SLACK_SIGNING_SECRET,
scopes: [
"chat:write",
"chat:write.public",
"channels:history",
"app_mentions:read",
"im:write",
"im:history",
],
});启动开发服务器:
npm run devAgent 现运行于 http://localhost:8787。
Agent 本地运行后,需要将其暴露给 Slack。使用 Cloudflare Tunnel 创建安全隧道:
npx cloudflared tunnel --url http://localhost:8787将输出类似 https://random-subdomain.trycloudflare.com 的公开 URL。
返回 Slack App 设置:
-
前往 Event Subscriptions(事件订阅)。
-
将 Enable Events 切换为 On。
-
输入 Request URL:
https://random-subdomain.trycloudflare.com/slack。 -
Slack 会发送验证 request — 若 Agent 运行正确,应显示 Verified。
-
在 Subscribe to bot events(订阅机器人事件) 下添加:
app_mentionmessage.im
-
选择 Save Changes(保存更改)。
在浏览器中访问 http://localhost:8787/install。将重定向到 Slack 授权页。选择 Allow(允许) 将应用安装到工作区。
授权后,浏览器应显示 "Successfully registered!"。
打开 Slack。然后:
- 向 bot 发送私信 — 应收到 AI 生成的回复。
- 在频道中 @提及 bot(例如
@My AI Assistant hello)— 应在线程中回复。
若一切正常,即可部署到生产环境!
- 部署前,将 secret 添加到 Cloudflare:
npx wrangler secret put SLACK_CLIENT_ID
npx wrangler secret put SLACK_CLIENT_SECRET
npx wrangler secret put SLACK_SIGNING_SECRET
npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put OPENAI_BASE_URL- 部署 Agent:
npx wrangler deploy部署后将获得类似以下生产 URL:
https://my-slack-agent.your-account.workers.dev返回 Slack App 设置:
- 前往 Event Subscriptions(事件订阅)。
- 将 Request URL 更新为生产 URL:
https://my-slack-agent.your-account.workers.dev/slack。 - 选择 Save Changes(保存更改)。
Agent 部署后,可与他人共享:
- 单工作区:通过
https://my-slack-agent.your-account.workers.dev/install安装。 - 公开分发:将应用提交到 Slack App Directory ↗。
每个安装应用的工作区都将获得具有专用存储的隔离 Agent 实例。
Slack Agent 使用 Durable Objects 为每个 Slack 工作区提供隔离、有状态的实例:
- 每个工作区的
team_id用作 Durable Object ID。 - 每个 Agent 实例在 KV 存储中保存自己的 Slack access token。
- 对话按需从 Slack API 获取。
- 所有 Agent 逻辑在隔离、一致的环境中运行。
Agent 处理 Slack 的 OAuth 2.0 流程:
- 用户访问
/install> 重定向到 Slack 授权。 - 用户选择 Allow(允许) > Slack 重定向到
/accept并携带 authorization code。 - Agent 用 code 交换 access token。
- Agent 将 token 存储在工作区的 Durable Object 中。
Slack 发送事件时:
- Request 到达
/slack端点。 - Agent 使用 HMAC-SHA256 验证 request 签名。
- Agent 将事件路由到正确工作区的 Durable Object。
onSlackEvent方法处理事件并生成响应。
在 src/index.ts 中更新 model:
const response = await openai.chat.completions.create({
model: "gpt-4o", // 或其他 model
messages: input,
});在 Durable Object 存储中保存对话历史:
async storeMessage(channel: string, message: SlackMsg) {
const history = await this.ctx.storage.kv.get(`history:${channel}`) || [];
history.push(message);
await this.ctx.storage.kv.put(`history:${channel}`, history);
}在 onSlackEvent 中添加自定义逻辑:
async onSlackEvent(event: { type: string } & Record<string, unknown>) {
if (event.type === "message") {
const e = event as unknown as SlackMsg & { channel: string };
if (e.text?.includes("help")) {
await this.sendMessage("Here's how I can help...", {
channel: e.channel
});
return;
}
}
// ... rest of your event handling
}将 OpenAI 替换为 Workers AI:
import { Ai } from "@cloudflare/ai";
export class MyAgent extends SlackAgent {
async generateAIReply(conversation: SlackMsg[]) {
const ai = new Ai(this.ctx.env.AI);
const response = await ai.run("@cf/meta/llama-3-8b-instruct", {
messages: normalizeForLLM(conversation, await this.ensureAppUserId()),
});
return response.response;
}
}- 添加 Slack Interactive Components ↗(按钮、modal)
- 将 Agent 连接到 MCP server
- 添加速率限制以防止滥用
- 实现对话 state 管理
- 使用 Workers Analytics Engine 跟踪使用情况
- 添加 schedules 用于定时任务