はじめに
こんにちは、株式会社キカガク、プラットフォーム部の西村です。 みなさんは AI を好きな声で喋らせたいと思ったことはありませんか? 今回は Next.js と OpenAI API、そして VOICEVOX を使って、喋る AI を作成する方法を紹介します。 なお、この記事ではローカルサーバーで動かすことまでを目指します。(公開は想定していません)
必要な事前知識
- Next.js (App Router) の基本知識
- OpenAI の API の基本知識
完成像
テキストエリアにテキストを入力して、音声生成ボタンをクリックすると、音声が再生されます。
ざっくり説明すると、以下のような流れで実装しています。
ここから順に説明していきます。
手順
Next.js のセットアップ
create-next-app
でプロジェクトを作成してください。細かい説明は割愛します。
本記事は バージョン 14.2.5、App Router で実装しています。
OpenAI の API を叩くセットアップ
必要なパッケージをインストールします。
npm install openai #or yarn add openai
次に、API キーを.env.local
ファイルに保存します。
API キー を取得する方法は省略します!使ったことない方は調べてみてください!
# .env.local OPENAI_API_KEY=your-openai-api-key
次に、API エンドポイントを作成します。このエンドポイントは、ユーザーからのメッセージを OpenAI のチャットモデルに送信し、返信を取得します。 App Router を使ったことがない方は、公式のドキュメントを確認してイメージを掴んでいただければと思います。
// src/app/api/openai/route.ts import { NextResponse } from "next/server"; import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export async function POST(request: Request) { const { message } = await request.json(); if (!message) { return NextResponse.json( { message: "Message is required" }, { status: 400 } ); } try { const chatCompletion = await openai.chat.completions.create({ model: "gpt-4", messages: [ { role: "user", content: message, }, ], }); return NextResponse.json( { result: chatCompletion.choices[0].message.content, }, { status: 200 } ); } catch (error) { return NextResponse.json( { error: error instanceof Error ? error.message : "エラーが発生しました", }, { status: 500 } ); } }
このコードでは、ユーザーからの POST リクエストを受け取り、メッセージが含まれているかを確認します。含まれていない場合はエラーレスポンスを返します。メッセージがある場合は、OpenAI の API を呼び出してチャットの返信を取得し、その結果を JSON 形式で返します。
API クライアントを使用して、機能するか確認します。(ここでは Thunder Client を使用します)
問題なく返信が取得できていますね!これで Open AI の API を使う準備は完了です。
VOICEVOX のセットアップ
VOICEVOX とは、無料で使える中品質なテキスト読み上げ・歌声合成ソフトウェアです。VOICEVOX を使用して実現したいことは以下の 2 つです。
1. テキストからクエリを作成する。
2. 作成したクエリを基に合成音声を作成する
まずはVOICEVOX の公式サイトから VOICEVOX 本体をダウンロードしてください。
VOICEVOX は API を提供しています。アプリの起動後に以下の URL にアクセスするとドキュメントを確認できます。
早速、Thunder Client を使って確認してみましょう。
1. テキストからクエリを作成する
クエリとは、音声合成するために必要な情報で、セリフ以外にもアクセントなどの設定が含まれます。 もしアクセントを修正したい場合は、このクエリを編集してチューニングします。 例えば、http://127.0.0.1:50021/audio_query?text=あいうえお&speaker=1 で POST すると以下のようなクエリが取得できます。
{ "accent_phrases": [ { "moras": [ { "text": "ア", "consonant": null, "consonant_length": null, "vowel": "a", "vowel_length": 0.27477818727493286, "pitch": 5.669236183166504 }, // 略
text で内容を、speaker でキャラクターとスタイルを選択しています。 speaker の id について、誰が何に該当するかは http://127.0.0.1:50021/speakers で確認することができます。 (speaker=1 は「ずんだもん」の「あまあま」スタイルになります。)
2. クエリに沿って音声合成する
body に先ほど取得したクエリをコピペして、http://127.0.0.1:50021/synthesis?speaker=1 に送信してみましょう。成功した場合、レスポンスとして音声のバイナリが返ってくるので、ファイルとして保存します(Thunder Client の場合は「Save File」) 。この時点で音楽再生ソフトで再生できるハズです。
実装
次に、これらを使用して、テキストから音声合成を行うエンドポイントを作成します。
// src/app/api/voicevox/route.ts~ import { NextResponse } from "next/server"; export async function POST(request: Request) { const { text } = await request.json(); if (!text) { return NextResponse.json({ message: "Text is required" }, { status: 400 }); } try { // 音声合成用のクエリを作成 const queryResponse = await fetch( `http://127.0.0.1:50021/audio_query?text=${encodeURIComponent( text )}&speaker=1`, { method: "POST", } ); if (!queryResponse.ok) { const errorData = await queryResponse.text(); throw new Error(`Failed to create audio query: ${errorData}`); } const queryData = await queryResponse.json(); // 音声合成 const synthesisResponse = await fetch( "http://127.0.0.1:50021/synthesis?speaker=1", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(queryData), } ); if (!synthesisResponse.ok) { const errorData = await synthesisResponse.text(); throw new Error(`Failed to synthesize audio: ${errorData}`); } const audioBlob = await synthesisResponse.blob(); return new Response(audioBlob, { status: 200, headers: { "Content-Type": "audio/wav", }, }); } catch (error: unknown) { return NextResponse.json( { error: error instanceof Error ? error.message : "Something went wrong", }, { status: 500 } ); } }
このコードは、POST リクエストで受け取ったテキストを元に音声合成用のクエリを作成し、そのクエリを使って音声を合成します。生成された音声は音声ファイルとしてレスポンスに含められます。
最後に、Thunder Client で挙動を確認してみましょう。Save File で音声ファイルを保存し、プレイヤーで問題なく再生できることを確認してください。body に入れた text の内容で音声ファイルが作成できれば成功です。
注意点
利用規約は遵守してください。規約はキャラクター毎に異なるので必ずチェックしてください。
クライアント側で実行
では、ここまで実装した内容を、クライアント側で実行してみましょう。ユーザーが入力したテキストを OpenAI API に送り、その結果を VOICEVOX API で音声に変換する UI を作成します。
※スタイルは適当です。
// src/app/page.tsx "use client"; import React, { useState, useRef, useEffect } from "react"; export default function Page() { const [message, setMessage] = useState<string>(""); const [generatedMessage, setGeneratedMessage] = useState<string>(""); const [audioUrl, setAudioUrl] = useState<string>(""); const audioRef = useRef<HTMLAudioElement>(null); const handleGenerateVoice = async () => { if (!message) { alert("プロンプトを入力してください"); return; } try { // OpenAI APIを呼び出してメッセージを生成 const openaiResponse = await fetch("/api/openai", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message }), }); if (!openaiResponse.ok) { const errorText = await openaiResponse.text(); alert("OpenAI APIの呼び出しに失敗しました。"); return; } const openaiData: { result: string } = await openaiResponse.json(); setGeneratedMessage(openaiData.result); // VOICEVOX APIで音声を生成 const voicevoxResponse = await fetch("/api/voicevox", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text: openaiData.result }), }); if (!voicevoxResponse.ok) { const errorText = await voicevoxResponse.text(); alert(`VOICEVOX APIの呼び出しに失敗しました。${errorText}`); return; } const voicevoxBlob = await voicevoxResponse.blob(); const voicevoxUrl = URL.createObjectURL(voicevoxBlob); setAudioUrl(voicevoxUrl); } catch (error) { alert("API呼び出し中にエラーが発生しました。"); } }; useEffect(() => { if (audioRef.current && audioUrl) { audioRef.current.play(); } }, [audioUrl]); return ( <div style={{ padding: "20px" }}> <h1 style={{ fontSize: "2em", color: "white", textAlign: "center" }}> おしゃべりAI (VOICEVOX:ずんだもん) </h1> <textarea value={message} onChange={(e) => setMessage(e.target.value)} placeholder="プロンプトを入力してください" style={{ width: "100%", height: "100px", marginBottom: "20px" }} /> <button onClick={handleGenerateVoice} style={{ display: "block", width: "100%", padding: "10px", backgroundColor: "#FF0080", color: "white", border: "none", borderRadius: "5px", cursor: "pointer", }} > 音声生成 </button> {generatedMessage && ( <div style={{ marginTop: "20px" }}> <h2 style={{ fontSize: "1.5em", color: "white" }}> 生成されたメッセージ: </h2> <p>{generatedMessage}</p> </div> )} {audioUrl && ( <div style={{ marginTop: "20px" }}> <h2 style={{ fontSize: "1.5em", color: "white" }}>生成された音声:</h2> <audio ref={audioRef} src={audioUrl} controls style={{ width: "100%" }} /> </div> )} </div> ); }
このコードは、ユーザーが入力したテキストを基に OpenAI の API と VOICEVOX の API を呼び出し、生成されたメッセージを音声として再生します。useEffect を使って音声が生成された後に自動的に再生されるようにしています。
テキストエリアにテキストを入力して、音声生成ボタンをクリックすると、音声が再生されます! これで喋る AI の実装は完了です!
さいごに
いかがでしたか?今回は、Next.js、OpenAI API、そして VOICEVOX を使って喋る AI を作成する方法をご紹介しました。 アイデア次第で様々な用途に応用できるので、ぜひ、自分なりの工夫を加えて開発してみてください!
余談ですが、私は Speech-to-Textを使って、会話ができるようにしてみました!音声認識の精度も高く、問題なく会話ができました。機会があれば記事にしようと思います!