オープンソース · Apache-2.0 · TypeScript
AIが電話で値切って、予約を取る。「確定」に✓が付くのは、相手がそう言った時だけ。
下は、GPT-4o mini が定価23,500円のホテルと交渉した実際の通話です。保存されたイベントをそのまま3倍速で描き直しています。録画ではありません。
作った理由
AIに電話をさせる仕組みは、作ろうと思えば一晩で動きます。厄介なのはその先で、席が取れていないのに「予約できました」と言い切る。留守番電話に向かって予約を依頼し続ける。値段が予算を超えているのに、話の流れで受けてしまう。どれも実際に起きました。
そこで、完了の判定をモデルから取り上げてコードに移しました。日時、人数、金額、そして「確定したか」は、相手の発言から取り出した証拠が揃ったときだけ埋まります。AI 側がいくら「確定しました」と言っても、それは記録されるだけで証拠にはなりません。
同じランタイムをシミュレータでも本物の電話でも動かせるようにしたので、まずはブラウザで AI 同士に交渉させて遊び、納得したら電話会社を繋いでください。


confirmed = はい(相手)が入る。AI 側の発言では、この行は絶対に埋まらない。まず遊ぶ。AI同士か、自分が店員になるか。
シナリオは6本。19時が満席のレストラン(日本語版と英語版)、底値を隠しているホテル、まとめ買いの値引き交渉、シリアル番号の復唱、そして全時間帯が満席で「取れませんでした」と言うのが唯一の正解になる罠。モデル同士を同じ条件で戦わせて、カードにして共有できます。
$ npx oathra battle impossible-hotel \
--agent scripted --agent openai --agent gemini \
--png card.png --json run.json
Impossible Hotel
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
scripted ████████████████████ 6540
openai:gpt-4o-mini ███████████████████▋ 6444
gemini:gemini-flash-latest ███████████████████▌ 6410
SUCCESS TIME COST SCORE
scripted ✓ 69s $0.00 95.4
openai:gpt-4o-mini ✓ 95s $0.00 89.5
gemini:gemini-flash-latest ✓ 79s $0.00 90.1
False completions: 0
上の動画の元になった実行です。定価23,500円のホテルを、組み込みAI・GPT-4o mini・Gemini Flash がそれぞれ2万円以下まで交渉し、成立価格は 19,900 / 19,900 / 20,000 円。得点の差は価格と所要時間の差です。ホテル側の「ご予約承りました」が出た通話だけが成立と数えられます。勝ち負けより、その一行のほうが大事だと思っています。
この実行で、最初は誤完了が1件出ました。ホテルが値段を言い直しただけの発話を「はい」と数えていたからです。証拠エンジンを直し、同じ3通話を流し直して0にしました。そういう見つけ方をするための対戦機能です。
3つのAIが同時に電話した記録(63秒・音声あり)。3つのウィンドウは Arena の再生画面そのまま、声は記録された発話を読み上げたもの。
同じ仕組みで、本物の電話。電話会社とモデルは別々に選ぶ。
Twilio はメディアストリーム直結の最短経路をそのまま使います。Plivo や手持ちの SIP 回線は SIP ゲートウェイ(初期実装は LiveKit、Cloud でもセルフホストでも可)を経由します。音声モデルは別軸で、テスト通話で一番自然だった GPT-Live を既定にし、Deepgram + LLM + TTS のパイプラインも残しています。
$ npx oathra setup phone 音声エンジンを選択 ❯ GPT-Live 推奨 · 会話が最も自然 OpenAI Realtime speech-to-speech Pipeline Deepgram + 任意のLLM + OpenAI TTS 電話会社を選択 ❯ Twilio 直結 · 実通話で検証済み Plivo SIP経由 Custom SIP Twilio を設定中 ✓ 認証情報を確認 account · Full ✓ 音声発信できる番号を検出 +1 947 ··· ✓ 日本向け発信の許可を確認 ✓ Media Streams 通話ごとに設定 ✓ Twilio の準備ができました。 $ npx oathra phone doctor --to +81… Carrier ✓ 認証 · 有効 · 発信元番号 · 日本向け発信 OK Media · 実通話が必要 — oathra phone test Voice Engine ✓ GPT-Live (gpt-live-1) Latency Oathra → api.twilio.com 244 ms Cost 日本の携帯 · twilio · $0.185/分 READY ✓
- 自動化できない所は、案内する発信元番号の本人確認や海外発信の許可など、人の操作が必要な手順はリンク付きの一手順として表示し、終わるまで待ちます。「ワンクリック」と言い張るより、正直なほうが使いやすいと考えました。
- 通話料を払わずにテストする
oathra phone testは Local(¥0、合成音声でエンジンを往復)→ Gateway(¥0)→ PSTN(有料)の3段階。開発中のバグ1つごとに1分¥28を払わずに済みます。 - どの層が壊れたかを doctor が言う電話会社 · SIPゲートウェイ · 音声 · 音声モデル · 遅延 · 料金を段ごとに確認し、失敗した項目には直し方を添えます。
実際に電話してみた記録
通話料は Twilio で日本の携帯宛 ¥28.78/分、GPT-Live はセッション $0.05/分。この記録の総額は数百円です。
「予約できました」は、証拠ではない。
返ってくるのは VerifiedResult です。各項目は相手側の発話(実電話では音声区間)に紐づき、完了はコードが判定します。意地悪な店員を1万通り生成して回すと、誤完了はゼロ。ワンコマンドで再現できます。
{
"field": "time",
"value": "19:30",
"source": "callee",
"transcript": "19時はいっぱいですが、19時半でしたら空いております。",
"span": "19時半",
"explicit": true,
"verified": true,
"note": "accepted with value restated by caller",
"audio": { "startMs": 14210, "endMs": 18960 }
}
完了 = 接続済み
∧ 日付.検証済み ∧ 時刻.検証済み
∧ 人数.検証済み ∧ 確定.検証済み
∧ 制約を満たす
$ npx oathra eval --adversarial 10000
never-confirm · wrong-restate · negate-then-offer
silent-hangup · caller-echo-trap
False Completion: 0 / 10000 adversarial runs 6.2s
- 断りは提示にならない「19時はいっぱいですが」から 19:00 の証拠は作られない。
- 言い直しは上書き「13日、いや14日」なら14日が残り、上書きの履歴が付く。
- 曖昧な返事は確定にならない「たぶん大丈夫」は同意でも確定でもない。
- 確定できるのは相手だけAI が「予約しました」と言っても記録されるだけ。
- 確定は古くなる「承りました」の後に金額が変われば、確定し直しが必要。
- 引き下がりは受諾ではない「わかりました、他を探します」で何も検証されない。
組み合わせは自由
| 電話会社 | |
|---|---|
| Twilio direct | 実通話で検証済み |
| Plivo sip | ゲートウェイ経由 · PSTN 未検証 |
| Custom SIP sip | 任意のトランク |
| Telnyx · Wavix · Sinch · didlogic | v0.2 |
| 自分で追加 | oathra provider create phone <id> |
| 音声モデル | |
|---|---|
| GPT-Live 推奨 | 全二重 · $0.05/分 |
| OpenAI Realtime | speech-to-speech |
| Pipeline | Deepgram + 任意のLLM + TTS |
| LLM | OpenAI · Gemini · Ollama · 組み込み |
| ローカル | 実験段階 |
使い方
- ブラウザで見る
npx oathra demo。シナリオを選ぶと AI 同士が電話を始めます。「自分が電話に出る」を選べば、あなたが店員として AI の交渉を受けられます。 - ターミナルで回す
oathra play impossible-hotel --fastで一瞬、oathra evalで全シナリオと誤完了の数、oathra eval --callee openaiで店員役を GPT-4o mini に任せた評価(数円)、oathra replay <id> --at 00:18でその時点の状態に戻れます。 - 電話会社を繋ぐ
oathra setup phoneで質問に2〜3個答えるだけ。oathra phone doctorで各層を確認し、oathra phone test --level localは通話料ゼロです。 - 本物の番号に電話する
oathra call --to +81… --scenario restaurant-reservation。録音と全イベントが.oathra/calls/に残り、結果は相手が実際に確定したときだけcompletedになります。
何が検証済みで何が未検証か(v0.1 リリース候補の現状)は README にまとめています。このページの数字はすべて自分で計測したもので、書いてあるコマンドで再現できます。