シンプルなAIアプリ3

シンプルなAIアプリ2の続きです。

esbuild実行環境を整備する

前回はesbuildをコマンドラインから起動していましたが、それには、esbuildをnpm installしなくてはなりません。

Denoを使っているなら、build用スクリプトを作ってしまうことで、esbuildをインストールしなくても使えます。

src/server/build.tsを以下のように作成します。

import { build } from 'npm:esbuild';

const result = await build({
    entryPoints: ['src/client/script.ts'],
    bundle: true,
    minify: true,
    outdir: 'public',
    format: 'esm',
    charset: 'utf8',
});

console.log(result);

これを実行すると、public/script.jsが作成されます。以下が実行ログです。

> ls public/
index.html

> deno run --allow-read --allow-write --allow-env --allow-run src/server/build.ts
{
  errors: [],
  warnings: [],
  outputFiles: undefined,
  metafile: undefined,   
  mangleCache: undefined 
}

> ls public/
index.html  script.js

deno.jsonを更新する

プログラムに対して次の変更を加える前に、deno.jsonを作って、開発環境を少し整備します。

以下が、deno.jsonです。

{
  "tasks": {
    "build": "deno run --allow-read --allow-write --allow-env --allow-run src/server/build.ts",
    "start": "deno run --allow-net --allow-read  --allow-env src/server/main.ts"
  },
  "compilerOptions": {
    "lib": [
      "deno.window",
      "dom"
    ]
  },
  "exclude": [
    "public/*"
  ]
}

これは、大きく分けて2つの効果があります。

効果1: deno task の利用

Denoサーバーを起動するときに必要な引数が多くなってきました。"task" を設定することで、

deno task start

とすれば、 Denoサーバーが起動します。長い引数をつけて deno を起動しなくてもよくなるので、便利です。

同様に、public/script.jsを作成するための esbuild の実行も task に設定しています。

効果2:疑似エラーの解消

"compilerOptions" と "exclude"は、VSCodeのエラー対策です。

client/script.tsに対して、次のエラーが出ています。

Cannot find name 'document'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.
Cannot find name 'HTMLButtonElement'.
Cannot find name 'document'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.
Cannot find name 'HTMLTextAreaElement'.
Cannot find name 'document'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.

"compilerOptions" の "lib" に "dom" を追加します。"dom"だけを指定すると、別のエラーが出てしまうので、"deno.window"も一緒に指定します。

また、public/script.jsに対して、次のエラーが出ています。

`var` keyword is not allowed.

public/script.js はトランスパイルの生成物ですし、Deno が読み込むものでもありません。そのため "exclude" でチェックの対象外にします。

Gemini APIを利用する

いよいよ、Gemini API を利用する部分のコードを作りましょう。

以下が、変更後のsrc/server/main.tsのコードです。

import { serveDir } from "https://deno.land/std@0.224.0/http/mod.ts";
import { GoogleGenerativeAI } from "npm:@google/generative-ai";
import { IClientToServer, IServerToClient } from "../common/interface.ts";

// 環境変数からGEMINI APIキーを取得し、AIを準備する。
const apiKey = Deno.env.get('GEMINI_API_KEY')!;
const genAI = new GoogleGenerativeAI(apiKey);

/**
 * promptをGemini APIに送信し、結果を返す。
 * 
 * promptがない場合は、プロンプトがないというエラーを返す。
 * 
 * Gemini APIが何らかのエラーになった場合は、そのエラーを返す。
 * @param prompt 
 * @returns 
 */
async function sendPromptToAI(prompt: string): Promise<IServerToClient> {
    if (!prompt) {
        return { status: 'Error', result: "プロンプトがありません。" };
    }
    try {
        const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
        const result = await model.generateContent(prompt);
        return { status: 'Ok', result: result.response.text() }
    } catch (error) {
        return { status: 'Error', result: `エラーが発生しました。: ${(error as Error).message}` }
    }
}

async function handler(req: Request) {
    const url = new URL(req.url);

    if (url.pathname === "/ai-api" && req.method === "POST") {
        const { prompt } = await req.json() as IClientToServer;
        const sendData = await sendPromptToAI(prompt);
        return new Response(JSON.stringify(sendData), {
            headers: { "content-type": "application/json" }
        });
    }

    return serveDir(req, {
        fsRoot: "public"
    })
}

Deno.serve({ port: 3456 }, handler);

このコードは、Denoを試す3のコードを流用しています。

sendPromptToAI()関数を追加し、AIにプロンプトを送信します。返された結果をIServerToClientのオブジェクトにするようにしました。

"/ai-api" に対する処理では、sendPromptToAI()をコールし、Responseを返すようにしています。

実際の実行例

prepare と start の task を実行します。

>deno task prepare
Task prepare esbuild --bundle --outdir=public --format=esm --charset=utf8 --minify src/client/script.ts

  public/script.js  524b 

⚡ Done in 35ms

> deno task start
Task start deno run --allow-net --allow-read  --allow-env src/server/main.ts
Listening on http://0.0.0.0:3456/

そして、ブラウザで localhost:3456/index.html にアクセスし、前回と同じプロンプトを入力しました。
その結果、次のように表示されました。

今回までの作業状況

今回の作業でbuild.tsとdeno.jsonが増えました。以下に最新のディレクトリ構成と状況を記載します。

/project-root
|-- deno.json(New)
|-- /public
|   |-- index.html
|   |-- styles.css(未着手)
|   |-- script.js
|-- /src
|   |-- /common
|   |   |-- interface.ts
|   |-- /client
|   |   |-- script.ts
|   |-- /server
|       |-- main.ts(Update:AIとの通信処理を実装)
|       |-- build.ts(New)

コメント