Gemini API + Cloudflare + Astro で作るアイスブレイクジェネレーター


HRBrain Advent Calendar 2024 24日目の記事です。

https://qiita.com/advent-calendar/2024/hrbrain

## はじめに

こんにちは。[@yug1224(Yuji Yamaguchi)](https://x.com/yug1224)です。

MTGをスムーズに進めるためのアイスブレイク。しかし、毎回ありきたりな質問になりがちで、新しいアイデアを考えるのに苦労することもあります。そこで今回は、Googleの大規模言語モデルGeminiを活用し、アイスブレイクの話題を生成するWebアプリケーションを作ってみました。

## Google AI Studio を使った準備

まず、Google AI StudioでGemini APIのAPI Keyとサンプルコードを取得します。Google AI Studioは、Geminiを利用するための環境が整っており、API Keyの管理やプロンプトの実行が簡単に行えます。

https://ai.google.dev/aistudio?hl=ja

1. Google AI Studioのプロジェクトを開きます
1. API Keyとサンプルコードを取得します

| API Key                                                                                                                                                                        | Gemini Code                                                                                                                                                                        |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![daab3ab50a74df57971d_スクリーンショット_2024-12-24_API_Key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106236/a63e1375-3904-a38c-e31b-5da952158da4.png) | ![daab3ab50a74df57971d_スクリーンショット_2024-12-24_Gemini_Code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106236/e5244cce-a6d2-e043-53f5-cb0251f56eed.png) |

```js:サンプルコード
const {
  GoogleGenerativeAI,
  HarmCategory,
  HarmBlockThreshold,
} = require('@google/generative-ai')

const apiKey = process.env.GEMINI_API_KEY
const genAI = new GoogleGenerativeAI(apiKey)

const model = genAI.getGenerativeModel({
  model: 'gemini-1.5-flash',
})

const generationConfig = {
  temperature: 2,
  topP: 0.95,
  topK: 40,
  maxOutputTokens: 8192,
  responseMimeType: 'text/plain',
}

async function run() {
  const chatSession = model.startChat({
    generationConfig,
    history: [],
  })

  const result = await chatSession.sendMessage('INSERT_INPUT_HERE')
  console.log(result.response.text())
}

run()
```

上記は取得可能なサンプルコードです。JavaScript以外にもGoやPythonのコードがあるので、ご自身の環境に合わせて選択できます。

## Astro Actionsを用いたAPIリクエスト

このアプリケーションでは、フレームワークに[Astro](https://astro.build/)を採用しました。

まだExperimentalな機能ですが、バックエンド関数を呼びだすことができる[Astro Actions](https://docs.astro.build/ja/guides/actions/)を利用して、Gemini APIにリクエストを送信します。

https://docs.astro.build/ja/guides/actions/

1. `#generateTalkTheme`ボタンの押下時、Gemini APIにリクエストを送信する
1. `nanostores`を利用して、生成結果はクライアントサイドで保持しておく
1. `#generateTalkTheme`ボタンの再押下時、これまでの生成結果も渡すことで、類似の話題が生成されないようにする

```ts:src/stores/index.ts
import { atom } from 'nanostores'

export const $talkThemes = atom<string[]>([])
```

```ts:src/actions/index.ts
import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'
import { GoogleGenerativeAI } from '@google/generative-ai'

const Message = `
## 要求事項
- アイスブレイクのお題を1つ生成し、生成した結果のみを出力してください。
- ただし、下記履歴と同様のお題は除外していてください。

## 履歴
`

export const server = {
  myAction: defineAction({
    input: z.array(z.string()),
    handler: async (input) => {
      const apiKey = import.meta.env.GOOGLE_AI_API_KEY
      const genAI = new GoogleGenerativeAI(apiKey)

      const model = genAI.getGenerativeModel({
        model: import.meta.env.GOOGLE_AI_MODEL,
      })

      const generationConfig = {
        temperature: 2,
        topP: 0.95,
        topK: 40,
        maxOutputTokens: 8192,
        responseMimeType: 'text/plain',
      }

      const chatSession = model.startChat({
        generationConfig,
        history: [],
      })
      const sendMessage = Message + '- ' + input.join('- ')

      const result = await chatSession.sendMessage(sendMessage)
      return result.response.text()
    },
  }),
}
```

```ts:src/components/Hero.astro
<div class="hero bg-base-200 min-h-screen">
  <div class="hero-content text-center">
    <div class="w-full">
      <h1 class="text-4xl font-bold break-keep">
        アイスブレイク<wbr />ジェネレーター
      </h1>
      <p id="talkTheme" class="text-2xl py-12">
        あのイーハトーヴォのすきとおった風
      </p>
      <button id="generateTalkTheme" class="btn btn-primary">Ice Break</button>
    </div>
  </div>
</div>

<script>
  import { actions } from 'astro:actions'
  import { $talkThemes } from '../stores'

  const button = document.querySelector('#generateTalkTheme')
  const paragraph = document.querySelector('#talkTheme')

  button?.addEventListener('click', async () => {
    const { data, error } = await actions.myAction($talkThemes.get())
    if (!error && paragraph) {
      console.log(data)
      $talkThemes.set([data, ...$talkThemes.get()])
      paragraph.textContent = $talkThemes.get()[0]
    }
  })
</script>
```

## Cloudflare Workersにデプロイする

最後に、アプリケーションを[Cloudflare Workers](https://www.cloudflare.com/ja-jp/developer-platform/products/workers/)にデプロイします。Cloudflare Workersは、Cloudflareが提供するサーバーレスアプリケーションを構築・デプロイするためのプラットフォームであり、迅速なデプロイとスケーリングを可能にします。

https://www.cloudflare.com/ja-jp/developer-platform/products/workers/

https://docs.astro.build/en/guides/integrations-guide/cloudflare/

1. `@astrojs/cloudflare`をAstroプロジェクトに追加し、SSRを有効にします
1. `astro build`で、Astroプロジェクトをビルドします
1. `npx wrangler pages deploy dist`で、生成された成果物をCloudflare Workersにデプロイします

```js:astro.config.mjs
import { defineConfig } from 'astro/config'
import tailwind from '@astrojs/tailwind'
import cloudflare from '@astrojs/cloudflare'

export default defineConfig({
  integrations: [tailwind()],
  adapter: cloudflare({ mode: 'directory' }),
  output: 'server',
})
```

![daab3ab50a74df57971d_画面収録_2024-12-24_アイスブレイクジェネレーター.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106236/b5bc5e34-275c-2f1d-2fc2-d47641f4fec3.gif)

## まとめ

Gemini API・Cloudflare・Astroを組み合わせることで、高機能でスケーラブルなアイスブレイクジェネレーターを簡単に構築できました。

実際に作ったアプリケーションとソースコードも公開しているので、良かったら参考にしてみてください！

https://icebreaker.pages.dev/

https://github.com/yug1224/icebreaker

## We're hiring!

HRBrainでは一緒に働く仲間を募集しています。歴史に残るトライをしよう！

https://www.hrbrain.co.jp/recruit