voice-agent について
voice-agent は、電話に応答し、コードを実行して必要に応じて人へ引き継ぐ Zavu の音声エージェントを構築するスキルです。音声設定、ツール、デプロイ、通話管理を扱います。
主な機能
- 01音声エージェントと通話動作の設定
- 02ツールによる独自処理の実行
- 03有人転送と留守番電話の設定
- 04CLI によるデプロイと通話管理
- 05プロンプトとツール処理のテスト
活用シーン
- 01着信に応答するエージェントの構築
- 02空き時間の確認と会議予約
- 03必要に応じた担当者への転送
- 04通話記録とツール結果の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zavudev/ zavu-skills --skill voice-agentインストール後、Claude Code で/voice-agentを入力して読み込まれたか確認します。
詳細
音声エージェント
音声エージェントは電話に応答して会話し、何かを行う必要があるときはコードを呼び出し、必要に応じて担当者に引き継ぎます。メッセージングエージェントと同じエージェントオブジェクトに、voice ブロックを追加したものです。
Zavu は、音声認識、エージェントのモデル、音声合成、中断処理まで、パイプライン全体を実行します。用意するのはプロンプトとスキルです。
まず知っておくべきことが1つあります。 ツールが実際に実行されるチャネルは音声です。プレーンテキストでは、モデルにツールは提示されません(ai-agent を参照)。エージェントに何かを実行させる必要がある場合、現時点では音声とフローがその手段です。
送信者とアカウント(1段落)
Sender は Zavu-Sender として渡す API ハンドルです。一方、アカウント(WhatsApp Business Account、Facebook Page、Telegram ボット、電話番号)はルーティング先となる接続であり、課金対象でもあります。Sender は無料です。ダッシュボードでアカウントを接続すると、Sender が自動作成されます。GET /v1/senders で確認し、送信可能な内容は channels 配列を基準にしてください。全体像については channel-setup スキルを参照してください。
最短の方法:ファクトリーエージェントを使う
Zavu には、すぐに使える音声エージェントが用意されています。zavu.dev でエージェントと会話した後、そのコードを自分のものにして使うこともできます。同じエージェントです。
npx zavudev agents catalog # what's available
npx zavudev agents pull kepler --dir kepler # scaffold it locally
# booking agents: add --calendar calcom for a working Cal.com client
cd kepler
npm install # types + local runs
npx zavudev senders list # find your sender id
npx zavudev fn secrets set SENDER_ID <senderId>
npx zavudev deploy
npx zavudev agents list # confirm what actually landed
エージェントは複数の電話番号に対応できます。ID で接続します。
npx zavudev agents senders connect --agent <agentId> --sender <senderId>
agents pull は、実際の TypeScript プロジェクトを書き出します。エージェントとそのスキルを含む index.ts、tsconfig.json、そしてプロジェクトの型チェックとローカル実行を可能にする @zavudev/functions のローカル代替実装が含まれます。自由に編集できます。ファイルはあなたのものです。npx zavudev deploy を実行すると、宣言した内容に合わせて調整されます。
コードで音声エージェントを宣言する
import { defineAgent, defineTool } from "@zavudev/functions"
defineAgent({
senderId: process.env.SENDER_ID!,
name: "Kepler",
provider: "zavu",
model: "openai/gpt-5.6-luna",
channels: ["voice", "whatsapp"],
voice: {
enabled: true,
model: "openai/gpt-4o",
greeting: "Hi, I'm Kepler. What day would you like to book?",
// Per-language greeting, used when the caller's language differs.
greetings: { es: "Hola, soy Kepler. ¿Qué día querés reservar?" },
// language: "es", // omit to follow the caller's language
interruptible: true, // caller can talk over the agent
maxCallDurationMinutes: 12,
voiceSpeed: 1.15,
},
prompt: `# Personality
You book meetings. One idea per turn.
# Environment
You are on a phone call. They can hear you, they cannot see a screen.
Never read a URL or a code block aloud.`,
})
voice ブロック
| フィールド | 機能 |
|---|---|
enabled | エージェントが通話に応答するかどうかを指定します。必須です。 false の場合、電話番号への着信には応答せず、発信通話は拒否されます。 |
greeting | 通話がつながったときに最初に話す内容です。省略すると、先に発信者が話せるようになります。 |
greetings | 言語タグをキーにした、言語別の挨拶です。例:{ es: "Hola…" } |
language | BCP-47 タグ(en、es、pt-BR)です。省略すると、発信者の言語に合わせます。 |
model | 会話を行うモデルです。テキスト用モデルとは異なるモデルを指定できます。 |
ttsProvider / ttsVoiceId | 音声合成に使用する音声を指定します。省略すると、ニュートラルなデフォルトが使われます。 |
sttProvider / sttModel | 音声認識を指定します。省略すると、デフォルトが使われます。 |
voiceSpeed | 0.5~1.5 です。速度調整に対応する音声でのみ適用されます。 |
interruptible | 割り込み発話です。true(デフォルト)の場合、発信者がエージェントの発話を遮ることができます。 |
maxCallDurationMinutes | 通話時間の上限です。1~120 分で、上限に達すると通話が終了します。 |
maxIdleSeconds | エージェントが通話を切るまでの無音時間です。5~300 秒です。 |
voicemailAction | 留守番電話につながったときの動作です。hangup(デフォルト)または leave_message を指定します。 |
voicemailMessage | voicemailAction が leave_message の場合に再生されます。省略すると greeting が使われます。 |
transferPhoneNumber | 担当者への引き継ぎに使う E.164 形式の電話番号です。設定すると、エージェントに転送ツールが追加されます。 |
channels と voice は別々の設定です。channels: ["voice"] は通話をこのエージェントにルーティングし、voice.enabled は通話時の動作を設定します。両方が必要です。 voice ブロックがないまま音声用にルーティングされたエージェントは、正しく応答しません。
担当者への引き継ぎ
「応答し、問題を解決し、担当者に引き継ぐ」が主要な特徴です。引き継ぎには transferPhoneNumber を使います。これを設定すると、エージェントに転送ツールが追加され、必要に応じて使うかどうかを判断できます。プロンプトで、いつ使うべきかを指示してください。
voice: {
enabled: true,
transferPhoneNumber: "+14155551234",
},
prompt: `Transfer to a human when the caller asks for one, is upset,
or asks something about their account that you cannot answer.`,
バックエンドを書かずに予約する
ミーティングを予約する音声エージェントに、コードは一切必要ありません。ダッシュボード(エージェント、ツール、ライブラリ)から check_availability
と book_meeting を追加し、同じ場所で Cal.com または Google カレンダーを接続します。どちらのスキルも Zavu がホストします。
エージェントがカレンダーを確認して予定を入れるだけなら、予約関数のひな形を作る代わりにこちらを使ってください。予約ロジックを自分で実装する場合は、
npx zavudev agents pull kepler --calendar calcom を使います。たとえば、最初に条件を確認する、別々のホストに振り分ける、自分のシステムにも書き込む、といった場合です。
エージェントが呼び出せるスキル
ツールは音声上で実行されます。エージェントの隣に宣言すると、Zavu Cloud 内の関数で実行されます。
defineTool({
name: "check_availability",
description: "Find open meeting slots. Call before offering a time.",
parameters: {
type: "object",
properties: {
preferred_time: { type: "string", description: "What the caller asked for" },
},
required: ["preferred_time"],
},
handler: async (args, ctx) => {
const res = await fetch(`https://api.example.com/slots?q=${args.preferred_time}`)
if (!res.ok) return { ok: false, reason: "lookup_failed", slots: [] }
return { ok: true, slots: (await res.json()).slots }
},
})
デプロイせずにローカルで実行するには:
npx zavudev fn invoke --tool check_availability --args '{"preferred_time":"tomorrow 3pm"}'
偽の成功を絶対に返さないでください。 何もしていないのに { booked: true, confirmationCode: "ABC-123" } を返すハンドラーがあると、エージェントは実際の発信者に予約が完了したと伝えてしまいます。{ ok: false, reason: "not_configured" } を返し、プロンプトに処理を任せてください。エージェントは現在予約できないと伝えられます。それなら後から対処できます。虚偽の確認は取り消せません。
通話の発信と確認
npx zavudev calls create --to +14155551234 # outbound. COSTS MONEY.
npx zavudev calls list --status completed
npx zavudev calls get <callId> # includes the transcript
npx zavudev calls hangup <callId>
calls get を実行すると、ツール呼び出しを含む会話がターンごとに表示されます。エージェントが実際に何を話したかを記録した唯一の記録であり、通話で問題が起きたときに最初に確認すべき場所です。
SDK から使用する場合(@zavudev/sdk 0.56.0 以降。それより前のバージョンには calls リソースがありません。REST エンドポイントを直接呼び出してください):
const { call } = await zavu.calls.create({
to: "+14155551234",
greeting: "Hi, this is Acme calling about your appointment.",
language: "es-ES", // or "auto" to follow the caller
metadata: { campaign: "reminders" },
})
費用をかける前にテストする
電話またはブラウザーがなければ、エージェントの音声を聞く方法はありません。ただし、音声より前のすべての処理は無料でテストできます。
# The agent's brain: prompt, model, knowledge base. Nothing is delivered.
npx zavudev agents test --agent <agentId> --message "do you have anything Tuesday?"
# A skill's handler, locally.
npx zavudev fn invoke --tool check_availability --args '{"preferred_time":"Tuesday"}'
agents test はテキスト経路を実行するため、音声ではツール呼び出しが行われる場合でも、それはテストされません。該当する場合は警告が表示されます。プロンプトのテストにはこれを、ハンドラーのテストには fn invoke を使ってください。
実際に音声を聞くには、電話番号と実際の通話が必要です。無料でエージェントの音声を聞く方法はありません。agents test はテキスト専用であり、実行のたびにそのことが表示されます。通話前のすべて(設定、プロンプト、ハンドラー)は検証できますが、音声そのものは検証できません。納期を約束する前に、その分の時間を見込んでください。
着信には、所有している電話番号が必要です。
npx zavudev phone-numbers search --country US
capabilities 列だけで判断しないでください。この値は通信事業者から報告されますが、実際とは逆の場合がどちらの方向でもありました。発信と応答ができる番号に ["sms"] と表示されたり、voice と表示された番号で通話を完了できなかったりしたことがあります。Zavu で販売されている番号は、購入時に通話用としてプロビジョニングされます。この確認には、送信元自身のチャネル一覧を使うのが確実です。
npx zavudev senders get <senderId> # channels includes "voice" once it can call
要件と料金
- 送信元エージェントで
voice.enabled: trueが必要です。そうでない場合、POST /v1/callsは400 "The sender's agent does not have voice enabled"を返します。 - テストモードの API キーでは利用できません。
- 着信には、
voice機能を持つ電話番号が必要です。 - 接続時間(分)ごとの料金に加えて、通話料金がプリペイド残高から請求されます。通話開始時に短時間分の見積もり額が確保され、終了時に実際の通話時間に基づいて請求されます。
402は残高不足を意味します。
Webhook イベント
すべての音声イベントには、callId、direction、from、to、status、
durationSeconds、endReason、transcriptAvailable が含まれます。
| イベント | 発生時 |
|---|---|
call.initiated | 発信時、または着信受信時。status: ringing |
call.answered | 接続され、エージェントが応答中。status: in_progress |
call.completed | 会話の後に終了 |
call.failed | 話し中、応答なし、キャンセル、またはエラー |
npx zavudev senders update <senderId> \
--webhook-url https://api.example.com/hooks/zavu \
--webhook-events call.completed,call.failed
うまくいかない場合
-
エージェントは応答しますが、役に立つことを何も言いません。 ナレッジベースを確認してください。「検索で返された内容だけを述べる」と指定したプロンプトにドキュメントが添付されていない場合、回答を拒否する代わりに内容をでっち上げてしまいます。
agents testで確認してください。使用されたナレッジチャンクの数が表示されます。 -
電話は鳴りますが、エージェントが応答しません。 まず送信者を確認してください。
npx zavudev senders get <senderId> # channels must be non-emptychannels配列が空の場合、送信者は何にも接続されておらず、音声を含むどのチャネルでも送受信できません。エージェント、voice.enabled、電話番号はすべて正しく見えるため見落とされがちですが、これが原因です。プロジェクトが所有する電話番号を割り当てて、音声を有効にしてください。npx zavudev phone-numbers update <phoneNumberId> --sender <senderId> npx zavudev senders update <senderId> --enable-voicechannelsにvoiceが含まれてから初めて、voice.enabledとエージェント自体が有効かどうかを確認する意味があります。 -
設定したフィールドが反映されません。
voiceSpeedが反映されるのは、速度制御に対応した音声のみです。greetingsには、発信者が実際に話す言語と一致する言語タグが必要です。 -
デプロイでは同期されたと表示されますが、何も変わりません。 ✓ より上の行を読み、終了コードを信用しないでください。
npx zavudev deployは成功行の前に警告を出力しますが、エージェントに接続できない状態になるケースでも終了コード 0 を返します。たとえば、1 つの送信者に 2 つのエージェントが割り当てられている場合、応答するのは 1 つだけです。緑のチェックマークが意味するのはデプロイが実行されたということであり、エージェントに接続できるということではありません。これを条件に CI を制御する場合は、終了ステータスではなく警告行を条件にしてください。
音声向けのプロンプト作成
音声では、文章表現が大きな影響を与えます。重要なのは、次の順です。
- 1 回のターンで 1 つの要点を伝えます。 最大 2 文にしてください。聞き手は流し読みできません。
- 答えから先に伝え、 その後で詳細を説明します。
- URL、コード、JSON を決して読み上げないでください。 それが何をするものか、どこにあるかを伝えます。
- 数字やコードは 1 文字ずつ読み上げます。
- 自社製品の名前を含め、文字起こしで名前が誤認識されることを想定します。 どのように聞こえる可能性があるかをエージェントに伝え、それが自分を指していると判断するよう指示してください。
- 対象外の内容と、その話題が出たときに何をすべきかを 1 文で伝えます。