OpenAI Realtime APIを試す

今回は、OpenAI のリアルタイムオーディオを試してみたいと思います。
音声とオーディオ用の GPT-4o Realtime API
リアルタイムオーディオは、音声の入出力をサポートしたものであり、マイク等で話した音声をそのまま OpenAI に投入し、その回答が直接音声出力できるようになります。これまでも、音声入力→テキスト化→LLM処理→回答のテキスト出力→音声出力といった形で組み合わせることで実現できていましたが、その処理の実装が簡略化できます。
※本記事は、2024/11/05 時点のAzure版リアルタイムオーディオの機能/API/サンプルコードをもとにしています。
リアルタイムオーディオを試す
リアルタイムオーディオは、Azure OpenAI Studio (または、Azure AI Studio)で簡単に試すことができます。(1) Azure OpenAI Studio を開き、「リアルタイムオーディオ」を選択します。
(2) リアルタイムオーディオ対応モデルのデプロイを選択します。
![]() |
(3) 対応したデプロイが作成されていない場合は、「新しいデプロイの作成」リンクから作成することができます。
※執筆時点では、「gpt-4o-realtime-preview」モデルのみが対応しており、リージョンは [East US2] と [Sweden Central] のみとなっていました。
![]() |
(4) 必要に応じて、「サーバーターン検出」「パラメーター」を設定します。音声は、3種類から選択できるようになっていました。
![]() |
(4) 設定が完了したら、「聞き取りを開始」ボタンをクリックすると音声入力が可能となります。
話しかけると、それに対応した回答が音声で聞こえてきます。
APIを試してみる
リアルタイム オーディオ用の APIも使用できるようになっているため、独自のアプリケーションに組み込むことも可能です。GitHubにサンプルがありますので、そちらを試してみます。APIサンプルは、こちら にあります。サンプルをそのまま実行する
サンプルは、.NET / Javascript /Python が用意されていましたので、.NET版を見ていきます。
.NET版は、コンソールアプリでマイク入力のものとファイル入力のサンプルがありました。今回は、マイク入力(console-from-mic)を試していきます。
サンプルの Program.cs の以下の箇所で、環境変数から Azure OpenAI の接続情報を取得していますので、環境変数に設定すればすぐに試すことができます。
private static RealtimeConversationClient GetConfiguredClient() { string? aoaiEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string? aoaiUseEntra = Environment.GetEnvironmentVariable("AZURE_OPENAI_USE_ENTRA"); string? aoaiDeployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT"); string? aoaiApiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); string? oaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
内容をもう少し見てみます。 設定は、以下の部分(ConversationSesstionOptions)でしています。
ここでは、Toolsと InputTranscriptionOptions しか設定していませんが、 Azure OpenAI Studioの画面で設定できるような 指示文や音声の指定や Temperture 等のパラメータを設定できます。
await session.ConfigureSessionAsync(new ConversationSessionOptions() { Tools = { finishConversationTool }, InputTranscriptionOptions = new() { Model = "whisper-1", }, });
Tools に [finishConversationTool] という指定があり、関数定義は以下のようになっていました。
Descriptionに「ユーザーが別れを告げたり、終了を表明したり、あるいは対話を中止したいように見えるときに呼び出される。」(日本語訳)と記載されています。音声でこのサンプルプログラムを終了させるための関数のようです。処理ループを見てみると、[finishConversationTool ] が呼び出されたときに、ループを抜けるような実装となっています。
(実際に実行してみると、発話の誤認で意図しないタイミングで終了してしまうこともありましたが、、、)
ConversationFunctionTool finishConversationTool = new() { Name = "user_wants_to_finish_conversation", Description = "Invoked when the user says goodbye, expresses being finished, or otherwise seems to want to stop the interaction.", Parameters = BinaryData.FromString("{}") };
それでは、サンプルに手を加えずにそのまま実行してみます。
![]() |
[USER:] の部分(緑枠部分)が、音声認識された情報となります。少し表示が怪しいですが回答を見るとそれっぽく拾えているようにも見えます。
1回目は「クイックソリューションズについて教えて」と拾われてしまったようで、謎の回答が得られました。
改めて、「QUICK E-Solutions という会社を教えて」と、対象を会社に絞って問いかけてみました。
会社であることは認識してくれましたが、もう少し詳しく教えてとの回答になりました。
続けて、「東京にある QUICK E-Solutions という会社を教えて」としてみました。
それっぽく認識してくれましたが、結局、具体的な情報が見つからないとのことでした。
サンプルをカスタマイズして実行する
サンプルを少し修正してみます。今回は、先ほど回答できなかった「QUICK E-Solutions」という会社の質問に対して、答えられるようにします。
実用的ではありませんが、「QUICK E-Solutions」という会社の情報を答えられるだけの関数を無理やり追加してみます。
関数は、以下のように定義しました。キーワードは、どのようなことを知りたいかを示す項目で「会社概要」と「従業員数」を設定しています。
ConversationFunctionTool quickESolutionsQuestion = new() { Name = "getQuickESolutions", Description = "ユーザーが、QUICK E-Solutions(クイックイーソリューションズ)という会社の情報を求めている場合に呼び出される。", Parameters = BinaryData.FromString( """ { "type": "object", "properties": { "keyword": { "type": "string", "description": "QUICK E-Solutions の何が知りたいかを指定する。", "enum": ["会社概要", "従業員数"] } }, "requred": ["keyqord"] } """) };作成した関数を呼び出せるように、設定に追加します。指示文と Voiceの設定も追加しました。
await session.ConfigureSessionAsync(new ConversationSessionOptions() { Instructions = "あなたはユーザーからの質問に答えるAIボット。日本語で話してください。", Tools = { finishConversationTool, quickESolutionsQuestion, }, InputTranscriptionOptions = new() { Model = "whisper-1", }, Voice = ConversationVoice.Shimmer });関数が呼び出された場合の処理を追加します。サンプルなので、ベタ書きの雑な実装です。
else if (itemFinishedUpdate.FunctionName == quickESolutionsQuestion.Name) { var arg = itemFinishedUpdate.FunctionCallArguments; Console.WriteLine(arg); var jsonArg = JsonDocument.Parse(arg); string keyword = ""; if (jsonArg.RootElement.TryGetProperty("keyword", out var placeElement)) { keyword = placeElement.GetString() ?? string.Empty; } string message = ""; if (keyword == "会社概要") { message = $@"株式会社QUICK E-Solutions 設立:1983年3月1日 事業内容: QUICKの金融情報サービスシステムの開発、運用 情報ネットワークの設計・施工・管理 情報機器・ソフトウェア等の販売 企業本社オフィスや証券・金融店舗内装の企画・設計・施工 グループ: 日本経済新聞社グループの一員として、デジタル事業を担っています。 日本を代表する金融情報ベンダーであるQUICKの金融情報システムを支えるとともに、 日経グループNo.1のITプロ集団として、その使命と経験を礎とし、 お客さまにとっての最適かつ確実なソリューションの実現に貢献します。 "; } else if (keyword == "従業員数") { message = $"231人(2024年4月現在)です。"; } Console.WriteLine($"tool : {message}"); var toolResult = ConversationItem.CreateFunctionCallOutput(itemFinishedUpdate.FunctionCallId, message); await session.AddItemAsync(toolResult); }これを実行してみると、以下のようになりました。
![]() |
想定通り動作し、会社概要や従業員数を回答できるようになりました。
「QUICK E-Solutionsについて教えて」と何を知りたいか伝えずに質問したところ、会社概要か従業員数のどちらが必要か聞き返してくれました。その後に 「会社概要」または「従業員数」を聞いてみると、関数呼び出しの引数に指定され、tool が該当する応答を返し(青枠部分)、それをもとに音声での回答(赤枠部分)できるようになりました。基本的には、Function Calling の動きになります。
おわりに
今回は、簡単な音声のみのチャットボットサンプルを試してみました。関数呼び出し部分に、RAG構成の検索処理を追加すれば追加の知識にも対応できそうです。QESでは、「AIチャットボット構築サービス」をはじめとして、各AIサービスを利用したシステム導入のお手伝いをしております。それ以外でも様々なアプリケーションの開発・導入を行っております。提供するサービス・ソリューションにつきましては こちら に掲載しております。
システム開発・構築でお困りの問題や弊社が提供するサービス・ソリューションにご興味を抱かれましたら、是非一度 お問い合わせ ください。
※このブログで参照されている、Microsoft、Azure OpenAI、その他のマイクロソフト製品およびサービスは、米国およびその他の国におけるマイクロソフトの商標または登録商標です。
※その他の会社名、製品名は各社の登録商標または商標です。