社則AIに対する技術的な試みについて

はじめに

今回のコラムは、既存のAIアプリケーションを異なるAIモデルに載せ替える、
という少し技術的な試みについてのお話です。
社内向けにTypeScriptとReactで構築された「社則AI」の質問応答機能を、
OpenAIからGeminiに換装した際の開発プロセスと、
そこから得られた学びを共有します。

そもそも…なぜOpenAIからGeminiへ?

「すでに動いているのになぜ?」と疑問に思うのは当然です。
理由は単純で、開発に着手した当時、私自身がプロジェクト用の
OpenAIアカウントを所持しておらず、手元ですぐに試せる
Geminiで代替できないかと考えたのがきっかけでした。
(アカウントを作れば済む話ではありますが…)

また、ベースとなっている技術がTypeScriptとReactであり、
私自身これらの知識がほとんどない状態でした。
ゼロから手動で改修するには時間がかかりすぎるため、普段から開発補助で活用していた
Geminiに手伝ってもらおう、という結論に至りました。

作成したもの

まず、完成したアプリケーションがこちらです。
見た目は元の質問箱ページとほとんど変わりません。

元の機能に加え、AIへの質問と回答を保存する機能を追加しました。
具体的には、回答生成後にAIがその内容をカテゴリ分類し、
ユーザーに保存するかどうかを確認。承認されるとデータベースに保存される、
という流れです。

今回作成する上で試してみたこと

Geminiへの換装プロセス

正直に言うと、中心的な機能である「OpenAIからGeminiへのAPI呼び出しの切り替え」
自体は、1時間もかからずに完了しました。
ここでは、APIキーの取得から実際の対話による実装までの流れを追っていきます。

1. Gemini APIキーの取得

まず、以下の手順でAPIキーを取得します。

  1. Google AI Studioへアクセス:
    https://aistudio.google.com/welcome にアクセスします。
  2. ログイン: Googleアカウントでログインします。
  3. プロジェクトを作成: 新しいプロジェクトを作成します。

  4. APIキーを作成: 「Get API key」から新しいAPIキーを生成します。
  5. APIキーを保存: 生成されたAPIキーをコピーし、プロジェクトの
    .env.local ファイルに
    VITE_GEMINI_API_KEY="YOUR_API_KEY" の形式で保存します。

2. Geminiとの対話による実装プロセス(応答集)

APIの換装から追加機能の実装、レイアウト調整まで、Geminiとの
主なやり取りは以下の通りです。
特にレイアウト調整(6番以降)では、抽象的な指示が原因で意図通りに進まず、
試行錯誤している様子がわかると思います。

  • 実際にGeminiを使用して回答を返すことはできない?
    • 可能です。App.tsx内のgetAiResponseStream関数を、
      実際にAIのAPIを呼び出すように変更します。
    • APIキーを安全に管理するため、プロジェクトのルートに.env.localファイル
      を作成し、キーをそこに記述します。
    • これにより、目安箱に入力された内容がAIに送信され、リアルタイムで
      回答が生成されるようになります。
  • OpenAIではなくGeminiで作成したい
    • 可能です。App.tsx内のgetAiResponseStream関数を、
      OpenAI APIの代わりにGoogle Gemini APIを呼び出すように変更します。
    • APIキーを安全に管理するため、プロジェクトのルートに.env.localファイル
      を作成し、VITE_GEMINI_API_KEY="YOUR_API_KEY" のように
      キーを記述します。
    • これにより、目安箱に入力された内容がGeminiに送信され、リアルタイム
      で回答が生成されるようになります。
  • 今使用しているGeminiAPIキーは確認できる?
    また、その内容を.env.local の中に設定できる?
    • APIキーはコード内には保存されておらず、
      import.meta.env.VITE_GEMINI_API_KEY という
      環境変数経由で読み込まれる。
    • .env.local ファイルをプロジェクトルートに作成し、
      VITE_GEMINI_API_KEY="YOUR_API_KEY" の形式で設定するのが正しい方法
      であることを確認した。
  • 目安箱で質問した内容と、その回答をそれぞれ分類分けし、
    データベースに保存したい
    • App.tsxに、質問と回答をGeminiに送信してカテゴリを推論させる
      categorizeSuggestion関数を追加。
    • SuggestionBoxPagehandleSubmitを修正。AIの回答が完了した後、
      categorizeSuggestionを呼び出す。
    • 質問、回答、推論されたカテゴリ、タイムスタンプを含むオブジェクトを
      作成し、databaseService経由で永続化する処理を追加した。
  • 質問内容を分類分けしたうえで再度ユーザーに聞き返すようにしたい。
    • SuggestionBoxPageに確認ステップを追加。
    • AIによる分類が完了した後、すぐに保存せず、分類結果と内容を
      ユーザーに提示。
    • 「はい」が選択された場合にのみデータベースに保存するよう
      handleSubmitロジックを修正した。
  • 目安箱のレイアウトを修正したい。入力欄は固定で、
    表示欄(チャット履歴)がスクロールするようにしてほしい。
    • App.tsx内のSuggestionBoxPageコンポーネントのレイアウトを修正。
    • flexboxのプロパティを調整し、チャット表示エリア(ScrollArea)が
      可変の高さを持ち、入力欄が常に下部に固定されるようにした。
  • 結果表示欄の横に検索履歴が欲しい。スクロールがうまくできていない。
    • SuggestionBoxPageを2カラムレイアウトに変更。
      左にチャットUI、右に保存された質問の履歴を表示。
    • databaseServiceから履歴を取得し、新しい質問が保存されるたびに
      更新されるようにした。
    • Flexboxレイアウトを改善し、min-h-0を適切に配置することで、
      コンテンツが長い場合でも入力欄が隠れることなく、表示エリアのみが
      スクロールするように修正した。
  • 質問内容の表示スペースの横幅を少し減らしてそこに履歴情報を表示したい。
    相変わらず検索欄は見えなくなってしまう
    • SuggestionBoxPageのレイアウトを調整し、チャットUIと履歴表示の横幅の
      比率を「3:2」に変更。
    • スクロール問題を解消するため、Flexboxのレイアウトを再確認し、
      下部の確認欄と入力フォームが縮まないようにshrink-0クラスを追加した。
  • app.tsxの内容を更新してください。
    • SuggestionBoxPageコンポーネントのレイアウトを2カラム構成に更新。
    • 左カラム(3/5幅)にチャットUI、右カラム(2/5幅)に保存された質問の履歴を
      表示。
    • databaseServiceから履歴を読み込み、新しい質問が保存されると
      自動更新されるようにuseEffectを追加。
    • Flexboxレイアウトを修正し、チャットの回答が長くなっても入力欄が
      画面外に隠れないようにスクロール問題を解決した。
  • 質問の回答内容が見切れてしまうのは対応できない?
    • SuggestionBoxPageのレイアウトを修正。
    • AIの回答が長い場合に、チャットの表示エリアがコンテナの高さを
      超えてしまい、下部の入力欄が見えなくなる問題を修正。
    • flexboxレイアウトにおいて、入力欄と確認欄のコンテナにshrink-0
      追加することで、これらの要素が縮むことなく高さを維持し、
      チャット表示エリアのみがスクロールするようにした。
  • 【保留】データベースをPostgreSQLに移行する対応
    • バックエンドサーバーの構築とAPI連携を進めていたが、一旦保留とする。
    • フロントエンドのデータ保存先を一時的にlocalStorageに戻した。
  • 目安箱機能の復元とSyntaxErrorの修正
    • 前回の対応で目安箱機能を削除してしまったが、ユーザーの意図は
      機能の復元であったため、コードを再生成。
    • npm run devで発生していたSyntaxErrorの原因が、
      App.tsx内の<TabsList>タグの重複にあることを特定。
    • 重複していたタグを修正し、5つのタブが正しく表示されるようにレイアウトを
      修正した。これにより、エラーが解消され、目安箱機能が
      利用可能な状態に復元された。
  • package.jsonが空になってしまったため、元に戻してほしい。
    • PostgreSQL対応時に誤ってファイルが空になった可能性があるため、
      バックアップからpackage.jsonの内容を復元した。
    • これにより、プロジェクトの依存関係が正しく定義され、npm install
      npm run devが正常に動作するようになる見込み。

Tips: 開発ログをAIに取らせる

開発中、AIとのやり取り自体を記録に残すために、
作業開始前に以下のような指示を出しておきました。

以降の質問とその回答について、今日の日付で
マークダウン形式のファイルを作成して履歴を作ってほしいです。

この状態で次の質問を行いました。

目安箱ページの改善点を教えて

回答の詳細についてはエラーハンドリングやローディング時の
インジケーターなどを提案してくれましたが、長いので省略します。

その後、以下のような回答が追加できました。

これにより、VSCodeで開いているMarkdownファイルに、AIが自動で対話履歴を
追記してくれるようになります。これは非常に便利でした。
(ただし、AIが生成したファイルの日付が間違っていることも。
 このあたりはご愛嬌です。)

今回の学び

今回の試みを通じて得られた学びは、大きく2つあります。

  1. AIへの指示は、具体的かつ明確に
    AIを使って開発を進める上で最も重要なのは、曖昧な指示をしないことです。
    「いい感じにして」のような抽象的な依頼では、意図しない修正
    (今回の例ではpackage.jsonが空になるなど)が発生しかねません。
    「AIを小学生だと思って、誰が聞いても同じ解釈になる言葉で説明する」
    くらいの意識が丁度良いと感じました。
  2. AIは万能ではない。最終確認は人間の仕事
    日付の間違いのように、AIは時として誤った情報を生成します。生成されたコードや
    内容を鵜呑みにせず、必ず人間の目でレビューし、テストするプロセスが
    不可欠です。AIはあくまで強力な「アシスタント」であり、
    最終的な品質責任は開発者にあります。

とはいえ、TypeScriptやReactの知識がほとんどない状態からでも、
AIの助けを借りることでアプリケーションの改修が実現できたのは大きな収穫でした。
AIをうまく活用すれば、新しい技術領域にも挑戦しやすくなるという実感を得られた、
非常に有意義な試みだったと思います。