INTEGRITY Документация

Slack agent

Разверните своего первого Slack Agent

Это руководство покажет вам, как собрать и развернуть на Cloudflare Workers Slack-бота на основе AI, который умеет:

Ваш Slack-агент будет мультитенантным приложением: одно развёртывание сможет обслуживать несколько рабочих пространств Slack. Каждое рабочее пространство получает собственный изолированный экземпляр агента с выделенным хранилищем на основе Agents SDK.

Полный код этого примера можно посмотреть здесь ↗.

Предварительные требования

Прежде чем начать, вам потребуется:

1. Создайте Slack App

Сначала создайте новый Slack App, через который ваш агент будет взаимодействовать со Slack:

  1. Перейдите в api.slack.com/apps ↗ и выберите Создать новое приложение.
  2. Выберите С нуля.
  3. Присвойте своему приложению имя (например, «My AI Assistant») и выберите рабочее пространство.
  4. Выберите Create App.

Configure OAuth & Permissions

В настройках вашего Slack App перейдите в OAuth & Permissions и добавьте следующее Bot Token Scopes:

Включите Event Subscriptions

URL-адрес Event Subscriptions вы настроите позже, после развёртывания агента. А пока перейдите на Подписки на события в настройках вашего Slack App и подготовьтесь к его включению.

Подпишитесь на следующие события бота:

Пока не включайте это. Вы включите это после развёртывания.

Получите учётные данные Slack

В настройках вашего Slack App соберите следующие значения:

  1. Основная информация > Учётные данные приложения:
    • Client ID
    • Секрет клиента
    • Секрет подписи

Держите их под рукой: они понадобятся вам на следующем шаге.

2. Создайте проект Slack Agent

  1. Создайте новый проект для своего Slack Agent:
npm create cloudflare@latest -- my-slack-agent
  1. Перейдите в каталог проекта:
cd my-slack-agent
  1. Установите необходимые зависимости:
npm install agents openai

3. Настройте переменные окружения

  1. Создайте .env файл в корне проекта для локальных секретов разработки:
touch .env
  1. Добавьте свои учётные данные в .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"
  1. Обновите ваш 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-28",
	"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-28"
compatibility_flags = [ "nodejs_compat" ]

[[durable_objects.bindings]]
name = "MyAgent"
class_name = "MyAgent"
script_name = "my-slack-agent"

[[migrations]]
tag = "v1"
new_classes = [ "MyAgent" ]

4. Создайте агента Slack

  1. Сначала создайте базовый SlackAgent класс в src/slack.ts. Этот класс отвечает за OAuth, проверку запросов и маршрутизацию событий. Вы можете посмотреть полная реализация на GitHub ↗.

  2. Теперь создайте реализацию своего агента в src/index.ts:

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",
	],
});

5. Протестируйте локально

Запустите сервер разработки:

npm run dev

Теперь ваш агент работает по адресу http://localhost:8787.

Настройте Slack Event Subscriptions

Теперь, когда ваш агент запущен локально, его нужно сделать доступным для Slack. Используйте Cloudflare Tunnel чтобы создать защищённый туннель:

npx cloudflared tunnel --url http://localhost:8787

В результате будет выведен публичный URL вида https://random-subdomain.trycloudflare.com.

Вернитесь к настройкам своего Slack App:

  1. Перейдите в Подписки на события.

  2. Переключить Включите Events к Включено.

  3. Введите ваш Request URL: https://random-subdomain.trycloudflare.com/slack.

  4. Slack отправит запрос на проверку. Если ваш агент работает корректно, должно отобразиться Проверено.

  5. В разделе Подпишитесь на события бота, добавьте:

    • app_mention
    • message.im
  6. Выберите Сохранить изменения.

Установка приложения в Slack

Перейдите на http://localhost:8787/install в вашем браузере. Это перенаправит вас на страницу авторизации Slack. Выберите Allow чтобы установить приложение в ваше рабочее пространство.

После авторизации вы должны увидеть в браузере сообщение "Successfully registered!".

Протестируйте своего агента

Откройте Slack. Затем:

  1. Отправьте боту личное сообщение: он должен ответить сообщением, сгенерированным ИИ.
  2. Упомяните своего бота в канале (например, @My AI Assistant hello), он должен отвечать в теме.

Если всё работает, вы готовы к развёртыванию в продакшене!

6. Разверните в продакшене

  1. Перед развёртыванием добавьте свои секреты в 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
  1. Разверните своего агента:
npx wrangler deploy

После развёртывания вы получите рабочий URL, например:

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

Обновите Slack Event Subscriptions

Вернитесь к настройкам своего Slack App:

  1. Перейдите в Подписки на события.
  2. Обновите Request URL на ваш продакшен-URL: https://my-slack-agent.your-account.workers.dev/slack.
  3. Выберите Сохранить изменения.

Распространите своё приложение

Теперь, когда ваш агент развёрнут, вы можете поделиться им с другими:

Каждое рабочее пространство, устанавливающее ваше приложение, получит собственный изолированный экземпляр агента с выделенным хранилищем.

Как это работает

Мультитенантность с Durable Objects

Ваш Slack-агент использует Durable Objects чтобы предоставлять изолированные экземпляры с сохранением состояния для каждого рабочего пространства Slack:

OAuth-поток

Агент обрабатывает поток OAuth 2.0 сервиса Slack:

  1. Пользователь посещает /install > перенаправлено на авторизацию Slack.
  2. Пользователь выбирает Allow > Slack перенаправляет на /accept с кодом авторизации.
  3. Agent обменивает код на токен доступа.
  4. Agent сохраняет токен в Durable Object рабочего пространства.

Обработка событий

Когда Slack отправляет событие:

  1. Запрос поступает на /slack конечная точка.
  2. Agent проверяет подпись запроса с помощью HMAC-SHA256.
  3. Agent направляет событие в Durable Object нужного рабочего пространства.
  4. onSlackEvent метод обрабатывает событие и формирует ответ.

Настройка агента

Смените модель ИИ

Обновите модель в src/index.ts:

const response = await openai.chat.completions.create({
	model: "gpt-4o", // or any other 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
}

Используйте разных провайдеров LLM

Замените 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;
	}
}

Следующие шаги

Документация Agents

Полная документация по фреймворку Agents.

Durable Objects

Подробнее о базовой инфраструктуре с состоянием.

Slack API

Официальная документация API Slack.

OpenAI API

Официальная документация API OpenAI.