Wipple CPaaS
メニュー
Function リファレンス / LLM

LLM

session.llm({
  vendor: 'openai',
  model: 'gpt-realtime',
  auth: { apiKey },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  events: [
    'conversation.item.*',
    'response.output_audio_transcript.done',
    'input_audio_buffer.committed'
  ],
  llmOptions: {
    response_create: {
      output_modalities: ['audio'],
      instructions: 'Greet the caller warmly in English and ask how you can help today.',
      audio: {
        output: {
          voice: 'alloy',
          format: { type: 'audio/pcm', rate: 24000 }
        }
      },
      max_output_tokens: 4096
    },
    session_update: {
      type: 'realtime',
      instructions:
        'You are a friendly, helpful voice assistant on a phone call. ' +
        'Always respond in English unless the caller explicitly speaks another language. ' +
        'Keep responses concise and natural for spoken conversation. ' +
        'If asked about weather, call the get_weather function.',
      tools: [
        {
          name: 'get_weather',
          type: 'function',
          description: 'Get the weather at a given location',
          parameters: {
            type: 'object',
            properties: {
              location: {
                type: 'string',
                description: 'Location to get the weather from',
              },
              scale: {
                type: 'string',
                enum: ['fahrenheit', 'celsius'],
              },
            },
            required: ['location', 'scale'],
          },
        },
      ],
      tool_choice: 'auto',
      audio: {
        input: {
          format: { type: 'audio/pcm', rate: 24000 },
          transcription: { model: 'whisper-1' },
          turn_detection: {
            type: 'server_vad',
            threshold: 0.8,
            prefix_padding_ms: 300,
            silence_duration_ms: 500
          }
        },
        output: {
          format: { type: 'audio/pcm', rate: 24000 },
          voice: 'alloy'
        }
      }
    }
  }
});

パラメータ

modelstring必須

LLMモデルの名前です。


vendorstring必須

LLMベンダーの名前です。


actionHookstring

LLMセッションの終了時に呼び出されるWebhookです。


authobject

認証情報を含むオブジェクトです。形式はモデルに応じて異なります。


connectOptionsobject

接続先URIなどの情報を含むオブジェクトです。


eventHookstring

要求したLLMイベント(文字起こしなど)が発生したときに呼び出されるWebhookです。


eventsarray

要求するイベントを列挙したイベント名の配列です(ワイルドカード使用可)。


handoffobject

宣言的な人間へのトランスファー設定です。指定すると、ランタイムが transfer_to_human ツールを注入し、モデルがそのツールを呼び出したときにパッケージ済みの transfer 一連の処理を実行します。 人間へのハンドオフを参照してください。


hangupobject

組み込みの切断ツールを有効にします。指定すると、ランタイムがモデルのツールセットに hangup ツールを注入し、モデルがそのツールを呼び出すと通話が終了します。サポートされているすべてのs2sベンダーで動作します。後述の組み込み切断ツールを参照してください。


hangup.reasonstring

発信側BYEの X-Reason SIPヘッダーに設定されるデフォルトの理由です。通話時にモデルが独自の理由を渡さなかった場合のフォールバックとして使用されます。


llmOptionsobject

LLMへの指示を含むオブジェクトです。形式はLLMモデルに依存します。


toolHookstring

LLMが関数を呼び出そうとするときに呼び出されるWebhookです。


現在、次のLLMがサポートされています。

  • OpenAI Realtime API
  • OpenAI GPT Live API(限定アクセスのアルファ版)
  • Deepgram Voice Agent
  • Ultravox
  • ElevenLabs
  • Google Gemini Live API(Gemini Developer API または Vertex AI 経由)
  • AssemblyAI Voice Agent
  • xAI Voice Agent
  • Azure Voice Live

OpenAI Realtime

vendor: 'openai' を設定し、auth.apiKey でOpenAI APIキーを指定します。llmOptions には response_create と session_update の2つのペイロードを含めます。これらはそれぞれ対応する response.create および session.update クライアントイベントとしてOpenAIに転送されます。このページ冒頭の例はGA形式を使用しています。

モデルの選択

modelstring必須

GA版のrealtimeモデル名です。一般的な選択肢は次のとおりです。

  • gpt-realtime — 標準のGA版realtime会話モデルです。最初に試すモデルとして推奨します。
  • gpt-realtime-2 — 推論機能を備えたバリアントです。出力アイテムにおいて phase: "final_answer" の前に phase: "commentary" メッセージを出力します。どちらのフェーズも音声を生成するため、発信者には実際の回答の前にモデルが「考え事を口に出す」ように聞こえます。一部のアシスタントには有用ですが、まず挨拶から始める一般的なフローでは想定外の挙動になります。
  • gpt-realtime-whisper — ストリーミング文字起こしモデルです。response.output_audio_transcript.delta イベントを出力しますが、サーバー側でターンの終端検出を行いません(現在のGAでは .completed がありません)。通常の音声対音声(speech-to-speech)フローや gather フローには適していません。

OpenAIは2026年5月12日にRealtimeプレビューモデル(gpt-4o-realtime-preview-*)を非推奨にしました。GAモデルを使用してください。


インストラクション

session_update.instructions はセッション全体で持続するペルソナを設定します。response_create.instructions は1回のレスポンスに限ってそれを上書きします。GA版realtimeモデルはデフォルトで英語を前提としません。Wipple CPaaSが先に話す場合(発信者の音声より先に response.create が送信される場合がこれに当たります)、instructions で言語を明示的に固定してください(例: "Always respond in English unless the caller speaks another language")。そうしないと、任意の言語で挨拶が返ることがあります。

音声フォーマット

OpenAIとの間でやり取りされる音声フォーマットは {type: 'audio/pcm', rate: 24000} に固定されています。Wipple CPaaSはチャネルのネイティブレート(通常はG.711)との間で自動的にリサンプリングします。アプリケーションで設定した audio.input.format や audio.output.format はすべて上書きされます。例に含まれているのは網羅性のためにすぎません。

旧プレビュー形式との互換性

プレビューAPI向けに書かれたアプリは変更なしでそのまま動作します。Wipple CPaaSは session_update または response_create 内の旧フラット形式(トップレベルの modalities、voice、input_audio_format、output_audio_format、input_audio_transcription、turn_detection、temperature)を検出し、送信時にGA形式へ変換し、eventHook へ転送する前にGAイベント名をプレビュー時の名前にエイリアスし直します。たとえば、response.audio_transcript.done を購読していたアプリは、サーバーが現在 response.output_audio_transcript.done を出力していても、引き続きその type のままイベントを受け取ります。旧形式が検出されると、非推奨の警告が1回だけログに記録されます。

GAで無効なフィールドはWARNログを出力したうえで暗黙に除去されます。

  • response_create 内の temperature — GAでは受け付けられなくなりました。
  • フラットな文字列としての output_audio_format — audio.output.format: {type, rate} に置き換えられました(いずれにせよWipple CPaaSが pcm 24 kHzに上書きします)。

この互換レイヤーは移行期間中の一時的なシムとして提供されています。削除される前にアプリケーションをGA形式に更新する計画を立ててください。

OpenAI固有のllmOptionsフィールド

response_createobject必須

セッション開始時にOpenAIへ送信される初期の response.create.response ペイロードです。アシスタントからの最初のレスポンスを生成します。


response_create.output_modalitiesarray

出力モダリティの配列です。音声の場合は通常 ['audio'] です(GAでプレビュー時の modalities から名称変更されました)。


response_create.instructionsstring

レスポンス単位のインストラクション上書きです。この1回のレスポンスに限ってセッションレベルのインストラクションを置き換えます。最初の挨拶に便利です。モデルが先に話す場合は言語を明示的に固定してください。


response_create.audioobject

音声設定オブジェクトです。audio.output.voice で音声(例: alloy、marin)を選択し、audio.output.format を指定します(Wipple CPaaSにより pcm 24 kHzに上書きされます)。


response_create.max_output_tokensnumber

このレスポンスの最大トークン数です。


session_updateobject

初期の session.update.session ペイロードです。セッション全体のペルソナ、ツール、音声設定、VADを設定します。


session_update.typestring必須

GAでは 'realtime' でなければなりません。指定する場合は必須です。


session_update.instructionsstring

セッション全体のペルソナプロンプトです。response_create.instructions で上書きされない限り、セッション内のすべてのレスポンスで持続します。


session_update.audio.input.transcription.modelstring

会話ログ用に発信者の音声をテキストに変換する補助的な文字起こしモデルです。一般的な値は whisper-1、gpt-4o-transcribe、gpt-realtime-whisper です。会話モデルとは別のものです。


session_update.audio.input.turn_detectionobject

サーバー側VAD設定です: {type: 'server_vad', threshold, prefix_padding_ms, silence_duration_ms}。省略した場合はサーバー側のデフォルトが使用されます。対応するモデルでは、eagerness フィールドを伴う type: 'semantic_vad' もサポートされています。


session_update.toolsarray

モデルに公開する関数ツール定義の配列です。各エントリには name、type: 'function'、description、およびJSON Schema形式の parameters が必要です。モデルは item.type: 'function_call' を含む response.output_item.done を出力することでツールを呼び出します。Wipple CPaaSはそれを toolHook にルーティングします。


session_update.tool_choicestring

'auto'(デフォルト)、'none'、または強制する特定のツール名です。


OpenAI GPT Live

vendor: 'gptlive' を設定し、auth.apiKey にOpenAI APIキーを指定して、OpenAIの GPT Live API と通信します。

アルファ版API

GPT Liveを利用するには、OpenAIのEarly Access Programへの登録が必要です。通常のOpenAIキーでは接続自体は完了しますが、最初のサーバーイベントの時点でOpenAIから Voice session access denied として拒否されます。Wipple CPaaSはこれを completion_reason: 'server error' として報告します。

APIがアルファ版の間は、イベント名やフィールドが変更される可能性があることを想定してください。

vendor: 'openai' の単純な置き換えではありません

GPT Liveは OpenAI Realtime API とは異なるプロトコルであり、Realtime API向けの新しいモデルではありません。vendor: 'openai' からアプリを移行する場合は、まずRealtime APIからの移行をお読みください。ペイロードには互換性がありません。

最小構成は次のとおりです。ただし、これだけでは誰もモデルに先に話すよう指示していないため、無音の通話になることに注意してください(エージェントに先に話させるを参照)。

session.llm({
  vendor: 'gptlive',
  model: 'gpt-live-1-boulder-alpha',
  auth: { apiKey: process.env.OPENAI_API_KEY },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  llmOptions: {
    session_update: {
      instructions:
        'You are a friendly, helpful voice assistant on a phone call. ' +
        'Always respond in English unless the caller explicitly speaks another language. ' +
        'Keep responses concise and natural for spoken conversation.',
      audio: {
        output: { voice: 'marin' }
      }
    }
  }
});

モデルの選択

modelstring

GPT Liveモデル名です。デフォルトは gpt-live-1-boulder-alpha です。Functionに設定してください。session_update の内部には決して含めないでください。その1か所にだけ設定した場合でも、Wipple CPaaSはFunctionを拒否します。


音声フォーマット

設定すべき音声フォーマットはありません。GPT Liveは24 kHzモノラルPCMに固定されており、Wipple CPaaSが発信者のコーデックとの間で変換を行います。

session_update は必須

ここでは llmOptions.session_update は省略できません。GPT Liveは設定を受け取るまで発信者の音声を受け付けません。Wipple CPaaSはまずこれを送信し、session.started を待ちます。次に説明するとおり、挨拶はこのイベントを起点に行います。

response_create はありません。セッションが開始されると、モデル自身が会話を進めます。

エージェントに先に話させる

response_create がないため、モデルに最初のターンを取るよう指示するものが何もありません。そして、instructions に挨拶を書くだけでは不十分です。モデルは発信者を待ち、発信者には無音が聞こえます。

通話を開始するには、session.started を受信したらすぐに session.context.append を送信します。話してほしい文言と、いつ話すかを指定します。

session.on('/event', (evt) => {
  if (evt.type === 'session.started') {
    session.updateLlm({
      type: 'session.context.append',
      content: [{
        type: 'input_text',
        text: 'Immediately greet the caller using the exact text below. Do not wait for the '
          + 'caller to speak first. After the greeting, pause and listen.\n\n'
          + 'Hi, I am your support assistant. How can I help you today?',
      }],
    });
  }
});

通話中にエージェントに特定の内容を話させる必要があるときは、いつでも同じパターンを使用します。たとえば、開示事項、転送の案内、締めの挨拶などです。

挨拶は要求であり、保証ではありません

コンテキストの追加はモデルを誘導するものです。OpenAIによれば、モデルは文言を言い換えることがあり、まれに沈黙することもあります。文言を省略した場合、モデルは独自の文言を使用します。

正確な文言が重要な場合(法的な開示事項やブランドの挨拶など)は、モデルに任せないでください。llm Functionの前に say Functionで自分で再生してください。

注意

挨拶の要求には WebSocket トランスポートが必要です。llm:update コマンドはREST updateCall APIでは受け付けられないため、Webhookのみのアプリケーションには送信する手段がありません。

デリゲーション: モデルが作業を依頼する方法

デリゲーションはGPT Liveを特徴づける概念であり、このページの他のベンダーには直接対応するものがありません。モデルが音声会話の外部から必要とするもの(知らない事実、実行したい関数など)はすべてデリゲーションとして届きます。選択した種類によって、ツールを使用できるかどうかが決まります。

session_update.delegationobject

完全に省略すると、モデルは何も要求しなくなります。ツールもコンテキスト要求もありません。これが最も簡単な始め方です。


session_update.delegation.typestring

'client' — モデルはアプリケーションに対して、背景情報を散文形式で要求します。実装は簡単ですが、関数呼び出しはできません。

'responses' — モデルはOpenAIの Responses API 上でターンを実行し、関数を呼び出すことができます。ツール、mcpServers、handoff、hangup を使用する場合は必須です。


Responsesデリゲーション(関数呼び出し)

デリゲーション内でツールを宣言します。responses.model はデリゲートされたターンを実行する別の2つ目のモデルであり、Functionに指定した音声モデルではないことに注意してください。

delegation: {
  type: 'responses',
  responses: {
    model: 'gpt-5.5',          // required
    tools: [
      {
        type: 'function',
        name: 'get_weather',
        description: 'Get the current weather for a city.',
        parameters: {
          type: 'object',
          properties: { location: { type: 'string' } },
          required: ['location']
        }
      }
    ]
  }
}

警告

ツールは delegation.tools ではなく delegation.responses.tools に配置します。また、ネストされた responses オブジェクトとその model はどちらも必須です。

これらなしで handoff、hangup、mcpServers を設定した場合、Wipple CPaaSは通話が接続される前にFunctionを拒否します。ただし、独自のツール宣言までは検査できないため、そこで responses.model を省略すると、OpenAIが起動時にセッションを拒否し、completion_reason: 'server error' になります。

Clientデリゲーション(コンテキストの提供)

モデルは item.target: 'client' を含む delegation.created を出力します。アプリケーションは、アイテムのidと、単一の input_text パートに最大500トークンの散文を入れて応答します。

session.on('/event', (evt) => {
  if (evt.type === 'delegation.created' && evt.item.target === 'client') {
    session.updateLlm({
      type: 'delegation.context.append',
      delegation_item_id: evt.item.id,
      content: [{ type: 'input_text', text: 'It is 62 degrees and raining in Seattle.' }]
    });
  }
});

clientデリゲーションには必ず応答してください。キャンセルする方法はないため、応答しないまま放置するとモデルは待ち続け、発信者には無音が聞こえます。

ツール呼び出しの処理

responses デリゲーションでは、他のすべてのベンダーと同様に、Wipple CPaaSが {name, args, tool_call_id} を付けて toolHook を呼び出します。異なるのは、結果を返す際のエンベロープです。

session.on('/toolCall', async(evt) => {
  const { tool_call_id, name, args } = evt;

  session.sendToolOutput(tool_call_id, {
    type: 'delegation.function_call_output.create',
    item: {
      type: 'function_call_output',
      call_id: tool_call_id,
      output: JSON.stringify({ temperature: 62, conditions: 'rain' })
    }
  });
});

output は文字列でなければなりません。モデルが複数のツールを要求した場合は、呼び出しごとに1つずつ送信してください。また、その後に response.create を送らないでください。Realtime APIとは異なり、サーバーが自動的に再開します。

組み込みの handoff ツールと hangup ツール、および設定した mcpServers もここで動作します。ただし、client デリゲーションには関数を呼び出す手段がないため、responses デリゲーションでのみ動作します。

会話を追跡する

GPT Liveはアルファ版で公開されたイベントリファレンスがないため、以下がイベントの一覧です。必要なものをFunctionの events 配列に指定してください。response.* のようなワイルドカードや、'all' に -eventName による除外を組み合わせる指定も使用できます。

イベント 得られる情報
session.started セッションが開始されました。ここで挨拶の要求を送信します
session.updated, session.context.appended クライアントイベントに対する確認応答
turn.created, turn.delta, turn.done 発話単位で見た会話です。turn.done には turn.role('user' または 'assistant')と turn.transcript が含まれます
input_transcript.added, output_transcript.added 発信者およびエージェントのリアルタイムな部分的文字起こし断片
delegation.created, delegation.context.appended, delegation.function_call_output.created デリゲーションのライフサイクル
response.* デリゲートされたResponsesターン: response.created、response.output_text.delta、response.completed、response.failed、response.done など
output_audio.playback_started, output_audio.playback_stopped OpenAIではなくWipple CPaaSが出力するイベントです。エージェントの音声が実際に発信者へ届き始めたとき、および止まったときに出力されます
session.usage.updated 累積トークン使用量です。最終的な合計は session.closed で届きます
error 拒否されたクライアントイベント、またはサーバー側の問題

警告

events を完全に省略すると、Wipple CPaaSはすべてを転送します。これには量の多い文字起こし断片も含まれます。必要なイベントを明示的に指定してください。

会話を追跡するには、*_transcript.added の断片よりも turn.done を優先してください。断片の境界は完結した思考ではなく発話のリズムに従うため、1つの文が複数の断片に分かれて届くことがあります。

バージインについては何もする必要がありません。Wipple CPaaSが検出し、キューに入っているエージェントの音声を破棄します。基になるシグナルは eventHook には転送されません。

通話中にセッションを更新する

llm:update コマンド(Node SDKでは session.updateLlm())は、次の5つのイベントを受け付けます。

  • session.update — 設定を変更します。差分指定です。省略したフィールドは既存の値を保持します。delegation を置き換えるにはオブジェクト全体が必要です。
  • session.context.append — コンテキストを追加する、または発話メッセージを要求します
  • delegation.context.append — clientデリゲーションに応答します
  • delegation.function_call_output.create — ツールの結果を返します
  • session.close — セッションを正常に終了します

それ以外はすべて暗黙に破棄されます。エラーは返らないため、更新が何も効果を持たないように見える場合はtypeを確認してください。

セッションの終了のしかた

actionHook は completion_reason を伴って発火します。

completion_reason 意味
normal conversation end セッションが正常に終了しました
session closed: <reason> OpenAIが自身の理由でセッションを終了しました。その理由が含まれます
disconnect from remote end OpenAIが接続を切断しました
server error GPT Liveが起動時にセッションを拒否しました。ペイロードにはOpenAIの理由を含む error オブジェクトも含まれます。まずそれを確認してください
connection failure Wipple CPaaSが接続をまったく開けませんでした。ネットワーク、DNS、またはプロキシの問題です
hangup モデルが組み込みの hangup ツールを使用して通話を終了しました

ブリッジに至った handoff は、代わりにトランスファーの結果を報告します。他の一部のベンダーとは異なり、GPT Liveが server failure を報告することはありません。

すべての問題が通話を終了させるわけではありません。セッションの実行中に拒否されたクライアントイベント(古い delegation_item_id、長すぎるコンテキスト追加など)や失敗したデリゲーションは eventHook に報告され、会話は継続します。復旧したい場合はこれらのイベントを処理してください。無視した場合、モデルは送信しようとしていたコンテキストやツール結果なしで続行します。

注意

cancelOnBargeIn と cancelOnResponseTimeout は、このベンダーでは効果がありません。GPT Liveにはキャンセルする対象がないためです。responseTimeoutMs は動作しますが、デフォルトでは無効です。0以外の値を設定すると、デリゲーションが停滞したときに response.timeout イベントが発生し、llm:update で復旧できます。

Realtime APIからの移行

vendor: 'openai' からアプリを移植する場合:

Realtime(vendor: 'openai') GPT Live(vendor: 'gptlive')
llmOptions.response_create 非対応 — 削除してください。代わりに session.context.append で挨拶します
session_update.tools session_update.delegation.responses.tools
session_update.turn_detection 非対応 — ターン検出は自動で処理されます
session_update.audio の入出力フォーマット 非対応 — 音声は24 kHzモノラルPCMに固定されています
conversation.item.create によるツール結果 delegation.function_call_output.create
ツール結果の後に続けて送る response.create 不要 — サーバーが自動的に再開します
通話中の response.create / response.cancel 非対応
バージイン用の input_audio_buffer.speech_started Wipple CPaaSがバージインを自動検出します。シグナルは eventHook には転送されません

トラブルシューティング

症状 原因と対処
エージェントが一度も話さず、発信者に無音が聞こえる session.started 時に session.context.append を送っていません。エージェントに先に話させるを参照してください。この場合でもイベントの流れは正常に見えるため、再生イベントをエージェントが話した証拠と見なさないでください
エージェントが話していたのに、通話の途中で発信者側が無音になる 未応答の client デリゲーションです。キャンセルする方法はないため、必ず delegation.context.append で応答してください
Functionが server error で即座に終了する 多くの場合、Early Access Programに登録されていないキーが原因です(OpenAIは Voice session access denied で拒否します)。実際の理由は actionHook の error オブジェクトで確認してください
エージェントが発信者に挨拶するが、独自の文言を使う 文言を指定せずに挨拶を要求したか、モデルが文言を言い換えました。文言が固定されている場合は say Functionを使用してください
モデルが関数を一度も呼び出さない ツールが delegation.responses.tools の外で宣言されているか、delegation.type が 'client' になっています
通話が接続される前にFunctionが拒否される session_update 内に model を設定しているか、responses デリゲーションなしで handoff/hangup/mcpServers を設定しています
session.updateLlm() の呼び出しが何も効果を持たないように見える イベントtypeが受け付けられる5種類のいずれでもありません。認識されないtypeはエラーなしで破棄されます

xAI Voice Agent

vendor: 'xai' を設定し、auth.apiKey でxAI APIキーを指定します。xAIの Voice Agent は、上記の OpenAI Realtime セクションで説明したものと同じOpenAI Realtime GA方言を使用します。llmOptions には同じGA形式で同じ response_create と session_update のペイロードを含め、それらは response.create および session.update クライアントイベントとして転送されます。Wipple CPaaSは wss://api.x.ai/v1/realtime に接続します。

session.llm({
  vendor: 'xai',
  model: 'grok-voice-latest',
  auth: { apiKey: process.env.XAI_API_KEY },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  llmOptions: {
    session_update: {
      type: 'realtime',
      instructions:
        'You are a friendly, helpful voice assistant on a phone call. ' +
        'Always respond in English unless the caller explicitly speaks another language. ' +
        'Keep responses concise and natural for spoken conversation.',
      turn_detection: { type: 'server_vad' },
      audio: {
        output: { voice: 'eve' }
      }
    },
    response_create: {
      output_modalities: ['audio'],
      instructions: 'Greet the caller warmly in English and ask how you can help today.'
    }
  }
});

モデルの選択

modelstring必須

xAI Voice Agentのモデル名です。

  • grok-voice-latest — デフォルト。現在は grok-voice-think-fast-1.0 のエイリアスです。
  • grok-voice-think-fast-1.0 — フラッグシップモデルです。

音声(ボイス)

音声は、OpenAIと同じフィールドである session_update.audio.output.voice(または response_create.audio.output.voice)で設定します。利用可能な音声: eve(デフォルト)、ara、rex、sal、leo。

必須の session_update

OpenAIとは異なり、xaiでは llmOptions.session_update が必須です。最初の session.updated サーバーイベントを受信するまで、音声は流れ始めません。

音声フォーマット

xAIとの間でやり取りされる音声は24 kHzのpcm16です。Wipple CPaaSは session_update.audio の設定内容にかかわらず、このレート/フォーマットを強制します。別のフォーマットやサンプルレートを選択しようとしないでください。

ターン検出

ターン検出を session_update.audio.input.turn_detection の下にネストするOpenAI GAとは異なり、xAIは session_update のトップレベルに turn_detection を置くことを期待します。つまり、session_update.audio.input.turn_detection ではなく session_update.turn_detection です。{ type: 'server_vad' } が一般的な設定です。turn_detection を省略するか null に設定した場合(手動ターンモード)、アプリケーションがターンテイキングの責任を負い、llm:update コマンドで input_audio_buffer.commit / input_audio_buffer.clear クライアントイベントを送信できます。

ツール呼び出し

ツール/関数呼び出しはOpenAIと同じフィールド(session_update.tools、tool_choice)を使用します。完了したツール呼び出しは、OpenAIの response.output_item.done ではなく response.function_call_arguments.done イベントで届きます。Wipple CPaaSはそれを同じ方法で toolHook にルーティングします。

入力の文字起こし

session_update.audio.input.transcription を設定すると、発信者の発話の文字起こし結果が conversation.item.input_audio_transcription.updated で届きます。OpenAIの .completed イベントとは異なり、このイベントは累積型です。各更新には差分だけでなく、その時点までの文字起こし全体が含まれます。

Azure Voice Live

vendor: 'voicelive' を設定し、connectOptions.host にMicrosoft FoundryまたはAzure Speechリソースを指定して、Azure Voice Live API を使用します。Wipple CPaaSは wss://{host}/voice-live/realtime に接続します。

Voice Liveは vendor: 'microsoft' と同じものではありません。そのベンダーは従来のAzure OpenAI Realtimeデプロイメントエンドポイント(openai/realtime)を対象とし、ペイロードの形状は vendor: 'openai' と共通です。Voice Liveは別のサービスで、Realtimeのイベント語彙は維持しつつ独自のフラットなセッション形状を使用し、Azure固有の機能を追加しています。具体的には、Azure Speechの音声、セマンティックVAD、サーバー側のノイズ抑制とエコーキャンセル、単語タイムスタンプとビゼーム、そしてAzure speech to textと組み合わせた非realtimeチャットモデル(例: gpt-4.1)です。

session.llm({
  vendor: 'voicelive',
  model: 'gpt-realtime',
  auth: { apiKey: process.env.AZURE_VOICELIVE_API_KEY },
  connectOptions: { host: 'my-resource.services.ai.azure.com' },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  llmOptions: {
    session_update: {
      modalities: ['text', 'audio'],
      instructions: 'You are a friendly, helpful voice assistant on a phone call.',
      voice: { name: 'en-US-Ava:DragonHDLatestNeural', type: 'azure-standard' },
      turn_detection: { type: 'azure_semantic_vad', silence_duration_ms: 500, remove_filler_words: true },
      input_audio_noise_reduction: { type: 'azure_deep_noise_suppression' }
    },
    response_create: {
      instructions: 'Greet the caller warmly and ask how you can help today.'
    }
  }
});

接続オプション

connectOptions.hoststring必須

リソースのホスト名です。例: my-resource.services.ai.azure.com(古いリソースの場合は my-resource.cognitiveservices.azure.com)。


connectOptions.apiVersionstring

Voice Liveの api-version です。デフォルトは 2026-04-10 です。


connectOptions.agentIdstring

Foundry Agent Serviceのエージェントidです。設定すると、セッションは model ではなくエージェント(agent_id、および project_id としての projectId)で指定されます。


認証

auth.apiKey と auth.accessToken のいずれかを指定します。auth.apiKey はWipple CPaaSが api-key クエリパラメータとして送信し、auth.accessToken はMicrosoft Entra IDトークンで Authorization: Bearer ヘッダーとして送信されます。Entraトークンは有効期間が短いため、Webhook内で通話ごとに発行してください。トークンは https://ai.azure.com/.default スコープ向けに発行されている必要があります。

必須の session_update

llmOptions.session_update は必須です。最初の session.updated サーバーイベントが届くまで、発信者の音声は流れません。

これはVoice Liveのフラットな形状のままそのまま送信されます。voice、turn_detection、modalities、input_audio_transcription は session のトップレベルに置かれ、OpenAIのGA Realtime形式のように audio.input / audio.output の下にネストされません。openai_s2s の session_update を変更せずにそのまま移植しないでください。

音声(ボイス)

voice は文字列ではなくオブジェクト { name, type } として設定します。type は azure-standard(Azure Neural、HD、MAIの各音声)、azure-custom、または azure-realtime-native(azure-realtime モデル向けに厳選された音声セット)のいずれかです。HD音声は任意の temperature を受け付け、すべてのAzure音声は 0.5 から 1.5 の範囲の rate を受け付けます。

ターン検出

server_vad に加えて、Voice Liveは azure_semantic_vad と azure_semantic_vad_multilingual を提供します。これらは音量ではなく意味からターンの終了を判断し、フィラー語を除去(remove_filler_words: true)できるため、「えーと」のような発話がバージインを引き起こさなくなります。OpenAIの semantic_vad とは異なり、これらはすべてのモデルで動作します。

音声フォーマット

やり取りされる音声は24 kHzのpcm16で、Voice Liveのデフォルトの input_audio_sampling_rate と一致します。Wipple CPaaSはこのレートを強制します。input_audio_sampling_rate: 16000 を設定しないでください。

入力の文字起こし

Azure speech to textが自動的に有効になるのは、非マルチモーダルモデル(例: gpt-4.1)の場合のみです。gpt-realtime-2.1 のようなネイティブ音声モデルは、明示的に文字起こしを要求しない限り conversation.item.input_audio_transcription.completed イベントをまったく出力しません。

session_update: {
  input_audio_transcription: { model: 'azure-speech' }
}

非マルチモーダルモデルでは、Azure speech to textが自動的に有効になります。別のものを選択するには input_audio_transcription.model を設定します: azure-speech、mai-transcribe、または gpt-realtime / gpt-realtime-mini の場合は whisper-1、gpt-4o-transcribe、gpt-4o-mini-transcribe、gpt-4o-transcribe-diarize。

Voice Live固有のイベント

response.audio_timestamp.delta / .done(単語タイムスタンプ。output_audio_timestamp_types: ['word'] で有効化)および response.animation_viseme.delta / .done(animation: {outputs: ['viseme_id']} で有効化)は、標準のRealtimeイベントとともに eventHook に転送されます。

アバター出力はサポートされていません

Voice Liveのtext to speechアバターは、サービスとの間で別途WebRTC SDP交換を必要としますが、SIP通話にはその居場所がありません。session_update に avatar を設定しても映像は生成されません。

Google Gemini Live

vendor: 'google' を設定して、Geminiの Live API を使用します。同じ vendor: 'google' 統合で、2つのアクセス経路からLive APIに到達できます。

アクセス経路 ホスト 認証 用途
Gemini Developer API generativelanguage.googleapis.com Google AI Studio で取得したAPIキー(AIza…) 開発 / プロトタイプ / 趣味用途。SLAなし、共有クォータ。
Vertex AI Live API {LOCATION}-aiplatform.googleapis.com GCPサービスアカウントJSONから発行したOAuth 2ベアラートークン(ya29.…) 本番用途。SLA、IAM、監査ログ、プロジェクト単位のクォータ。

どちらも同じJSONワイヤプロトコルを使用します。setup、realtimeInput、serverContent、modelTurn、toolCall、sessionResumptionUpdate は同一です。異なるのはURL、認証、モデルリソースの形式だけです。llmOptions.setup は、WebSocket接続後にGoogleの BidiGenerateContentSetup メッセージとしてそのまま転送されます。

Gemini Developer API(APIキー)

デフォルトのアクセス経路です。auth.apiKey でAPIキーを指定します。キーは AIza で始まります。モデル名は短い形式 models/<id> を使用します。connectOptions は任意で、デフォルトはGemini Developerエンドポイントです。

session.llm({
  vendor: 'google',
  model: 'models/gemini-2.0-flash-live-001',
  auth: { apiKey: process.env.GEMINI_API_KEY },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  llmOptions: {
    setup: {
      generationConfig: {
        speechConfig: {
          voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Aoede' } }
        }
      },
      systemInstruction: {
        parts: [{ text: 'You are a helpful assistant named Barbara.' }]
      }
    },
    greeting: 'Greet the caller warmly and ask how you can help.',
    sessionResumption: {}
  }
});

Vertex AI Live API(OAuthベアラー)

本番用のアクセス経路です。アプリケーションがGoogle CloudのサービスアカウントJSONキーからOAuthアクセストークンを発行し、そのトークン文字列(ya29. で始まる)を auth.apiKey として渡します。Wipple CPaaSは ya29. プレフィックスを検出し、WebSocketアップグレード時に Authorization: Bearer … ヘッダーとして送信します。モデル名はVertexの完全なリソースパスでなければなりません。

const { JWT } = require('google-auth-library');
const fs = require('fs');

const saKey = JSON.parse(fs.readFileSync(process.env.GOOGLE_SERVICE_ACCOUNT_KEY_PATH, 'utf8'));
const jwtClient = new JWT({
  email: saKey.client_email,
  key: saKey.private_key,
  scopes: ['https://www.googleapis.com/auth/cloud-platform']
});

const { token } = await jwtClient.getAccessToken();   // "ya29...."
const projectId = saKey.project_id;
const location = 'us-central1';

session.llm({
  vendor: 'google',
  model: `projects/${projectId}/locations/${location}/publishers/google/models/gemini-live-2.5-flash-native-audio`,
  auth: { apiKey: token },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  connectOptions: {
    host: `${location}-aiplatform.googleapis.com`,
    path: '/ws/google.cloud.aiplatform.v1beta1.LlmBidiService/BidiGenerateContent'
  },
  llmOptions: {
    setup: {
      generationConfig: {
        speechConfig: {
          voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Aoede' } }
        }
      },
      systemInstruction: {
        parts: [{ text: 'You are a helpful assistant named Barbara.' }]
      }
    },
    greeting: 'Greet the caller warmly and ask how you can help.'
  }
});

Vertex AIのセットアップチェックリスト:

  1. aiplatform.googleapis.com APIが有効化され、課金が設定されたGCPプロジェクト。
  2. プロジェクトに対して roles/aiplatform.user IAMロールを持つサービスアカウント。
  3. アプリケーションからアクセスできるサービスアカウントJSONキーファイル(リポジトリの外に保存し、環境変数で参照してください)。
  4. connectOptions.host のリージョンプレフィックスは、model の locations/<region> セグメントと一致している必要があります。例: projects/.../locations/us-central1/... に対して us-central1-aiplatform.googleapis.com。リージョンが一致しないと Publisher Model not found が返ります。

OAuthトークンの有効期間: サービスアカウントJWTから発行されたトークンは約1時間で失効します。Wipple CPaaSはセッション開始時にアプリケーションが提供したトークンをそのまま使用し、セッション中に更新しません。WebSocketは有効期限を過ぎても開いたままですが、古いトークンでの再接続は失敗します。一般的な通話時間であれば問題ありません。通話ごとに新しいトークンを発行してください。

Google固有の connectOptions フィールド

hoststring

WebSocketホストです。デフォルト: generativelanguage.googleapis.com(Gemini Developer)。Vertex AIの場合は、model リソースパスのリージョンと一致する {LOCATION}-aiplatform.googleapis.com を使用します。


pathstring

WebSocketパスです。デフォルト: /ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent(Gemini Developer)。Vertex AIの場合は /ws/google.cloud.aiplatform.v1beta1.LlmBidiService/BidiGenerateContent を使用します。


versionstring

レガシー — Gemini Developer APIのバージョン文字列(v1、v1beta、v1alpha)です。path が設定されている場合は無視されます。後方互換性のために残されています。


Google固有のllmOptionsフィールド

setupobject必須

WebSocket接続直後にGeminiへ送信される BidiGenerateContentSetup オブジェクトです。model フィールドはFunctionの model パラメータから自動的に設定されます。generationConfig.responseModalities は audio に強制されます。


greetingstring | object

任意の能動的な挨拶です。設定すると、Wipple CPaaSはセットアップ直後にGeminiへテキストメッセージを送信し、発信者の発話を待たずにエージェントが先に話します。文字列、または text フィールドを持つオブジェクトを受け付けます。値は文字どおりの文言ではなくモデルへの指示です。たとえば "Hello, how can I help?" ではなく "Greet the caller warmly" のように指定します。

realtimeInput.text を使用して実装されているため、2.0 Liveモデルと gemini-3.1-flash-live-preview の両方で動作します(3.1では clientContent は履歴の投入用に予約されており、モデルのレスポンスをトリガーしません。そのため realtimeInput.text が使用されています)。


sessionResumptionobject

セッション再開を有効にします。オプトインするには {} を、以前のセッションを再開するには { handle: "..." } を渡します。再開ハンドルは llm_event の sessionResumptionUpdate メッセージでアプリケーションに返されます。


AssemblyAI Voice Agent

vendor: 'assemblyai' を設定し、auth.api_key でAssemblyAI APIキーを指定します。llmOptions は AssemblyAI Voice Agentの session ペイロードをそのまま渡すもので、Wipple CPaaS固有のラッパーはありません。Wipple CPaaSはこれを {type: 'session.update', session: <llmOptions>} としてラップし、wss://agents.assemblyai.com/v1/ws へのWebSocket接続後に最初のクライアントメッセージとして送信します。概要は AssemblyAI Voice Agent製品ページおよび Voice Agent APIドキュメントを参照してください。

音声フォーマットは設定できません。AssemblyAI Voice Agentは24 kHzの audio/pcm のみを受け付け、Wipple CPaaSは無条件にこれを使用します。アプリケーションが設定した session.input.format / session.output.format は上書きされます。Wipple CPaaSはチャネルのネイティブレートとの間で自動的にリサンプリングします。

session.llm({
  vendor: 'assemblyai',
  auth: { api_key: process.env.ASSEMBLYAI_API_KEY },
  actionHook: '/final',
  eventHook: '/event',
  toolHook: '/toolCall',
  events: ['all'],
  llmOptions: {
    system_prompt: 'You are a helpful voice agent.',
    greeting: 'Hello, how can I help you today?',
    output: { voice: 'ivy' },
    input: {
      keyterms: ['weather', 'temperature'],
      turn_detection: {
        vad_threshold: 0.5,
        min_silence: 1000,
        max_silence: 3000,
        interrupt_response: true
      }
    },
    tools: [
      {
        type: 'function',
        name: 'getWeather',
        description: 'Get current weather for a given city',
        parameters: {
          type: 'object',
          properties: {
            location: { type: 'string', description: 'City name' },
            scale: { type: 'string', enum: ['celsius', 'fahrenheit'] }
          },
          required: ['location']
        }
      }
    ]
  }
});

AssemblyAI固有のauthフィールド

api_keystring必須

AssemblyAI APIキーです。WebSocketハンドシェイク時に Authorization: Bearer <api_key> として送信されます。


AssemblyAI固有のllmOptionsフィールド

AssemblyAIのプロトコルでは session.update メッセージが必要ですが、その中のフィールドはすべて任意です。llmOptions: {} を渡すと、すべてサーバーのデフォルトで開始します。

system_promptstring

エージェントのシステムプロンプトです。


greetingstring

セッション開始時にエージェントが話す最初の挨拶です。


outputobject

出力音声の設定です。voice をサポートします。利用可能なIDは AssemblyAIの音声リファレンスを参照してください。format サブフィールドはWipple CPaaSによって上書きされます。


inputobject

入力音声の設定です。keyterms(バイアス用の語句の配列)と turn_detection(vad_threshold、min_silence、max_silence、interrupt_response)をサポートします。format サブフィールドはWipple CPaaSによって上書きされます。


toolsarray

ツール定義の配列です。各エントリには type: "function"、name、description、および parameters(JSON Schema)を含める必要があります。type: "function" が省略された場合、Wipple CPaaSが自動的に補完します。


ツール呼び出し

エージェントは tool.call サーバーイベントを出力してツールを呼び出します。Wipple CPaaSはそれを {name, args, tool_call_id} とともにアプリケーションの toolHook にルーティングします。アプリケーションは session.sendToolOutput(tool_call_id, {type: 'tool.result', tool_call_id, result}) で応答します。result は文字列である必要があります(オブジェクトは送信前にJSON文字列化してください)。文字列以外の result 値はWipple CPaaSが自動的にJSON文字列化します。

人間へのハンドオフ

handoff ブロックを追加すると、realtimeモデルが発信者を人間へトランスファーできるようになります。ランタイムが transfer_to_human ツールを注入するため、このツール用の toolHook は不要です。発信者が人間との会話を 求め、モデルがこのツールを呼び出すと、Wipple CPaaSはパッケージ済みの transfer 一連の処理を、設定された宛先に対して実行します。

session
  .llm({
    vendor: 'openai',
    model: 'gpt-realtime',
    auth: { apiKey: process.env.OPENAI_API_KEY },
    llmOptions: {
      response_create: { instructions: 'You are a helpful support agent.' },
      session_update: { type: 'realtime', instructions: 'You are a helpful support agent.' },
    },
    handoff: {
      mode: 'blind',
      blindMethod: 'dial',
      target: [{ type: 'user', name: 'agent-desk@sip.example.com' }],
    },
    actionHook: '/llm-complete',
  })
  .send();

handoff ブロックは、transfer のすべてのオプション(mode、 target、blindMethod、disposition、confirm など)に加えて、brief('auto' | 'none' | { template })、briefSynthesizer、toolName、toolDescription を受け付けます。詳細は agent Functionのハンドオフセクションを参照してください。 ここでもまったく同じ内容が適用されます。人間側のレグがブリッジされると、actionHook は トランスファーの結果を報告します。

注意

vendor: 'gptlive' の場合、注入される transfer_to_human ツールには関数呼び出しのチャネルが必要なため、 llmOptions.session_update.delegation.type は 'responses' でなければなりません。そうでない場合、発信者が人間に到達する手段のないまま 黙って放置されるのではなく、Functionが拒否されます。OpenAI GPT Live を参照してください。

組み込み切断ツール

hangup を設定すると、モデルが自身で通話を終了できるようになります。ランタイムがモデルのツールセットに hangup ツールを注入します。llmOptions で定義する必要はなく、toolHook で処理する必要もありません。ランタイムが呼び出しを横取りして通話を切断し、llm Functionを終了します。これはサポートされているすべてのs2sベンダー(OpenAI、Deepgram Voice Agent、Ultravox、ElevenLabs、Google Gemini Live、AssemblyAI、xAI Voice Agent、Azure Voice Live、OpenAI GPT Live。最後のものは delegation.type: 'responses' が必要です)で同じように動作します。

session.llm({
  vendor: 'openai',
  model: 'gpt-realtime',
  auth: { apiKey },
  hangup: { reason: 'conversation complete' },
  actionHook: '/final',
  llmOptions: {
    session_update: {
      type: 'realtime',
      instructions:
        'You are a helpful voice assistant. When the caller says goodbye, call the hangup tool.',
    },
  },
});

注入されるツールは任意の reason 引数を受け付け、モデルは通話を終了すると判断した際にこれを設定できます。発信側BYEの X-Reason SIPヘッダーに設定される理由は、次のように決定されます。

  1. モデルが渡した reason 引数(存在する場合)。
  2. それがなければ、アプリが設定した hangup.reason のデフォルト(設定されている場合)。
  3. それもなければ、X-Reason ヘッダーは送信されません。

デフォルトの理由なしでツールを有効にするには、空のオブジェクト(hangup: {})を渡します。

注意

モデルが切断ツールを呼び出すと、通話は即座に切断されます。actionHook は引き続き発火しますが、そこから返される後続のFunctionは、通話がすでに終了処理中であるため破棄されます。

注意

ElevenLabsの場合、ツールはセッションごとに注入されるのではなくElevenLabsのエージェント側で設定します。エージェントに hangup という名前のクライアントツールを設定すると、Wipple CPaaSがそれを横取りして通話を終了します。