ページの一覧

ドキュメント

電話でつなぐ

Twilio の Media Streams で電話を voicast につなぐ手順。TwiML の例、クラウドの電話・CTI からのつなぎ方、人への引き継ぎと転送。

Markdown で見る

電話番号と回線はサービスの側(Twilio など)が持ちます。着信の途中で <Connect><Stream> の TwiML を返すと、通話の音声が voicast の音声サーバーに届き、AI が応対します。AI が会話を終えると音声の接続が閉じ、Twilio は action の URL を呼ぶので、そこで結果を見て人への転送や切断を決めます。

着信 → サービスの TwiML(<Connect><Stream>)→ voicast の AI が応対
     → AI が会話を終える → Twilio が action を呼ぶ → 結果を見て <Dial> か <Hangup/>

TwiML#

xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Connect action="https://saas.example.com/twilio/after">
    <Stream url="wss://voice.voicast.jp/ws">
      <Parameter name="tenant" value="ten_…" />
      <Parameter name="secret" value="vts_…" />
      <Parameter name="from" value="+819012345678" />
    </Stream>
  </Connect>
</Response>
Parameter 必須 内容
tenant ○ テナントの ID(ten_…)
secret ○ テナントの接続の鍵(vts_…。テナントを作ったときに返るもの)
agent 会話の設定の ID(agt_…)。省くとテナントの既定
from かけてきた番号。通話の記録の from に残り、通話中の道具や連携で顧客を探すのに使えます

Twilio の <Parameter> は値をそのまま送るので、from などはサーバーで TwiML を作るときに入れます。

Node.js の例#

Twilio の番号の「A call comes in」に https://saas.example.com/twilio/voice を設定します。

js
import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: false }));

const xml = (s) => String(s).replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);

// 着信: かかってきた番号(To)から、自社の顧客と voicast のテナントを引く
app.post('/twilio/voice', async (req, res) => {
  const t = await findTenantByNumber(req.body.To); // { voicastTenantId, voicastSecret }
  res.type('text/xml').send(`<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Connect action="https://saas.example.com/twilio/after">
    <Stream url="wss://voice.voicast.jp/ws">
      <Parameter name="tenant" value="${xml(t.voicastTenantId)}" />
      <Parameter name="secret" value="${xml(t.voicastSecret)}" />
      <Parameter name="from" value="${xml(req.body.From)}" />
    </Stream>
  </Connect>
</Response>`);
});

引き継ぎ#

AI が会話を終えると、voicast は結果(outcome)を書いてから音声の接続を閉じます。Twilio は <Connect> の action を CallSid 付きで呼ぶので、GET /v1/calls/by-provider/{CallSid} で結果を引き、次の TwiML を返します。

js
// AI の応対のあと: 結果を見て、人に転送するか切る
app.post('/twilio/after', async (req, res) => {
  const r = await fetch(`https://api.voicast.jp/v1/calls/by-provider/${req.body.CallSid}`, {
    headers: { Authorization: `Bearer ${process.env.VOICAST_API_KEY}` },
  });
  const call = r.ok ? await r.json() : null;
  // 記録が無い(つながらなかった)・引き継ぎ・途中の障害は、人につなぐ
  const toHuman = !call || call.outcome === 'handoff' || call.outcome === 'error';
  res.type('text/xml').send(
    toHuman
      ? '<Response><Say language="ja-JP">担当者におつなぎします。</Say><Dial>+81312345678</Dial></Response>'
      : '<Response><Hangup/></Response>',
  );
});
outcome 次の処理の例
handoff 担当者・着信グループへ <Dial>
completed・answered <Hangup/>(折り返しは結果の fields と要約で)
error 人へ転送(AI の応対が途中で切れた)
hangup 何もしない(相手はもう切っている)
記録が無い(404) 人へ転送(下の「断られたとき」)

引き継ぐ理由は reason に、そこまでに聞き取った項目は fields に入っています。担当者の画面に出すと、同じことを聞き直さずに済みます。Webhook の call.handoff_requested でも届きますが、届く順は保証しないので、転送の判断には action で引いた結果を使います。

断られたとき#

次のときは、音声サーバーがすぐに接続を閉じ、通話の記録は作られません。Twilio は action に進むので、上の例のように人へ転送します。

  • tenant か secret が違う、テナントが止まっている(status: disabled)
  • 残高が 0 以下・支払いの遅れ・使いすぎの上限で、新しい通話を断っているとき(料金と請求)

始まった通話は、残高が尽きても途中で切りません。

鍵の作り直し#

POST /v1/tenants/{id}/secret で接続の鍵を作り直せます。古い鍵も 24 時間は通るので、そのあいだにサービスの側の設定を差し替えます。

音声と動き#

  • 音声は Twilio の Media Streams(8kHz の μ-law)です。voicast が Twilio の REST で通話を切ることはありません(通話の制御はサービスの側の Twilio が持ちます)。
  • 相手が話し始めると、AI は話している途中でも止めて聞きます。
  • 相手がしばらく黙っていると聞き直し、それでも返事が無ければ hangup で終えます。
  • 同じ CallSid で音声の接続がつなぎ直されたとき(音声サーバーの再起動など)は、同じ通話の記録を使い続けます。

クラウドの電話・CTI から#

Twilio の上に作られたクラウドの電話・コールセンターのシステム・IVR なら、AI に渡したい場面で上と同じ <Connect><Stream> を返すだけでつながります。IVR の 1 つの部品として「AI の受付」を置き、終わったら action で元の流れ(着信グループ・留守番電話など)に戻す形が扱いやすい方法です。

  • 番号ごと・時間帯ごとにテナントや会話の設定(agent)を替えられます。夜間だけ AI に出てもらう、といった使い方ができます。
  • action を置けない構成では、</Connect> のあとに続けたい動詞(<Dial> など)を書きます。音声の接続が閉じると Twilio は次の動詞に進みます。
  • Twilio の Media Streams 以外の方式(SIP など)でつなぎたいときは、お問い合わせください。

試すとき#

テストの環境のテナントで、Twilio の試用の番号から試せます。通話の秒数はテストの環境でも数えます。電話をかけずに会話の設定を確かめるには、Web・アプリから話すのが手早い方法です。