#66　アシスタントに画像の認識の機能を実装してみる

# 概要
以前の[記事](https://qiita.com/nxted_sapporo/items/157adffa3c6931b3b238)でDiscord上でOpenAIのAPIを使ってアシスタントを作成しました

今回の記事では、アシスタントに画像の認識の機能を実装してみます

# Discordで送信された画像のURLを受け取る
### 実装
Discordではメッセージに複数のファイルを添付することができます

複数の添付ファイルを取得し、画像ファイルの判定を行うコードを書いてみます

ここでのポイントは以下の3点です
- message.attachments が添付ファイルになります
- attachment.contentType にファイルタイプ(MINEタイプ)が格納されています
- attachment.url に添付ファイルのURLが格納されています

```typescript
client.on('messageCreate', async message => {
    const attachments = message.attachments;
    for (let attachment of attachments) {
        if (attachment[1].contentType == "image/png" || attachment[1].contentType == "image/jpeg" || attachment[1].contentType == "image/gif") {
            logger.debug("url": attachment[1].url);
        }
    }
});
```

# OpenAIのAPIに画像を渡す
### 実装
前回実装した以下の部分を修正していきます
```typescript
//メッセージをOpenAIのAPIに渡せる形に成型する
const messages: ChatCompletionMessageParam[] = [];
for (let message of messagesCollection) {
    const role = ((message[1].author.id == client.user?.id) ? "assistant" : "user");
    messages.push(
        {
            role: role,
            content: message[1].content
        }
    );
}

logger.debug(message);

//APIにメッセージを投げて返答を抽出
const content = (await openai.chat.completions.create({
    messages,
    model: 'gpt-4-turbo-preview'
})).choices[0].message.content;
if (!content) {
    return;
}

//返信
message.reply(content);
```
取得した画像のURLをOpenAIのAPIに渡すコードを書いてみます

ここでのポイントは以下の点です
- Userは画像ファイルを渡すことができますが、Assistantは画像ファイルを渡せないという制約があります
  - 画像のURLなどを渡す場合はChatCompletionContentPart[]の形である必要があります
- モデルはgpt-4-turbo-previewではなく、gpt-4-turboを使用します
```typescript
//メッセージをOpenAIのAPIに渡せる形に成型する
const messages: ChatCompletionMessageParam[] = [];
for (let message of messagesCollection) {
    const role = ((message[1].author.id == client.user?.id) ? "assistant" : "user");
    if (role == "assistant") {
        //前回と同じコード
        messages.push(
            {
                role: role,
                content: message[1].content
            }
        );
    }
    else if (role == "user") {
        const content: ChatCompletionContentPart[] = [
            {
                type: "text",
                text: message[1].content
            }
        ]
    
        //添付ファイルを取得
        const attachments = message[1].attachments;
        for (let attachment of attachments) {
            //ファイルが画像の場合はURLを抽出
            if (attachment[1].contentType == "image/png" || attachment[1].contentType == "image/jpeg" || attachment[1].contentType == "image/gif") {
                content.push({
                    type: "image_url",
                    image_url: {
                        "url": attachment[1].url
                    }
                });
            }
        }
        messages.push(
            {
                role: role,
                content: content
            }
        );
    }
}

logger.debug(message);

//APIにメッセージを投げて返答を抽出
const content = (await openai.chat.completions.create({
    messages,
    model: 'gpt-4-turbo'
})).choices[0].message.content;
if (!content) {
    return;
}

//返信
message.reply(content);
```
## 動作確認
画像を渡してあげると適切に画像を解釈してくれることが確認できました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843084/dc765c84-4d77-058d-747c-14e9ffb0a58b.png)


# まとめ
前回に続き、Discord上でOpenAIのAPIを呼び出してみました

gpt-4-vision-previewがgpt-4-turboに統合されたので1つのモデルで完結することができました


## 公式ドキュメント
- Discord.js: https://discord.js.org/docs/packages/discord.js/main
- OpenAI: https://platform.openai.com/docs/api-reference