构建一个 Slack Agent
部署你的第一个 Slack Agent
本指南将带你在 Cloudflare Workers 上构建并部署一个由 AI 驱动的 Slack 机器人,它可以:
- 回复私信
- 在频道中被 @ 时回复
- 在线程(thread)中保持对话上下文
- 使用 AI 生成智能回答
你的 Slack Agent 将是一个多租户应用,也就是说一次部署可以服务多个 Slack workspace。每个 workspace 都拥有自己独立的 Agent 实例和专属存储,由 Agents SDK 提供支持。
可以在 这里 ↗ 查看本示例的完整代码。
前提条件
开始之前,你需要:
- 一个 Cloudflare 账户 ↗
- 已安装 Node.js ↗(v18 或更高版本)
- 一个你有权限安装应用的 Slack workspace ↗
- 一个 OpenAI API key ↗(或其他 LLM 提供商)
1. 创建一个 Slack App
首先,创建一个新的 Slack App,你的 Agent 将通过它与 Slack 交互:
- 前往 api.slack.com/apps ↗,选择 Create New App。
- 选择 From scratch。
- 给你的 App 取个名字(例如 “My AI Assistant”),并选择你的 workspace。
- 选择 Create App。
配置 OAuth 与权限
在 Slack App 设置中进入 OAuth & Permissions,添加以下 Bot Token Scopes:
chat:write—— 以机器人身份发送消息chat:write.public—— 在不加入频道的情况下向频道发送消息channels:history—— 查看公开频道中的消息app_mentions:read—— 接收 @ 提及im:write—— 发送私信im:history—— 查看私信历史
启用 Event Subscriptions
部署 Agent 之后,你需要配置 Event Subscriptions URL。但现在,先在 Slack App 设置中前往 Event Subscriptions,准备启用它。
订阅以下机器人事件:
app_mention—— 机器人被 @ 时message.im—— 发送给机器人的私信
先不要启用它,等部署完成后再开启。
获取 Slack 凭据
从 Slack App 设置中收集以下值:
- Basic Information > App Credentials:
- Client ID
- Client Secret
- Signing Secret
把这些保存好 —— 下一步会用到。
2. 创建 Slack Agent 项目
- 为 Slack Agent 创建一个新项目:
npm yarn pnpm
npm create cloudflare@latest -- my-slack-agent
yarn create cloudflare my-slack-agent
pnpm create cloudflare@latest my-slack-agent
- 进入项目目录:
Terminal window
cd my-slack-agent
- 安装所需依赖:
Terminal window
npm install agents openai
3. 设置环境变量
- 在项目根目录创建一个
.env文件,用于本地开发的 secrets:
Terminal window
touch .env
- 把你的凭据加到
.env:
Terminal window
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"
注意
OPENAI_BASE_URL 是可选的,但建议设置。使用 Cloudflare AI Gateway 可以为你的 AI 请求提供缓存、限流和分析能力。
- 更新
wrangler.jsonc来配置你的 Agent:
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-slack-agent",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-04-29",
"compatibility_flags": [
"nodejs_compat"
],
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent",
"script_name": "my-slack-agent"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": [
"MyAgent"
]
}
]
}
Explain Code
TOML
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-slack-agent"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-04-29"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "MyAgent"
class_name = "MyAgent"
script_name = "my-slack-agent"
[[migrations]]
tag = "v1"
new_classes = [ "MyAgent" ]
Explain Code
4. 创建你的 Slack Agent
- 首先,在
src/slack.ts创建基础的SlackAgent类。这个类负责处理 OAuth、请求验证和事件路由。完整实现可以在 GitHub 上查看 ↗。 - 然后在
src/index.ts创建你的 Agent 实现:
TypeScript
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",
],
});
Explain Code
5. 本地测试
启动开发服务器:
Terminal window
npm run dev
你的 Agent 现在运行在 http://localhost:8787。
配置 Slack Event Subscriptions
现在 Agent 已经在本地运行,你需要把它暴露给 Slack。使用 Cloudflare Tunnel 创建一个安全隧道:
Terminal window
npx cloudflared tunnel --url http://localhost:8787
它会输出一个公网 URL,例如 https://random-subdomain.trycloudflare.com。
回到 Slack App 设置:
- 进入 Event Subscriptions。
- 把 Enable Events 切换为 On。
- 输入 Request URL:
https://random-subdomain.trycloudflare.com/slack。 - Slack 会发送一个验证请求 —— 如果你的 Agent 运行正常,会显示 Verified。
- 在 Subscribe to bot events 下添加:
app_mentionmessage.im
- 选择 Save Changes。
注意
Cloudflare Tunnel URL 是临时的。本地测试时,每次重启隧道都需要更新一次 Request URL。
把你的应用安装到 Slack
在浏览器中访问 http://localhost:8787/install。这会把你重定向到 Slack 的授权页面。选择 Allow 把应用安装到你的 workspace。
授权完成后,你应该会在浏览器看到 “Successfully registered!”。
测试你的 Agent
打开 Slack,然后:
- 给机器人发送一条 DM —— 它应该用 AI 生成的消息回复你。
- 在频道里 @ 你的机器人(例如
@My AI Assistant hello) —— 它应该在线程里回复。
如果一切正常,你就可以部署到生产环境了!
6. 部署到生产环境
- 部署前,把 secrets 添加到 Cloudflare:
Terminal window
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
注意
如果你不使用 AI Gateway,可以跳过 OPENAI_BASE_URL。
- 部署你的 Agent:
Terminal window
npx wrangler deploy
部署后,你会得到一个生产环境的 URL,例如:
https://my-slack-agent.your-account.workers.dev
更新 Slack Event Subscriptions
回到 Slack App 设置:
- 进入 Event Subscriptions。
- 把 Request URL 更新为生产环境 URL:
https://my-slack-agent.your-account.workers.dev/slack。 - 选择 Save Changes。
分发你的应用
Agent 部署好之后,你就可以把它分享给别人了:
- 单一 workspace:通过
https://my-slack-agent.your-account.workers.dev/install安装。 - 公开分发:把你的应用提交到 Slack App Directory ↗。
每个安装你的应用的 workspace 都会得到一个独立的 Agent 实例和专属存储。
工作原理
用 Durable Objects 实现多租户
你的 Slack Agent 使用 Durable Objects 为每个 Slack workspace 提供隔离、有状态的实例:
- 每个 workspace 的
team_id被用作 Durable Object 的 ID。 - 每个 Agent 实例都把自己的 Slack access token 存在 KV 存储中。
- 对话内容按需从 Slack 的 API 拉取。
- 所有 Agent 逻辑都在一个隔离、一致的环境中运行。
OAuth 流程
Agent 处理 Slack 的 OAuth 2.0 流程:
- 用户访问
/install> 被重定向到 Slack 授权页面。 - 用户选择 Allow > Slack 带着授权码重定向到
/accept。 - Agent 用授权码换取 access token。
- Agent 把 token 存到该 workspace 对应的 Durable Object 中。
事件处理
当 Slack 发送事件时:
- 请求到达
/slack端点。 - Agent 使用 HMAC-SHA256 验证请求签名。
- Agent 把事件路由到对应 workspace 的 Durable Object。
onSlackEvent方法处理事件并生成响应。
自定义你的 Agent
更换 AI 模型
在 src/index.ts 中更新模型:
TypeScript
const response = await openai.chat.completions.create({
model: "gpt-4o", // or any other model
messages: input,
});
添加对话记忆
把对话历史存到 Durable Object 存储中:
TypeScript
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 中添加自定义逻辑:
TypeScript
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
}
Explain Code
使用其他 LLM 提供商
把 OpenAI 替换为 Workers AI:
TypeScript
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;
}
}
Explain Code
下一步
- 添加 Slack Interactive Components ↗(按钮、模态框)
- 把 Agent 连接到 MCP server
- 添加限流以防止滥用
- 实现对话状态管理
- 用 Workers Analytics Engine 跟踪使用情况
- 添加 schedules 处理定时任务
相关资源
Agents documentation 完整的 Agents 框架文档。
Durable Objects 了解底层的有状态基础设施。
Slack API Slack API 官方文档。
OpenAI API OpenAI API 官方文档。