ChatGPTからの返答をVOICEVOXの合成音声を使ってDiscordに返す

2023/03/30に公開

はじめに

今回は以下の記事の続きです
https://zenn.dev/yatsu_tomi/articles/c149d56d1667b3

Discord・VOICEVOX・OpenAIのAPIを使います

始め方

Dockerを利用するのであれば、pullしてrunですぐ実施可能!
(すごくありがたい)

コマンド
docker pull voicevox/voicevox_engine:nvidia-ubuntu20.04-latest
docker run --rm --gpus all -p '127.0.0.1:50021:50021' voicevox/voicevox_engine:nvidia-ubuntu20.04-latest

しかし、上記のコマンドを叩くと以下のエラーが...

コマンド
docker: Invalid ip address: '127.0.0.1.
See 'docker run --help'.

検索してもあまり良い記事がヒットせず...
公式の「ポートの公開と露出(-p、--expose)」のサンプルを見ると以下のようにIPアドレスとポートを''を囲まず実行しています

コマンド
docker run -p 127.0.0.1:80:8080 ubuntu bash

https://docs.docker.jp/engine/reference/commandline/run.html
同様に以下のように実行すると成功...

コマンド
docker run --rm -p 127.0.0.1:50021:50021 voicevox/voicevox_engine:cpu-ubuntu20.04-latest

http://localhost:50021/docs にアクセスするとAPI仕様書が確認できました

axiosを使ってVOICEVOXと通信をする

axiosの新しいインスタンスを作成
axios.get("http://localhost:50021")と同じ動作をする
複数のエンドポイントを持つときエンドポイントごとにインスタンスを作成しておくと便利

index.js
const instance = axios.create({
 baseURL: "http://localhost:50021",
});

その他、リクエストのconfigは以下の公式ページ
https://axios-http.com/docs/req_config

application/x-www-form-urlencoded形式でPOSTしたいが、以下のエラーが出てしまいうまくできない...

idnex.js
const instance = axios.create({
  baseURL: "http://localhost:50021",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
  },
});

const params = new URLSearchParams();
params.append("text", getResponse.data.choices[0].message.content);
params.append("speaker", 1);

const audio_query = await instance.post("/audio_query", params);
AxiosError: Request failed with status code 422
    at settle (C:\Users\XXXX\MyApps\chatgpt-chatbot\node_modules\axios\dist\node\axios.cjs:1900:12)
    at IncomingMessage.handleStreamEnd (C:\Users\XXXX\MyApps\chatgpt-chatbot\node_modules\axios\dist\node\axios.cjs:2952:11)

POSTなのにURIの後にクエリパラメータをつけてますが、ひとまずこれで動いたのでこのまま
いまだにbodyで送る方法がわからないので今後も調べます

postの第二引数のdataはrequest bodyとして扱われるためクエリリクエストがうまくできていない?のかと思いましたがまだよくわかっていません

VOICEVOXの/audio_queryのRequest URLが以下のような形になるので、postのURLを直接作って送ることにしたら成功しました

idnex.js
const audio_query = await instance.post(
      "audio_query?text=" +
        encodeURI(getResponse.data.choices[0].message.content) +
        "&speaker=1"
    );

あとは受け取った音声合成用のクエリを音声合成します

idnex.js
const synthesis = await instance.post(
      "synthesis?speaker=1",
      JSON.stringify(audio_query.data),
      {
        responseType: "arraybuffer",
        headers: {
          accept: "audio/wav",
          "Content-Type": "application/json",
        },
      }
    );
fs.writeFileSync("demo.wav", new Buffer.from(synthesis.data), "binary");

あとはwavファイルをdiscordに送信する仕組みをいれて完成です

idnex.js
await message.channel.send({
    files: ["./demo.wav"],
  });

最終的にできたコードがこちら

idnex.js
require("dotenv").config();
const { Client, GatewayIntentBits, Attachment } = require("discord.js");
const { Configuration, OpenAIApi } = require("openai");
const axios = require("axios").default;
const fs = require("fs");

const clientData = {
 intents: [
   GatewayIntentBits.Guilds,
   GatewayIntentBits.GuildMessages,
   GatewayIntentBits.MessageContent,
 ],
};

const configurationData = {
 organization: process.env.OPENAI_ORG,
 apiKey: process.env.OPENAI_KEY,
};

//prepare to connect
const client = new Client(clientData);
const configuration = new Configuration(configurationData);
const openai = new OpenAIApi(configuration);

const instance = axios.create({
 baseURL: "http://localhost:50021",
 headers: {
   "Content-Type": "application/x-www-form-urlencoded",
 },
});

//check for when a message on discord
client.on("messageCreate", async function (message) {
 try {
   //don't response
   if (message.author.bot) return;
   if (message.channel.name !== "chatgpt") return;

   const getResponse = await openai.createChatCompletion({
     model: "gpt-3.5-turbo",
     messages: [{ role: "user", content: `${message.content}` }],
   });

   const audio_query = await instance.post(
     "audio_query?text=" +
       getResponse.data.choices[0].message.content +
       "&speaker=1"
   );

   const synthesis = await instance.post(
     "synthesis?speaker=1",
     JSON.stringify(audio_query.data),
     {
       responseType: "arraybuffer",
       headers: {
         accept: "audio/wav",
         "Content-Type": "application/json",
       },
     }
   );

   fs.writeFileSync("demo.wav", new Buffer.from(synthesis.data), "binary");

   await message.channel.send({
     files: ["./demo.wav"],
   });

   message.reply(`${getResponse.data.choices[0].message.content}`);

   return;
 } catch (err) {
   console.log(err);
 }
});

client.login(process.env.DISCORD_TOKEN);
console.log("ChatGPT Bot is Online on Discord");

ChatGPTからの返答をVOICEVOXを使って合成音声化した後、Discordにwavファイルとして送信する仕組みを作りました。
ずんだもんがしゃべってくれるのでかわいいですね。

今後はwavファイルを読み上げてくれる仕組みも作りたいですね。

参考サイト

https://qiita.com/ahogemarumetano/items/554e95faf8749fac8631

Discussion