# 電話でつなぐ

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

Source: https://voicast.jp/docs/phone

電話番号と回線はサービスの側（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` に残り、[通話中の道具](https://voicast.jp/docs/tools.md)や連携で顧客を探すのに使えます |

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 以下・支払いの遅れ・使いすぎの上限で、新しい通話を断っているとき（[料金と請求](https://voicast.jp/docs/pricing.md)）

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

## 鍵の作り直し

`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・アプリ](https://voicast.jp/docs/web.md)から話すのが手早い方法です。
