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 SID と Auth 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 の追加
-
Squid Console の Connectors タブへ移動します。
-
Available Connectors をクリックします。
-
Twilio connector を見つけ、Add Connector を選択します。
-
次の設定詳細を指定します。
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 に電話回線が付与されます。
-
Squid Console の Studio タブに移動して agent を開くか、新規作成します。
-
Add Abilities をクリックし、Communication セクションまでスクロールして、作成した Twilio connector を選択します。
-
Phone number を選択します。一覧には、まだ他の agent が使用していないアカウントの番号が表示されます。1 つの番号が呼び出す agent は 1 つだけです。
-
Voice engine を選択します。Twilio speech は常に利用できます。application に ElevenLabs または OpenAI Voice connector があると、ElevenLabs と OpenAI voice が表示されます。engine の下にある設定は、その engine に応じて変わります。
-
通話開始時の Language、Greeting、および engine 固有の設定を指定します。これらはすべて電話回線設定で説明されています。
-
保存します。connector は番号を設定します。番号をこの application に向けるか(ElevenLabs engine では ElevenLabs agent に渡します)、結果を ability の status line に表示します。例:"+1 555 123 4567 rings this agent (OpenAI voice)"。セットアップに失敗すると、その理由が表示されます。たとえば、別の agent がすでに使用している番号などです。
-
OpenAI engine では、status line に OpenAI project で一度登録する webhook URL も表示されます。incoming-call webhook の登録を参照してください。
その番号に電話をかけます。ability を切断すると、番号は再び解放されます。
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。 |
openAiVoiceIntegrationId | OpenAI | 通話に応答する OpenAI Voice connector。 |
voiceModel | OpenAI | 通話で会話する realtime model。connector の default model、それがない場合は最新の realtime model がデフォルトです。 |
voice | すべて | 音声:OpenAI voice name(marin、cedar、alloy、…)、ElevenLabs voice id、または turn-based Twilio mode 用の Twilio provider voice id。デフォルトは engine の default voice です。 |
allowInterruptions | OpenAI | 発信者の発話によって model が中断されるかどうか。コードから設定します。省略時は OpenAI のデフォルトを維持します。 |
interruptionSensitivity | OpenAI | low、normal、または high:発信者の発話をどの程度検出しやすいか。コードから設定します。 |
elevenLabsIntegrationId | ElevenLabs | 通話を実行する ElevenLabs connector。 |
language | すべて | 通話を開始する言語の BCP-47 tag。例:en-US、es-MX、he-IL。デフォルトは en-US です。speech services を制御し、agent に応答する言語を伝えます。 |
welcomeGreeting | すべて | 通話に応答するとすぐに発話されます。Squid にその言語向けの短い挨拶があればそれがデフォルトです。挨拶がない言語では、回線は発信者が先に話し始めるのを待ちます。 |
greetingMode | Twilio、OpenAI | phrase(デフォルト)は、指定どおりに挨拶を発話します。agent は通話接続後に agent が開始メッセージを作成するため、発信者を検索し、既知の発信者には名前で挨拶できます。指定した greeting は fallback です。応答後に少し無音になる代償があります。console では Opening line と表示されます。 |
languageMode | OpenAI、ElevenLabs | single(デフォルト)は通話全体で language を維持します。selected は発信者に language と additionalLanguages 間の切り替えを許可します。any は engine が切り替え可能な任意の言語に発信者を追従します。 |
additionalLanguages | OpenAI、ElevenLabs | selected mode で発信者が切り替え可能な ISO codes(en、es、fr)。 |
fillerMessage, fillerAfterSeconds | ElevenLabs | 応答に少し時間がかかる際に発話するフレーズと、待機する秒数(デフォルトは 4)。デフォルトは通話の言語のフレーズです。 |
voiceMode | Twilio | relay(デフォルト)は Twilio ConversationRelay を通して会話を stream します。gather は turn-based で、発信者の 1 発話ごとに 1 つの完全な回答を発話し、無音のターンが 2 回続くと通話を終了します。 |
ttsProvider, transcriptionProvider | Twilio(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 に反映されます。
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.twilioCallSidとagentContext.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、質問、転送を行えます。
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 を処理します。
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 を保持します。
| Collection | Document |
|---|---|
twilio_calls | 通話ごとに 1 つの TwilioCallRecord。Twilio call SID を key とします:agentId、direction、from、to、status(in-progress、completed、busy、failed、no-answer、canceled、transferred)、voiceEngine、channel(phone または web)、startedAt、endedAt、durationSeconds、instructions。 |
twilio_call_transcripts | 発話ターンごとに 1 つの TwilioCallTranscriptEntry:callSid、role(user または agent)、text、at。 |
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 が必要です。
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 ?? '' });
}
}
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 は通話の所有者を記録します。
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およびtransferTwilioCallfunctions は拒否されます("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 }))で自ら通話を移動してください。 handoffUrlはhttps://(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
import { SquidTwilioClient } from '@squidcloud/twilio-client';
const twilio = new SquidTwilioClient(this.squid, 'twilio'); // your connector id
| Method | Executable name | 説明 |
|---|---|---|
sendSms({ to, body, agentId?, from? }) | sendSms | Agent の番号から SMS を送信します(agentId がない場合は最初に接続された agent)。 |
placeCall({ to, agentId, from?, instructions? }) | placeCall | 応答後に agent が会話する通話を発信します。 |
getCallTranscript({ callSid }) | getCallTranscript | 通話 record と順序どおりの transcript。 |
listPhoneNumbers() | twilioListPhoneNumbers | アカウントの番号。それぞれに、すでに呼び出す agent が付きます。 |
provisionAgent({ agentId }) | twilioProvisionAgent | Agent の番号を再度設定し、結果を返します。 |
createCustomerVoiceToken({ agentId, webCallerKey }) | twilioCreateCustomerVoiceToken | Agent との 1 回の browser call 用 Twilio Voice SDK credential。 |
getCustomerVoiceCall({ identity }) | twilioGetCustomerVoiceCall | Browser 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 を利用できます:sendTwilioSms、placeTwilioCall、endTwilioCall、transferTwilioCall。
トラブルシューティング
| 症状 | 原因と解決策 |
|---|---|
| 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 を確認してください。 |
createCustomerVoiceToken で CUSTOMER_VOICE_REQUIRES_TWILIO_API_KEY | Browser calls は Twilio access tokens を発行するため、connector に API Key SID と secret が必要です。 |
setHumanHandoff で CALL_ALREADY_ENDED または CALL_HAS_ANOTHER_HANDOFF | claim 前に通話が終了したか、別の handoffId がすでに所有しています。まず通話 record を読み取ってください。 |