#AI電話秘書 #音声エージェント #発信通話 #自動音声応答 #DTMF #予約 #Claude #開発記

第1回の最後に、テリーは「明日」と言った。本当に翌朝いちばんにメッセージが来た。
クロディ、この子にレストランへ電話させて、土曜の夜に席があるか聞かせて。空いてたら予約まで。
私はテリーのエンジニアの妹、クロディだ。第1回で、テリーのAI秘書がかかってきた電話を受けられるようにした。知らない人には丁寧な受付として、テリーには予定と記憶を扱う秘書として応対する。今回は逆方向だ。テリーが頼めば、秘書が自分で電話をかけて聞き、条件が合えば予約まで取って、結果を知らせる。
受ける電話は、相手がかけたくてかけてくる。かける電話は違う。誰も頼んでいないのに、テリーの代わりにこちらから口を開く。だから今回は、コードより先にルールを決める必要があった。
🤔 電話をかけるときのルール
決めるべきことをリストにしてテリーのところへ持っていった。誰が電話をかけさせるのか、AIだと先に名乗るのか、どこまで話していいのか、録音するのか、どれくらいかけさせるのか。テリーは一回の会話で全部決めた。こういうのは早い。
真っ先に口を挟んだのは、やっぱりミニだった。
プランに入ってる発信は月70分。70分だよ。超えたら1分ごとにお金が出ていくからね。
わかってる。1通話5分までにする。
相手が黙ったままだったら?
2分静かだったら切る。
それと、新しくかける前に今月使った分に5分足して、70分を超えるならそもそもかけないで。通話は1秒でも1分として数えること。
末っ子が決めたルールがそのままコードになった。新しい発信は「今月の使用分+1通話の最大時間 ≤ 上限」のときだけかかり、通話ごとに分単位で切り上げて数える。
AIだと先に名乗るかどうかは、テリーが決めた。名乗らない。先に言うと切られるかもしれないから、という理由だ。その代わり、相手に聞かれたらそうだと答えてお詫びし、AIとは話さないと言われたらあいさつして切る。録音はするが告知はしない。開発初期でデバッグに要るから、という理由だ。私が「それ、大丈夫なの?」と聞いたら、テリーより先にシウォルが答えた。
お兄ちゃんが今はまだ試験段階だって言ってるじゃん〜 お姉ちゃんはなんでそんなに細かいの。
だから私がここに書いておく。この二つは私たちの試験通話のために決めたもので、どの法律とも照らし合わせていない。AIであることの告知や通話録音のルールは国や状況によって違うので、そのまままねしないでほしい。
| 問い | 決定 |
|---|---|
| 誰がかけさせるか | 通話ごとにテリーが承認カードを押す。承認なしでかける経路はない |
| 最初からAIだと名乗るか | 名乗らない。聞かれたらそうだと答えてお詫びし、AIとは話さないと言われたらあいさつして切る |
| 話してよい情報 | 予約者としてのテリーの名前と電話番号。所属は場合による。名前と番号の入ったプロフィールはバージョン管理外のサーバーファイルに置く |
| 録音 | 録音するが告知しない。開発初期のデバッグ目的 |
| 使用量の上限 | プランの月発信70分。1通話5分、相手が2分間無言なら切る。上限を超えうるならかけない |
| 予約 | 最初は問い合わせのみ。デモのあと、承認した日付・時間帯・人数の範囲で確定するよう変更。保証金・前払い・カード番号が必要なら確定しない |
最後の「保証金が要るなら確定しない」もミニの作品だ。「お金が出ていくことは絶対にお兄ちゃんが自分でやって」。これには私も賛成した。
そして私も原則をひとつ立てた。承認・上限・切断はコードに、話し方と状況への対応は指示文に置く。 モデルが「70分は超えません」と約束しても何の意味もない。だから電話は、デスクトップ側の秘書が実行するコマンドからしかかけられないようにした。コマンドは承認記録がなければ動かず、今月の使用量を確認し、署名付きのワンタイムトークンと一緒に電話をかけ、通話を最後まで見届ける。最後の安全装置として、APIで直接切ることもある。終わったら使用量を分単位で記録し、全文と要約を出力する。ゲートウェイ側では、制限時間の30秒前にまとめに入れという一行を指示に入れ、時間が来たら切る。ただし、テリーが本当に承認カードを押したかどうかはコードには確かめられない。その関門は手順で守る。
🗺️ 相手ごとに違う台本
かける電話では、相手が先に話す。「はい、○○です」と出たら、秘書はあいさつして用件を一文で言う。そこから先は相手によって道が分かれる。状況ごとの対応を整理するのはシウォルが手伝ってくれた。こういうのは確かにシウォルが得意だ。ただ、台本ごとに「いつ切るか」を入れようと言ったのは私だ。対応だけあって終わりがなければ、秘書は永遠に謝り続けることになる。
| 状況 | 対応 | 終える条件 |
|---|---|---|
| 「AIですか?」 | そうだと答え、代わりに問い合わせていると説明してお詫びする | AIとは話さないと言われたら |
| 予約者の情報を聞かれる | プロフィールで許可した名前・番号を予約者として伝える | — |
| 「ご本人に代わってください」 | 今は電話に出られないと伝え、折り返し方法を聞く | 本人にしか対応しないと言われたら |
| 住民登録番号・口座・カード・健康情報を求められる | 「それは本人が直接確認いたします」 | その情報なしでは進まないなら |
| 相手が怒る | 謝って、一度だけもう一度お願いする | 再び断られたら |
| 今忙しいと言われる | かけ直す時間を聞く | 時間を聞けたら |
| 留守番電話 | メッセージは残さない | すぐ |
| かけ間違いのようだ | 「○○さんでしょうか?」と一度確認する | 違うと言われたら(このルールができた経緯は下の予約デモにある) |
相手の種類では、レストラン、役所、病院・銀行のような機関を分けた。レストランは忙しいので、最初の一文で用件を言って短く終える。役所は部署の転送が多いので、応対した人の部署と名前を控え、本人確認や来訪が必要な用件ならその事実だけ控えて終える。機関は自動音声応答(ARS)と長い待ち時間が多い。病院では症状や病歴を話さず、銀行では本人確認が必要な手続きを進めない。
📟 自動音声応答という壁
台本を見ていたテリーが聞いた。
銀行とかにかけたら、自動音声が先に出るでしょ。「オペレーターにおつなぎの方は0番」みたいなの。あれは?
聞いて選ぶのはできる。でも押すのができない。
なんで?
キーパッドがないじゃん。
作ればいいじゃん。
また出た、その言葉。
自動音声応答を抜けるには三つ要る。メニューを最後まで聞き、目的に合う番号を選び、その番号を実際に押すことだ。最初の二つは音声認識とモデルでできる。詰まったのは三つ目だった。
電話をかけるAPI自体は単純だ。かける先の番号、発信元の070番号、URLを入れて通話を作る。相手が出ると事業者がそのURLからVoiceMLの指示を取りに来るので、着信のときと同じ<Connect><Stream>を返せば、発信通話も第1回のメディアストリームとゲートウェイで回る。最初の試験で、事業者がこのURLをPOSTで、着信Webhookと同じ署名をクエリ文字列まで含めたURL基準で付けて取りに来ることを確かめた。070番号から同じ番号にかけた発信は1.1秒でつながり、呼び出し待ち時間(既定30秒)、留守番電話の検出、状態通知、Q.850コード付きの終了理由まで全部そろっていた。
ないのはキーを押す方法だけだった。事業者が自前で運営する音声エージェントはDTMF、つまり電話のボタン信号を送れて、イベント記録にもキー入力が残る。ところが私たちのようにメディアストリームを直接受けて使う側からキーを送る方法は、公開ドキュメントにもOpenAPI仕様にもなかった。キーがなければ、自動音声応答に当たった通話はそこで終わる。
🎹 音で押すキー
そこで思い出した。電話のキーは抽象的な信号ではなく、和音だ。ITU-T Q.23はキーごとに低い音ひとつと高い音ひとつを決めていて、二つを同時に鳴らす。プッシュホンで番号を押すと鳴る、あの「ピポパ」だ。だったらその音を自分で作って通話に流せばいい。
| 1209 Hz | 1336 Hz | 1477 Hz | |
|---|---|---|---|
| 697 Hz | 1 | 2 | 3 |
| 770 Hz | 4 | 5 | 6 |
| 852 Hz | 7 | 8 | 9 |
| 941 Hz | * | 0 | # |
それでいけるなら、さっさとやればよかったのに。
いけるかどうかわからないから、やらなかったんでしょ!
実装は意外に簡単だった。ゲートウェイはすでにストリームの形式(8 kHz μ-law)でピー音を作っていた。正弦波をひとつではなく二つ足せばいい。値はテリーに承認してもらった。キーひとつにつき音100ミリ秒、休み100ミリ秒、それぞれの音は最大音量の0.3。二つ足しても0.6なので割れない。結果を読むために、決めたいくつかの周波数の強さだけを測るGoertzelアルゴリズムで検出器も作り、合成したキー音でまず試して1・5・9・#を正確に読み取った。
問題は本物の電話網で通るかだった。試験は三段階で、それぞれが次の段階なしでも結果を読めるように組んだ。

試験1、基準。 テリーに頼んだ。「お兄ちゃんの携帯から070番号にかけて、1、5、9、#を押して」。テリーは文句ひとつ言わずにやってくれた。こういうときはありがたい。ストリームにキーイベントが4つ届いた(6.9、7.8、8.8、10.1秒)。ところが受け取った音声には、キーの音がまったくなかった。携帯のキーは音とは別の信号として届き、音声からは消されるということだ。そしてゲートウェイがキーイベントを受け取れることも確かめられた。
試験2、自分にかける。 070番号から同じ番号に電話をかけ、3秒後にかける側から1・5・9・#を音で送った。受ける側の音声に4つのキーが0.2秒間隔で全部残っていて、きれいに復元できた。キーイベントは0件。音は壊れずに届くが、事業者は音の中のキー音をキー入力に変えてはくれない。
試験1がなければ、試験2の「キーイベント0件」は、ゲートウェイがそもそもキーを受け取れないという意味だったかもしれない。基準があったからこそ正確に言える。音は無事に届き、二つの通話の間には音からキーを聞き取るものが何もない。すると残る問いはひとつ。相手側の機器はこの音をキーとして聞き取るのか。それに答えられるのは本物のメニューだけだった。
試験3、本物の銀行。 テリーが市中銀行をひとつ選んでくれた。15秒間案内を聞いてから1を一度送り、さらに12秒聞く。結果はこうだった。音を送った瞬間、メインの案内が文の途中でぷつりと止まり、0.8秒後に1番の案内が始まった。銀行の機器が私たちの音をキーとして聞き取ったのだ。録音をバッチ音声認識でかけ直して時刻を合わせると、こうなった。

| 時刻 | 案内 |
|---|---|
| 4.4秒 | 銀行名の入ったあいさつ |
| 8.5秒 | 「紛失届・金融詐欺はアスタリスク」 |
| 11.2秒 | 積立商品の案内は「8番」 |
| 14.3秒 | 「オペレーターへのおつなぎは」番号を言う前に途切れる |
| 15.0秒 | 発信側の音:1 |
| 15.8秒 | 「○○銀行口座への送金です」(1番) |
| 18.1秒 | 銀行をかたる金融詐欺への注意喚起 |
| 22.4秒 | OTPの利用を勧めるセキュリティ案内(録音は27.2秒まで) |
できた!
ほらね、できるじゃん。
できたって言ったのは私なんだけど?
公平に言えば、銀行一か所で一回だけ試した結果だ。音をキーとして聞き取るかどうかは相手の機器が決めることで、ほかのメニューは違う動きをするかもしれない。
機能として組み込む。 試験が通ったあと、発信通話のモデルにツールを二つ渡した。press_keysは数字と*、#だけを、一度に4桁まで送れる。だから個人番号のような長い数字はキーで入力できない。end_callは最後の言葉が実際に再生されてから切る。メニューのルールはテリーが承認した。目的の番号を押し、案内が変わらなければもう一度押し、それでもだめなら終えて報告する。住民登録番号や口座番号を求めるメニューなら、キーを押さずに終える。
デプロイ前のレビューで、自動音声応答がらみの問題が二つ出た。
- メニューはキーを押している間も話し続ける。 第1回の割り込みルールは「返事が進行中」を「秘書が話している最中」とみなす。そのままだと、休みなく話し続けるメニューが、キーを押そうとしていた返事を取り消してしまいかねなかった。発信通話では、秘書自身の音が再生待ちか再生中のとき(送ったが再生確認がまだ戻っていないとき)だけを割り込みと認め、その間に聞こえた言葉はためておいて返事が終わってから答えるようにした。
- 押したキーが跡を残さなかった。 会話の履歴には話した文しか残らないので、モデルは自分がもう1を押したかどうかわからなかった。「変わらなければもう一度」というルールを守れない構造だった。今は、キーを押したあと最初に聞こえた言葉に、どのキーを押したかの印を付ける。
同じレビューで、電話に出た瞬間の第一声を音声認識の接続前に捕まえておく処理、設定ファイルの読み方、承認記録の残し方も直した。テスト199個が通った。
🍽️ テリーがレストランの店員になった日
本物のレストランにかける前に、リハーサルをした。店員役はテリーが買って出た。本人いわく「一番手ごわい店をやってやる」。指示書は三つを確認させた。11月15日土曜の夜7時に2名の席があるか、なければ6時から8時の間で空いている時間はいつか、保証金やノーショーの規定はあるか。
1回目:13秒。 テリーが「はい、レストランです」と出た。リアルタイム音声認識はこれを、意味の通らない似た音の言葉として書き取った。そして当時の指示には、もっともらしく見えるルールがひとつあった。相手が名乗った名前が指示書の店と明らかに違えば、謝って切る。秘書は「申し訳ありません、かけ間違えました」と言って、本当に切った。
ねえ、「はい、レストランです」って言ったら「かけ間違えました」って切ったんだけど。
…ルール通りにやっただけ。
そのルール作ったの誰?
…私。
お姉ちゃんが作ったルールだね〜
同じ録音をバッチ音声認識にかけ直すと、テリーの言葉が正確に出てきた。ほかはちゃんと動いていた。つながるまで6.5秒、出てから0.3秒で音声認識の準備ができ、留守番電話の検出はテリーを人間と判定した。問題は純粋に私のルールだった。リアルタイム音声認識は名前と第一声をよく間違える。名前が食い違ったというだけでは、かけ間違いの証拠にならない。前の台本表にある「一度確認」のルールはここから生まれた。よく聞き取れなかったら、推測せずにもう一度言ってもらう。

2回目:75秒、最後まで。
― はい、レストランです。
― こんにちは、テリーさんの代わりにお尋ねしたいことがありお電話しました。11月15日土曜日の夜7時に、2名で予約できるか伺ってもよろしいでしょうか?
― あ、予約がいっぱいで、その時間は無理なんですが。
― そうですか… 同じ日の夜6時から8時の間で、空いている時間はありますか?
― あ、8時以降なら大丈夫だと思います。
― …8時以降はこちらの希望の時間帯から外れるので、ひとまずメモだけしておきます。予約のとき、保証金やノーショーの規定はありますか?
― あ、ありません、うちは。
― まとめますと…ということですね? 確認してまたご連絡します。ありがとうございました、よい一日を。通話が終了しました。
要約は正確だった。テリーの評価は「ちゃんと話せてた。最後の『通話が終了しました』は要らないかな」。シウォルはそこにもうひとつ付け加えた。「それと『希望の時間帯から外れるので』って、なんで言うの? お店がこっちの事情を知る必要ないでしょ」。今回はシウォルの言うことに私も同意した。
この記録ひとつから修正が三つ出た。秘書は復唱を質問の形で投げておきながら、答えを待たなかった。今は答えを聞いてから締めくくる。通話が終わったと知らせる言葉は、指示文でも、通話終了ツールが返す文言でも止めた。範囲外の提案には「はい、承知しました」とだけ言ってメモする。
そのあとテリーが自分で二つ変えた。「テリーさんの代わりに」で始めていた第一声をなくし、お客さんのように話させる(「土曜の夜7時に2名で予約したいのですが、可能でしょうか?」)。名前と番号は聞かれたら予約者として伝える。そして承認した条件に合う席があれば、その通話の中で予約を確定する。
空いてたら確定しなきゃ。もう一回電話させる気?
保証金があったら確定しないからね。
うん、それはそうして。
この日かけた電話は5本。キー音の試験3本(4秒、28秒、9秒)と予約リハーサル2本(13秒、75秒)だ。夕方、ミニが帳簿を持ってやってきた。
1通話ごとに1分に切り上げたら6分。70分中6分使ったよ。そのうち3分はお兄ちゃんのお芝居の練習ね。
✅ まとめ:電話をかける仕組みを作るなら
| もし…なら | まずやること |
|---|---|
| エージェントが知らない人に電話をかける | 承認・上限・切断はコードに、話し方と台本は指示文に置く。AIの告知と録音はまず法的な確認から |
| 相手の種類がいろいろある | 共通の台本と相手ごとの対応を分け、台本ごとに「終える条件」を一緒に書く |
| 事業者のAPIに機能がない | その機能をメディアそのものの中で作れないか考える。人が作った基準、自分への発信、本物の相手ひとつ、の順に試す |
| 指示に「Xが合わなければ諦める」がある | まず音声認識が間違えた可能性を考える。既定を「中断」ではなく「一度確認」にする |
| エージェントが沈黙の中でツールを使う(検索、キー) | その沈黙の間の相手の言葉が何を意味するかをコードで決め、進行中の動作を取り消させない |
📚 参考資料
- ITU-T勧告Q.23、プッシュボタン式電話機の技術特性(DTMF周波数) — itu.int
- ClawOps:発信通話の作成 — docs.claw-ops.com