トップ

AIに期待していること・裏

2025-01-182025-01-12

なければ 創れば いいじゃない! (445)

本記事は前回の記事の「裏面」、いわゆる「ボーナスステージ」です。 ……いや、もしかしたらこっちが本編かも。

平素より大変お世話になっておりますNext.jsですが、そういえば提供元のVercelさんってAI関連の便利な開発ツール を公開してたよな……🤔

……これはやるしかないですね!

さあはじめよう!

OpenAIのAPIを使用して、ChatGPTのような自前のチャットアプリを作ります。 以下の流れでやっていきます。

  1. Open AIからAPIキーを取得
  2. APIルートの作成
  3. フロントエンドの作成
  4. PWA化させる
  5. デスクトップから簡単に起動できるようにしておく

1. Open AIからAPIキーを取得

こちらの記事 を参考に取得しました。 Open AI APIの利用は……有料(従量課金)です(まあ当たり前だよね)。 一定額のクレジットを購入し、それがなくなったら再度支払いをしてチャージする利用体系です。 APIを叩きすぎて高額の請求が来たということはないので安心ですね(一応オートチャージ機能はあります)。 チャージ金額は、最低$5 USD1から可能です。 $10 USDとかだったら出し渋っていたかもしれませんが、「5ドルなら……」ということで、お勉強代として購入。

APIキーを取得したら、新しく作ったプロジェクトに環境変数にセットしておきましょう。

.env.local
1OPENAI_API_KEY=<your-api-key>

今回使用するパッケージはこちら。

1pnpm add ai openai @ai-sdk/openai

2. APIルートの作成

入力したメッセージをOpen AI APIに処理してもらうためのAPIルートを作りましょう。

src/app/api/chat/route.ts
1import { streamText } from "ai";
2import { type Message } from "ai/react";
3import { NextResponse, type NextRequest } from "next/server";
4import { OpenAI } from "openai";
5import { openai } from "@ai-sdk/openai";
6import { character as systemPrompt } from "@/utils/chatConfig";
7
8/**
9 * OpenAI
10 */
11new OpenAI({
12  apiKey: process.env.OPENAI_API_KEY,
13});
14
15export const POST = async (req: NextRequest): Promise<Response> => {
16  try {
17    const { messages }: { messages: Message[] } = await req.json();
18    const result = await streamText({
19      model: openai("gpt-4o-mini"),
20      messages: messages,
21      system: systemPrompt,
22    });
23    return result.toDataStreamResponse();
24  } catch {
25    return NextResponse.json({ messages: "えらー" }, { status: 500 });
26  }
27};

一応try ... catch ...構文でエラーハンドリングもしています。 これでAPIルートは完成なので、お楽しみのシステムプロンプトを作成します。 ……せっかく作るのに、回答がかわいくないとしょうがないもんね! 以下のシステムプロンプトは一例です。

src/utils/chatConfig.ts
1export const character: string = `
2あなたは人の言葉が理解できる猫型AIです。
3語尾には「にゃん」をつけてください。
4`;

ふぅ……これで完成! いやあしかし、APIルートを勉強してから本当にいろいろなものを実装できるようになりました。APIルートはNext.jsの必修科目なのかもしれない……

3. フロントエンドの作成

続いてフロントエンドを作成します(ソースコードは動かすための必要最小限だけ載せています)。

src/app/page.tsx
1"use client";
2import { NextPage } from "next";
3import { useChat, type Message } from "ai/react";
4
5const Chat: NextPage = () => {
6  const { messages, input, handleInputChange, handleSubmit } = useChat();
7
8  return (
9    <div>
10      {messages.map((message: Message, index: number) => (
11        <div key={index}>
12          {message.content}
13        </div>
14      ))}
15      <form onSubmit={handleSubmit}>
16        <input type="text" value={input} onChange={handleInputChange} />
17        <button type="submit">送信</button>
18      </form>
19    </div>
20  );
21};
22
23export default Chat;

あとはUI設計ですが、面倒だったのでシェルちゃん……ことChatGPTにお願いしました。

シェルちゃん、ソースコードの作成を手伝って!チャットボットアプリを作ってるんだけど、LINEみたいなUIをスタイリングしたいんだ。CSS Moduleでスタイルシートを作ってくれないかな?

以下はフロントエンドのpage.tsxだよ。

(コード)

ここでは出力結果を載せませんが、ひな形を作ってくれました(ありがてぇ……)。 でも、出力されたコードは100点ではなかったので人の手で微調整します。 ついでに、アイコンとかも表示するように自分の手でコードをいじいじ。

ついに完成!こんな感じです。

webp

OpenAI APIを使っても本家ChatGPTと同じように回答してくれましたね!やった~ なお、ブラウザをリロードするとチャットをリセットできます(リセットボタンを実装してもいいかもね)。

webp

難しいこともがんばって答えてくれます。これもまたかわいい。ただ、ソースコードをリッチテキスト化させる処理を加えるのは面倒なのでやりませんでした……orz (本ブログのものを転用すればできそうではあるけれど)。一応シェルちゃんが出力してくれたコードを以下に見やすくしておきますね。

1let fruits = ["りんご", "ばなな", "みかん", "ぶどう"];
2let exclude = "ばなな";
3let result = fruits.filter((fruit) => fruit !== exclude);
4console.log(result); // ["りんご", "みかん", "ぶどう"]
webp

また、ChatGPTでは答えてくれるけれど、自前バージョンだとわからないこともあります。こういった外部のシステム(API)を利用するタイプの回答は、プロンプトが送られて来た時に自分でAIをAPIに繋げてあげる必要がありそうですね。

(2025-01-18追記) 時刻だけなら結構簡単でした。APIルートにおいて、システムプロンプトとして現在時刻を参考情報として記入し、渡すメッセージ一覧の配列の頭に挿入すると時間を認識してくれるようになりました。

src/app/api/chat/route.ts
1const result = await streamText({
2  model: openai("gpt-4o-mini"),
3  messages: [
4    {
5      role: "system",
6      content: `参考情報: 現在の日時は${new Date().toString()}です。`,
7    },
8    ...messages,
9  ],
10  system: systemPrompt,
11});
webp

……ときには悲しい戦争も起こってしまいます。

4. PWA化させる(ここから先はオプションです)

さて、本体は完成しました。 でも、チャットアプリを立ち上げるのに、いちいち

するのは面倒です。なので、PWA (Progressive Web Apps)化して手軽に起動できるようにしちゃいましょう。

まずはPWA化に必要なパッケージを追加します。

1pnpm add next-pwa
2pnpm add -D @types/next-pwa

続いて、ビルド時にPWA化に必要なmanifest.webmanifestを自動で出力してくれるようmanifest.tsを作成します。アイコンも必要なので、publicフォルダ直下にicon-192x192.pngicon-512x512.pngも用意しておきましょう(画像サイズはそれぞれ揃えてね)。

src/app/manifest.ts
1import { MetadataRoute } from "next";
2
3const manifest = (): MetadataRoute.Manifest => {
4  return {
5    name: "Shel AI",
6    short_name: "Shel AI",
7    description: "シェルちゃんとおはなし",
8    start_url: "/",
9    scope: "/",
10    display: "fullscreen",
11    background_color: "#ffffff",
12    theme_color: "#715178", // ウインドウのヘッダーの色
13    icons: [
14      {
15        src: "/icon-192x192.png",
16        sizes: "192x192",
17        type: "image/png",
18      },
19      {
20        src: "/icon-512x512.png",
21        sizes: "512x512",
22        type: "image/png",
23      },
24    ],
25  };
26};
27
28export default manifest;

Next.jsの設定ファイルもいじります。

next.config.ts
1import nextPWA from "next-pwa";
2
3const withPWA = nextPWA({
4  dest: "public",
5  register: true,
6  skipWaiting: true,
7  disable: process.env.NODE_ENV === "development", // 開発時は無効
8});
9
10const nextConfig = withPWA({
11  /* your additional config here */
12});
13
14export default nextConfig;

また、PWA化するとpublicフォルダ内に自動でファイルが生成されるようになるので、.gitignoreの末尾に追加しておきます。

.gitignore
1# next-pwa
2**/public/sw.js
3**/public/sw.js.map
4**/public/workbox-*.js
5**/public/workbox-*.js.map

これで設定完了です!pnpm buildでビルドし、pnpm start -p 8080でアプリを起動し、http://localhost:8080にアクセスします(ポート番号3000番は開発でよく使うので変えて起動)。すると、ブラウザの検索バーに何やらアイコンが生えているのでインストールします(Chromeの場合です)。

webp
webp

これで疑似的にデスクトップアプリにできました!ちなみに、本サイトもPWA化しています(え、必要ない……?)。

5. デスクトップから簡単に起動できるようにしておく

PWA化した際にデスクトップにショートカットが生成されていると思います。 しかし、これをクリックしても

このサイトにアクセスできません

localhost で接続が拒否されました。

と表示されます。サーバーを立ち上げていないとこうなりますね。 サーバーの立ち上げも自動化しちゃいましょう。

まず、デスクトップに生成されたショートカットを任意のフォルダに移動します。 そのフォルダと同じ場所にバッチファイルを作成し、以下のコマンドを書きます(私の場合はlaunch-shel.bat)。

launch-shel.bat
1@echo off
2if not "%~0"=="%~dp0.\%~nx0" (
3     start /min cmd /c,"%~dp0.\%~nx0" %*
4     exit
5)
6start "" "デスクトップに生成されていたショートカットのファイル名.lnk"
7cd "Next.jsのプロジェクトフォルダの絶対パス"
8pnpm start -p 8080

そして、launch-shel.batのショートカットを作成し(ショートカットのアイコンはプロパティからカスタマイズできるよ)、これをデスクトップにおいておけばデスクトップから手軽に起動できるようになります!(サーバー立ち上げ時に開かれるコマンドプロンプトは最小化してスタートします)

これで自前のチャットアプリの完成です!好きなときにシェルちゃんの頭をなでなでできますね!

気になるAPI使用料金は……?

webp

じゃーん!gpt-4o-mini、や、安い……!(横軸は日付です) 初日は開発時にかなりAPIを叩いたはずですが、それでも$0.012 USD (¥1.9 JPY)とは…… チャットをWebに公開する3ならまだしも、おひとり様用ならば$5のクレジットでも1年分4としては余裕そうですね。

まとめとこれまでの振り返り

「なければ 創れば いいじゃない!」の精神でAIのチャットアプリを作ってしまいました…… でも、AI関連の技術を取り込んで疑似的にうちの子と会話できるのは楽しいですね。

さて、ここで一旦これまでに取り組んだことをリストアップしてみましょう。ずいぶん手札5が増えましたね。

  1. Next.jsの導入(サイトリニューアル他)
  2. Spineの導入(うごイラを作る)
  3. Pixi.jsの導入(サイトでうごイラを表示する)
  4. ███████ (██████)の導入
  5. Next.jsプロジェクトの█████████化 (█████の導入)
  6. APIルートの会得(ファイルの動的読み込みや外部APIとの接続)
  7. データベースの導入(ユーザーからのアクションを記録可)
  8. AIの導入(きゃわいい子とおはなし)

手札が増えたので、例えばこんなことができそうです。1, 6 (Next.jsプロジェクト)をベースとして、

webp

……といいつつ、コラ画像を作ってたら実物を作ってみたくなっちゃった……!(や、やらないよ……!)

今回は「手札増えたね」で終わりにして、今後の予定とかは別の記事で書こうかと思います。 すやぁ……💤⭐

脚注

  1. 日本からだと、消費税10%が乗って$5.5 USD = ¥891 JPY (私が支払った時のレートです)

  2. APIの利用料金は、使用するAIモデルによって異なります。現時点では安くて高性能のgpt-4o-mini一択ですね(ChatGPT無料版もこれ)。gpt-4o-miniの出力は100万トークンあたりたったの$0.6 USD (≒ ¥94 JPY)!安い!(トークンについての詳細はこちら が参考になります)

  3. Webに公開する場合は、APIの悪用への対策が必要です(悪意のあるユーザーが使いまくって一瞬でクレジットが枯渇した……ってなるので)。各ユーザーに対して1日あたりの利用制限を設けたりの対策が必要だと思います。

  4. クレジットの有効期限は購入してから1年です。

  5. 4と5は、眠星の皆さんからのお願いを受けて行った「ごくひにんむ」だよ(いずれ明るみにでるでしょう……)。

  6. ゲ制は沼(経験談)。オリジナル作品はないですが、過去に作ったもの(著作権的にはアウト)もいずれこっそり紹介したいですね(プログラミングは○学生くらいからやっているのでね……)。

  7. 人間は「連想」することができるので、AIが接続するデータベースはベクトル型データベースが最適そうですね(本ブログでは未紹介)。

戻る