语音Agent怎么进CI:ADK Live Eval实战
语音 Agent 最大的测试误区,是拿文字稿做回归。
文字测试能发现:
答案错了
Tool没调用
上下文丢了
但它发现不了:
用户插话时Agent会不会停
语音转写是否漏掉关键信息
多轮身份验证有没有顺序错误
同一句话换口音后Tool参数是否变化
音频流切换时上下文是否丢失
Google ADK 在 8 月 24 日新增原生 Live Evaluation 后,这类问题终于可以进入自动化测试:测试用户不再只是 JSON 里的文本,而是由模拟用户根据 Persona 和 Conversation Plan 生成真实语音,再流式送给 Live Agent;Agent 的语音回复、Tool Execution 和多轮 Trajectory 可以一起评分,并且可以从 CLI 直接接进 CI/CD。
这比“人工打电话听一遍”更接近生产工程。
先看一个最小Live Workflow
Google 的示例用三个单功能 Live Agent 串成一个 Graph:
Greeter
↓
DOB Verifier
↓
Goals Agent
中间还穿插一个验证生日的 Tool。
模型:
LIVE_MODEL = "gemini-live-2.5-flash-native-audio"
核心代码可以简化成:
from google.adk.agents.llm_agent import Agent
from google.adk.workflow import START, Workflow
greeter = Agent(
model=LIVE_MODEL,
name="greeter",
mode="task",
instruction=(
"确认用户身份,"
"一次只问一个问题。"
),
)
verifier = Agent(
model=LIVE_MODEL,
name="dob_verifier",
mode="task",
tools=[validate_date_of_birth],
instruction=(
"询问生日,复述确认,"
"再调用验证工具。"
),
)
goals = Agent(
model=LIVE_MODEL,
name="goals",
mode="task",
instruction=(
"仅在身份验证完成后,"
"告知预约信息。"
),
)
root = Workflow(
name="live_workflow",
edges=[
(START, greeter),
(greeter, verifier),
(verifier, goals),
],
)
真正值得测试的是:
身份验证前
绝不能泄露预约信息
这是 Trajectory Constraint,不只是最终答案要求。
语音测试第一类:Scenario
ADK 的一个重要设计是:
测试Case
和
执行方式
解耦
你可以写 Conversation Scenario:
{
"eval_id": "identity_flow_01",
"conversation_scenario": {
"starting_prompt": "Hello?",
"conversation_plan": "确认姓名。被问到生日时回答1985年7月12日。确认生日后询问下次预约需要准备什么。",
"user_persona": "NOVICE"
},
"session_input": {
"app_name": "live_workflow",
"user_id": "test_user",
"state": {}
}
}
NOVICE 的意义不是“用户水平低”。
更接近:
模拟用户不会主动一次性把所有信息说完
Agent 必须自己推进对话。
这比固定脚本更能测:
Agent有没有真正掌握流程
第二类:Fixed Conversation
有些回归必须完全可控。
例如:
{
"eval_id": "fixed_dob_case",
"conversation": [
{
"user_content": {
"role": "user",
"parts": [
{"text": "Yes, this is John Doe."}
]
}
},
{
"user_content": {
"role": "user",
"parts": [
{"text": "My date of birth is July 12th, 1985."}
]
}
}
]
}
固定对话适合:
Bug Regression
Scenario 适合:
Behavior Coverage
两种都要。
真正让Live Eval成立的是Audio Simulator
Google 示例配置里:
{
"live_model_config": {
"timeout_seconds": 300
},
"user_simulator_config": {
"type": "llm_audio",
"model": "gemini-3.7-flash",
"max_allowed_invocations": 10,
"audio_model": "gemini-3.1-flash-tts-preview",
"audio_model_configuration": {
"response_modalities": ["AUDIO"],
"speech_config": {
"voice_config": {
"prebuilt_voice_config": {
"voice_name": "Kore"
}
},
"language_code": "en-US"
}
}
}
}
这里有两个模型角色:
model
负责模拟用户的对话逻辑。
audio_model
负责把用户回合变成语音。
不要把这两个参数混为一谈。
max_allowed_invocations是成本和死循环保险
动态模拟用户可能出现:
Agent不结束
Simulator继续问
Agent继续回答
所以:
"max_allowed_invocations": 10
不只是测试参数。
它实际上是:
Conversation Budget
企业场景我会按业务设置:
身份验证:
最多6轮
退款:
最多12轮
复杂售后:
最多20轮
超过阈值:
FAIL
或
转人工
Voice Eval最值得做的是Trajectory Rubric
例如:
{
"rubric_id": "identity_before_disclosure",
"rubric_content": {
"text_property": "Agent必须在确认姓名并验证生日后,才能披露预约信息。"
}
}
阈值:
{
"rubric_based_multi_turn_trajectory_quality_v1": {
"threshold": 0.7
}
}
但生产里我不会只用自然语言 Rubric。
能确定性判断的规则,应该写程序。
身份验证顺序最好用确定性事件检查
例如 Event Ledger:
NAME_CONFIRMED
DOB_TOOL_CALLED
DOB_VERIFIED
APPOINTMENT_DISCLOSED
检查:
def assert_identity_before_disclosure(events):
verified = False
for event in events:
if event.type == "DOB_VERIFIED":
verified = True
if event.type == "APPOINTMENT_DISCLOSED":
assert verified
这比 LLM Judge 更稳定。
Rubric 负责评:
语气
自然度
是否一次问一个问题
程序负责评:
权限顺序
Tool顺序
关键状态
我会把Live Eval拆成5层指标
第一层:ASR / Input
用户语音有没有被正确理解
指标:
关键实体识别率
生日识别率
订单号识别率
第二层:Turn-taking
Agent有没有抢话
是否正确处理插话
是否停顿太久
第三层:Task
业务流程有没有完成
第四层:Tool
Tool有没有正确调用
参数有没有正确
第五层:Audio Output
回答内容和语音体验
不要把所有问题揉成一个:
Judge Score = 0.82
一个更实用的测试结果
{
"case": "identity_flow_01",
"asr_entity_accuracy": 1.0,
"task_success": true,
"tool_sequence_pass": true,
"identity_gate_pass": true,
"max_turn_latency_ms": 1180,
"interrupt_handling": 0.92,
"voice_quality": 0.88,
"total_turns": 7
}
这样失败以后知道去哪查。
语音Agent一定要测不同声音
Google 当前配置允许换:
voice_name
language_code
企业回归不要永远只有一个标准声音。
最少:
男声
女声
快语速
慢语速
弱口音
强口音
背景噪声
如果底层 TTS Simulator 暂时不能生成全部噪声,可以对音频做后处理。
一个测试矩阵
Scenario × Voice × Speed × Noise
例如:
10业务场景
×
3声音
×
2语速
×
2噪声
=
120 Cases
比只测 10 条文本更接近真实风险。
但不要一上来全矩阵跑CI
120 个 Live Case 成本和时间都不低。
可以分层:
PR:
10个快速Smoke
main:
40个核心场景
nightly:
全矩阵
release:
全矩阵 + 高风险Case
CLI非常适合接CI
Google 当前示例命令:
uv run adk eval \
contributing/samples/live/live_workflow \
contributing/samples/live/live_workflow/live_workflow.evalset.json \
--config_file_path \
contributing/samples/live/live_workflow/test_config.json
需要:
uv pip install -e ".[eval]"
并配置 Live API 和 Gemini TTS Credentials。
GitHub Actions里怎么跑
name: live-agent-eval
on:
pull_request:
paths:
- "agent/**"
- "eval/**"
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv sync
- run: |
uv run adk eval \
agent/live_workflow \
eval/smoke.evalset.json \
--config_file_path eval/test_config.json
Secret 通过 CI Secret Store 注入。
不要写进 JSON。
结果必须转成机器可读Gate
CLI 跑完以后,如果只是把长日志留在 CI:
没人会看
最好转:
{
"passed": 38,
"failed": 2,
"critical_failed": 1
}
然后:
critical_failed > 0
→ block merge
高风险Case不能平均
例如:
40个Case
39个通过
1个失败
总体:
97.5%
看起来很好。
但唯一失败的是:
身份验证前泄露医疗预约
那必须:
BLOCK
所以 Case 要有:
severity
Eval Case模型
public record EvalCaseMeta(
String caseId,
RiskLevel risk,
boolean releaseBlocking,
Set tags) {
}
高风险:
releaseBlocking=true
不参与平均豁免。
语音Latency也必须进门禁
内容全对,但每轮:
4.5秒
用户会直接打断或挂电话。
我会监控:
Time to First Audio
Turn End to Response Start
Tool Round-trip
Total Conversation Duration
例如:
latency_gate:
p50_first_audio_ms: 600
p95_first_audio_ms: 1200
p95_turn_response_ms: 1800
打断处理必须单独测
场景:
Agent正在说:
“您的下次预约是……”
用户:
“等等,是几点?”
Agent 应:
停止当前播报
处理插话
回答
再决定是否继续
而不是把整段话说完。
可以在模拟器加入:
interrupt after appointment date
并验证:
agent_stopped_audio = true
Tool失败也要注入
不要所有 Fixture 都成功。
例如生日验证:
Tool Timeout
Tool 500
Wrong Record
Agent 应:
不能泄露信息
不能假装验证成功
这类 Case 通常比正常流程更重要。
Session State必须跨Agent保持
Google 示例里三个 Live Agent 切换时,音频流不会中断,并且 session state 和 conversation history 会向后传。
所以回归要专门测:
Greeter已经确认姓名
↓
DOB Agent能否读取
不要让每个 Sub-Agent 重新问一遍。
可以做State Assertion
assert session.state["name_confirmed"] is True
assert session.state["dob_verified"] is True
最终 Goals Agent 执行前:
两个条件必须同时满足
ADK Web适合人工Debug,不应该成为唯一评测入口
Google 当前会把 Live Run 重建成 Transcript,并提供每回合的可播放音频。
这对失败排查非常好。
但发布门禁必须来自:
CLI
或
AgentEvaluator
否则还是人工点页面。
我会保留失败音频Artifact
CI 失败时:
transcript.json
audio-turn-03.wav
tool-events.json
metrics.json
一起上传 Artifact。
开发者不用重新复现就能听出问题。
成本也要统计
Live Eval 同时会调用:
Agent Model
Simulator Model
TTS Model
Judge
一条 Case 可能有多个模型成本。
所以记录:
cost_per_case
Nightly 全量矩阵才能知道预算。
一个完整Release Gate
voice_agent_gate:
critical_cases:
pass_rate: 1.0
normal_cases:
pass_rate: 0.95
tool_sequence:
pass_rate: 0.99
p95_first_audio_ms: 1200
max_conversation_turns: 15
max_cost_per_case_usd: 0.30
如果某个版本:
质量不变
成本翻3倍
也不应该无条件发布。
Live Agent 从 Demo 走向生产以后,真正的难点不是“能听、能说”。
而是:
多轮状态
Tool顺序
语音延迟
插话
口音
异常恢复
这些问题单靠文本 Eval 根本覆盖不到。
ADK 现在把模拟用户、真实 TTS 音频、Trajectory Rubric、Tool Evaluation、CLI 和 ADK Web 串进同一个 Eval Loop,我觉得它最重要的价值不是多一个测试功能。
而是终于让语音 Agent 可以像普通服务一样:
改代码
→跑回归
→看失败
→阻断发布
只要这一环建立起来,语音 Agent 才算真正进入工程化阶段。
更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界:
https://www.zyentor.com/