はじめに
音声AIエージェントを作る機会があり、Vapi を使って「音声 + テキスト」両対応のサポートエージェントを実装しました。英語での構築はスムーズでしたが、日本語対応には若干苦戦しました。
STT(文字起こし)、TTS(音声合成)、ターンテイキング、モデル選定……英語前提で作られている部分が多く、日本語で自然な会話にするには一つずつ潰していく必要があります。
この記事では、その過程でハマったポイントと解決策を体験記としてまとめました。Vapi の日本語運用に関する日本語の実践情報はまだ少ないので、同じ壁にぶつかる方の役に立てば嬉しいです。デモサイトも公開しているので、最後にリンクを載せときます。GitHubにもソース公開しときました。https://github.com/soonbig/jp-en-voice-agent
Vapiとは
Vapi は、音声AIエージェントを作るための開発者向けプラットフォームです。ざっくり言うと、音声まわりの面倒な部分を丸ごと引き受けてくれるマネージドサービスです。検証用に無償枠もあるので、テストにも使いやすいです。
音声エージェントは普通、こういう3段構えのパイプラインで動きます:
- STT(音声 → テキスト)
- LLM(テキストで考えて返答を生成)
- TTS(テキスト → 音声)
さらに「相手が話し終わったかの判定(endpointing)」「割り込みの処理」といったリアルタイム制御も必要になります。このオーケストレーションをまとめてやってくれるのが Vapi です。
- STT・LLM・TTS はそれぞれプロバイダーを選べます(Deepgram / OpenAI / Azure / ElevenLabs など)
- function calling(ツール呼び出し) に対応しています。外部APIやDBを叩けます
- パイプライン方式のほかに、speech-to-speech(OpenAI Realtime のような、音声入力→音声出力の単一モデル)にも対応しています
- API / SDK で組み込めて、Web でも電話でも動きます
作ったもの
- 想定:ゼロトラスト系SaaS「Gatewise」のサポート窓口(SSO / SCIM / デバイスポスチャなどの問い合わせ対応)
- チャネル:音声(電話的なUI)と テキストチャット の2つ
-
できること:
- ナレッジベースを検索して根拠のある回答をする(RAG)
- 分からないことは推測せずにエスカレーション(チケット作成)
- 日本語・英語の両対応
構成はざっくりこんな感じです:
- Vapi:音声まわりのオーケストレーション(STT・LLM呼び出し・TTS・割り込み制御)
- Cloudflare Workers:ツール(ナレッジ検索・チケット作成)の実行ランタイム
- AI Gateway:全リクエストのロギングとモデルへのプロキシ
- AI Search / D1:ナレッジ(RAG)とチケットDB
音声とテキストは入り口が違うだけで、同じツール・同じデータにたどり着きます。「モデルは提案するだけ、実行は Worker」という切り分けにしています。
英語は簡単だった。問題は日本語
英語版は、デフォルトに近い設定でほぼ自然に動きました。ところが日本語に切り替えると、いろんなところが崩れました。以下、ハマった順に紹介します。
① モデル:gpt-4o-mini の日本語が微妙
最初は英語・日本語ともに gpt-4o-mini で動かしていました。英語は問題ないのですが、日本語の会話が微妙にぎこちない。言い回しが不自然だったり、敬語が崩れたり。
→ 日本語の音声だけ gpt-4o に切り替えました。コストとレイテンシは上がりますが、会話の自然さは明確に改善しました。テキストチャットは正確な入力が来るので mini のまま。「品質が要るところにだけ上位モデルを使う」という割り切りです。
② 音声(TTS):多言語ボイスが日本語だと微妙
Vapi は多くの音声プロバイダーを選べますが、汎用の多言語ボイスは日本語だと不自然になりがちでした。
→ 日本語特化のボイスを探し、Azure の ja-JP-NanamiNeural に落ち着きました。いくつか聴き比べた中で、これが一番自然でした。このへんは耳で判断するしかないですね。
③ 文字起こし(STT):専門用語が化ける
「シングルサインオン」が「イングルサインオン」、「Gatewise」が「ゲート合図」……のように、専門用語や製品名が頻繁に誤変換されました。
対策:
- Deepgram の keyterm(キーワードブースト)に用語を登録
- ナレッジベースに用語集ドキュメントを用意し、表記ゆれ(SSO / エスエスオー / シングルサインオン)を吸収
- ちなみに ElevenLabs Scribe は日本語の素の精度は良かったのですが、Vapi 経由だと keyterm 相当の項目が無く、用語の作り込みはできませんでした
STT はプロバイダーによって日本語精度がかなり違うので、自分の音声で A/B したほうがいいです。ベンチマークの数字は実環境(電話品質・ノイズ・専門用語)でズレるように思います。
④ 言語ドリフト:会話の途中で急に英語になる
日本語で話しているのに、エージェントが突然英語で喋り出すことがありました。原因を追うと、英語版と共通のツール定義の中に英語の指示文が埋め込まれていて、それに引きずられているようでした。
→ 日本語専用のツール(_JP サフィックス)を用意し、指示文も全部日本語で書きました。英語版と分離したことでドリフトが止まりました。
⑤ ターンテイキング(endpointing):smart endpointing は英語前提
Vapi には smart endpointing(話し終わりの自動検出)がありますが、英語の発話パターンに最適化されているらしく、日本語だと切るのが早すぎたり遅すぎたりしました。
→ smart endpointing に頼らず、待ち時間のタイマー(startSpeakingPlan と文字起こしの endpointing タイマー)を日本語のリズムに合わせて調整しました。小さいですが、会話の自然さにけっこう効きます。
⑥ ツール呼び出し前のフィラー:英語で「Just a moment」
ツールを呼ぶ前に、モデルが勝手にフィラー(「Just a moment」的なつなぎ)を喋ってしまうことがありました。しかも日本語音声なのに英語で。
→ 確実に日本語で待たせるために、ツール側に日本語の request-start メッセージ(「少々お待ちください」)を仕込みました。モデルの気まぐれに任せず、決め打ちで日本語を流すのが確実でした。
speech-to-speech(gpt-realtime)を試した話
途中で、OpenAI の Realtime(speech-to-speech)モデルも試しました。音声を入れて音声が返る、途中に文字起こし/音声合成を挟まないタイプです。日本語の品質はかなり良かったです。上記の STT/TTS 問題の多くが、そもそも発生しません。
ただしトレードオフがありました。Realtime は WebRTC ベースで、チャット補完API を前提とした AI Gateway を通りません。つまり「全リクエストを一箇所でロギングする」という観測性の設計が崩れます。
今回は観測性を優先して、パイプライン+gpt-4o のままにしました。品質は少し妥協しましたが、ログを一元管理できるメリットを取りました。もし顧客が「とにかく音声品質最優先」なら Realtime を選ぶかもしれません。speech-to-speechの実装例はこちらに公開してあるので、興味があれば参考にしてください。https://github.com/soonbig/agent-orchestrator
AI Gateway まわりのハマり(おまけ)
日本語音声を gpt-4o にするとき、AI Gateway の /compat(統合課金)エンドポイント経由だとエラーになりました。/openai(BYOK)エンドポイントに切り替えたら通りました。同じゲートウェイでもエンドポイントで挙動が違うので、gpt-4o を通したい方は注意してください。
まとめ
実運用にギリギリ耐えるレベルの日本語音声エージェントは作れたように思います。ポイントをまとめると、こんな感じです:
| 観点 | 学び |
|---|---|
| モデル | 日本語音声は上位モデル(gpt-4o)が効く。テキストは mini で十分 |
| 音声(TTS) | 多言語ボイスより日本語特化ボイス(Azure NanamiNeural 等) |
| 文字起こし(STT) | 専門用語は keyterm + 用語集で補強。プロバイダー差が大きく A/B必須 |
| 言語ドリフト | 言語ごとにツール・指示を分ける |
| endpointing | smart endpointing は英語前提。タイマー手動調整 |
| フィラー | 日本語の固定メッセージで決め打ち |
| speech-to-speech | Realtime は品質高いが Gateway を通らない。観測性とのトレードオフ |
一番の学びは、**「日本語対応 = 翻訳」ではなく「日本語対応 = 各レイヤーの作り込み」**だということです。モデル・音声・文字起こし・ターンテイキング……全部に日本語固有のチューニングが要ります。
デモサイト
実際に触れるデモを公開しています:
https://saas-console-apq.pages.dev
- 右上のトグルで EN / 日本語 を切り替え(音声は言語ごとにモデルが変わります)
- Request route で、リクエストがどのモデル・ツールを通ったかがリアルタイムに見えます
- テキストでも音声でも試せます(日本語の品質差を見るなら 音声 がおすすめです)
試せる質問例
回答はナレッジベース(RAG)に基づくので、下のトピックなら根拠のある回答が返ります。逆に、ナレッジに無いこと(料金・ロードマップなど)は推測せず「資料に見当たりません、チケットを作成しますか?」と返します——これも意図した挙動なので、両方試すと面白いです。
ちゃんと答えられる質問(グラウンディングされる)
| 日本語 | English |
|---|---|
| Okta とのシングルサインオンの設定方法を教えて | How do I set up SSO with Okta? |
| SCIM プロビジョニングの設定方法は? | How do I configure SCIM provisioning? |
| デバイスポスチャのチェックはどう動く? | How do device posture checks work? |
| SAML 証明書のローテーション手順は? | How do I rotate the SAML certificate? |
チケットを作らせる質問(エスカレーション)
| 日本語 | English |
|---|---|
| 全社で管理画面にログインできません。チケットお願いします | Our whole team can't log into the admin console. Can you open a ticket? |
→ 内容から優先度とチームを判断して、チケットを作成します。
わざと「答えられない」質問(推測せず断る)
| 日本語 | English |
|---|---|
| パスキー対応はいつ提供されますか? | When will passkey support be available? |
| Enterprise プランの料金は? | What's the price of the Enterprise plan? |
→ ナレッジに無いので、推測せずエスカレーションを提案します。個人的にはここが一番の推しポイントです。「答えを捏造しないこと」が重要なので、System Promptで調整しています。
試すときのコツ
- 1ターンにつき1つの質問にする(複数まとめて聞くと精度が落ちます)
- 日本語の品質差を見るなら音声で(テキストは正確な入力なので差が出にくいです)
- 答えてもらったあと、Request route でどのツール(ナレッジ検索 or チケット)を通ったか見ると、裏側の動きが分かります
以上!