1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Slack App で AI エージェントアプリ を作る方法

1
Last updated at Posted at 2026-05-11

2026年4月の TDX にて、Slack 上で AI エージェントアプリを開発するための新しい CLI コマンドや Bolt ユーティリティが発表されました。今年2月には Real-Time Search API と Slack MCP Server も GA となっており、Slack アプリのエージェント機能は大きく拡充されています。

本記事では、現時点 Slack でどのようなエージェントアプリが開発できるのかを探ります。

AI エージェントに求められる UX 要素

AI エージェントアプリに求められる UX 要素には、どのようなものがあるでしょうか。ChatGPT や Claude などで共通して見られる代表的な UX 要素を、その背景とともに振り返ってみます。

ChatGPT が登場した後、応答までの待ち時間を短くするため、ストリーミング応答 が定着しました。

何を聞けばよいか分からないユーザー向けに プロンプトのサジェスト が加わります。

外部データ参照や RAG が組み込まれると、ツール呼び出しの可視化出典・参照先の引用 も求められるようになりました。

reasoning モデルが登場すると、思考プロセスの可視化 も広がります。

エージェントが実アクションを取り始めると、人間による確認・承認 のフローも組み込まれるようになりました。

さらに継続改善には フィードバック収集 も欠かせません。

これらすべての要素を一から作る場合、フロントからバックエンドまで広範囲での実装が必要になります。Slack では、これらの UX 要素はプラットフォーム側に用意されています。

Slack エージェントの仕組み

Slack で動くエージェントは、接続した Salesforce 組織の Agentforce を呼び出す方法と、Slack App として作る方法があります。本記事では後者を扱います。

Slack 上で動く AI エージェントは Slack アプリとして作ります。アプリ設定で Agents & AI Apps 機能を有効化すると、通常の Slack アプリの機能に加えて、エージェントならではの機能が使えるようになります。

image.png

標準で提供される UI 要素

Agents & AI Apps を有効化すると、AI エージェントを呼び出すためのエントリーポイントが4つ用意されます。それぞれ対応する Slack イベントを購読して応答ループを実装します。

画面 概要 イベント
Agent Container (Split Pane) トップナビから常時開ける専用サイドペイン。通常業務と並行で相談できる assistant_thread_started / assistant_thread_context_changed
Direct Messages エージェントとの 1 対 1 の DM message.im
Channel Mentions チャンネルで @ メンションして呼び出す。業務スレッドの文脈に乗せて依頼できる app_mention
App Threads 会話を1つのスレッドにまとめる仕組み。複数ターンでも文脈が散らばらない スレッド配下の message

開発ツール - Slack CLI

slack create agent で、エージェント向けのサンプルテンプレートからプロジェクトを作成できます。

slack create agent

実行すると、IT サポートエージェント Casey などのサンプルテンプレートからプロジェクトを生成できます。フレームワークも、Claude Agent SDK / OpenAI Agents SDK などから選んでクイックスタートします。

Agentic Enterprise 時代の統合マーケットプレイス - AgentExchange

AppExchange / AgentExchange / Slack Marketplace は新しく、AgentExchange に統合されました。エージェント時代向けに刷新された統合マーケットプレイスで、開発したエージェントを配布できます。
公開して配布するには審査が必要です

AI エージェントに求められる UX の実現方法

ここからは、最初に挙げた UX 要素を Slack 上でどう実現するかを見ていきます。
サンプルコードは Bolt for JavaScript(@slack/bolt)のものです。

イベントリスナー

Bolt JS の Assistant クラスを使うと、2つのリスナーを軸にエージェントの土台を作れます。threadStarted は新しいスレッドが開かれたとき、userMessage はユーザーがメッセージを送ったときに呼ばれます。

import { App, Assistant } from '@slack/bolt';

const assistant = new Assistant({
  threadStarted: async ({ event, say, setSuggestedPrompts }) => {
    // ユーザーが新しいスレッドを開いたタイミング
    await say('How can I help you?');
    await setSuggestedPrompts({ prompts: [/* ... */] });
  },
  userMessage: async ({ message, client, setStatus, sayStream }) => {
    // ユーザーがメッセージを送ったタイミング
    await setStatus('thinking...');
    // LLM 呼び出し / ツール実行 ...
    const stream = sayStream();
    await stream.append({ markdown_text: 'Here is what I found...' });
    await stream.stop();
  },
});

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});
app.assistant(assistant);

(async () => { await app.start(); })();

それぞれのイベントを処理する中で、プロンプトのサジェスト、ステータス表示、ストリーミング応答、Thinking Steps、リッチな UI 表示などを行います。

プロンプトのサジェスト

assistant_thread_started イベントを受け取ったタイミングで、複数のプロンプト候補を提示します。Bolt JS の Assistant クラスでは、threadStarted ハンドラに setSuggestedPrompts ヘルパーが渡されます。

const assistant = new Assistant({
  threadStarted: async ({ say, setSuggestedPrompts }) => {
    await say('How can I help you?');
    await setSuggestedPrompts({
      prompts: [
        { title: 'Reset Password', message: 'I need to reset my password' },
        { title: 'Request Access', message: 'I need access to a system or tool' },
        { title: 'Network Issues', message: "I'm having network connectivity issues" },
      ],
    });
  },
});

ユーザーは提示されたプロンプトをタップするだけで、メッセージを送信できます。

ローディング / Thinking 表示

ユーザーの入力を受け取った直後に assistant.threads.setStatus を呼び出します。loading_messages でメッセージ文を複数渡すと、それらを順に表示できます。

app.event('<イベント名>', async ({ setStatus }) => {
  setStatus({
    status: 'thinking...',
    loading_messages: [
      'Untangling the internet cables…',
      'Consulting the office goldfish…',
      'Convincing the AI to stop overthinking…',
    ],
  })
});

ストリーミング応答

chat.startStreamchat.appendStream(複数回) → chat.stopStream の3つで、LLM の応答を生成途中から読めるようにします。Bolt なら sayStream ユーティリティ1つでこの3つの呼び出しをまとめられます。

app.event('<イベント名>', async ({ sayStream }) => {
  const stream = sayStream();
  await stream.append({ markdown_text: "Here's my response..." });
  await stream.append({ markdown_text: "And here's more..." });
  await stream.stop();
});

思考プロセスの可視化(Thinking Steps)

エージェントの「考えている過程」をリアルタイムに見せる Thinking Steps が使えます。chat.startStreamtask_display_mode で、2つの表示モードを選択できます。

表示モード 用途
plan タスク一覧を最初に提示し、進捗に応じてチェックを付ける(多段ステップ向き)
timeline 起きたことを時系列で表示する(ステップごとの実況中継向き)

タスクは Task Card として、pendingin_progresscomplete / error と状態遷移します。

app.event('<イベント名>', async ({ sayStream }) => {
  const stream = sayStream();
  await stream.append({
    task_display_mode: "plan",
    chunks: [
      { type: 'task', id: 'search', text: 'Search workspace', status: 'in_progress' },
      { type: 'task', id: 'build',  text: 'Build context',    status: 'pending' },
      { type: 'task', id: 'compose', text: 'Compose response', status: 'pending' }
    ]
  });
});

Task Card には sources を付与できるため、「このステップで参照した情報源」を一緒に表示できます。ツール呼び出しの可視化と出典提示も同じ仕組みで実現できます。

Block Kit によるリッチな応答(Card / Alert / Carousel / Code)

エージェント応答向けの新しい Block Kit 要素も追加されました。プレーンテキストの羅列ではなく、構造化された応答を返せるようになります。

Block 用途
Card アイコン、タイトル、本文、画像、アクションボタンをまとめたカード型 UI
Alert default / info / success / warning / error の5段階で重要度を表現
Carousel 最大10枚の Card を横スクロールで提示(候補提示や検索結果向き)

Card と Alert を組み合わせた例です。

blocks: [
  {
    type: 'card',
    title: { type: 'plain_text', text: 'Acme Corp' },
    description: { type: 'mrkdwn', text: '*Status:* Active customer since 2022' },
    icon: { type: 'emoji', emoji: ':office:' },
    actions: [
      { type: 'button', text: { type: 'plain_text', text: 'Open in CRM' }, action_id: 'open_crm' },
    ],
  },
  {
    type: 'alert',
    level: 'warning',
    title: { type: 'plain_text', text: 'Renewal due in 14 days' },
    description: { type: 'mrkdwn', text: 'Last touch was 32 days ago.' },
  },
]

確認・承認

外部システムへの書き込み(メール送信、レコード作成、削除など)は、Block Kit の buttonmenu で明示的に承認を求める実装が可能です。

{
  "type": "actions",
  "elements": [
    { "type": "button", "text": { "type": "plain_text", "text": "Always allow" },
      "action_id": "canvas_always_allow", "style": "primary" },
    { "type": "button", "text": { "type": "plain_text", "text": "Allow once" },
      "action_id": "canvas_allow_once" },
    { "type": "button", "text": { "type": "plain_text", "text": "Deny" },
      "action_id": "canvas_deny", "style": "danger" }
  ]
}

フィードバック収集

応答メッセージの末尾に Feedback Buttons(👍/👎)を添えると、ユーザーから手軽にフィードバックを集められます。context_actions ブロックの feedback_buttons 要素で実装でき、Bolt なら応答メッセージにブロックを数行追加するだけで組み込めます。

await client.chat.postMessage({
  channel: message.channel,
  thread_ts: message.thread_ts,
  blocks: [
    { type: 'section', text: { type: 'mrkdwn', text: 'Here is the summary you asked for…' } },
    {
      type: 'context_actions',
      elements: [
        {
          type: 'feedback_buttons',
          action_id: 'summary_feedback',
          positive_button: {
            text: { type: 'plain_text', text: 'Good' },
            value: 'positive_feedback',
          },
          negative_button: {
            text: { type: 'plain_text', text: 'Bad' },
            value: 'negative_feedback',
          },
        },
      ],
    },
  ],
});

app.action('summary_feedback', async ({ ack, action, client, body }) => {
  await ack();
  // action.selected_option.value で 'positive_feedback' / 'negative_feedback' を判定
  // ネガティブ時は理由を聞くモーダルを開く
  if (action.selected_option?.value === 'negative_feedback') {
    await client.views.open({ trigger_id: body.trigger_id, view: { /* ... */ } });
  }
});

さらに、reaction_added イベントを使えば、絵文字リアクションも同じようにフィードバックとして活用できます。

AI エージェントの可能性を広げる Slack ならではの機能

Slack の業務コンテキストを活用 - Real-Time Search API

2月に GA した Real-Time Search API を使うと、ワークスペース内の業務コンテキスト(会話、ファイル、チャンネル)に、リアルタイムかつ安全にアクセスできます。

利用できるのは、AgentExchange (Slack Marketplace) に公開された承認アプリまたは社内アプリのみです。未承認のアプリでは利用できません

メッセージ・ファイル・チャンネル・ユーザーをワークスペース横断で検索できます。検索範囲は 呼び出したユーザーがアクセスできる範囲に限定 されるため、エージェントが他人の DM をのぞき見るようなことはできない仕様です。

const result = await client.assistant.search.context({
  query: 'What are the latest decisions on project alpha?',
  action_token: event.action_token,
  content_types: ['messages', 'files', 'channels'],
  channel_types: ['public_channel', 'private_channel'],
  include_context_messages: true,
  limit: 20
});

のように、assistant.search.context メソッドで検索します。query はキーワード検索とセマンティック検索の両方をサポートしてます。セマンティック検索は Slack AI Search が有効なワークスペースでのみ動作します。

外部 LLM から Slack を操作する — Slack MCP Server

外部の LLM クライアント (OpenAI / Anthropic 等) から Slack を操作したい場面で使えるのが Slack MCP Server です。こちらも Real-Time Search API と同じ2月にリリースされました。

Slack 側がサーバーをホストし、ツールロジックも管理されます。外部エージェントから Slack の検索・送信・Canvas の作成・編集ができるようになります。

こちらは、OpenAI での利用例です。

const llmResponse = await openai.responses.create({
  model: 'gpt-4o-mini',
  input: `System: ${DEFAULT_SYSTEM_CONTENT}\n\n${parsedThreadHistory}\nUser: ${message.text}`,
  tools: [
    {
      type: 'mcp',
      server_label: 'slack',
      server_url: 'https://mcp.slack.com/mcp',
      headers: { Authorization: `Bearer ${context.userToken}` },
      require_approval: 'never',
    },
  ],
  stream: true,
});

アプリ設定の Agents & AI Apps で MCP を有効化します。必要な OAuth scope (chat:writesearch:read.public など) をユーザートークンに追加した上で、エンドポイントにはhttps://mcp.slack.com/mcp を指定します。

App Home / Slash Commands による状態の可視化

進行中のタスクや過去の対話をまとめて見られる場として、App Home が役立ちます。実行中のワークフロー、最近の完了履歴、ブロックされているタスクを並べ、操作用のアクションボタンを添えておきます。これにより、ユーザーがスレッドを遡らなくてもエージェントを操作できます。

app_home_opened イベントで views.publish を呼ぶ例です。

app.event('app_home_opened', async ({ event, client }) => {
  await client.views.publish({
    user_id: event.user,
    view: {
      type: 'home',
      blocks: [
        { type: 'header', text: { type: 'plain_text', text: 'Your agent activity' } },
        { type: 'section', text: { type: 'mrkdwn', text: '*In progress*\n• Drafting renewal email for Acme Corp' } },
        { type: 'section', text: { type: 'mrkdwn', text: '*Recently completed*\n• Summarized #project-alpha (2h ago)' } },
        {
          type: 'actions',
          elements: [
            { type: 'button', text: { type: 'plain_text', text: 'Pause all' }, action_id: 'pause_all' },
            { type: 'button', text: { type: 'plain_text', text: 'View history' }, action_id: 'view_history' },
          ],
        },
      ],
    },
  });
});

加えて、スラッシュコマンドから内部状態を確認できる入口を用意しておくと、運用がしやすくなります。

コマンド例
/agent logs      // 最近のエージェント活動
/agent state     // 現在の goal / constraints / decisions
/agent settings  // エージェントの挙動設定

考慮事項

開発時に押さえておきたい点をまとめます。

自律性のバランス

「自律的に何でもやってしまう」と「ユーザーの確認を毎回取る」のバランスを取ることが重要です。ユーザーが慣れていない初期は確認を多めにし、信頼が積み上がるにつれて自動化の範囲を広げる Progressive Trust が推奨されています。

プロンプトインジェクション対策

ユーザー入力や Slack 上の他者の発言、外部システムの応答などを LLM に渡す以上、プロンプトインジェクションのリスクは常にあります。機密情報の漏洩を防ぐためのガードレールを入れることが特に重要 です。

Real-Time Search API でのコンテキスト管理

毎ターン取得し直さないことがトークン消費、効率面の観点で重要です。古い対話は要約して { goal, constraints, decisions, artifacts, sources } のようにセッションステートとして管理することが推奨されています。

監査と管理者向けガバナンス

AgentExchange (Slack Marketplace) 公開エージェントは、ワークスペース管理者がアプリ単位で承認 / 無効化を一元管理できます。エージェントが行ったアクション(チャンネルアクセス、書き込み、MCP 経由のツール呼び出しなど)は Audit Logs API で追跡できます。チャンネル単位で「AI Excluded」インジケーターが表示されるなど、ユーザー側からも「ここは AI に使われない」と確認できる仕組みになっています。

Agentforce で作る選択肢

本記事とは全く別の方法になりますが、Salesforce のデータや業務プロセスを扱うエージェントであれば、Agentforce が選択肢になります。Slack と Salesforce を接続し、 Agentforce Builder で作成したエージェントをワークスペース上で動作させることが可能です。

おわりに

Slack の エージェント開発プラットフォームとしての機能は大きく拡充されました。エージェントならではの UX 要素は標準で備わり、メッセージや Canvas を通じて Slack の業務コンテキストも活かせます。開発者はビジネスロジックや業務シナリオの設計に集中できます。

手元で試してみたい方は、以下のステップでクイックスタートできます。

  1. Slack Developer Program でサンドボックスワークスペースを取得 (無償)
  2. Slack CLI をインストール
  3. slack create agent でテンプレートを選んで起動

興味あれば是非お試しください。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?