こんにちは、tacomsのMorixです。
私は現在Camel AI Callという飲食店向けの電話応対エージェントの開発・運用を行っています。
このプロダクトではAIを利用した品質保証を行っているのでその紹介をします!
なお、この記事の内容は三田データ vol.4 音声AI祭りで発表した内容と同じです。(いくつか補足をします)
音声AIの開発・運用は思ってたより大変
Camel AI Callは次のような会話をしてテイクアウト注文を受け付けます。
- あいさつ
- テイクアウト注文内容を聞き取る
- お客様に注文内容確認をする
- 注文確定をする
流れは単純ではありますが、音声での会話ということもあり入力パターンが無数にあります。
また注文を確定するまで無事会話を完了するには、LLMの応対精度次第ということもあり、どんな入力をされてもLLMの精度を安定させる必要があります。
そこで感じた音声AIならではの大変さがこちら
- LLMの出力は非決定的
- 毎回必ず同じ応答をしてくれるわけではないため、テストの期待値設定が難しい
- プロンプト1行変えただけでデグレする
- プロンプトを少しだけで思わぬところに影響が出ることがある
- 音声でのテストは自動化しづらい
- APIやフロントエンドのE2Eテストのようにエコシステムが整っていない
- 人間が毎回AIと喋って動作確認をする羽目になる
要はテストがしんどいって話です。
音声AIでテストしたい観点は色々ある
上記の大変さをもうちょっと落とし込むと、以下のようなテスト観点の整理になると思います。
- プロンプトの品質
- LLMが期待通りの振る舞いを毎回してくれるか?
- LLMと他のシステムとの連携
- 人間との会話の結果がAPIやDBにどう反映されているか?
- STT/TTS/電話との実挙動
- STT(Speech To Text)の結果は意図通りLLMに渡っているか?
- LLMの結果はTTS(Text To Speech)でどう発話されているか?
- 電話で正しく応対できるか?
3層テスト戦略
このプロダクトでは次のテスト手法でテスト観点をカバーしています。この3層のテスト戦略を用いてプロダクト全体の品質保証をしています。
| テスト観点 | テスト手法 |
|---|---|
| プロンプトの品質 | LLM振る舞いテスト |
| LLMと他のシステムとの連携 | E2Eテスト |
| STT/TTS/電話との実挙動 | テストエージェント |
それぞれのテスト手法について説明していきます。
LLM振る舞いテスト
LLM振る舞いテストとは、LLMがプロンプトの指示通りの振る舞いをするかをテストする手法です。テスト対象はLLMのみです。
どんなテストをしているかというと、次のような会話の流れをテストの1ケースとして作成し、LLMが期待通りの応対をしているか検証しています。
- ユーザー「カレー1つ」
- LLM「カレー1つでよろしいですか?」
- ユーザー「はい」
- LLMはツールを呼び出し注文確定
他にも例えばカレーが品切れだった場合の応対とか、トッピングが追加された場合の応対とか、会話のパターンがたくさんあるので、そのような会話の組み合わせを多数用意し、プロンプトの修正が発生したら全部テストしデグレを防いでいます。
次はコード例です。
# 受取時間を伝える result = await self.run_with_context(session, agent, "明日の18時でお願いします") next_event = result.expect.next_event() assert next_event.is_message(role="assistant") content = next_event.event().item.content[0] assert "18時" in content or "18:00" in content or "午後6時" in content result.expect.no_more_events() # 受取時間を承諾 result = await self.run_with_context(session, agent, "はい") next_event = result.expect.next_event() assert next_event.is_message(role="assistant") content = next_event.event().item.content[0] assert "商品" in content or "ご注文" in content result.expect.no_more_events()
このテストコードは次のような内容になっています。
ユーザーが「明日の18時でお願いします」といったら、LLMは「明日の18時でよろしいでしょうか?」という復唱をすべきというテストをしています。
1文字1句同じ文章を生成するとは限らないので、復唱してるかの確認は「18時」あるいは「午後6時」というワードが入ってるかという静的な検証をしています。
そしてその復唱に対しユーザーが「はい」と言ったら、次に注文商品の確認をするための確認文言を発話しているかテストをしています。
しかし、このようにある程度決まった出力をする場合は強引に静的な検証をすることができますが、そうではなく非決定的な応対をすることもあります。
例えば「今何を頼んだっけ?もう一度言ってみて」とユーザーが言った場合、LLMは今までの注文内容を復唱したうえで、追加注文を聞くことを期待しますが、どういう文章を生成するかわかりません。
こういった場合、LLMの生成結果をLLMに判定させています。
result = await self.run_with_context(session, agent, "今何を頼んだっけ?もう一度言ってもらえますか?") chat_assert = result.expect.next_event().is_message(role="assistant") await chat_assert.judge( judge_llm, intent="注文内容を伝えた上で、追加注文確認をしている", ) result.expect.no_more_events()
このようにLLMの振る舞いをLLMで検証することで、非決定性へ向き合っています。
E2Eテスト
LLMが関連システムと連携し期待通りの会話ができるかをテストする手法です。テスト対象はLLMとAPIとDB(などの外部システム)となります。
よくあるようなE2Eテストとは異なり、その都度会話シナリオを用意し実行しています。
このE2Eテストは、コーディングエージェントのために存在します。
コーディングエージェントで良い成果物を出すためには検証する手段を用意する必要がありますが、コーディングエージェントは音声対話できないので音声対話するための手段を用意しました。
このE2Eテストをコーディングエージェントの開発ワークフローの組み込み、E2Eテストを成功しないと開発完了とならないようにしています。
このE2EテストはClaude Codeのスキルとして用意しており、次のようにスキルに会話シナリオを渡すと会話履歴・DBの内容などを出力します。

E2Eテストスキルの仕組みは次のようになっています。

- Claude Codeスキル経由で起動し、テストシナリオを渡す
- E2Eテストエージェントが電話注文エージェントを起動
- E2Eテストエージェントが電話注文エージェントのログを監視
- 電話注文エージェントは仮想オーディオデバイスを使って音声入出力
- E2Eテストエージェントは仮想オーディオデバイスを使って音声入力
- 会話が完了したら結果をClaude Codeに報告
E2Eテストエージェントは、渡されたシナリオ通りに電話注文エージェントと会話するための音声AIです。
ここでの工夫ですが、電話注文エージェントの発話音声をE2Eテストエージェントは受け取っていません。代わりに、電話注文エージェントのログに発話内容が記録されるためその文字列を見て発話内容を生成しています。
こうすることで、電話注文エージェントの発話速度を最大まで高めることができ、高速に会話をすることができます。
テストエージェント
テストエージェントは、電話をかけることができる音声AIエージェントです。
テストエージェントを使って、Staging環境(検証環境)のテストを行います。
こちらのテストもE2Eテストと同様、修正内容に合わせて都度会話シナリオを用意してテストを実行します。
実行イメージは以下のように、プルリクエスト上でシナリオのコメントをするとそのシナリオに沿って会話を始めてくれます。

プルリクエスト上でStaging環境にデプロイすることができるので、デプロイ完了後にこのようなテストを行っています。
テストが完了したら、テスト結果をコメントしてくれます。テスト結果に管理ツールへのリンクがあり、そこで会話の録音や文字起こしデータが見れるので、どんなテストをしたか証跡が残ります。

このテストエージェントは電話注文エージェントと同様、会話をするために必要なすべての機能に加え、架電機能も持っています。
なので、次のような仕組みでテストエージェントとStagingの電話注文エージェントを会話させています。

さて、なぜわざわざテスト用のAIエージェントを作ったかという話ですが、主に2つの理由があります。
- 時間の節約のため。人間がやると時間を取られるから
- 電話の応対はどうしても時間がかかる。しかもうっかり言い間違えるときもある
- 人間のリソースはもっと別のことに注ぐべき
- 並列実行できる
- 複数パターン会話したい場合、人間がやると1個1個確認することになり時間がかかる
- テストエージェントならこれを同時に出来る
3層テストを開発フローにどう組み込んでいるのか
Camel AI Callの開発はコーディングエージェントを使って開発をしています。
AIに自律的な開発をさせるため、開発フローを定義し、そのとおり実行させています。
その開発フローは以下のようになっており、赤い箇所が今回の3層テストです。

緑丸がついているところが人間が関与するところです。ほかはAIや自動スクリプトでやっています。
実装はTDDで行っていますので、UnitTestを作って実装をしてます。プロンプトを修正する際もRedGreenになるように開発をするために、LLM振る舞いテストを実装後、プロンプト実装するような流れにしています。
まとめ
- 音声AIのテストを人間がやるのはしんどい
- 音声AIのテストはAIでやろう
- LLMのプロンプト品質チェックはLLM振る舞いテストで確認
- コーディングエージェントの成果物品質チェックはE2Eテストスキルで確認
- 全体的な動作確認はテストエージェントで確認
- テストをAIに任せることで開発速度向上と品質向上を実現できた!








