ChatGPTを使ってImageFlux Live Streamingを音声で操作してみた

こんにちは、テリーです。
ChatGPTにはFunction Callingという機能があります。ChatGPTが外部の関数やAPIを呼び出すための仕組みです。通常のテキスト応答ではなく、関数の引数を含むJSONを返し、システム側で処理を実行できます。例えば「東京の天気は?」と聞くと、天気APIを呼ぶ関数を提案し、その結果をもとに回答を生成します。これにより、最新のデータ取得や自動化が可能になり、アプリ開発での活用が広がります。

ここでポイントとなるのは、ChatGPTが直接関数を呼ぶのではなく、「会話の中で関数が呼ばれようとしていることをイベントで通知」する仕組みです。主となる操作の主体はブラウザ上で行われますのでChatGPTに認証キー等を提供する必要がなく、関数呼び出しイベントを受けて何をするかはアプリケーション次第です。

前回の記事ではRealTime APIを使って、人間とChatGPTが会話のキャッチボールをすることができました。今回は、人間が「ライブ配信を始めて」「ライブ配信を止めて」というような指示を出すとImageFlux Live StreamingのAPIを使ってライブ配信を開始・停止するサンプルを作ってみました。

動作確認環境

  • Chrome 134.0.6998.166
  • openai-realtime-console 2025年3月11日付 コミットID 059bf8d

RealTime APIのFunction Callingのサンプルを解説

openai-realtime-consoleが最もシンプルな公式サンプルです。まずはオリジナルのコードを実行し、動作を確認します。

git clone https://github.com/openai/openai-realtime-console
cd openai-realtime-console
npm i
.envを修正して環境変数 OPENAI_API_KEY を設定
npm run dev

ブラウザで http://localhost:3000/ にアクセスし、ChatGPTと会話ができることを確認します。

openai-realtime-consoleには、Function Callingのデモも含まれています。Function Callingの定義はToolと呼ばれ、カラーパレットを提案する機能が実装されています。まずは「カラーパレットを表示して」と口に出して、応答を見てみましょう。すると、ChatGPTからは次のような返答があります。

「どんなテーマのカラーパレットをお探しですか?例えば、春らしいものやビンテージ風など、何かイメージがあれば教えてください」

ここで「4月の日本をイメージして」と追加で依頼すると、画面右側に5つのカラーコードが表示されます。

この一連の流れは、単に音声で会話のキャッチボールをしただけではなく、ブラウザ上のHTMLを書き換えるJavaScriptが実行されたことを意味します。このコードはToolPanel.jsxで実装されています。

Function Callingを使うためには、Toolを事前に定義し、ChatGPTに知らせる必要があります。カラーパレットのToolは、ToolPanel.jsxの11〜34行目に定義されています。なかでも最も重要なのは、4行目に記述されているdescriptionです。ここには「ユーザーがカラーパレットを要求したときにこの関数を呼び出します」と書かれています。どのような文脈でこのツールが呼び出されるかを、できる限り具体的に記述する必要があります。

次に重要なのが、19〜30行目に定義されているプロパティです。これは、ChatGPTが会話の文脈を踏まえて、このJSON形式で値を返してきます。ここでは、テーマ文字列と、16進数のカラーコード配列が定義されています。それぞれのdescriptionに詳しく説明が書かれているほど、意図した動作が得られやすく、失敗も少なくなります。

3. const functionDescription = `
4. Call this function when a user asks for a color palette.
5. `;
6. 
7. const sessionUpdate = {
8.   type: "session.update",
9.   session: {
10.     tools: [
11.       {
12.         type: "function",
13.         name: "display_color_palette",
14.         description: functionDescription,
15.         parameters: {
16.           type: "object",
17.           strict: true,
18.           properties: {
19.             theme: {
20.               type: "string",
21.               description: "Description of the theme for the color scheme.",
22.             },
23.             colors: {
24.               type: "array",
25.               description: "Array of five hex color codes based on the theme.",
26.               items: {
27.                 type: "string",
28.                 description: "Hex color code",
29.               },
30.             },
31.           },
32.           required: ["theme", "colors"],
33.         },
34.       },
35.     ],
36.     tool_choice: "auto",
37.   },
38. };

この定義をChatGPTに知らせるのが79行目です。

78.   if (!functionAdded && firstEvent.type === "session.created") {
79.     sendClientEvent(sessionUpdate);
80.     setFunctionAdded(true);
81.   }

さらに深掘りしていくと、App.tsxの90行目で、ChatGPTにデータを送信していることがわかります。

84. function sendClientEvent(message) {
85.   if (dataChannel) {
86.     const timestamp = new Date().toLocaleTimeString();
87.     message.event_id = message.event_id || crypto.randomUUID();
88. 
89.     // send event before setting timestamp since the backend peer doesn't expect this field
90.     dataChannel.send(JSON.stringify(message));
91. 
92.     // if guard just in case the timestamp exists by miracle
93.     if (!message.timestamp) {
94.       message.timestamp = timestamp;
95.     }
96.     setEvents((prev) => [message, ...prev]);
97.   } else {
98.     console.error(
99.       "Failed to send message - no data channel available",
100.       message,
101.     );
102.   }
103. }

定義したToolが呼び出された後の処理は、ToolPanel.jsxの89〜105行目に記述されています。90〜91行目の条件式が、Toolの定義にマッチしたことを指します。このサンプルでは、Tool実行後に、ChatGPTが追加で質問を人間に投げかけるように指示を出しています。

89.     if (
90.       output.type === "function_call" &&
91.       output.name === "display_color_palette"
92.     ) {
93.       setFunctionCallOutput(output);
94.       setTimeout(() => {
95.         sendClientEvent({
96.           type: "response.create",
97.           response: {
98.             instructions: `
99.             ask for feedback about the color palette - don't repeat 
100.             the colors, just ask if they like the colors.
101.           `,
102.           },
103.         });
104.       }, 500);
105.     }

ImageFlux Live Streamingを呼ぶためのToolの定義

Toolの定義方法と呼び出し方がわかったところで、自分の作りたい機能を実装してみましょう。今回はライブ配信の開始と終了を音声で指示をすると、Function Callingが反応するように記述します。

ToolPanel.jsxの11行目あたりに、下記のようにToolの定義を追加します。Toolは最大10個まで定義できます。

7. const sessionUpdate = {
8.   type: "session.update",
9.   session: {
10.     tools: [
11.       {
12.         type: "function",
13.         name: "start_live_streaming",
14.         description: "Call this function to start live streaming.",
15.       },
16.       {
17.         type: "function",
18.         name: "stop_live_streaming",
19.         description: "Call this function to stop live streaming.",
20.       },

次に、Toolが反応したことをまずはログで表示します。

100.   if (
101.     output.type === "function_call" &&
102.     output.name === "start_live_streaming"
103.   ) {
104.     startLiveStreaming();
105.   }
106.   else if (
107.     output.type === "function_call" &&
108.     output.name === "stop_live_streaming"
109.   ) {
110.     stopLiveStreaming();
111.   }
・・・
200. async function startLiveStreaming() {
201.   console.warn("Start live streaming...");
202. }
203. 
204. async function stopLiveStreaming() {
205.   console.warn("Stop live streaming...");
206. }

「ライブ配信を開始して」「ライブ配信を終了して」と話しかけると、ログにメッセージが表示され、Function Callingが期待通り反応していることを確認できます。

実行結果の通知

ここまで来ると、一般的なイベント処理、例えば「ボタンを押したらライブ配信を開始する」時と同じようにコードを書きます。

200. const videoRef = useRef(null);
201. const sora = useRef(null);
202. const publisher = useRef(null);
203. const pub_stream = useRef(null);
204. async function startLiveStreaming() {
205.   console.warn("Starting live streaming...");
206.   const {channel_id, sora_url} = await getChannelId();
207.   pub_stream.current = await navigator.mediaDevices.getUserMedia({
208.     video: true,
209.     audio: true,
210.   });
211.   const options = {};
212.   sora.current = Sora.connection(sora_url, true);
213.   publisher.current = sora.current.sendonly(channel_id, null, options);
214.   await publisher.current.connect(pub_stream.current);
215.   videoRef.current.srcObject = pub_stream.current;
216. }
217. 
218. async function stopLiveStreaming() {
219.   console.warn("Stopping live streaming...");
220.   publisher.current?.disconnect();
221.   publisher.current = null;
222.   pub_stream.current?.getTracks().forEach((track) => track.stop());
223.   pub_stream.current = null;
224. }
225. 
226. async function getChannelId() {
227.   // (略)
228. }

ChatGPTに関数の実行結果を伝えると、それを踏まえて言葉で表現してくれます。下記の105〜112行目が関数の実行結果を伝える部分で、113行目がそれを受けてChatGPTに話をさせる処理です。同様の処理を、ライブ配信の停止処理にも追加します。

100.   if (
101.     output.type === "function_call" &&
102.     output.name === "start_live_streaming"
103.   ) {
104.     startLiveStreaming();
105.     sendClientEvent({
106.       type: "conversation.item.create",
107.       item: {
108.         type: "function_call_output",
109.         call_id: output.call_id,
110.         "output": "Live streaming started."
111.       },
112.     });
113.     sendClientEvent({ type: "response.create" });
114.   }
115.   else if (
116.     output.type === "function_call" &&
117.     output.name === "stop_live_streaming"
118.   ) {
119.     stopLiveStreaming();
120.     sendClientEvent({
121.       type: "conversation.item.create",
122.       item: {
123.         type: "function_call_output",
124.         call_id: output.call_id,
125.         "output": "Live streaming stopped."
126.       },
127.     });
128.     sendClientEvent({ type: "response.create" });
129.   }

サンプル

最後に、実際にライブ配信を行ったサンプル動画をお見せします。指示は日本語でも英語でも出すことができ、「終了」「止めて」「もう一回やって」など、多少のニュアンスの違いも文脈から解釈してくれるのが、ChatGPTならではの挙動です。

https://terry-pixiv.github.io/knowledge_movie/202503_function_calling.mp4

まとめ

ChatGPTのFunction Calling機能を使って、ImageFlux Live Streamingに配信を開始・停止するサンプルを紹介しました。本記事ではオプションなしの単純な一方向の指示のみですが、カメラの種類を選択したり、音声をミュートにすることもできます。ぜひ挑戦してみてください。より詳しい実装方法に興味がある方はお気軽にご相談ください。