見出し画像

SlackコマンドでGemini APIを呼び出してみる


SHIFTのKoshiishiです。
以前の記事 でgeminiのapiをhonoでラップしました。
直接叩くのは少し面倒なので、slackで呼び出せるようにしてみました。
最終的なコード です。

構成図

全体の構成としては図の通りになります。

Slack Signing Secretの準備


GeminiのAPIキーと、SlackのSigning Secretの2つが必要です。
Signing SecretはSlackからのリクエスト検証のため用います。
GeminiのAPIキーは以前の記事 で作成済みとさせてください。
SlackのSigning Secretは次の手順で作成します。
Create New App から、Slackアプリを作成します。 画像では、もともとaws_cost_notificatorというbotが存在するワークスペースに、hono-gemini-botというアプリを作成しています。


Signing Secretが生成されるので、こちらを控えておきます。

コードの準備


Slack Signing Secretでのバリデーション

前回の記事で使用したコードをパブリックテンプレート にしたので、右上のUse this tempateからコードを作成します。
wrangler.tomlや、package.jsonでhono-geminiとなっている箇所を、hono-gemini-slackに書き換えます。(名前衝突しないよう)
環境変数のinterfaceからAPI_KEYを廃止して、SLACK_SIGNING_SECRETを追加します。

interface Env {
  GEMINI_API_KEY: string
  SLACK_SIGNING_SECRET: string
}

API_KEYでのバリデーションを、SLACK_SIGNING_SECRETでのバリデーションに差し替えます。
validateSlackSignature.tsを作成し、ヘッダーから署名とタイムスタンプを取得するようにします。
署名が一致しない場合や、リクエストの送信日時が古すぎる場合などは弾くようにします。
コード全体はこちら

export const validateSlackSignature = (getSigningSecret: (c: Context) => string): MiddlewareHandler => {
  return async (c, next) => {
    const timestamp = c.req.header('X-Slack-Request-Timestamp')
    const signature = c.req.header('X-Slack-Signature')
    
    if (!timestamp || !signature) {
      return c.json({ error: 'Missing Slack signature headers' }, 401)
    }
...

Slackのタイムアウトを予防

レスポンスによってはタイムアウトします。
スラッシュコマンドからの実行は公式ドキュメント 曰く3秒らしく下記のコードは、タイムアウトしたり、しなかったりします。  

app.post('/slack', async (c) => {
  try {
    const parsedBody = c.get('parsedBody') as Record<string, string>
    const question = parsedBody.text || 'No question provided'

    const genAI = new GoogleGenerativeAI(c.env.GEMINI_API_KEY)
    const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" })
    const result = await model.generateContent(question)

    const response = result.response

    return c.json({
      response_type: "in_channel",
      text: response.text()
    })
  } catch (error) {
    return c.json({ error: 'error occurred' }, 500)
  }
})

公式の推奨通りの実装に変更します。
一旦すぐに200を返し、その時のresponse_urlを控えておき、そこにPOSTするようにします。

 const parsedBody = c.get('parsedBody') as Record<string, string>
    const question = parsedBody.text || 'No question provided'
    const response_url = parsedBody.response_url
    // Immediately return a processing response
    c.executionCtx.waitUntil((async () => {
      // Asynchronously call the model
      const genAI = new GoogleGenerativeAI(c.env.GEMINI_API_KEY)
      const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" })
      const result = await model.generateContent(question)
      const finalText = `${question}\n${result.response.text()}`
      // After processing, POST to Slack's response_url to update the message
      await fetch(response_url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          response_type: "in_channel",
          text: finalText
        })
      })
    })())
    return c.json({
      response_type: "ephemeral",
      text: `Your question: "${question}" is being processed. Please wait for the response.`
    })

CloudflareWorkerの準備

控えておいたSLACK_SIGNING_SECRETと、前回作成したGEMINI_API_KEYをCloudflareに登録します。

❯ wrangler deploy
❯ wrangler secret put GEMINI_API_KEY
❯ wrangler secret put SLACK_SIGNING_SECRET

Slackアプリ側の準備

アプリ設定のSlach Commandsから、新規のコマンドを登録します。
Request URLには、wrangler deploy後のエンドポイント/slakを書きます。

動作確認

(ログをみたい場合)

❯ wrangler tail

して、logを見れる状態にしておきます。
この状態にしておくことで、Slackからリクエストが来た際のステータスが見れます。
Slackから質問を投げると画像のようになります。
左側がSlack, 右側がターミナルです。
全体の流れとしては以下の通りです。

  1. ephemeralなテキスト(Onli visible to you)で自分にだけ質問が見えます

  2. tailコマンドで見ているターミナルでどこにPOSTが走ったか見えます

  3. Slackに通常テキストとして、geminiからの返答が返されます

(Slackはmrkdwn 形式のためかmarkdown崩れてしまっていますが、今回の記事では放置しています)

おわりに


ここまで、GeminiのAPIをCloudflare Workers上のHonoフレームワークでラップし、さらにSlackとの連携を組み合わせる方法を解説してきました。
これにより、わざわざエンドポイントを直接叩かなくても、Slackのスラッシュコマンドから便利にLLMを呼び出して応答を得ることができます。
今回の方法が、皆さんのワークフロー改善に少しでも役立てば幸いです。


執筆者プロフィール:Kenta Koshiishi

スタートアップ企業でFigmaを使用したUI設計から、Rustでのバックエンド開発、Reactでのフロントエンド開発まで、一気通貫で手掛けてきた。 SHIFTではシニアエンジニアとして入社し、既存のWebアプリに触れ始めた。

この筆者のおすすめ記事

✅SHIFTへのご相談、お問合せはお気軽に

SHIFTのサービスについて(サービスサイト)

SHIFTの導入事例

お役立ち資料はこちら

SHIFTの採用情報はこちら

PHOTO:UnsplashAndrew W