メインコンテンツまでスキップ

Voice agents

エージェントを電話やブラウザの音声会話に参加させ、バックエンドからそれらの通話を活用します。

Voice Agents を使う理由

エージェントはすでにチャットで質問に答え、アクションを実行しています。次に人々はそれへ電話をかけたいと考えます。たとえば予約を取りたい患者、配送状況を確認したい顧客、入力より会話を好むサイト訪問者です。音声機能をゼロから構築するには、telephony webhook、speech recognition、speech synthesis、割り込み処理、モデルにツールを提供する仕組みが必要であり、しかもすべてリアルタイムで行う必要があります。

Squid では、すでにある同じ agent definition を使ってエージェントに音声を与えられます。ブラウザ会話は 1 回の呼び出しです。

Client code
const call = await squid.ai().voice().startWebCall({ agentId: 'front-desk' });
call.onTurn((turn) => console.log(`${turn.role}: ${turn.text}`));

電話番号は connector と Agent Studio 内のいくつかの設定だけで利用でき、コードは不要です。また、通話に関するすべての情報がバックエンドに届きます。進行中の transcript、call record、受信テキスト、AI function 内の通話 context です。

概要

音声会話は、音声のフロントエンドを持つ通常の agent conversation です。相手が話し、エージェントはテキストを受け取り、その回答が音声で返されます。エージェントは instructions、knowledge bases、connectors、AI functions、memory、security rules を維持します。変わるのは、聞くことと話すことを担う voice engine です。

Engine実行場所
Twilio speech (Twilio connector)Twilio が発信者を transcription し、エージェントの streamed replies を音読します。エージェントは発話ごとに応答します。
OpenAI Voice (OpenAI Voice connector)OpenAI realtime model が話し聞き取り、knowledge や action が必要になるたびにエージェントを tool として呼び出します。ブラウザ通話も提供します。
ElevenLabs (ElevenLabs connector)ElevenLabs voices を持つ ElevenLabs agent が通話をホストし、Squid agent を LLM として使用します。

Voice connectors overview では engine を比較しています。このページではコードで行うことを説明します。

Voice agents を使う場面

Use Case推奨事項
電話の応答と発信、テキストの送受信エージェントに設定した Twilio phone line
電話番号なしで web page からエージェントと会話するOpenAI Voice connector を使用した startWebCall()
通話の記録・分析、staff への通知、CRM の更新voice session event handler
エージェントによる発信者認識、本人確認、予約call context を使用する AI functions
audio file をテキストへ、またはテキストを audio file へ変換するlive call ではなく AI audio
chat widget での音声入力enable-transcription を使用する AI chat widget

仕組み

  1. 通話が開始されます。Twilio がエージェントの番号に着信する、バックエンドが発信する、またはブラウザが startWebCall() を呼び出します。
  2. Squid は通話用に voice session を開きます。1 つの conversation memory、エージェントの instructions に加えた電話応対と通話情報、phone line または request で指定された chat options が含まれます。
  3. 発信者の各完了発話によってエージェントの turn が実行されます(OpenAI engine では、realtime model がエージェントを必要とするたびに turn を実行します)。回答は音声で再生されます。
  4. 各 milestone は application の event bus に送信されます。started、双方の各 spoken turn に対する 1 つの turnended です。Twilio connector はこれらを記録し、handler でも記録できます。
  5. どちらかが電話を切る、エージェントが通話を終了する、または転送されると、通話は終了します。

voice API は TypeScript client および backend SDK の一部です。Python では phone line にコードは不要で、connector operation は execute_function から利用できます。

Quick Start

電話番号を使用する前に、5 分でブラウザの音声会話を開始します。

Prerequisites

  • How to build an AI agent のような、agent を持つ Squid application
  • application に設定された OpenAI Voice connector(OpenAI project id と API key。ブラウザ通話には webhook は不要です)
  • squid init で初期化された backend project と、Squid client を持つ frontend

Step 1: ユーザーがエージェントと会話できるようにする

ブラウザ通話には、エージェントとのチャットと同じ security rules が適用されます。signed-in user を許可します。

Backend code
import { secureAiAgent, SquidService } from '@squidcloud/backend';

export class FrontDeskSecurityService extends SquidService {
@secureAiAgent('front-desk')
allowSignedInUsers(): boolean {
return this.isAuthenticated();
}
}

Step 2: backend を起動または deploy する

squid start

cloud に deploy するには、deploying your backend を参照してください。

Step 3: ブラウザから通話を開始する

Client code
const call = await squid.ai().voice().startWebCall({
agentId: 'front-desk',
greeting: 'Hi, this is the front desk. How can I help?',
});

// transcription された発信者の言葉と、音声で話されたエージェントの言葉。
call.onTurn((turn) => appendToTranscript(turn.role, turn.text));
call.onEnd(() => showCallEnded());

// ページ上のボタン。
muteButton.onclick = () => call.setMuted(true);
hangUpButton.onclick = () => call.stop();

ブラウザは microphone permission を要求し、WebRTC 経由で OpenAI realtime model に接続し、通話が作成する <audio> element を通じてエージェントの音声を再生します(独自の要素を sink として渡すこともできます)。Squid は signed-in user として通話の背後でエージェントを実行するため、エージェントの tools と data rules はその user を認識します。

Step 4: エージェントに電話番号を設定する

Twilio connector を追加し、Agent Studio で account の番号を使用する Twilio ability をエージェントに追加します。phone line は同じ OpenAI Voice connector を再利用し、ブラウザ通話では line の voice と language がデフォルトになります。

Authentication と Configuration

Operation呼び出し可能な対象
startWebCall() および startOpenAiWebCall()エージェントの @secureAiAgent rules に従う application user(または public agent の任意の user)、および API key を持つ backend code。エージェントは呼び出し元として実行されます。
createSession() および answerOpenAiCall()application API key を持つ backend code のみ。phone call をエージェントに紐付けます。
Connector operations (placeCall, sendSms, getCallTranscript, …)API key を持つ backend code。using the Twilio connector from code を参照してください。
Phone line settings (connectedIntegrations[].options)Agent Studio、または setAgentOptionInPath() による API key を持つ backend code。setting up the phone line from code を参照してください。

frontend code は API key が必要なすべての操作について backend の executable を呼び出すため、key がブラウザに到達することはありません。

ブラウザ通話の設定の取得元

startWebCall() のすべての設定は optional です。各設定は順番に、OpenAI engine を使用する agent の phone line、OpenAI Voice connector の defaults、Squid の defaults(gpt-realtime-2.1marin voice、en-US、通話全体で 1 つの language、記載どおりに発話される greeting)へ fallback します。integrationId は、agent にそのような phone line がなく、application に複数の OpenAI Voice connector がある場合にのみ必要です。

Core Concepts

voice session

すべての通話は sessionId を持つ voice session です。session の全 turn は、その id を key とする 1 つの conversation memory を共有するため、エージェントは最初の言葉から最後まで通話の context を維持し、session id は通話が発行するすべての event に含まれます。phone call では session に externalCallId(Twilio call SID)も含まれ、これは connector の call record の key です。

Turns と transcripts

turn は 1 つの完了発話です。{ role: 'user' | 'agent', text, at }。発信者の turn は speech の transcript であり、エージェントの turn は発話された内容です。割り込まれた回答は、発信者が割り込んだ箇所で切り取られます。OpenAI engine では、transcript は realtime model が独自に処理する small talk を含む会話全体を対象とします。

通話でエージェントに伝えられる内容

Squid は通話の各 turn で、電話応対をエージェントの instructions に追加します。短い spoken reply、markdown や link を使わないこと、名前・日付・番号を繰り返して確認すること、人が話すように日付を言うこと、会話が完了したら通話を終えることです。また、発信者の番号、着信先番号、現在の日付と時刻、応答する language という通話情報も追加します。独自の instructions はその上に適用されます。電話応対を自ら記述する必要はなく、エージェントに何をさせるかだけを記述します。

通話を開始する

デフォルトでは、line は通話に応答するとすぐ greeting を話します。greetingMode: 'agent' では、通話接続後にエージェント自身が opening line を作成します。該当する tool がある場合はまず発信者を検索するよう指示されるため、再度電話をかけた発信者には一般的な greeting ではなく「お帰りなさい、Dana」と応答できます。greeting text は personalization できる情報がない場合に発話する内容です。代償として、この turn の実行中には応答後に少し無音の時間が発生します。

Languages

languagees-MX などの BCP-47 tag)は、通話の開始 language です。OpenAI および ElevenLabs engine では、languageMode により発信者が切り替えられるかを決定します。single は通話全体をその language に維持し、selectedadditionalLanguages 内の任意の language へ発信者に合わせて切り替え、any は対応する任意の language へ発信者に合わせて切り替えます。切り替えは意図的に行われます。名前や 1 つの外国語の単語では language は変更されず、文全体の場合に変更されます。

AI functions における call context

Twilio および OpenAI engine では、phone call 中にエージェントが呼び出すすべての AI function が、agent context 内で通話を受け取ります。

ctx.agentContext field説明
twilioCallSidTwilio call SID。connector の call record および transcript の id です。
twilioIntegrationId通話が経由した Twilio connector です。
webCallerKeyTwilio 経由の browser call で、backend が発信者に渡した key です。

startWebCall() で開始した browser call には、chatOptions.agentContext で渡した内容に加え、通常の request context による発信者の identity(function 内の this.getUserAuth())が含まれます。

通話の活用

進行中の通話への反応

event handlerAI_VOICE_SESSION_EVENT_TYPE を subscribe し、すべての通話を live で追跡します。この handler は独自の transcript を保持し、通話終了時に staff 向けの summary を書き込みます。

Backend code
import { eventHandler, SquidService, TriggerEvent } from '@squidcloud/backend';
import { AI_VOICE_SESSION_EVENT_TYPE, AiVoiceSessionEvent } from '@squidcloud/client';

interface CallTurn {
sessionId: string;
role: 'user' | 'agent';
text: string;
at: string;
}

interface CallSummary {
sessionId: string;
agentId: string;
callSid?: string;
summary: string;
endedAt: string;
}

export class CallLogService extends SquidService {
private readonly turns = this.squid.collection<CallTurn>('call_turns');
private readonly summaries = this.squid.collection<CallSummary>('call_summaries');

@eventHandler<AiVoiceSessionEvent>(AI_VOICE_SESSION_EVENT_TYPE)
async onVoiceSession(event: TriggerEvent<AiVoiceSessionEvent>): Promise<void> {
const { kind, sessionId, agentId, externalCallId, turn, endReason } = event.payload;
switch (kind) {
case 'turn':
if (!turn) return;
// 各 turn に時刻を key とする 1 つの document。再配信された event は重複せず上書きされます。
await this.turns.doc(`${sessionId}_${turn.at}_${turn.role}`).upsert({ sessionId, ...turn });
return;
case 'ended': {
console.log(`Call ${sessionId} of ${agentId} ended: ${endReason}`);
const turns = await this.turns.query().eq('sessionId', sessionId).sortBy('at').snapshot();
if (turns.length === 0) return;
const transcript = turns.map((doc) => `${doc.data.role}: ${doc.data.text}`).join('\n');
// built-in agent は memory なしで要約するため、通話内容が別の chat に漏れることはありません。
const summary = await this.squid
.ai()
.agent()
.ask(`Summarize this phone call in three sentences for the staff, then list any follow-ups.\n\n${transcript}`, {
memoryOptions: { memoryMode: 'none' },
});
await this.summaries.doc(sessionId).upsert({
sessionId,
agentId,
callSid: externalCallId,
summary,
endedAt: new Date().toISOString(),
});
return;
}
case 'started':
return;
}
}
}

payload は AiVoiceSessionEvent です。

FieldType説明
kind'started' | 'turn' | 'ended'この event が報告する milestone。
sessionIdstringvoice session。
provider'twilio' | 'openai'通話の実行元。Twilio ConversationRelay、または OpenAI Realtime API(phone と browser)です。
agentIdstring通話中の agent。
externalCallIdstring, optionalphone call における Twilio call SID。
metadataobject, optional通話開始時に渡された free-form data。connectors は channelphone または web)と callSid を設定します。
turn{ role, text, at }, on turn events完了した turn。
endReasonstring, on ended eventssession が終了した理由。

event は少なくとも 1 回、順序保証なしで配信されるため、上記の handler は timestamp を key にして turn を保存します。turn-based Twilio calls(voiceMode: 'gather')および ElevenLabs engine の通話は voice session 外で実行され、event を発行しません。前者は引き続き connector の collections に記録されます。

AI functions で call context を使用する

エージェントが通話でできる最も便利なことは、発信者を認識することです。この function は Twilio connector が保持する call record を読み、番号から発信者を検索します。line が greetingMode: 'agent' を使用している場合、エージェントは opening turn でこれを呼び出します。

Backend code
import { aiFunction, AiFunctionCallContext, SquidService } from '@squidcloud/backend';
import { TwilioCallRecord } from '@squidcloud/twilio-client';

interface CallAgentContext {
twilioCallSid?: string;
twilioIntegrationId?: string;
}

interface Patient {
phone: string;
firstName: string;
lastName: string;
}

export class FrontDeskService extends SquidService {
private readonly calls = this.squid.collection<TwilioCallRecord>('twilio_calls');
private readonly patients = this.squid.collection<Patient>('patients');

@aiFunction('Looks up whether the caller is a known patient, by the number they are calling from', [])
async lookupCaller(_params: unknown, ctx: AiFunctionCallContext<unknown, CallAgentContext>): Promise<string> {
const callSid = ctx.agentContext?.twilioCallSid;
if (!callSid) return 'This conversation has no phone number. Ask the caller for the number on file.';
const call = await this.calls.doc(callSid).snapshot();
if (!call) return 'The call record is not available yet. Ask the caller for the number on file.';
// 発信者の番号は inbound call では origin、outbound call では destination です。
const phone = call.direction === 'inbound' ? call.from : call.to;
const [patient] = await this.patients.query().eq('phone', phone).limit(1).snapshot();
if (!patient) return 'No patient has this number on file. Offer to register the caller.';
// first name のみを使用します。ほかの情報を共有する前に発信者を確認してください。
return `A patient named ${patient.data.firstName} has this number. Greet them by name and verify before sharing details.`;
}
}

上記のように raw record ではなく、エージェントが次に行うべきことを伝える短い文を返してください。個人情報を共有する function では、まず本人確認を必須にする必要があります。たとえば登録済み番号に送信した code を使用します。phone assistant tutorial では全体の flow を示しています。

テキストへの応答と送信

エージェントの番号への incoming text は SMS_EVENT_TYPE event であり、引き継がない限りエージェントが応答します。エージェントと backend はエージェントの番号からテキストを送信します。Twilio connector ページの SMS を参照してください。

発信

backend は placeCall({ agentId, to, instructions }) で発信します。エージェントは応答者に挨拶し、指定した目的を遂行します。outbound calls を参照してください。

Transfers と human handoff

エージェントは built-in の transferTwilioCall function で live call を転送します。いつ、どの番号へ転送するかを instructions に記述してください。代わりに application が通話を引き継ぐ場合、たとえば発信者を staff conference につなぐ場合は、エージェントが退出しても発信者が切断されないように、まず human handoff operation で claim してください。

Browser calls

squid.ai().voice().startWebCall(options) はブラウザで実行されます。microphone を開き、WebRTC offer を生成して Squid に渡します。Squid は agent の setup で OpenAI Realtime API 上の通話を作成し、OpenAI の SDP で応答して audio を接続します。Squid を通過するのは offer と answer だけです。audio は browser と OpenAI の間で流れ、Squid は realtime model から要求されたときに agent を実行し、transcript event を発行することで側方から通話を制御します。

Client code
const call = await squid
.ai()
.voice()
.startWebCall({
agentId: 'front-desk',
// 以下はすべて optional で、agent の phone line、次に connector がデフォルトになります。
voice: 'cedar',
language: 'es-MX',
languageMode: 'selected',
additionalLanguages: ['en'],
greeting: 'Hola, habla la recepción. ¿En qué puedo ayudarle?',
instructions: 'The visitor is on the pricing page; help them pick a plan.',
chatOptions: { agentContext: { pageUrl: window.location.href } },
metadata: { source: 'pricing-page' },
sink: document.querySelector('audio#agent-voice') as HTMLAudioElement,
});

console.log(call.sessionId, call.callId);
call.onTurn((turn) => console.log(turn.role, turn.text));
call.onEvent((event) => {
// talking indicator または debugging 用の raw Realtime API events。
if (event.type === 'response.done') console.log('The agent finished speaking');
});
call.onEnd(() => console.log('Over'));

ブラウザが microphone を公開するのは https:// page と localhost のみです。custom WebRTC setup では、独自の offer とともに startOpenAiWebCall({ agentId, sdp }) を呼び出し、remote description として設定する { sessionId, callId, sdp } を受け取れます。

API key を持つ backend code も、startOpenAiWebCall() を使用して browser call を broker できます。page が offer を生成し、独自のチェックを行う executable が通話を作成して answer を返し、page がそれを remote description として設定します。この場合、エージェントは user としてではなく API key で実行されます。

API Reference

squid.ai().voice()

MethodReturns説明
startWebCall(options)Promise<AiWebVoiceCall>ブラウザ専用。microphone を開き、OpenAI realtime model に接続して、通話の背後で agent を実行します。
startOpenAiWebCall(request)Promise<StartOpenAiWebVoiceCallResponse>生成した WebRTC offer から通話を作成し、OpenAI の answer、call id、session id を返します。
createSession(request)Promise<CreateAiVoiceSessionResponse>API key。Twilio ConversationRelay call 用の voice session を開き、Twilio を接続する wss:// relay URL を返します。Twilio speech engine のすべての通話で Twilio connector が行う処理です。
answerOpenAiCall(request)Promise<AnswerOpenAiVoiceCallResponse>API key。OpenAI project の SIP endpoint に届いた phone call に、agent で応答します。OpenAI Voice connector がすべての phone call で行う処理です。

createSessionanswerOpenAiCall は、connectors の代わりに独自の Twilio webhook または OpenAI webhook を実行する application 向けです。createSessionagentIdprovider: 'twilio'、optional の externalCallId、すべての turn に適用される chatOptions、すべての event で echo される metadatagreetingModegreetingconnectTimeoutSeconds(relay URL の有効期間。デフォルトは 5 分)、tunneled backend 用の relayBaseUrl を受け取ります。answerOpenAiCallagentId、OpenAI の realtime.call.incoming webhook の callId、OpenAI Voice connector の integrationId、realtime model の instructions、および optional の modelvoiceallowInterruptionsinterruptionSensitivitygreetinggreetingModeexternalCallIdchatOptionsmetadata を受け取ります。

startWebCall() options

OptionType説明
agentIdstringrealtime model が knowledge と actions を求める agent。必須です。
integrationIdstringOpenAI Voice connector。agent にその connector 上の phone line がなく、application に複数ある場合のみ必要です。
model, voicestringrealtime model とその voice。
languagestring通話を開始する language。ISO code または BCP-47 tag で指定します。
languageMode'single' | 'selected' | 'any'発信者が通話途中で language を切り替えられるか。
additionalLanguagesstring[]selected mode で発信者が切り替えられる languages。
greetingstring通話開始時にすぐ発話されます。greeting がない場合、model は発信者を待ちます。
greetingMode'phrase' | 'agent'agent では、agent が opening line を作成し、greeting を fallback として使用します。
instructionsstringこの通話の目的。realtime model の instructions に追加されます。
allowInterruptionsboolean発信者の speech が model を中断するか。
interruptionSensitivity'low' | 'normal' | 'high'発信者の speech を検出する感度。
chatOptionsAiChatOptionsagent が実行するすべての turn に適用されます。instructionsagentContextfunctions などです。
metadataobjectsession のすべての event で echo されます。
sink{ srcObject, autoplay, play() }voice の再生先。<audio> element です。省略時は page 上に作成されます。

AiWebVoiceCall

Member説明
sessionIdSquid 内の voice session。その events に含まれます。
callIdOpenAI 上の call。
onTurn(listener)すべての spoken turn、{ role, text, at } を受け取ります。listening を停止する function を返します。
onEvent(listener)data channel 経由で送信される、call のすべての Realtime API event、{ type, ...rest } を受け取ります。
onEnd(listener)通話終了時に一度実行されます。agent が電話を切った、connection が切断された、または stop() が呼ばれた場合です。
setMuted(muted)通話を終了せずに microphone を mute または復元します。
stop()ブラウザ側から通話を終了し、microphone を解放します。

Error Handling

ErrorCauseSolution
A web voice call needs a browser with WebRTC supportstartWebCall() がブラウザ外、または RTCPeerConnectiongetUserMedia のないブラウザで実行されました。https:// page または localhost の browser code から呼び出してください。
ブラウザからの NotAllowedErroruser が microphone access を拒否した、または page が secure に配信されていません。user gesture から microphone を要求し、page を HTTPS で配信してください。
security error で通話が拒否されるagent が public ではなく、チャット拒否時と同様に @secureAiAgent rule も user を許可していません。rule を追加してください。securing AI agents を参照してください。
AGENT_NOT_FOUNDapplication にその id の agent が存在しません。agent id を確認してください。
OPENAI_VOICE_INTEGRATION_REQUIREDagent に OpenAI engine の phone line がなく、application に OpenAI Voice connector が 0 個または複数あります。connector を追加するか、integrationId を渡してください。
OPENAI_VOICE_API_KEY_SECRET_NOT_FOUNDconnector の API key secret が削除されました。key を使用して connector を再度保存してください。
API_KEY_REQUIREDcreateSession() または answerOpenAiCall() が application API key なしで呼び出されました。backend code から呼び出してください。
OPENAI_CALLS_ARE_ANSWERED_NOT_RELAYEDcreateSession()provider: 'openai' で呼び出されました。OpenAI call は answerOpenAiCall() で応答します。relay は Twilio 用です。
エージェントが謝罪して繰り返しを求めるたとえば AI function が throw した、または model provider が error を返したため、agent の turn が失敗しました。application log を確認してください。発信者を無音のままにはせず、失敗内容が音声で伝えられます。

Best Practices

  1. 電話ではなく業務のための instructions を書く。 Squid は各 turn で電話応対と通話情報を追加します。必要な flow を記述してください。挨拶、識別、確認、支援、確定、終了です。
  2. 共有する前に確認する。 発信者の番号はヒントであり、identity の証明ではありません。登録済み番号に code を送信し、個人情報を読み上げたり予約したりする前に agent に確認させてください。phone assistant tutorial のとおりです。
  3. 通話用 tools を高速に保つ。 function の実行中、発信者は無音で待ちます。OpenAI engine では、少し待つと model が待機を案内します。indexed lookup を優先し、遅い処理は通話後の event handler に延期してください。
  4. agent 向けの文で functions に応答する。 「この番号の患者はいません。発信者への登録を案内してください」は会話を導きますが、JSON dump にはその効果がありません。
  5. まずブラウザでテストする。 startWebCall() は、電話番号なしで、通話時間に課金されることなく、phone line と同じ agent、tools、voice settings をテストします。
  6. event handlers を idempotent にする。 event は少なくとも 1 回、順不同で到着します。書き込むデータを session id と turn timestamp で key にしてください。
  7. browser calls を chats と同様に secure にする。 public agent では page 上の誰でも通話できます。@secureAiAgent rule は通話を必要な users に限定し、agent はその user として実行されます。
  8. transcript の内容に注意する。 call transcript には発信者が言った内容がすべて含まれます。data retention rules を twilio_call_transcripts と handler が保存するすべてのデータに適用し、個人データを model に到達させてはならない場合は prompt privacy rules を使用してください。

See Also