生成AIにLINE Botを作ってもらう GPT4o編 その1 

# はじめに

いろいろなパターンの[LINE Botサーバーの開発](https://qiita.com/bathtimefish/items/34e128cb2578c4db0845)を解説してきていますが、最近ふと生成AIが普通にLINE Botのコードを書くようになったらもうこういう説明必要なくね？と気づきました。

なので今回から現時点の生成AIがLINE Messaging API SDKとかを使ってどの程度書けるかを試してみようと思います。

# やってみる

まず誰に聞いてみるかという話ですが、やはり超優秀と世界的に有名な[GPT4o](https://openai.com/index/hello-gpt-4o/)先生からだと思います。

ChatGPTのGPT4oモデルを選択した状態で、簡単なエコーサーバーを書いてもらおうと思います。最近の趣向の関係上、TypeScriptでDeno上で動くやつにしてもらいます。

## 最初のプロンプト

```prompt.txt
LINE Botを開発したいです。以下の条件で簡単なLINE Botサーバーのコードを書いてください。

# 条件
1. LINE Messaging API SDK for nodejs を利用する
2. TypeScriptで記述する
3. その他LINE Botとして確実に作動するよう配慮したコードとする
4. コード以外に作動するために必要な設定等があれば解説文を書く
```

## 応答（コピペしてテキストレイアウトを調整したもの）

以下に、LINE Botを開発するための基本的なTypeScriptコードと、その設定手順を説明します。

## 前提条件
1. Node.jsとnpmがインストールされていること
1. LINE Developersで開発者アカウントを作成し、Messaging APIチャネルを作成していること

## 1. プロジェクトのセットアップ
プロジェクトディレクトリを作成します:

```bash
mkdir line-bot-ts
cd line-bot-ts
```

npm initでpackage.jsonを作成します:

```bash
npm init -y
```

必要なパッケージをインストールします:

```bash
npm install @line/bot-sdk express
npm install --save-dev typescript @types/node @types/express ts-node
```

TypeScriptの初期設定を行います:

```bash
npx tsc --init
```

この後、tsconfig.jsonの中で、"target": "es6"や"moduleResolution": "node"を確認してください。

### 2. LINE Botサーバーのコード
以下が基本的なLINE Botのサーバーコードです。

```typescript
// src/app.ts
import express from 'express';
import { ClientConfig, Client, middleware, MiddlewareConfig, WebhookEvent } from '@line/bot-sdk';

const app = express();

// LINEのチャネルアクセストークンとチャネルシークレットを設定
const config: ClientConfig & MiddlewareConfig = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.LINE_CHANNEL_SECRET || '',
};

const client = new Client(config);

// ミドルウェアをセットアップ
app.use(middleware(config));

// Webhookエンドポイント
app.post('/webhook', (req, res) => {
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

// イベントハンドラ
function handleEvent(event: WebhookEvent) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  const replyText = `You said: ${event.message.text}`;
  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: replyText,
  });
}

// サーバーを起動
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on ${port}`);
});
```

### 3. 環境変数の設定

```bash
export LINE_CHANNEL_ACCESS_TOKEN="YOUR_CHANNEL_ACCESS_TOKEN"
export LINE_CHANNEL_SECRET="YOUR_CHANNEL_SECRET"
```

もしくは、.envファイルを作成して以下のように記述します:

```.env
LINE_CHANNEL_ACCESS_TOKEN=YOUR_CHANNEL_ACCESS_TOKEN
LINE_CHANNEL_SECRET=YOUR_CHANNEL_SECRET
```

.envファイルを使う場合は、dotenvパッケージをインストールし、以下のようにインポートします:

```bash
npm install dotenv
```

```typescript
// src/app.ts
import dotenv from 'dotenv';
dotenv.config();
```

### 4. TypeScriptコンパイルとサーバーの実行

TypeScriptコードをコンパイルして実行します:
```bash
npx tsc
node dist/app.js
```

もしくは、開発中はts-nodeを使って直接実行できます:

```bash
npx ts-node src/app.ts
```

### 5. Webhook URLの設定
LINE Developersコンソールで、Webhook URLを設定します。例えば、ローカル開発環境ではngrokを使って一時的な公開URLを作成し、そのURLをWebhook URLとして設定することが可能です。

```bash
ngrok http 3000
```

### 6. その他の注意点

- セキュリティを確保するため、Webhookの検証を確実に行うようにしてください。
- エラーハンドリングや例外処理を十分に行うことで、安定した動作を確保します。

これで、基本的なLINE Botのサーバーが完成です。追加機能を実装したり、イベントハンドリングを拡張することで、より高度なボットを開発できます。

## プロンプト更新

ざっと読むとめっちゃそれっぽく要点を押さえているように見えますが、条件で`Denoで動作すること`を忘れていました。なので追記します

```prompt.txt
ありがとう。とてもよく理解できましたが、しかし条件を一つ忘れていました。TypeScriptのコードをDeno上で動作するよう更新してみてください。
```

## 応答

Denoで動作するようにTypeScriptコードを更新します。DenoはNode.jsとは異なり、標準ライブラリに多くの機能が組み込まれており、npmパッケージを使う代わりにDeno用のモジュールを使用します。

### 1. プロジェクトのセットアップ

DenoはNode.jsのようにpackage.jsonファイルが不要で、直接コードを書き始めることができます。

### 2. LINE Botサーバーのコード

以下がDenoで動作するように書き直したコードです。

```typescript
import { serve } from "https://deno.land/std/http/server.ts";
import { Client, middleware } from "https://esm.sh/@line/bot-sdk";

// LINEのチャネルアクセストークンとチャネルシークレットを設定
const config = {
  channelAccessToken: Deno.env.get("LINE_CHANNEL_ACCESS_TOKEN") || "",
  channelSecret: Deno.env.get("LINE_CHANNEL_SECRET") || "",
};

const client = new Client(config);

// Denoのサーバー設定
const port = Deno.env.get("PORT") || 3000;
const server = serve({ port: +port });
console.log(`Server running on port ${port}`);

for await (const req of server) {
  if (req.method === "POST" && req.url === "/webhook") {
    const body = await req.json();
    await Promise.all(body.events.map(handleEvent))
      .then((result) => {
        req.respond({ status: 200, body: JSON.stringify(result) });
      })
      .catch((err) => {
        console.error(err);
        req.respond({ status: 500, body: "Internal Server Error" });
      });
  } else {
    req.respond({ status: 404, body: "Not Found" });
  }
}

// イベントハンドラ
async function handleEvent(event: any) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  const replyText = `You said: ${event.message.text}`;
  return client.replyMessage(event.replyToken, {
    type: "text",
    text: replyText,
  });
}
```

### 3. 環境変数の設定

Denoでは、環境変数を読み込むために--allow-envフラグを使用します。Denoを起動する際に、以下のように設定します。

```bash
deno run --allow-net --allow-env src/app.ts
```

LINE_CHANNEL_ACCESS_TOKENとLINE_CHANNEL_SECRETをDeno.env.getで取得していますので、.envファイルや環境変数の設定方法はNode.jsと同様です。

### 4. Webhook URLの設定
LINE Developersコンソールで、Webhook URLを設定します。Denoのサーバーも同様にngrokを使ってローカルホストを公開することが可能です。

```bash
ngrok http 3000
```

これで、Deno上で動作するLINE Botが完成です。Node.jsと比べて、Denoは簡潔で安全な実行環境を提供するため、--allow-netや--allow-envなどのパーミッションを明示的に指定する必要があります。

#実行してみる

AWSにEC2インスタンスを立てて実行してみます。

```bash
mkdir GPT4o-1 && cd $_
deno init
vi main.ts
```

```main.ts
import { serve } from "https://deno.land/std/http/server.ts";
import { Client, middleware } from "https://esm.sh/@line/bot-sdk";

// LINEのチャネルアクセストークンとチャネルシークレットを設定
const config = {
  channelAccessToken: Deno.env.get("LINE_CHANNEL_ACCESS_TOKEN") || "",
  channelSecret: Deno.env.get("LINE_CHANNEL_SECRET") || "",
};

const client = new Client(config);

// Denoのサーバー設定
const port = Deno.env.get("PORT") || 3000;
const server = serve({ port: +port });
console.log(`Server running on port ${port}`);

for await (const req of server) {
  if (req.method === "POST" && req.url === "/webhook") {
    const body = await req.json();
    await Promise.all(body.events.map(handleEvent))
      .then((result) => {
        req.respond({ status: 200, body: JSON.stringify(result) });
      })
      .catch((err) => {
        console.error(err);
        req.respond({ status: 500, body: "Internal Server Error" });
      });
  } else {
    req.respond({ status: 404, body: "Not Found" });
  }
}

// イベントハンドラ
async function handleEvent(event: any) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  const replyText = `You said: ${event.message.text}`;
  return client.replyMessage(event.replyToken, {
    type: "text",
    text: replyText,
  });
}

```

環境変数を設定します

```bash
export LINE_CHANNEL_ACCESS_TOKEN="YOUR_CHANNEL_ACCESS_TOKEN"
export LINE_CHANNEL_SECRET="YOUR_CHANNEL_SECRET"
```

実行します

```bash
deno run -A main.ts
```

以下のようなエラーが出ました

```bash
Warning Implicitly using latest version (0.224.0) for https://deno.land/std/http/server.ts
Download https://esm.sh/v135/axios@1.7.2/denonext/unsafe/platform/node/classes/URLSearchParams.js (6
Listening on http://localhost:8000/
Server running on port 3000
error: Uncaught (in promise) TypeError: server is not async iterable
for await (const req of server) {
                        ^
    at file:///home/ubuntu/llm-linebots/GPT4o-1/main.ts:17:25
```

# おわりに（つづく）

はい。たしかにこの部分はなんじゃこれ？と思って怪しんでましたがやっぱエラーでした。なんか全体的にやりたいことは伝わるけど辻褄があってない感じですよね。

ここから4o先生に質問攻めをして動くコードに持っていってくれるかをやっていきたいですが、今日は時間がないので次回からやっていこうと思います。

方針としては人間(ボク)は一切コードを補完せず、プロンプトのみで修正していきます。