Decisions API は GPT-Live に組み込まれており、ユーザーが話している間に低遅延で判断を下せます。
ネイティブ委譲では、アプリを経由する通信の往復を省けます。カスタム委譲では、Live がアプリに通知し、アプリが Decisions を呼び出します。ネイティブ委譲では、Live が Decisions を直接呼び出します。
判断のタイミング
- Live がアプリで処理できるリクエストを認識したら、Live から Decisions に判断を委譲します。
session.delegationに選択肢を設定し、どのような場合に委譲するかを Live に指示します。 - ユーザーの発話中や Live の応答中に会話に反応します。入力音声または出力音声の文字起こしの差分をトリガーにするには、
session.triggersを設定します。
Live セッションの進行中でも質問を変更できます。 session.update を送信し、対応する session.updated を受け取ってから、そのステップの判断に使う項目を追加します。委譲の種類はセッション作成時に設定し、そのセッション中は固定されます。
Live からの委譲
Live は 1 回のリクエストで、開発者が定義したアクションの選択と、アプリが Responses API を呼び出す必要があるかどうかの判断を Decisions に依頼できます。
車載音声アシスタントを作る例を考えます。ドライバーは、設定温度の上げ下げ、フロントガラスの曇り取りのオン・オフ、経路案内、飲食店の検索を依頼できます。none が選ばれた項目については、車内の設定を変更しません。
車内の設定変更
「ちょっと寒いな」
- 車内の設定温度上げる
- 曇り取り変更なし
- Responses の呼び出しなし
「設定温度を少し上げました」
車内の設定変更以外も含むリクエストの処理
- 車内の設定温度変更なし
- 曇り取りオン
- Responses の呼び出しあり
Responses からのツール呼び出しに従って、サンフランシスコまでの経路案内を開始し、現地のレストランを検索します。両方の結果を Responses に返します。
曇り取りの状態、経路案内を開始したかどうか、検索で見つかったレストラン。
委譲の設定
あらかじめ Live への接続が機能することを確認し、api.decisions.write 権限を用意します。ヘッダーは通常の Live 接続と同じものを使います。Decisions 専用のアルファ版ヘッダーは不要です。
WebRTC のセッションリクエストでは、次のオブジェクトの各フィールドを session 内の既存の transport と同じ階層に配置します。セッションの指示では、いつ委譲するか を Live に伝えます。選択式の質問では、何を返すか を Decisions に伝えます。1 回のネイティブ委譲リクエストで、Decisions は 3 つの質問にそれぞれ独立して回答します。
{
"model": "gpt-live-1",
"instructions": "Delegate the driver’s whole request, including cabin controls, navigation, and local search. Wait for the app to report what happened, then tell the driver in natural language.",
"delegation": {
"type": "decisions",
"decisions": {
"model": "gpt-6-luna",
"questions": [
{
"type": "choice",
"name": "cabin_temperature",
"instructions": "Choose temperature_up if the driver feels cold or wants more heat, temperature_down if they feel hot or want to cool down, or none if the request is unrelated.",
"choices": [
{ "value": "temperature_up" },
{ "value": "temperature_down" },
{ "value": "none" }
]
},
{
"type": "choice",
"name": "windshield_defogger",
"instructions": "Choose on if the driver reports a foggy windshield or asks to defog it, off if they say the glass is clear now or ask to turn the defogger off, or none if the request is unrelated.",
"choices": [
{ "value": "on" },
{ "value": "off" },
{ "value": "none" }
]
},
{
"type": "choice",
"name": "needs_response",
"instructions": "Choose yes if any part of the request goes beyond these cabin controls, such as getting directions or finding restaurants. Otherwise choose no.",
"choices": [{ "value": "yes" }, { "value": "no" }]
}
]
}
}
}
Live は会話の文字起こしを Decisions に自動で送信します。
アクションの実行と結果の報告
delegation.id を session.delegation.created から取得して保存します。decision.event のうち、その ID を delegation_id に持つイベントで event.type が decision.completed であれば、event.decision.answers から質問名ごとに選択結果を読み取ります。回答が none のアクションは実行しません。WebRTC イベントをフィルタリングする場合は、session.delegation.created と decision.event の両方を許可します。
「ちょっと寒いな」という発話に対して、Decisions は temperature_up、none、no を選びます。設定温度を上げて、実際の設定値を読み取ります。ここでは、ダッシュボードで自動モードの設定温度が 22 °C、曇り取りがオフになっているとします。確認した車内の状態を decision.item.create で Decisions に送ります。
{
"type": "decision.item.create",
"event_id": "evt_cabin_after_temperature_up_1",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Use the latest cabin state snapshot as the current state. Cabin temperature is set to 22 °C in automatic mode. Windshield defogger is off."
}
]
}
}
ダッシュボードに表示される状態が変わったときだけ、車内の設定全体を簡潔にまとめたスナップショットを追記します。最新のスナップショットを現在の状態として扱います。過去の入力をそのまま残すと、プロンプトキャッシュに使うプレフィックスを維持できます。
decision.item.created を受け取ってから、このリクエストの委譲 ID を指定して session.commentary.append で結果を Live に送ります。
{
"type": "session.commentary.append",
"delegation_id": "del_cabin_1",
"content": "I raised the cabin temperature and confirmed the new setting on the dashboard."
}
サンフランシスコへの案内を含む依頼では、Decisions は none、on、yes を選びます。曇り取りをオンにし、その状態を読み取って、先ほどと同じように更新後の車内のスナップショットを追記します。次に、api.responses.write 権限でサーバーから Responses を呼び出します。「そこ」がサンフランシスコを指すと Responses が判断できるように、車内の設定変更の結果とドライバーの依頼の全文を含めます。
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6.1-sol",
"instructions": "The car has handled the cabin controls. Use the tools for the rest of the request, then briefly tell the driver what happened based on the results.",
"input": "Driver: My windshield is foggy, so please defog it, navigate to San Francisco, and find me some good restaurants there.\nCabin results: Windshield defogger confirmed on. Temperature unchanged.",
"tools": [
{
"type": "function",
"name": "start_navigation",
"description": "Start navigation to a destination and report whether the route started.",
"parameters": {
"type": "object",
"properties": { "destination": { "type": "string" } },
"required": ["destination"],
"additionalProperties": false
},
"strict": true
},
{
"type": "function",
"name": "find_restaurants",
"description": "Find well-rated restaurants in the named city or area.",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"],
"additionalProperties": false
},
"strict": true
}
]
}'これが Responses の Function Calling の手順の最初のリクエストです。各 function_call で指定された関数を、渡された引数で実行します。次に、function_call_output に実際の結果またはエラーと、対応する call_id を含めて返します。Responses が最終回答を返すまで、この手順を繰り返します。
Live には、Responses からの簡潔な最終回答と、確認済みの車内の状態だけを送ります。session.commentary.append には同じ delegation_id を指定します。
文字起こしに応じた反応
会話のタイミングに合わせて反応させるには、文字起こしをトリガーにします。たとえば、デジタルキャラクターは「採用が決まったよ!」と聞いたら、ユーザーが話している間に笑顔でうなずくことができます。出力側のトリガーを使えば、Live が「それはよかったですね!」と話すのに合わせて、キャラクターをいっそうにこやかな表情にし、身を乗り出す動きも加えられます。
session.input_transcript.delta「採用が決まったよ!」
session.output_transcript.delta「それはよかったですね!」
session.input_transcript.delta「採用されなかったんだ」
次のようなトリガーを session.triggers に追加します。表情、その強さ、身振りをそれぞれ個別に選択するよう求めます。
{
"type": "decisions",
"event_type": "session.input_transcript.delta",
"decisions": {
"questions": [
{
"type": "choice",
"name": "expression",
"instructions": "Choose your expression as you listen to the person's latest words.",
"choices": [
{ "value": "neutral" },
{ "value": "happy" },
{ "value": "sad" },
{ "value": "concerned" }
]
},
{
"type": "choice",
"name": "intensity",
"instructions": "Choose how strongly to show that expression.",
"choices": [
{ "value": "low" },
{ "value": "medium" },
{ "value": "high" }
]
},
{
"type": "choice",
"name": "gesture",
"instructions": "Choose a gesture that fits what you just heard.",
"choices": [
{ "value": "none" },
{ "value": "nod" },
{ "value": "shake" },
{ "value": "lean" }
]
}
]
}
}
応答に合わせてキャラクターを動かすには、2 つ目のトリガーを追加します。event_type を session.output_transcript.delta に設定し、Live の発話内容に合った選択肢を選ぶよう指示します。モデルとトリガー ID は Live が指定します。文字起こしトリガーは委譲とは独立して動作します。
WebRTC イベントをフィルタリングする場合は、decision.event に加えて、設定した各トリガーに対応する文字起こしイベント(session.input_transcript.delta、session.output_transcript.delta、またはその両方)を許可します。Live がトリガーの結果を配信するのは、起点となった文字起こしイベントも許可されている場合だけです。
トリガーの結果には、きっかけとなった文字起こしの差分を特定する情報が含まれます。「採用が決まったよ!」の例で decision.event に含まれる主なフィールドは次のとおりです。
{
"type": "decision.event",
"delegation_id": null,
"trigger_id": "trg_listening_1",
"source_event_type": "session.input_transcript.delta",
"source_event_id": "evt_input_42",
"event": {
"type": "decision.completed",
"decision": {
"answers": [
{ "type": "choice", "name": "expression", "choice": "happy" },
{ "type": "choice", "name": "intensity", "choice": "medium" },
{ "type": "choice", "name": "gesture", "choice": "nod" }
]
}
}
}
source_event_id を使って、結果を対応する文字起こしの差分と照合します。Live は処理待ちの差分をまとめることがあり、結果が届く順序は前後する場合があります。入力側と出力側でそれぞれ該当する最新の結果を適用し、口の動きは音声に合わせて制御します。