AskOK — Slackで「聞きたいのに聞けない」を解くボット
社内では「聞きたいのに聞けない」「聞いても反応がない」「忙しくて返せない」が同時に起きる。
AskOK は、特定の Slack チャンネルに投稿するだけで、ボットが受け取り、Backlog / Bitbucket / Slack の過去知を横断で先に返し、足りなければチャンネル参加者へ橋渡しする。
人の発言の代行はせず、チャンネル参加が同意になる。
触ってほしいポイント
- 特定チャンネルに投稿する → すぐリアクション+出典つき要約がスレに出ること
- 人への橋渡しが helpers チャンネル参加者のみで、代弁投稿しないこと
現場でありがちな失敗
- 聞きにくい — 「そんなことも知らないの?」と言われそうで、質問をためらう
-
聞いても反応がない —
#randomは「誰かが返すだろう」で止まる - 忙しくて返せない — 関わりたい気持ちはあるが、常時レスは負担。かといって ボットに自分の言葉を代弁されるのは嫌
3つ目が効いている。「AIが自動で答えてくれる」だけでは解けない。
人が関わるかどうかを、人が決められる形にする必要があった。
| 意思 | 表明の仕方 |
|---|---|
| 質問したい |
#askok に投稿する |
| 聞かれてもよい |
#askok-helpers に参加する(=同意) |
| もう関わりたくない | チャンネルを退出する |
登録フォームも管理画面も作っていない。チャンネル参加そのものが同意になっている。
お題との接続
お題に対し、いつもの Slack 上で「既存知 → 合意した人」の二段にした。
専用 Web アプリを主UIにしないのは、課題が Slack 上で起きているから。別アプリを開かせると続かない。
AskOK とは
| 形態 | Slack ボット(専用の Web 画面は作らない) |
| 稼働 | AWS Lambda + API Gateway。常時稼働(誰の PC も要らない) |
| 検索対象 | Backlog / Bitbucket / Slack(本番接続) |
| 実装した会話 | 横断Q&A・出典提示・担当案内・helpers 橋渡し・Backlog 起票 |
使い方(短い)
- 答え手は
#askok-helpersに参加する(=聞かれてもよい同意) - 聞き手は
#askokに質問を投稿する(メンション不要) - スレの要約を見る。足りなければキーワードを足して聞き直す
- 人に聞きたければ「helpersに聞いて」→ 候補が出る → もう一度送ると共有される
- 関わりたくなくなったら
#askok-helpersを退出する
デモの流れ(4手)
-
#askokに質問 → 👀 → スレに要約が返る - 「helpersに聞いて」→ 候補が出る
- もう一度「helpersに聞いて」→
#askok-helpersに共有される - 元スレのリンクから戻って、人が自分の言葉で答える
見せどころは2つ。
検索結果のダンプではなく会話になっていることと、
人を巻き込む前に同意を取っていること。
システム構成(概念)
Slack (#askok)
→ API Gateway
→ Lambda(受信役:署名検証 → 即 200)
→ Lambda(処理役:横断検索・対話・投稿)※同一関数を非同期 invoke
→ DynamoDB(会話状態 / イベント重複排除)
→ Backlog / Bitbucket / Slack search
なぜ「受信」と「処理」を分けたか
Slack Events API は 3秒以内に 200 を返さないとリトライしてくる。
一方、横断検索は十数秒かかる。素直に書くと、同じ質問に何度も回答してしまう。
そこで 1つの Lambda を2つの役で使う。
- 受信役: 署名検証 → 自分自身を非同期 invoke → すぐ 200(実測ウォーム約170ms)
- 処理役: event_id の条件付き書き込みで重複排除 → 検索・要約 → スレ返信
関数を2つに分けなかったのは、環境変数もコードも同じものを2箇所で管理したくなかったから。
AIにやらせたこと/やらせなかったこと
プロダクトの中では
検索で上位 hit を絞ってから OpenAI に渡し、出典付きで会話として返す。
全チャンネル履歴を毎回プロンプトに詰めない。キー未設定や API 失敗時はテンプレ回答にフォールバックする。
A2(橋渡し理由)・A3(答え手向け返信案)は未実装。
作るときに効いたのは「止めた」場面
| 場面 | どう止めたか |
|---|---|
| 検索結果をそのまま並べる回答 | 「何が書いてあるか分からない」→ 会話形式に作り直した |
| DM が検索結果に出た | 気づいた時点で除外。回答は他人にも見える |
| 引用に生のメンションが混ざった |
<@U…> をそのまま返すと関係ない人に通知が飛ぶ → 平文に無害化 |
| Windows の Compress-Archive で作った zip | エントリ名が \ になり Lambda がモジュールを解決できない → zip を自前で組み立てた |
「AI に書かせた量」ではなく「AI の出力をどこで疑ったか」が本題だった。
こだわった判断
| 判断 | 理由 |
|---|---|
| 専用 Web アプリを作らない | 課題は Slack 上で起きている |
#askok / #askok-helpers の2本 |
聞く側と答える側の意思を分ける。登録画面は不要 |
| ボタンではなく自由入力 | 会話として自然で、キーワードを足して聞き直せる |
| 代弁しない | 現場の拒否感そのものが要件 |
| 即メンションしない | 候補を見せて同意を取ってから |
| 依存パッケージを配布物に入れない | Lambda の起動を軽くする。配布物は 100KB 未満 |
検索結果から意図的に外したもの
| 除外 | 理由 |
|---|---|
| DM・グループDM | 個人的なやり取りが第三者に見える |
#askok / #askok-helpers
|
受付口であって知識ソースではない |
| AskOK 自身の過去回答 | 自分の出力を根拠にして循環する |
手戻り・失敗談
- Slack の3秒制約 — 検索を同期でやるとリトライ地獄。受信/処理の分離で解消した
- Socket Mode → HTTP Events — ローカル検証と本番常時稼働の切り替えで詰まった。署名付き疎通確認を先に用意してから Slack に Request URL を貼る順にした
- User Token の検索権限 — 便利だが個人の権限に依存する。運用に載せるなら専用アカウントが要る
- メンションの再投稿 — 検索ヒットの本文をそのまま返すと通知事故になる。表示前に必ず無害化する
学び
- 「全自動で答える」より、「既存知 → 同意した人」の二段のほうが現場の拒否感に合う
- AI は要約に強いが、何を根拠に渡すかと何を絶対に出さないかは人が決める
- Slack ボットは「動いた」より「誰にも迷惑をかけない」が先に来る(DM除外・メンション無害化・同意後の橋渡し)
今後
- A2/A3(橋渡し理由・答え手向け返信案)
- シークレットを環境変数から Parameter Store などへ移す
- Slack 検索用の専用アカウント
- Teams コネクタ(現状は未接続)
正直に足りていないところ
- A2/A3 は未実装。A1(スレ要約)は OpenAI 接続済み
- Teams は未接続
- シークレットが Lambda 環境変数のまま
評価用の触り方・チャンネル案内は Slack 側に置いている(本記事には公開URL・社内ドメイン・アカウントIDは書かない)。