Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

构建一个 Slack Agent

部署你的第一个 Slack Agent

本指南将带你在 Cloudflare Workers 上构建并部署一个由 AI 驱动的 Slack 机器人,它可以:

  • 回复私信
  • 在频道中被 @ 时回复
  • 在线程(thread)中保持对话上下文
  • 使用 AI 生成智能回答

你的 Slack Agent 将是一个多租户应用,也就是说一次部署可以服务多个 Slack workspace。每个 workspace 都拥有自己独立的 Agent 实例和专属存储,由 Agents SDK 提供支持。

可以在 这里 ↗ 查看本示例的完整代码。

前提条件

开始之前,你需要:

1. 创建一个 Slack App

首先,创建一个新的 Slack App,你的 Agent 将通过它与 Slack 交互:

  1. 前往 api.slack.com/apps ↗,选择 Create New App
  2. 选择 From scratch
  3. 给你的 App 取个名字(例如 “My AI Assistant”),并选择你的 workspace。
  4. 选择 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 设置中收集以下值:

  1. Basic Information > App Credentials:
    • Client ID
    • Client Secret
    • Signing Secret

把这些保存好 —— 下一步会用到。

2. 创建 Slack Agent 项目

  1. 为 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
  1. 进入项目目录:

Terminal window


cd my-slack-agent


  1. 安装所需依赖:

Terminal window


npm install agents openai


3. 设置环境变量

  1. 在项目根目录创建一个 .env 文件,用于本地开发的 secrets:

Terminal window


touch .env


  1. 把你的凭据加到 .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 请求提供缓存、限流和分析能力。

  1. 更新 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

  1. 首先,在 src/slack.ts 创建基础的 SlackAgent 类。这个类负责处理 OAuth、请求验证和事件路由。完整实现可以在 GitHub 上查看 ↗
  2. 然后在 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 设置:

  1. 进入 Event Subscriptions
  2. Enable Events 切换为 On
  3. 输入 Request URL:https://random-subdomain.trycloudflare.com/slack
  4. Slack 会发送一个验证请求 —— 如果你的 Agent 运行正常,会显示 Verified
  5. Subscribe to bot events 下添加:
    • app_mention
    • message.im
  6. 选择 Save Changes

注意

Cloudflare Tunnel URL 是临时的。本地测试时,每次重启隧道都需要更新一次 Request URL。

把你的应用安装到 Slack

在浏览器中访问 http://localhost:8787/install。这会把你重定向到 Slack 的授权页面。选择 Allow 把应用安装到你的 workspace。

授权完成后,你应该会在浏览器看到 “Successfully registered!”。

测试你的 Agent

打开 Slack,然后:

  1. 给机器人发送一条 DM —— 它应该用 AI 生成的消息回复你。
  2. 在频道里 @ 你的机器人(例如 @My AI Assistant hello) —— 它应该在线程里回复。

如果一切正常,你就可以部署到生产环境了!

6. 部署到生产环境

  1. 部署前,把 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

  1. 部署你的 Agent:

Terminal window


npx wrangler deploy


部署后,你会得到一个生产环境的 URL,例如:


https://my-slack-agent.your-account.workers.dev


更新 Slack Event Subscriptions

回到 Slack App 设置:

  1. 进入 Event Subscriptions
  2. 把 Request URL 更新为生产环境 URL:https://my-slack-agent.your-account.workers.dev/slack
  3. 选择 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 流程:

  1. 用户访问 /install > 被重定向到 Slack 授权页面。
  2. 用户选择 Allow > Slack 带着授权码重定向到 /accept
  3. Agent 用授权码换取 access token。
  4. Agent 把 token 存到该 workspace 对应的 Durable Object 中。

事件处理

当 Slack 发送事件时:

  1. 请求到达 /slack 端点。
  2. Agent 使用 HMAC-SHA256 验证请求签名。
  3. Agent 把事件路由到对应 workspace 的 Durable Object。
  4. 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

下一步

相关资源

Agents documentation 完整的 Agents 框架文档。

Durable Objects 了解底层的有状态基础设施。

Slack API Slack API 官方文档。

OpenAI API OpenAI API 官方文档。