この記事はdoda Developer Group Advent Calendar 2024の14日目の記事です。
こちらの記事以外にも沢山の記事が掲載されていますので、興味のある方は # doda Developer Group Advent Calendar 2024 で検索してみてください。
目次
はじめに
初めまして。パーソルキャリア株式会社のdoda CONNECTエンジニアリンググループ、doda マイクロサービスグループ所属の松本と申します。
普段はdodaサイトにて主にバックエンドエンジニアを担当しています。
最近では各企業でAI活用が盛んとなっていますよね。
dodaでも職務経歴書の自動生成機能がリリースされたり、社内生成AIツールが活用されていたりと今後の活用も期待されます。
個人的にも生成AIなどを触っていたことはあるものの、AWSのAIサービスの知識はあまりありませんでした。
今回の記事では「せっかくだから個人的に気になっていたAWSのAIサービスを使いたい!」という願望を叶えるべく、英会話のための音声チャットボットを作成してみました。
ざっくりと流れを紹介していきたいと思います。
開発環境
Next.js: 14.2.13 Typescipt: 5.6.2 Material UI: 6.1.1 AWS SDK for JavaScript: 3.682.0
アーキテクチャ
最終的なアーキテクチャは下記のようになりました。
今回は簡易に機能を試すため、アプリケーション部分はローカルに建てたNext.jsで完結させました。
基本的なデータの流れは下記のようになります。
- デバイスから音声ストリームを取得する
- Amazon Transcribe APIに音声ストリームを渡し、文字起こしの結果を取得する
- Amazon Bedrock APIにテキストを渡し、会話の返答を取得する
- Amazon Polly APIにテキストを渡し、読み上げ結果の音声ストリームを取得する
- デバイス上で音声を再生する
チャット画面
今回アプリケーションはNext.js、UIコンポーネントライブラリについてはMaterial-UIを採用しました。
Material UIは簡単に一貫した品質のUIを作成でき、React向けのテンプレートも公開しているため、個人開発で工数を抑えたい場合などにも重宝しています。
Speech to Text
文字起こし部分は Amazon Transcribeを利用しました。
Amazon Transcribeは大きく「標準」「コール音声用」「医療用」と用途やモデルが分かれており、さらに標準は個人を特定できる情報をマスキングまたは消去したり(PIIリダクション)、カスタム言語モデル(CLM)での文字起こしを行うことができます。
Amazon Transcribeの詳細な料金については下記をご覧ください。利用開始から12 か月間、1 か月あたり 60 分の無料枠があります。
今回はリアルタイムストリーミングの用途で利用するため、公式の導入方法に従って TranscribeStreamingClient
を入れました。
基本的な使い方についても公式のサンプルを見れば大まかわかるのでありがたいです。下記のように StartStreamTranscriptionCommand
を作成し、別途マイクから取得したaudioストリームを渡して実行します。
// クライアントの生成 const client = new TranscribeStreamingClient({ region: "ap-northeast-1", credentials: { accessKeyId: // accessKeyId secretAccessKey: // secretAccessKey sessionToken: // sessionToken }, }); // 音声データを送信 const command = new StartStreamTranscriptionCommand({ LanguageCode: "en-US", MediaEncoding: "pcm", MediaSampleRateHertz: 44100, AudioStream: audioStream(), }); const response = await client.send(command);
余談ですが、今回JavaScript上で実行しているためサーバーサイドでの認証情報(EC2やContainerにアタッチされているロールや、環境変数)の取得ができません。
Publicな環境変数で認証情報を渡すことはセキュリティ的に脆弱なため、API Routeで sts:GetSessionTokenCommand
を用いて、10分程度の有効期間で一時的な認証情報を取得しています。
オーディオについては、下記のようにマイクからオーディオストリームを取得し、適宜PCMエンコードなどに変換したものを渡しています。
// ユーザーから音声取得の許可を得てメディアストリームを生成 const mic = new MicrophoneStream(); mic.setStream( await window.navigator.mediaDevices.getUserMedia({ video: false, audio: true, }) ) // オーディオストリームを生成(エンコードを適宜行う) const audioStream = async function* () { for await (const chunk of mic as unknown as Buffer[]) { yield { AudioEvent: { AudioChunk: pcmEncodeChunk( chunk ) /* pcm Encoding is optional depending on the source */, }, }; } };
Text to Text
会話の推論についてはAmazon Bedrockを利用しました。
利用できる基盤モデルはAmazon社のTitan、Anthropic社のClaude、Meta社のLlamaなど数多くありますが、一部東京リージョンでは使用できないものもあります。
今回は Claude 3 Haiku
を採用しました。Bedrockのモデルの中でチャットに最適化されているのが限られており、その中で安価に利用できます。モデルカタログはAWSコンソールから確認でき、利用する場合は「アクセス権の付与」を行う必要があります。
詳細な料金体系については公式の情報も参考にしてみてください。
まずは公式の導入方法に従って BedrockRuntimeClient
を入れます。
基本的なチャットの用途となるため、こちらも公式のサンプルのように ConverseCommand
を作成し、送信するメッセージを渡してコマンドを実行しています。
// クライアントの生成 const client = new BedrockRuntimeClient({}); // コマンドの送信 const command = new ConverseCommand({ modelId: "anthropic.claude-3-haiku-20240307-v1:0", messages: messages, system: [ { text: notification, }, ], inferenceConfig: { maxTokens: 64, temperature: 0.5, topP: 0.9, }, }); const response = await client.send(command);
余談ですが、サーバサイドでクライアントを作成する場合は AWS_ACCESS_KEY_ID
や AWS_SECRET_ACCESS_KEY
などの環境変数、またはEC2やContainerにアタッチされているロールを優先順位に従って反映してくれます。
コマンド中で送信するメッセージの形式は下記のように役割(User/Assistant)と内容が入っています。
const messages = [ { role: "user", content: [{ text: "今日はいいお天気ですね。"}], }, { role: "assistant", content: [{ text: "そうですね。" }], }, ];
また、システムプロンプトとしてAnthropic社の公開しているプロンプトライブラリなども参考に、下記のようなプロンプトを与えています。
const notification = ` あなたは英語の教師です。以下の条件や推奨事項、過去の会話を元に新たな会話文を返却してください。 ### 条件 下記の条件は必ず守ってください。守らない場合はペナルティ対象です。 ・必ず英語で回答してください。 ・短いテキストで返却してください。 ### 推奨 下記は推奨される振る舞いです。良い振る舞いの場合は報酬を与えます。 ・文法やミスが疑われる場合は指摘をしてください。 ・円滑な会話となるようにカジュアルな英語を使ってください。 ・ユーザーが返答しやすいように質問を投げかけてください。 `;
実際に先ほどのステップで音声入力をしてから、そのテキストをAmazon Bedrockに投げてみました。
Text to Speech
最後のステップとして、Amazon Bedrockから渡されたテキストを読み上げます。
音声合成ではAmazon Pollyを利用しました。
Amazon Polly は、40 以上の言語と言語バリアントで 100 以上の男性と女性の音声を提供しており、より自然な発話となるような「ニューラル合成音声」も選択可能です。
Amazon Pollyの詳細な料金については下記の公式情報を参照ください。
こちらも公式の導入方法に従って PollyClient
を入れました。公式のサンプルのように、 SynthesizeSpeechCommand
にテキストを渡してコマンドを実行します。
// クライアントの生成 const client = new PollyClient({ region: "ap-northeast-1", credentials: { accessKeyId: // accessKeyId secretAccessKey: // secretAccessKey sessionToken: // sessionToken }, }); // テキストの送信 const command = new SynthesizeSpeechCommand({ OutputFormat: "mp3", Text: textResponse, VoiceId: "Danielle", SampleRate: "22050", TextType: "text", LanguageCode: "en-US", Engine: "neural", }); const response = await client.send(command);
返却されたオーディオストリームをデバイス上で再生できるように変換し、バックグラウンドで自動再生するようにしました。
const [audioUrl, setAudioUrl] = useState<string>(); // ブラウザで利用できるようにオーディオストリームを変換 const buffer = Buffer.from( await response.AudioStream.transformToByteArray() ); const blob = new Blob([buffer], { type: "audio/mpeg" }); const url = URL.createObjectURL(blob); setAudioUrl(url);
// 上記データが更新されたときに自動再生する {audioUrl && ( <audio controls autoPlay hidden key={audioUrl}> <source src={audioUrl} type="audio/mpeg" /> </audio> )}
完成形
GIFで載せている都合上音声は確認できませんが、一通りの英会話が出来るようになりました。
終わりに
ここまで読んでいただきありがとうございました。 実装の詳細は省きましたが、少しでも皆さんの参考になれば幸いです。
今回は個人的に気になっていたAWSの生成AIサービスを使って、簡単な英会話チャットアプリを作成してみましたが、AWS SDKを入れてしまえばドキュメントも豊富なため、比較的容易に利用を開始できるのがうれしいですね。
実際にサービスを提供する場合には、各工程でのモデルの選定や最適化、認証情報の管理なども必要となるためそこは課題かと思いますが、今後は社内でも生成AIを活用していきたいと思います。 それでは、皆さんも良い「やってみた」ライフを!
松本 佳紘 Yoshihiro Matsumoto
プロダクト&マーケティング事業本部 クライアントP&M本部 プロダクト統括部 クライアントサービス開発部 dodaCONNECTエンジニアリンググループ エンジニア
2022年4月にパーソルキャリアへ新卒入社。企業メディア領域の開発チームにてエンジニア・スクラムマスターに従事。2023年10月にdodaサイトのマイクロサービスPJTに参画。
※2024年12月現在の情報です。