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

Twilio

AI agent に電話番号を付与:その番号で着信応答、発信、SMS の送受信を行います

Twilio connector の機能

Twilio connector は、Twilio アカウントを agent 用の電話回線に変換します。

  • Agent ごとに 1 つの番号。 Twilio ability を agent に接続し、アカウントの番号を 1 つ選択します。その番号への通話と SMS は、その agent に届きます。
  • 着信通話。 Agent は、instructions、knowledge bases、connectors、AI functions を使用してリアルタイムに応答します。発信者番号、着信番号、時刻を把握します。
  • 発信通話。 Agent は、依頼されたとき、または backend から指示されたときに、目的を持って自分の番号から相手へ電話をかけます。
  • SMS。 受信 SMS は agent が返信するか、独自の handler に渡されます。Agent は確認メッセージやリンクを送信でき、backend は agent の番号から SMS を送信できます。
  • 通話中のアクション。 Agent は別れの挨拶後に通話を終了したり、別の番号へ転送したりできます。アプリケーションは、進行中の通話を人間の担当者へ引き継げます。
  • 記録。 すべての通話と各発話ターンは、アプリケーションの組み込みデータベースに保存されます。
  • 音声エンジンの選択。 Twilio 独自の speech services はすぐに利用できます。OpenAI Voice または ElevenLabs connector を追加し、任意の電話回線のエンジンとして選択できます。音声エンジンの選択を参照してください。

始める前に

  • voice と SMS の機能を持つ電話番号が少なくとも 1 つある Twilio アカウント。trial account では、認証済みの番号にのみ発信・SMS 送信できます。
  • アカウントの Account SIDAuth Token(Twilio Console の Account Info)。Auth Token は必須です。Twilio が送信する webhook の署名を検証できる唯一の credential です。
  • 任意:Twilio API Key(Console の Account > API keys & tokens)。指定すると、Squid は REST calls に Auth Token ではなくこれを使用します。Twilio 経由の browser callsにも必要です。
  • デフォルトの real-time mode で Twilio speech engine を使用する場合、Twilio アカウントで ConversationRelay を有効化する必要があります。Twilio Console の Voice > Settings > General で、Predictive and Generative AI/ML Features Addendum に同意してください。turn-based mode、OpenAI、ElevenLabs の各 engine には不要です。
  • 米国の番号へ SMS を送るには、Twilio Console で A2P 10DLC(brand と campaign)に登録済みの番号、または検証済みの toll-free number が必要です。Twilio は未登録のトラフィックをブロックします。

Squid application への Twilio connector の追加

  1. Squid ConsoleConnectors タブへ移動します。

  2. Available Connectors をクリックします。

  3. Twilio connector を見つけ、Add Connector を選択します。

  4. 次の設定詳細を指定します。

Connector ID:コード内で connector を一意に識別する文字列です。例:twilio

Account SID:Twilio Account SID(AC…)。

Auth Token:Twilio Auth Token。secret として保存されます。

API Key SID:任意。Twilio API Key SID(SK…)。API key を指定すると、すべての REST call で Auth Token の代わりに使用されます。

API Key Secret:その API key の secret。secret として保存されます。API Key SID と併せて必須です。

Advanced セクションには、proxy、tunnel、custom domain の背後でのみ必要になる 2 つの設定があります。

Webhook Base URL:Twilio がこの application に到達する public base URL。デフォルトは application の backend URL です。Squid 上では production とlocal developmentのどちらでも public です。

API Base URL:Twilio の API base URL。デフォルトは https://api.twilio.com です。

connector ごとにアカウント以外の設定はありません。電話番号、voice engine、agent の音声に関するすべての設定は、次に説明する agent の電話回線に属します。connector を再保存すると、それを使用するすべての電話回線が再確認されます。

Agent を電話番号へ接続する

Agent Studio で Twilio ability を追加すると、agent に電話回線が付与されます。

  1. Squid ConsoleStudio タブに移動して agent を開くか、新規作成します。

  2. Add Abilities をクリックし、Communication セクションまでスクロールして、作成した Twilio connector を選択します。

  3. Phone number を選択します。一覧には、まだ他の agent が使用していないアカウントの番号が表示されます。1 つの番号が呼び出す agent は 1 つだけです。

  4. Voice engine を選択します。Twilio speech は常に利用できます。application に ElevenLabs または OpenAI Voice connector があると、ElevenLabsOpenAI voice が表示されます。engine の下にある設定は、その engine に応じて変わります。

  5. 通話開始時の LanguageGreeting、および engine 固有の設定を指定します。これらはすべて電話回線設定で説明されています。

  6. 保存します。connector は番号を設定します。番号をこの application に向けるか(ElevenLabs engine では ElevenLabs agent に渡します)、結果を ability の status line に表示します。例:"+1 555 123 4567 rings this agent (OpenAI voice)"。セットアップに失敗すると、その理由が表示されます。たとえば、別の agent がすでに使用している番号などです。

  7. OpenAI engine では、status line に OpenAI project で一度登録する webhook URL も表示されます。incoming-call webhook の登録を参照してください。

その番号に電話をかけます。ability を切断すると、番号は再び解放されます。

Backend の実行場所

Twilio は application の webhook を呼び出すため、application が到達可能である必要があります。backend をデプロイするか、squid start を使用してローカルで実行してください。後者では https://[APP_ID]-dev-[DEVELOPER_ID].[REGION].squid.cloud 形式の public URL も提供されます。connector 自体のコードは Squid がホストするため、デプロイは不要です。

電話回線設定

これらは Twilio ability の agent ごとのオプションです。コードでは、agent の integration 接続上の connectedIntegrations[].options にあり、@squidcloud/twilio-client では AiAgentTwilioIntegrationOptions として型付けされます。

オプションEngines説明
phoneNumberすべてアカウント上の agent の番号。E.164 形式(+15551234567)。必須です。
voiceEngineすべてtwilio(デフォルト)、openai、または elevenlabs
openAiVoiceIntegrationIdOpenAI通話に応答する OpenAI Voice connector。
voiceModelOpenAI通話で会話する realtime model。connector の default model、それがない場合は最新の realtime model がデフォルトです。
voiceすべて音声:OpenAI voice name(marincedaralloy、…)、ElevenLabs voice id、または turn-based Twilio mode 用の Twilio provider voice id。デフォルトは engine の default voice です。
allowInterruptionsOpenAI発信者の発話によって model が中断されるかどうか。コードから設定します。省略時は OpenAI のデフォルトを維持します。
interruptionSensitivityOpenAIlownormal、または high:発信者の発話をどの程度検出しやすいか。コードから設定します。
elevenLabsIntegrationIdElevenLabs通話を実行する ElevenLabs connector。
languageすべて通話を開始する言語の BCP-47 tag。例:en-USes-MXhe-IL。デフォルトは en-US です。speech services を制御し、agent に応答する言語を伝えます。
welcomeGreetingすべて通話に応答するとすぐに発話されます。Squid にその言語向けの短い挨拶があればそれがデフォルトです。挨拶がない言語では、回線は発信者が先に話し始めるのを待ちます。
greetingModeTwilio、OpenAIphrase(デフォルト)は、指定どおりに挨拶を発話します。agent は通話接続後に agent が開始メッセージを作成するため、発信者を検索し、既知の発信者には名前で挨拶できます。指定した greeting は fallback です。応答後に少し無音になる代償があります。console では Opening line と表示されます。
languageModeOpenAI、ElevenLabssingle(デフォルト)は通話全体で language を維持します。selected は発信者に languageadditionalLanguages 間の切り替えを許可します。any は engine が切り替え可能な任意の言語に発信者を追従します。
additionalLanguagesOpenAI、ElevenLabsselected mode で発信者が切り替え可能な ISO codes(enesfr)。
fillerMessage, fillerAfterSecondsElevenLabs応答に少し時間がかかる際に発話するフレーズと、待機する秒数(デフォルトは 4)。デフォルトは通話の言語のフレーズです。
voiceModeTwiliorelay(デフォルト)は Twilio ConversationRelay を通して会話を stream します。gather は turn-based で、発信者の 1 発話ごとに 1 つの完全な回答を発話し、無音のターンが 2 回続くと通話を終了します。
ttsProvider, transcriptionProviderTwilio(relay)Twilio の text-to-speech provider(ElevenLabs がデフォルト、Google または Amazon)および speech-to-text provider(Deepgram がデフォルト、または Google)。
disableBuiltInSmsReplyすべてtrue の場合、connector は agent で受信 SMS に返信しません。独自のSMS handlerが処理します。コードから設定します。
provisioningすべて保存のたびに connector が書き込みます:{ state: 'ready' | 'error', message?, updatedAt, openAi?, elevenLabs? }。番号が設定済みかを確認するために読み取ります。

コードから電話回線を設定する

ability が行うすべての操作は、application API key を保持する backend code からも利用できます。これは agent を大規模に provision する方法です。接続を設定すると、ability を保存した場合と同様に番号が設定されます。結果は数秒後に provisioning に反映されます。

Backend code
import { executable, SquidService } from '@squidcloud/backend';
import { AiAgentTwilioIntegrationOptions, SquidTwilioClient } from '@squidcloud/twilio-client';

export class PhoneLinesService extends SquidService {
/** Gives the agent a number of the Twilio account, answered by an OpenAI realtime voice. */
@executable()
async assignPhoneLine(agentId: string, phoneNumber: string): Promise<AiAgentTwilioIntegrationOptions> {
this.assertIsAuthenticated();
const line: AiAgentTwilioIntegrationOptions = {
phoneNumber,
voiceEngine: 'openai',
openAiVoiceIntegrationId: 'openai_voice',
voice: 'marin',
language: 'en-US',
welcomeGreeting: 'Thanks for calling Bright Smile Dental. How can I help you today?',
// The agent composes the opening line itself, so a known caller is greeted by name.
greetingMode: 'agent',
};
// `connectedIntegrations` is set as a whole: keep the agent's other connections and replace its Twilio one.
const agent = await this.squid.ai().agent(agentId).get();
const others = (agent?.options.connectedIntegrations ?? []).filter((item) => item.integrationId !== 'twilio');
await this.squid
.ai()
.agent(agentId)
.setAgentOptionInPath('connectedIntegrations', [...others, { integrationId: 'twilio', integrationType: 'twilio', options: line }]);

// Ask the connector to wire the number up now and report the outcome, instead of polling `provisioning`.
const twilio = new SquidTwilioClient(this.squid, 'twilio');
const { provisioning } = await twilio.provisionAgent({ agentId });
if (provisioning.state === 'error') throw new Error(`The number could not be connected: ${provisioning.message}`);
return { ...line, provisioning };
}
}

setAgentOptionInPath('connectedIntegrations', [...]) は agent の接続リスト全体を置き換えるため、この例では他の接続も引き継いでいます。既存の回線の 1 つのオプションを変更するには、その接続を直接指定します:setAgentOptionInPath('connectedIntegrations[integrationId=twilio].options', { ...currentOptions, greetingMode: 'agent' })。この path は既存の接続のみを更新し、新たな接続は追加しません。

空いている番号を確認するには、listPhoneNumbers() を呼び出します。アカウント内のすべての番号が返され、agent がすでに使用している番号には usedByAgentId が付きます。

通話時の動作

audio の処理方法にかかわらず、agent は chat と同じように通話に参加します。

  • 通話を認識します。 各ターンには電話応対の作法(短い音声応答、markdown を使用しない、名前と番号は繰り返して確認する)と、通話情報(発信者番号、着信番号、現在時刻)が含まれます。Agent は通話の言語で応答します。
  • 通話を記憶します。 1 回の通話のすべてのターンは 1 つの conversation memory を共有するため、agent は最初の言葉から最後まで context を保持します。
  • tools が通話を認識します。 Agent が呼び出すすべての AI function は agentContext.twilioCallSidagentContext.twilioIntegrationId を受け取ります。そのため function は通話記録を読み取り、たとえば番号で発信者を検索できます。AI functions で通話 context を使用するを参照してください。
  • 終了・転送できます。 Twilio ability は、通話中の AI functions を 2 つ agent に追加します。endTwilioCall は別れの挨拶をして切断し、transferTwilioCall は案内を発話して別の番号へ発信し、agent の番号を caller ID として発信者を接続します。たとえば「発信者が担当者を求めた場合、+1 555 010 0100 に転送し、接続することを伝えてください」のように、使用するタイミングを agent の instructions で指定してください。

ElevenLabs engineは例外です。ElevenLabs が通話をホストし、独自の tool で終了します。また、通話 context は agent の functions に渡されないため、そこで転送は利用できません。

発信先番号に agent が接続されていない場合、発信者には「Sorry, no assistant is available to take this call right now」と流れ、通話は終了します。無効化された connector は新しい通話に「Sorry, this number is not taking calls right now」と応答し、SMS には応答しません。

発信通話

Agent は placeTwilioCall AI function を使用して発信します(「Call +1 555 010 0100 and confirm tomorrow's appointment」)。backend からも placeCall で同じことができます。通話は agent の番号から発信されます。応答されると、agent は相手に挨拶し、その通話向けの instructions に追加された目的に従います。応答者は、着信の発信者と同じように interrupt、質問、転送を行えます。

Backend code
import { executable, SquidService } from '@squidcloud/backend';
import { SquidTwilioClient } from '@squidcloud/twilio-client';

export class RemindersService extends SquidService {
@executable()
async callToConfirm(patientPhone: string, appointmentText: string): Promise<{ callSid: string }> {
this.assertIsAuthenticated();
const twilio = new SquidTwilioClient(this.squid, 'twilio');
return twilio.placeCall({
agentId: 'front-desk',
to: patientPhone,
// What the agent should accomplish once the call is answered.
instructions: `Confirm the appointment ${appointmentText}. Offer to reschedule if it no longer works.`,
});
}
}

instructions は Twilio engine と OpenAI engine に適用されます。ElevenLabs engine は ElevenLabs 経由で通話を発信し、これらを無視します。

SMS

Agent の番号への SMS は検証、重複排除され、SMS_EVENT_TYPE event として application の event bus に broadcast されます。デフォルトでは、その後 connector 自身が応答します。送信者ごとに維持される conversation で agent に問い合わせ、agent の番号から回答を SMS で返します。Agent には SMS 中であることが伝えられるため、320 文字未満の plain text で回答します。

独自の方法で SMS に応答するには、電話回線で disableBuiltInSmsReply: true を設定し、backend で event を処理します。

Backend code
import { eventHandler, SquidService, TriggerEvent } from '@squidcloud/backend';
import { SMS_EVENT_TYPE, SquidTwilioClient, TwilioSmsEvent } from '@squidcloud/twilio-client';

export class TextsService extends SquidService {
@eventHandler<TwilioSmsEvent>(SMS_EVENT_TYPE)
async onText(event: TriggerEvent<TwilioSmsEvent>): Promise<void> {
const { integrationId, agentId, from, to, body } = event.payload;
// A text to a number no agent has carries no agentId; ignore it or route it yourself.
if (!agentId) return;
const answer = await this.squid
.ai()
.agent(agentId)
.ask(body, {
instructions: 'You are answering an SMS. Plain text only, under 320 characters.',
memoryOptions: { memoryMode: 'read-write', memoryId: `sms_${from}` },
});
const twilio = new SquidTwilioClient(this.squid, integrationId);
await twilio.sendSms({ to: from, from: to, body: answer });
}
}

Agent は、たとえば住所を含む予約確認のような SMS を sendTwilioSms AI function で自ら送信できます。backend は、agent の番号から sendSms({ to, body, agentId }) で送信できます。

通話記録と transcript

connector は application の組み込み database に 2 つの collection を保持します。

CollectionDocument
twilio_calls通話ごとに 1 つの TwilioCallRecord。Twilio call SID を key とします:agentIddirectionfromtostatusin-progresscompletedbusyfailedno-answercanceledtransferred)、voiceEnginechannelphone または web)、startedAtendedAtdurationSecondsinstructions
twilio_call_transcripts発話ターンごとに 1 つの TwilioCallTranscriptEntrycallSidroleuser または agent)、textat

backend code から getCallTranscript({ callSid }) を使用して読み取ると、record と順序どおりのターンが返されます。また、collection を直接 query できます。たとえば agent の本日の通話を一覧表示できます。通話中に反応するには、transcript の作成元となるvoice session eventsを subscribe してください。

ElevenLabs engine は Squid 外部で通話をホストします。これらの transcript は ElevenLabs dashboard にあります。

Twilio 経由の browser calls

web page の訪問者は、電話を使わずに Twilio の browser client を介して agent に電話をかけられます。backend が agent に紐付く credential を発行し、browser がその credential で接続すると、通話は agent の番号に対する通話と同様に agent へ到達します。通話 record では channel: 'web' になります。これは Twilio engine と OpenAI engine で動作し、connector に Twilio API Key が必要です。

Backend code
import { executable, SquidService } from '@squidcloud/backend';
import { CreateTwilioCustomerVoiceTokenResponse, SquidTwilioClient } from '@squidcloud/twilio-client';

export class BrowserCallsService extends SquidService {
/** A credential for one call with the agent, valid for five minutes. */
@executable()
async createBrowserCallToken(): Promise<CreateTwilioCustomerVoiceTokenResponse> {
this.assertIsAuthenticated();
const twilio = new SquidTwilioClient(this.squid, 'twilio');
// webCallerKey is echoed on the call record and in the agent's function context, so you can tell who called.
return twilio.createCustomerVoiceToken({ agentId: 'front-desk', webCallerKey: this.getUserAuth()?.userId ?? '' });
}
}
Client code
import { Device } from '@twilio/voice-sdk';

const { token } = await squid.executeFunction('createBrowserCallToken');
const device = new Device(token);
const call = await device.connect();
call.on('disconnect', () => console.log('The call ended'));

credential は 1 回の発信通話のみを許可し、それ以外は許可しません。未使用の場合は 5 分後に失効します。getCustomerVoiceCall({ identity }) は、credential がどの call SID に使用されたか、および通話 status を backend に通知します。

ヒント

OpenAI engine では、squid.ai().voice().startWebCall() を使用し、Twilio を介さず WebRTC 経由で browser から agent に電話をかけることもできます。browser calls を電話とまったく同様に記録・route したい場合、または人間の担当者へ引き継ぎたい場合は、Twilio route を選択してください。

通話を人間の担当者に引き継ぐ

application は、たとえば発信者をスタッフとの conference に参加させるために、agent が途中で通話を切断することなく、進行中の通話を引き継ぐことができます。setHumanHandoff は通話の所有者を記録します。

Backend code
import { SquidTwilioClient } from '@squidcloud/twilio-client';

const twilio = new SquidTwilioClient(this.squid, 'twilio');

// 1. Claim the call: from now on Squid routes it to your handoff URL instead of ending it.
await twilio.setHumanHandoff({
callSid,
handoffId: ticketId,
action: 'claim',
// A webhook of yours that answers with the TwiML joining the caller to the human, e.g. <Dial><Conference>.
handoffUrl: this.squid.getWebhookUrl('joinConference') + `?ticket=${ticketId}`,
});

// 2. Once the human is on the line, confirm; a confirmed claim can no longer be released.
await twilio.setHumanHandoff({ callSid, handoffId: ticketId, action: 'confirm' });

// Or give the call back to the agent while the human has not joined yet.
await twilio.setHumanHandoff({ callSid, handoffId: ticketId, action: 'release' });

claim された通話は次のように動作します。

  • Agent の endTwilioCall および transferTwilioCall functions は拒否されます("A human handoff owns this call")。また、agent の voice session が終了しても、発信者の通話は切断されません。
  • Twilio がその通話に対して行う次回の callback は、handoffUrl への redirect で応答されます。OpenAI engine では、agent が通話側を終了するとすぐにこれが発生します。そのため、独自の function で通話を claim した後、agent に別れの挨拶をさせることができます。Twilio engine では、claim 後に Twilio REST API(calls(callSid).update({ url: handoffUrl }))で自ら通話を移動してください。
  • handoffUrlhttps://(development では http://localhost)である必要があり、credentials を含められません。異なる handoffId または URL による 2 回目の claim は拒否されます。すでに終了した通話に対する claim も拒否されます。

通話 record の webCallerKey は、handoff をまたいで browser caller を識別するため、conference page で待機中の人間の担当者を表示できます。

コードから Twilio connector を使用する

typed methods で connector の操作を呼び出すには、client package を install します。すべての method には application API key が必要なため、executablesなどの backend code から呼び出してください。

npm install @squidcloud/twilio-client
Backend code
import { SquidTwilioClient } from '@squidcloud/twilio-client';

const twilio = new SquidTwilioClient(this.squid, 'twilio'); // your connector id
MethodExecutable name説明
sendSms({ to, body, agentId?, from? })sendSmsAgent の番号から SMS を送信します(agentId がない場合は最初に接続された agent)。
placeCall({ to, agentId, from?, instructions? })placeCall応答後に agent が会話する通話を発信します。
getCallTranscript({ callSid })getCallTranscript通話 record と順序どおりの transcript。
listPhoneNumbers()twilioListPhoneNumbersアカウントの番号。それぞれに、すでに呼び出す agent が付きます。
provisionAgent({ agentId })twilioProvisionAgentAgent の番号を再度設定し、結果を返します。
createCustomerVoiceToken({ agentId, webCallerKey })twilioCreateCustomerVoiceTokenAgent との 1 回の browser call 用 Twilio Voice SDK credential。
getCustomerVoiceCall({ identity })twilioGetCustomerVoiceCallBrowser credential が使用された通話。
setHumanHandoff({ callSid, handoffId, action?, handoffUrl? })twilioSetHumanHandoff進行中の通話の人間による所有権を claim、confirm、release します。

package を install しない場合、executable names は squid.executeFunction() に渡す値です。その場合は request に integrationId を渡してください。

Agent Studio および ask() では、Twilio 接続済みの agent は次の AI functions を利用できます:sendTwilioSmsplaceTwilioCallendTwilioCalltransferTwilioCall

トラブルシューティング

症状原因と解決策
Ability に "Choose a phone number for this agent" と表示される回線に phoneNumber がありません。番号を選択して再度保存してください。
"… is not a phone number of Twilio account …"番号が connector のアカウントにないか、表記が異なります。Twilio に表示される E.164 形式(例:+15551234567)を使用してください。
"… already rings agent …"別の agent がその番号を使用しています。1 つの番号が呼び出す agent は 1 つだけです。そちらで解放するか、別の番号を選んでください。
"Choose the OpenAI Voice integration …" または "… ElevenLabs integration …"Engine には connector が必要です。openAiVoiceIntegrationId または elevenLabsIntegrationId を設定してください(console は最初の connector を自動選択します)。
通話が "no assistant is available" と応答される着信番号が agent に接続されていないか、セットアップに失敗しています。ability を開き、status line を確認してください。
Twilio の debugger が webhook で INVALID_TWILIO_SIGNATURE error を報告するTwilio が request する URL が connector の想定 URL と異なります。通常は backend の前に proxy または tunnel があります。Webhook Base URL を public URL に設定して保存してください。
通話は接続するものの何も話されず、その後切断される(Twilio speech、real-time mode)Account で ConversationRelay が有効ではありません。Twilio Console で AI/ML addendum に同意するか(始める前にを参照)、回線を turn-based mode に切り替えてください。
応答後に数秒間無音になるOpening line が「agent による作成」に設定されている場合は想定どおりです。Agent は発話前にターンを実行します。即時に応答するには fixed greeting を使用してください。
米国の番号へ SMS が配信されない番号が A2P 10DLC に登録されていません。Twilio の Messaging logs で error code を確認してください。
createCustomerVoiceTokenCUSTOMER_VOICE_REQUIRES_TWILIO_API_KEYBrowser calls は Twilio access tokens を発行するため、connector に API Key SID と secret が必要です。
setHumanHandoffCALL_ALREADY_ENDED または CALL_HAS_ANOTHER_HANDOFFclaim 前に通話が終了したか、別の handoffId がすでに所有しています。まず通話 record を読み取ってください。