← Создание агентов на Cloudflare / agents / examples
Slack agent
Разверните своего первого Slack Agent
Это руководство покажет вам, как собрать и развернуть на Cloudflare Workers Slack-бота на основе AI, который умеет:
- Отвечать на личные сообщения
- Отвечайте при упоминании в каналах
- Поддерживайте контекст беседы в тредах
- Используйте ИИ для генерации интеллектуальных ответов
Ваш Slack-агент будет мультитенантным приложением: одно развёртывание сможет обслуживать несколько рабочих пространств Slack. Каждое рабочее пространство получает собственный изолированный экземпляр агента с выделенным хранилищем на основе Agents SDK.
Полный код этого примера можно посмотреть здесь ↗.
Предварительные требования
Прежде чем начать, вам потребуется:
- A Аккаунт Cloudflare ↗
- Node.js ↗ установлен (v18 или новее)
- A Рабочее пространство Slack ↗ где у вас есть разрешение на установку приложений
- Одна OpenAI API key ↗ (или другой провайдер LLM)
1. Создайте Slack App
Сначала создайте новый Slack App, через который ваш агент будет взаимодействовать со Slack:
- Перейдите в api.slack.com/apps ↗ и выберите Создать новое приложение.
- Выберите С нуля.
- Присвойте своему приложению имя (например, «My AI Assistant») и выберите рабочее пространство.
- Выберите Create App.
Configure OAuth & Permissions
В настройках вашего Slack App перейдите в OAuth & Permissions и добавьте следующее Bot Token Scopes:
chat:write: отправка сообщений от имени ботаchat:write.public: отправка сообщений в каналы без присоединения к нимchannels:history: просмотр сообщений в публичных каналахapp_mentions:read: получение упоминанийim:write: отправка личных сообщенийim:history: просмотр истории личных сообщений
Включите Event Subscriptions
URL-адрес Event Subscriptions вы настроите позже, после развёртывания агента. А пока перейдите на Подписки на события в настройках вашего Slack App и подготовьтесь к его включению.
Подпишитесь на следующие события бота:
app_mention: когда бота упоминают через @message.im: личные сообщения боту
Пока не включайте это. Вы включите это после развёртывания.
Получите учётные данные Slack
В настройках вашего Slack App соберите следующие значения:
- Основная информация > Учётные данные приложения:
- Client ID
- Секрет клиента
- Секрет подписи
Держите их под рукой: они понадобятся вам на следующем шаге.
2. Создайте проект Slack Agent
- Создайте новый проект для своего Slack Agent:
npm create cloudflare@latest -- my-slack-agent- Перейдите в каталог проекта:
cd my-slack-agent- Установите необходимые зависимости:
npm install agents openai3. Настройте переменные окружения
- Создайте
.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-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
-
Сначала создайте базовый
SlackAgentкласс вsrc/slack.ts. Этот класс отвечает за OAuth, проверку запросов и маршрутизацию событий. Вы можете посмотреть полная реализация на GitHub ↗. -
Теперь создайте реализацию своего агента в
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:
-
Перейдите в Подписки на события.
-
Переключить Включите Events к Включено.
-
Введите ваш Request URL:
https://random-subdomain.trycloudflare.com/slack. -
Slack отправит запрос на проверку. Если ваш агент работает корректно, должно отобразиться Проверено.
-
В разделе Подпишитесь на события бота, добавьте:
app_mentionmessage.im
-
Выберите Сохранить изменения.
Установка приложения в Slack
Перейдите на http://localhost:8787/install в вашем браузере. Это перенаправит вас на страницу авторизации Slack. Выберите Allow чтобы установить приложение в ваше рабочее пространство.
После авторизации вы должны увидеть в браузере сообщение "Successfully registered!".
Протестируйте своего агента
Откройте Slack. Затем:
- Отправьте боту личное сообщение: он должен ответить сообщением, сгенерированным ИИ.
- Упомяните своего бота в канале (например,
@My AI Assistant hello), он должен отвечать в теме.
Если всё работает, вы готовы к развёртыванию в продакшене!
6. Разверните в продакшене
- Перед развёртыванием добавьте свои секреты в 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- Разверните своего агента:
npx wrangler deployПосле развёртывания вы получите рабочий URL, например:
https://my-slack-agent.your-account.workers.devОбновите Slack Event Subscriptions
Вернитесь к настройкам своего Slack App:
- Перейдите в Подписки на события.
- Обновите Request URL на ваш продакшен-URL:
https://my-slack-agent.your-account.workers.dev/slack. - Выберите Сохранить изменения.
Распространите своё приложение
Теперь, когда ваш агент развёрнут, вы можете поделиться им с другими:
- Единое рабочее пространство: Установите его через
https://my-slack-agent.your-account.workers.dev/install. - Публичное распространение: Отправьте своё приложение в Slack App Directory ↗.
Каждое рабочее пространство, устанавливающее ваше приложение, получит собственный изолированный экземпляр агента с выделенным хранилищем.
Как это работает
Мультитенантность с Durable Objects
Ваш Slack-агент использует Durable Objects чтобы предоставлять изолированные экземпляры с сохранением состояния для каждого рабочего пространства Slack:
- У каждого рабочего пространства есть собственный
team_idиспользуется как ID Durable Object. - Каждый экземпляр агента хранит собственный токен доступа Slack в хранилище KV.
- Разговоры запрашиваются из API Slack по требованию.
- Вся логика агента выполняется в изолированной, согласованной среде.
OAuth-поток
Агент обрабатывает поток OAuth 2.0 сервиса Slack:
- Пользователь посещает
/install> перенаправлено на авторизацию Slack. - Пользователь выбирает Allow > Slack перенаправляет на
/acceptс кодом авторизации. - Agent обменивает код на токен доступа.
- Agent сохраняет токен в Durable Object рабочего пространства.
Обработка событий
Когда Slack отправляет событие:
- Запрос поступает на
/slackконечная точка. - Agent проверяет подпись запроса с помощью HMAC-SHA256.
- Agent направляет событие в Durable Object нужного рабочего пространства.
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;
}
}Следующие шаги
- Добавить Slack Interactive Components ↗ (кнопки, модальные окна)
- Подключите Agent к MCP-сервер
- Добавьте ограничение частоты запросов, чтобы предотвратить злоупотребления
- Управление состоянием разговора
- Используйте Workers Analytics Engine чтобы отслеживать использование
- Добавить расписания для запланированных задач