Google Developers Blog

Step by Step guide: Build a Voice Agent That Never Goes Silent using ADK

8.5内容质量

TL;DR · AI 摘要

使用ADK和Gemini 3.8 Live的流式工具可将语音代理首次响应时间从14.4秒缩短至1.2秒,避免对话中断。

核心要点

  • 流式工具使语音代理首次响应时间从14.4秒降至1.2秒
  • ADK支持非阻塞FunctionTool定义和响应调度
  • Gemini 3.8 Live提供Python原生的异步工具实现

结构提纲

按章节快速跳转。

  1. 语音对话中13秒的阻塞操作会导致用户挂断,需通过架构优化解决。

  2. 零死寂、可听见进度更新、支持多任务并行和精细的交付控制。

  3. 传统工具定义为异步函数,完成工作后返回单个值导致阻塞。

  4. ›ADK实现方案

    通过流式工具、非阻塞FunctionTool和响应调度构建语音代理。

  5. 销售报告工作流测试显示响应时间显著优化。

  6. 控制最终结果在对话中的交付时机和方式。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 构建无沉默语音代理
    • 核心机制
      • 流式工具处理
      • 非阻塞FunctionTool
    • 实现方案
      • ADK框架
      • Gemini 3.8 Live
    • 关键特性
      • 响应调度
      • 多任务并行

金句 / Highlights

值得收藏与分享的关键句。

#ADK#Gemini 3.8 Live#语音代理#非阻塞工具#Python
打开原文

分步指南:使用 ADK 构建永不沉默的语音代理 - Google 开发者博客

Google Tag Manager (noscript)

结束 Google Tag Manager (noscript)

HTML

分步指南:使用 ADK 构建永不沉默的语音代理

2026 年 10 月 19 日

Stephen Allen

解决方案架构师

AI 应用与平台

Kaz Sato

高级开发者倡导者

分享

  • Facebook
  • Twitter
  • LinkedIn
  • 邮件

使用 ADK 和 Gemini 3.8 Live 构建永不沉默的语音代理

关于 ADK Gemini Live API 工具包中流式工具、非阻塞函数调用和响应调度的指南。

在文本聊天中,13 秒的数据库查询或 RAG 流程会显示加载指示器。但在实时语音对话中,13 秒的阻塞工具执行会导致 13 秒的静默,使用户误以为通话中断并挂断。

你无法总是让后端查询在亚秒级完成,因此解决方案必须是架构层面的:在后台运行缓慢工具的同时保持代理的对话能力。ADK 和 gemini-3.8-live 不强制你通过自定义客户端 WebSocket 事件循环来管理异步工具状态,而是让你在 Python 中原生实现非阻塞语音工具。在我们的 gemini-3.8-live 销售报告工作流基准测试中,将阻塞工具切换为流式工具,将代理首次发言的时间从 14.4 秒缩短至 1.2 秒。

本指南将介绍如何使用流式工具、非阻塞 FunctionTool 定义和响应调度来构建响应迅速、自然的语音代理。

观看 1 分钟 ADK Live AI 顾客单元演示视频,聆听调度响应的实际效果:

抱歉,你的浏览器不支持播放此视频

ADK Live AI 顾客单元演示视频解释了调度响应的工作原理

非阻塞工具能实现什么

在 ADK 中实现非阻塞工具调用可以带来以下用户体验优势:

  • 零静默时间:代理可以在对话中立即确认请求("正在查看这些数据..."),而不是中途冻结,保持对话的连贯性。
  • 可听进度更新:对于多阶段工作流(如连接、查询和对账),代理可以叙述中间里程碑,使等待听起来像是主动进展。
  • 多任务处理和并行对话:用户可以在后台长时间查询执行时提出后续或无关问题,并及时获得答案而不会中断会话。
  • 精细的交付控制:你可以决定最终结果何时以及如何进入对话:等待自然的说话停顿、立即中断以发出关键警报,或静默更新对话上下文。

基线:为什么标准工具会阻塞对话

在标准代理开发中,工具被定义为一个异步函数,完成工作后返回单一值:

code
from typing import Any

async def query_sales_database(region: str) -> dict[str, Any]:
    """运行某个地区的季度销售报告查询。

    Args:
      region: 要报告的销售地区,例如 "EMEA" 或 "APAC"。

    Returns:
      完成后包含报告指标的字典。
    """
    report = await run_expensive_query(region)       # 例如,13 秒的工作
    return {"status": "success", "report": report}   # 最后单一返回值

纯文本

已复制

当该函数直接传递给 ADK 中的 Agent 时,会创建一个阻塞工具调用。在 Gemini Live API 双向语音会话(run_live)期间,执行循环会等待工具返回结果后,才会生成后续的模型响应。

如果 query_sales_database 需要 13 秒执行时间,代理将产生 13 秒的完全静音。像 "告诉用户你正在处理" 这类提示指令无法解决这个问题,因为模型响应被阻塞等待函数返回数据。

为了保持对话流畅性,可以根据预期用户体验选择两种主要的非阻塞模式。

模式 1:流式工具(当进度有明确阶段时)

当操作包含可识别的阶段(如连接数据库、聚合记录、格式化结果),流式工具可以让代理实时叙述进度。

在 ADK 中,只需将标准工具的 return 改为 yield 即可将其转换为流式工具。通过将函数定义为 AsyncGenerator,ADK 会自动将工具声明标记为 NON_BLOCKING,并立即向模型返回状态确认。

code
import asyncio
from typing import AsyncGenerator
from google.adk.agents import Agent

async def query_sales_database(region: str) -> AsyncGenerator[str, None]:
    """运行季度销售报告。请仅调用一次;它会自动推送更新。

    Args:
      region: 要报告的销售区域,例如 "EMEA" 或 "APAC"。

    Yields:
      查询运行期间的进度更新,随后是最终摘要。
    """
    stages = [
        ("Connecting to the sales warehouse...", 1.5),
        ("Scanning order records...", 3.5),
        ("Aggregating revenue by product line...", 3.5),
        ("Formatting the report...", 1.5),
    ]
    for message, delay in stages:
        yield f"PROGRESS: {message}"
        await asyncio.sleep(delay)

    yield f"DONE: {region} Q2 revenue reached $4.81M, up 12.4% year over year."

agent = Agent(
    name="sales_analyst",
    model="gemini-3.8-live",
    tools=[query_sales_database],
    instruction=(
        "You are a helpful sales assistant. When the user asks about sales data, "
        "call query_sales_database once. Acknowledge the request immediately, "
        "briefly paraphrase each progress update, and present the final figures "
        "when completed. Keep answers conversational and concise."
    ),
)

实际工作原理

  • 立即确认:当模型触发 query_sales_database 时,ADK 会立即向模型发送异步状态事件。代理会立即回应("现在为您查看EMEA的销售数据...")。
  • 部分更新:每个 yield 会向上游发送部分函数响应。模型在收到每个更新时会进行改写("连接到仓库...","按产品线聚合数据..."),尽管叙述可能会比工具进度延迟数秒。
  • 无缝多线程:如果在查询进行时用户提出无关问题,代理可以在不取消或中断后台任务的情况下回答问题。

流式工具的生产环境考量

  • 单次飞行工具调用:由于流式工具会跨多轮对话推送更新,模型在反复提示时可能会尝试再次调用工具。请在文档字符串中明确标注“请仅调用一次”的指令,并在生产环境中考虑在工具实现中添加内存锁或幂等性密钥以确保安全性。
  • 使用 stop_streaming 实现用户取消:ADK 为长时间运行的流式工具提供了内置的退出机制。通过在代理工具列表中包含名为 stop_streaming(function_name: str) 的函数,ADK 会拦截该调用并取消生成器底层的 asyncio.Task,同时在您的函数内部抛出 asyncio.CancelledError,使您能够优雅地关闭数据库连接或文件句柄。
  • 用户打断不会取消工具执行:当用户打断代理讲话时,Live API 会停止模型输出并触发 interrupted=True 的事件,使客户端能够停止音频播放。正在运行的流式或后台工具不会被取消,其结果仍会正常送达。要停止工具,必须由模型主动调用 stop_streaming。

模式 2:后台执行(当等待只是等待时)

并非所有场景都适合持续进展说明。当用户请求报告后立即转向其他话题时,持续发送状态更新的代理可能会显得侵入性。

当等待期间没有可叙述的故事时,请静默地在后台运行任务。

方案 A:单次产出生成器(最快实现)

一种无需持续进展说明即可立即获得语音确认的有效方式,是使用仅在完成时产出一次的异步生成器:

code
async def query_sales_database(region: str) -> AsyncGenerator[dict, None]:
    """执行季度销售报告。请仅调用一次;结果将在准备就绪时返回。"""
    report = await run_expensive_query(region)   # 后台执行(约13秒)
    yield report                                 # 完成时单次产出

由于这是异步生成器,ADK 会立即发送待处理响应。代理会立即确认请求("我正在为您整理这份报告"),在计算期间保持静默,并在结果准备就绪或用户结束讲话时呈现数据。

方案 B:使用 `response_scheduling` 的 `FunctionTool`

如果您希望继续使用标准协程(async def ... -> dict),可以将函数封装在 FunctionTool 中并配置 response_scheduling:

code
from google.adk.tools import FunctionTool
from google.genai import types

sales_tool = FunctionTool(query_sales_database)
sales_tool.response_scheduling = types.FunctionResponseScheduling.WHEN_IDLE
(注:在 ADK 2.10+ 版本中,您还可以设置 sales_tool.behavior = types.Behavior.NON_BLOCKING 以在后台运行工具而无需修改调度模式。)

理解权衡:当使用 response_scheduling 与一次性返回的协程时,模型会在整个函数完成前接收不到任何数据负载。因此,模型通常不会在用户继续讲话前发出初始语音确认。这种模式适用于用户立即转向其他问题的场景,您希望在下次自然停顿时将结果融入对话。

response_scheduling 属性控制模型何时对传入的工具响应做出反应。WHEN_IDLE 模式不会打断正在进行的对话回合:如果模型仍在说话或用户仍在讲话,结果会等待当前回合结束,并在下一个自然停顿时由模型响应。ADK 支持三种调度行为:

在 gemini-3.8-live 的基准会话中:

  • 使用 WHEN_IDLE 时,传入的销售报告礼貌地等待模型完成回答一个20秒的独立问题,随后无缝地传递了数据。
  • 使用 INTERRUPT 时,传入的结果在句子中间打断了当前语音;模型随后完成回答并附加了数据。
  • 使用 SILENT 时,结果被添加到对话历史中,可在后续回合中使用,而不会触发不必要的公告。

流式传输与调度的结合

执行模式(流式传输 vs 单次返回)和交付时机(response_scheduling)是相互独立的,可以组合使用。在流式工具上设置 response_scheduling 会将该行为标记到每个部分更新中。

此外,在 ADK 2.8 及更新版本中,流式工具可通过 yield types.FunctionResponse 动态指定每条更新的调度策略:

code
# 确认并保持中间进度静默:
yield types.FunctionResponse(
    response={"stage": "aggregating"},
    scheduling=types.FunctionResponseScheduling.SILENT,
)

# 在发言人结束时传递最终结果:
yield types.FunctionResponse(
    response={"report": final_data},
    scheduling=types.FunctionResponseScheduling.WHEN_IDLE,
)

架构概览:选择合适的工具模式

经验法则:如果等待有明确的进展过程,请使用流式传输;如果等待只是单纯的等待,请在后台运行并在空闲时返回结果。

结论

非阻塞工具不会减少后端执行时间:一个13秒的查询仍然需要13秒。改变的是代理在对话中的存在感。

一个需要十秒加载的网页应用会被认为很慢;一个在十秒内保持沉默的对话式语音代理会被认为是故障的。通过将 ADK 的流式工具和灵活的响应调度与 gemini-3.8-live 的实时能力相结合,您可以构建在复杂操作中始终保持专注、自然和在场的语音助手。

亲自体验:交互式演示在 gemini-3.8-live 上通过每种工具模式和 response_scheduling 模式运行相同的13秒查询,您可以听到差异。

#### 要求与下一步

  • 框架:google-adk >= 2.10.0
  • 模型:Gemini Enterprise 上的 gemini-3.8-live
  • API 环境:流式工具和实时调度需要双向实时会话(run_live)

#### 参考资料

  • 流式工具
  • ADK Gemini Live API 工具包
  • 函数工具

posted in:

  • AI
  • 操作指南
  • 最佳实践
  • 学习
  • 探索
  • 代理平台
  • 语音代理
  • 资源
  • Gemini

上一篇

下一篇

相关文章

列表

AI

云

公告

解决方案

三层架构,一个仓库:google/adk-recipes 项目巡礼

2026年10月15日

案例研究

操作指南

在 MaxText 中重现 Olmo 3 7B 预训练:TPU 上大规模训练的案例研究

2026年9月24日

最佳实践

行业趋势

评估 AI 代理:超越直觉判断

移动

网络

宣布 Google OAuth 支持 DPoP:保护刷新令牌

导航点