LINE予約「LUCA」の機能と料金はこちら

Vapiで日本語のAI電話を作る:声の選び方と設定の落とし穴

実務ノート 実務ノート 公開

Vapi(電話で話す会話AIを組み立てるサービス)で日本語の電話受付を作ると、最初に声で迷います。人間らしい声を選ぶと漢字を読み違え、読みが正しい声はアナウンスのように聞こえます。設定にも、送ったはずの値が消える、発話が途中で切れる、といった落とし穴がありました。

この記事の結論

  • 日本語の声は「人間らしく聞こえる」と「漢字を正しく読む」が方式ごとに裏返ります。声を聴き比べる前に、その方式が日本語の読みを持っているかで分けます。
  • 読み違える語は、Vapi の読み替え辞書(voice.chunkPlan.formatPlan.replacements)で直します。ただし全部をカタカナにすると訛ります。
  • stopSpeakingPlan を入れないと、相手の音が入った瞬間に AI が話をやめます。numWords を 2 にすると今度は割り込みが効かなくなったので、日本語では 1 にします。realtime のモデルでは model.maxTokens を明示しないと、発話が約8秒で切れました。
  • Vapi の PATCH で、送らなかった値が消えることがありました。GET した全体を書き戻し、もう一度 GET して確かめます。

実際に起きたこと

弊社で動かしているワークフローには、電話の受付を受け持つものがあります。電話に出るのは Vapi のアシスタントです。聞き取った内容は、Vapi のツール(AI が途中で呼び出す外部の処理)でワークフローへ渡します。

声や設定の比較は、本番のアシスタントには触らずに行いました。本番をコピーして試験用のアシスタントを作り、受付を登録するツールを外してから電話をかけています。以下の不具合は、この試験用のアシスタントと、別に作った見本の電話で起きたものです。

2026-09-07 に、声の方式を実際に電話をかけて比べました。結果は次のとおりです。

方式 声 日本語の読み
Polly・Google・Azure の日本語音声 アナウンス調 正しい
ElevenLabs の日本語の声(Kana) 人間らしい 漢字を推測で読む
OpenAI gpt-realtime-2(音声を受けて音声で返す) 会話は自然 外国人のアクセント

ElevenLabs の声には、30語を読ませて機械で確かめました。家賃は「やがみ」、騒音は「すもう」、蛇口は「じゅぎょう」、敷金は「ふきん」と読みました。読めたのは駐車場や漏電などで、約半分が誤りでした。

そこで読み辞書を作り、語をカタカナに置き換えました。読み違いは機械の判定では0になりました。ところが聴いた印象は外国の人の話し方になりました。全部の語をカタカナにしたことで、外来語の訛りが出たのです。

realtime のモデルでは、別の問題が出ました。AI が話している途中で無言になり、会話になりません。調べると、1回の発話が約8秒で止まっていました。

直すために {"model":{"maxTokens":4000}} だけを PATCH(一部を書き換える API の呼び出し)で送りました。すると、プロンプトとツールの指定が消えました。指示を持たない素のモデルが電話に出る状態です。ツールの PATCH でも、送らなかった server.url が消えました。

同じ時期に、設定を変えたと報告したのに、実際には変わっていなかったこともあります。聞き取りの方式を差し替えたと書いた項目は、読み直すと deepgram nova-3 のままでした。比べるための GET だけを実行して、変えたつもりになっていました。

ほかにも、次のことが起きました。

  • 見本の電話で numWords を 2 にしていたところ、相手が話し続けても AI が話をやめず、答えを聞き流して約7秒黙った。
  • 通話を切るツールが、日本語の通話の最後に英語で「Goodbye」と言った。
  • ツールの結果が返った瞬間に、AI が「ちょっと待って」「これは一瞬で終わります」と独り言を挟んだ。
  • realtime のモデルが、ツールが返した候補を一度も声に出さず「確認中です」を繰り返した。

原因

声の違いは、方式の作りから来ています。Polly・Google・Azure の日本語音声は、文を語に分けて読みを決める仕組み(形態素解析)を持っています。そのため読みは正しいのですが、声の素性がアナウンス向けです。ElevenLabs は声そのものは人間らしい一方で、日本語の文を解析せず、漢字を推測で読みます。

話し始めるまでの間(ま)も、方式で決まります。聞き取り、文章の生成、読み上げの3段を順に通す形(パイプライン型)では、約3秒が限界でした。OpenAI の読み上げで測ると、相手が話し終えてから返すまで平均3.0〜3.5秒かかりました。内訳は聞き取り 1.4秒、生成 0.8秒、読み上げ 1.2秒です。

ただし realtime でも、Vapi は聞き取り(transcriber)と話し終わりの判定を通して、話し始める時刻を決めます。聞き取りが遅いと、その分だけ間が延びます。聞き取りの言語を付けないと既定の英語で聞き取り、会話が英語に崩れました。

発話が約8秒で切れたのは、model.maxTokens(1回の応答で作ってよい量の上限)を設定していなかったためです。Vapi の既定は 250 でした。realtime のモデルは音声のトークンを多く使うので、この上限にすぐ届きます。

割り込みの効き方は、stopSpeakingPlan.numWords で変わります。公式の説明では、この値は「相手が何語話したら AI が話をやめるか」を決めます。何も設定しないと numWords は 0 で、相手の音が入った瞬間に AI が止まりました。一方で、語は空白で数えるため、空白の無い日本語は1回の発話が「1語」になります。2 にしていた見本では、相手が話し続けても割り込みになりませんでした。

設定が消えたのは、PATCH の働きです。公式の説明は「指定したフィールドを更新する」です。ところが実際には、model に maxTokens だけを送ると、同じ model の中のプロンプト(messages)とツールの指定(toolIds)が消えました。ツールの PATCH では、送らなかった server.url が消えました。一方で、送らなかった endCallPhrases は残りました。残る値と消える値の両方がありました。そこで弊社は、一部の値だけを送るのをやめ、GET した全体を書き戻すことにしました。

通話を切るツール(endCall)の最後の言葉は endCallMessage で決まります。公式の説明では、ここが空だと「Goodbye」を話し、ツールに request-start の言葉があればそちらを話します。弊社の日本語の通話では英語の「Goodbye」になりました。

endCallPhrases は別の設定です。公式の説明では「アシスタントがこの言葉を話したら通話を切る」ための言葉の一覧です。日本語と中国語では、これが効きませんでした。読み上げの区切りで「お電話あり がとうございました」のように言葉が割れていたので、一覧の言葉と一致しなかったと見ています。英語では効きました。

独り言と候補の無視は、realtime のモデルとツールの組み合わせで起きました。ツールを呼ぶ前の言葉(request-start)が無いと、待ち時間をモデルが自分の言葉で埋めます。また realtime のモデルは、ツールの結果の中身を声に出しませんでした。

直し方

1. 声は「読みを持っているか」で先に分ける

漢字の読みを外したくないなら、日本語の読みを持つ方式から選びます。OpenAI の読み上げ gpt-4o-mini-tts は漢字を正しく読み、instructions で口調も指示できます。Vapi では次の形で保存できました。

"voice": {
  "provider": "openai",
  "voiceId": "nova",
  "model": "gpt-4o-mini-tts",
  "instructions": "XXXX(口調の指示)"
}

このとき使える声は alloy・echo・fable・onyx・nova・shimmer です。ash・ballad・coral・sage・verse は realtime 専用で、読み上げには使えませんでした。なお voice.provider に google はありません。

2. 読み違える語だけを辞書に入れる

人間らしい声を使うなら、読み違える語を Vapi の読み替え辞書に入れます。場所は voice.chunkPlan.formatPlan.replacements です。1件は type・key・value の組で書きます。

"voice": {
  …GET した中身をそのまま…,
  "chunkPlan": {
    "formatPlan": {
      "replacements": [
        { "type": "exact", "key": "家賃", "value": "やちん" }
      ]
    }
  }
}

かなで書けば、その語は正しく読まれます。ただし全部の語をかなにすると訛るので、読み違えた語だけを直します。Vapi のログには置き換えた後の文字が出ます。カタカナが並んで見えても、それは正常です。

読点や中黒で区切る細工も、測ってから使います。ある語では、電話の音質で焼いて数えると、読点ありが4本中1本、読点なしが6本中6本で正しく読めました。一方で、区切っても外しても差が出ない語もありました。声を替えたら、前の声のための細工を全部見直します。

3. 方式を変える前に台本を変える

同じ gpt-4o-mini-tts でも、書き言葉の敬語の台本と、話し言葉の指示では聞こえ方が変わりました。話し言葉の指示は「短く話す」「受け止めてから聞く」「決まり文句の敬語を使わない」「相づちを打つ」です。AI は「あ、寝られないんですね。それはつらいです」と返すようになりました。

4. realtime でも聞き取りを日本語にする

音声を受けて音声で返すモデルでも、transcriber に日本語を指定します。OpenAI の聞き取りなら、provider を openai、language を ja にします。弊社は聞き取りのモデルを gpt-4o-mini-transcribe に替えました。

"transcriber": {
  "provider": "openai",
  "model": "gpt-4o-mini-transcribe",
  "language": "ja"
}

言語を外すと既定の英語の聞き取りになり、会話が英語に崩れて無言のまま切れました。

5. maxTokens を明示する

realtime のモデルでは、maxTokens を大きくします。4000 にしたところ、10.3秒・13.0秒・15.9秒の発話が最後まで届きました。

"model": {
  "provider": "openai",
  "model": "gpt-realtime-2",
  "messages": [ …GET した中身をそのまま… ],
  "toolIds": [ …GET した中身をそのまま… ],
  "maxTokens": 4000
}

6. stopSpeakingPlan を日本語に合わせる

未設定のときは、相手の音が入った瞬間に AI が止まります。そこで値を入れます。日本語では numWords を 1 にします。

"stopSpeakingPlan": {
  "numWords": 1
}

弊社の試験用のアシスタントでは、numWords 2 に voiceSeconds 0.3 と backoffSeconds 1.0 を組み合わせて入れていました。numWords を 1 にしたときに、この2つの値と組み合わせて確かめた記録はありません。ほかの値は、入れたあとに電話で割り込みを試して決めてください。

7. ツールには呼ぶ前の言葉を置き、結果は Vapi に読ませる

すべてのツールに、短い request-start の言葉を置きます。たとえば「お待ちくださいね。いま登録しますね。」です。プロンプトで独り言を禁止しても出たので、プロンプト側の前置きの指示は外しました。

ツールを受けるサーバーの応答は、公式の説明どおり results の配列で返します。各要素に toolCallId と result を入れ、result は1行の文字列にします。結果を必ず声に出したいときは、この要素に message を足します(Vapi の API の定義にある ToolCallResult.message)。こうすると Vapi がその文をそのまま話し、モデルには応答させません。

{
  "results": [
    {
      "toolCallId": "XXXX",
      "result": "XXXX",
      "message": {
        "type": "request-complete",
        "role": "assistant",
        "content": "XXXX、でしょうか?"
      }
    }
  ]
}

この場合、結果の文には「システムがもう読み上げて確認した。繰り返すな」と書きます。「いますぐ相手に聞くこと」と書くと、読み上げの直後にモデルが同じ質問をもう一度しました。

8. 締めの言葉はモデルに言わせる

endCall の request-start は句点だけにします。締めの言葉はモデルに相手の言語で言わせ、相手の返事のあとで endCall を呼ばせます。endCallPhrases に頼って切らせる形は、日本語では使いません。

endCall の request-start
  "content": "。"

プロンプトには順番も書きます。「番号を聞いた直後に endCall を呼ぶな」と書かないと、番号を聞いた瞬間に電話を切りました。

9. PATCH は GET した全体で書き戻す

Vapi の設定を変えるときは、次の順にします。

  1. アシスタント(またはツール)を GET して、全体をファイルに保存する。
  2. 保存した全体のうち、変えたい値だけを書き換える。
  3. model や voice のように中身を持つキーは、中身を丸ごと送る。
  4. 消したいキーは、空の配列か null を明示して送る。省いただけでは残ることがある。
  5. もう一度 GET して、変えた値と、変えていないはずの値を見比べる。

n8n でも、API で更新した値が実行に反映されない落とし穴があります。考え方は同じで、書いた後に読み直します(n8nのAPIでワークフローを更新する方法と、反映されない・止まらない原因)。

直ったかを確かめる

まず、変えた項目を live から読み直して値を見ます。値を貼れない項目は「直した」と言いません。「〜します」と「〜しました」を書き分け、実行していないものは宿題として残します。

試験は本番のコピーで行います。コピーからは、受付を登録するツールを外します。試験機の辞書は本番と揃えます。試験機だけ古い辞書だと、直したものを試して「まだ直っていない」と誤った結論が出ます。

読みの合否は、耳でなく機械で決めます。手順は次のとおりです。

  1. macOS の say -v Kyoko で、正しい読みと疑っている読みの参照の音声を2本作る。
  2. 調べたい声の音声と参照の2本を同じ文字起こしにかけ、書かれ方を比べる。参照の2本が別の文字になり、調べたい声がどちらかと同じ文字になれば、それで決まる。
  3. 調べたい声の音声が、どちらの読みでも同じ文字になるときは、文字起こしでは決めない。各音声の末尾220msのスペクトル重心と、2kHz以上のエネルギー比を出し、参照のどちらに近いかで決める。

3つ目が要るのは、文字起こしが文脈で正しい漢字に寄せるからです。ElevenLabs の声に「オオサカホンチョウ」と「オオサカホンマチ」を読ませると、文字起こしはどちらも「大阪本町」と書きました。末尾の音で測ると、参照は「ほんまち」が 2400Hz・0.564、「ほんちょう」が 769Hz・0.029 で、はっきり分かれました。末尾の子音や母音の違いは、この方法で測れます。

間は録音から測ります。Vapi の録音は artifact.presignedAssistantUrl・presignedCustomerUrl・presignedStereoUrl を使います。recordingUrl は署名の無い URL で、取りに行くと 400 が返りました。

声の自然さだけは人の耳で決めます。音声を聞けるモデルに採点させたところ、本物の人間の声も含めて全部に同じ点を付けました。声を聴き分けていないので、合否には使えません。

それでも直らないとき

  • すべての通話が英語になる。Vapi の残高がマイナスになると、通話は英語の「Your wallet balance is negative」だけになりました。外部の AI の残高切れを検知する考え方は、OpenAIの残高切れをn8nで検知する方法に書いています。
  • 試験の発信が途中から通らない。Vapi で買った番号には、1日の発信の上限があります。「Numbers Bought On Vapi Have A Daily Outbound Call Limit」と出たら、この上限です。
  • 録音には入っているのに「聞こえなかった」と言われる。Vapi 側の記録は、電話が受け取った音の証拠になりません。受話側で録って確かめます。Twilio で録るときは Trim=do-not-trim にします。既定のままだと録音の頭が削られ、鳴っていた第一声が消えて見えます。
  • Vapi の文字起こしと、AI の受け答えが合わない。Vapi の transcript は真実源ではありません。相手の言葉を取り違えて書いていても、AI 本体は正しく受けていたことがありました。
  • 間が3秒前後から縮まない。パイプライン型の限界です。弊社はその後、OpenAI の GPT-Live を Twilio から直接 SIP でつなぐ形を試し、電話は GPT-Live の cedar という声で動かすと決めました。GPT-Live は、Vapi では招待制のベータでのみ使える状態でした。

まとめ

  • 日本語の声は、聴き比べる前に「日本語の読みを持つ方式か」で分ける。
  • 読み違える語だけを voice.chunkPlan.formatPlan.replacements に入れる。全部カタカナにすると訛る。
  • 区切りの細工は語ごとに測り、声を替えたら全部見直す。
  • stopSpeakingPlan は必ず入れ、日本語では numWords を 1 にする。realtime では maxTokens を明示し、聞き取りも日本語にする。
  • ツールには request-start を置き、必ず言わせたい結果は results の要素の message(request-complete)で Vapi に読ませる。締めはモデルに言わせる。
  • 読みの合否は参照の音声と比べて決め、文字起こしで分からない語は末尾の音を測る。
  • PATCH は GET した全体で書き戻し、もう一度 GET して確かめてから「直した」と言う。

参考にした公式ドキュメント

n8nの点検と構築のご相談

VapiのAI電話とn8nをつないだ受付の仕組みづくりや、「設定したはずの値が入っていない」といった不具合の点検をお引き受けしています。

お問い合わせはこちら

この記事を書いた人

貫名 孝夫(マルタマーケティング株式会社 代表取締役)。n8nで140本を超えるワークフローを本番で動かしながら、実際に起きた不具合と直し方を記録しています。会社概要