谷歌-ADK-实时语音智能体笔记-全-
谷歌 ADK 实时语音智能体笔记(全)
001:课程介绍与概述 🎤

在本课程中,我们将学习如何使用 Google 的 Agent Development Kit (ADK) 来构建多智能体 AI 应用。我们将创建一个播客 AI 代理,它能接收用户的实时语音输入、研究主题、规划并起草一集多人对话的播客文稿,最终生成一个播客音频文件。
Google ADK 是一个开源框架,它让构建简单的智能体变得轻而易举,只需几行代码,并且可以扩展到复杂的多智能体系统。ADK 包含了用于控制不同智能体行为的构建工作流模式,并支持灵活的驱动编排,其中一个智能体可以决定执行步骤的序列。它提供了常见的智能体构建模块,包括模型、工具、内存以及可观测性功能,例如提供追踪和评估。
本课程也是对实时语音对话界面的快速入门,你将用几行代码就能构建它。我们还将探索多智能体系统,其中每个智能体专注于特定任务,多个这样的智能体协作完成更复杂的目标。最终,你将构建出自己的智能体系统。
课程核心概念与构建模块
上一节我们介绍了课程的整体目标,本节中我们来看看构建智能体所需的核心概念和基础模块。
要开始本课程,你将首先使用 Google ADK 和 Gemini 模型创建一个简单的语音代理。你的代理将接收用户的语音输入,使用语言模型进行推理,然后发送语音输出。你将了解 ADK 的核心构建模块,如会话状态和内存。
- 会话:会话就像一个容器,它将一次交互的所有部分保持在一起。如果你开始与一个智能体对话,会话就开始了,你所说的一切和智能体的回复都保留在该会话中。
- 状态:状态是你的智能体的便签或短期记忆。它跟踪智能体的进度,这样智能体就不会在流程中忘记正在做什么。
- 内存:内存进一步扩展了这一点,它允许智能体回忆过去的输入、输出或工具调用,并在响应时使用。
这些基本构件构成了你用 ADK 构建的每个智能体的基础。
扩展智能体能力:工具与回调
智能体通过工具(如 Google 搜索或任何外部 API)变得更加有用。这使你的智能体能够访问更广阔的世界、获取新鲜且真实的信息并执行操作。工具可以是代码中的函数、HTTP 服务或公共 API。
随着你的智能体系统变得更强大,它可以做更多事情,你需要约束或控制它可以做什么以及不应该做什么。这可以通过 ADK 中的回调轻松实现,回调允许你在每次模型、工具和智能体调用之前和之后进行拦截。你可以添加代码、远程 API 或简单地使用一个专门的智能体来处理这些回调。
从单智能体到多智能体系统
你将从一个构建单个播客智能体开始,然后将其转变为一个多智能体系统。在多智能体系统中,你可以让一个智能体负责制定计划,第二个智能体进行背景研究,第三个智能体负责撰写。你将看到如何将一个复杂任务分解为子任务,由不同的智能体来执行,这被证明是实现智能体工作流的重要设计模式。
致谢与课程价值
许多人参与了本课程的贡献。感谢来自 Google 的 Boy Yao Han Fei L、Alan Bunt、Julia Weiseinger、Hollong L、Erwin Husinger 以及许多幕后使 ADK 成为可能的人。来自 DeepLearning.AI 的 Brendan Brown 和 Imer Greggari 也为本课程做出了贡献。
本课程不仅将帮助你学习如何使用 Google ADK 构建智能体,还将教你如何为你的 AI 应用带来语音功能。
总结与下一步
本节课中我们一起学习了课程概述、Google ADK 的核心概念(会话、状态、内存)、如何通过工具和回调扩展智能体能力,以及从单智能体扩展到多智能体系统的设计模式。

在下一课中,你将构建你的第一个语音代理。让我们进入下一个视频,开始动手实践吧。
002:构建你的第一个代理 🎙️

在本节课中,我们将学习如何使用 Google ADK 构建你的第一个 AI 代理,并赋予它从网络获取实时信息的能力。你将了解代理的基本结构,探索 ADK Web UI,并简要了解其他构建代理的方法。
什么是代理?🤔
想象一个不仅能对话,还能“做事”的 AI,这就是代理的核心。它拥有一个强大的语言模型(如 Gemini)作为其“大脑”。但使其真正特别的是,我们可以赋予它“工具”,例如搜索网络或访问其他应用程序的能力,使其能够与世界互动并完成任务。在本课程中,我们将构建一个能够帮助我们研究和制作播客的 AI 代理。在第一课结束时,你将构建出一个功能性的对话代理。
安装 ADK 🔧

首先,我们需要在系统上安装 ADK。这是一个简单的过程。我们将使用 pip install google-adk 来安装 Google ADK。安装完成后,你将可以访问 ADK 库和一系列方便的命令行工具,这将使开发过程更加顺畅。

创建代理基础 🏗️
现在,让我们为代理创建基础。使用 adk create 命令来创建一个新的代理结构。
adk create a01
这个命令做了什么?adk create 命令简化了创建完整代理结构的过程。它创建了一个新文件夹(本例中是 a01),并在其中创建了三个新文件:一个 Python __init__.py 文件、一个 agent.py 文件(将包含所有代理代码)以及一个 .env 文件(包含 API 密钥等认证凭证)。在本课程中,我们将使用 adk create 命令创建文件夹,然后使用单元格魔法将代码写入 agent.py。
编写第一个代理代码 🖥️
现在,让我们创建第一个代理。以下是代理的核心代码,非常简单和简洁。
from adk import Agent
agent = Agent(
name="News Assistant",
model="gemini-2.0-flash-exp",
instruction="You are an AI news assistant."
)
我们有一个名为 agent 的根代理变量,它被实例化为一个 Agent 对象。让我们看看每个参数的含义:
- name:代理的名称。
- model:使用的模型,这里是
gemini-2.0-flash-exp。需要注意,这个模型支持文本和音频两种模态。 - instruction:给代理的指令,这里设定为“你是一个 AI 新闻助手”。
运行代理并探索 Web UI 🌐

接下来,我们打开一个终端来运行代理。我们将调用 adk web 命令来打开 ADK Web UI。
adk web --host localhost --port 8080
这将启动一个本地服务器。运行相关单元格后,你将获得访问服务器的 URL。在浏览器中打开此 URL,你将看到 UI。从下拉菜单中选择我们创建的代理 a01。
现在,让我们尝试询问它:“最新的 AI 新闻是什么?告诉我谷歌最近发布的 LLM 模型。” 代理可能会回答:“谷歌最近发布了 Gemini 1.5 Pro,并介绍了 Imagen 3。你想了解更多关于其中任何一个的细节吗?”
这正是我们想要的回应吗?请注意,我们使用的模型有其知识截止日期,你无法期望它获取实时信息。这就是我们需要为代理添加工具,赋予其获取外部信息能力的原因。

为代理添加工具 🛠️

让我们创建一个新文件夹,并复制之前的代码,但做一个简单的修改:添加一个工具。
from adk import Agent
from adk.tools import google_search
agent = Agent(
name="News Assistant",
model="gemini-2.0-flash-exp",
instruction="You are an AI news assistant.",
tools=[google_search] # 添加内置的谷歌搜索工具
)
这是一个 Google ADK 内置的工具,意味着它随库预打包,你无需为其编写任何代码。添加此工具后,让我们再次运行代理。
在 Web UI 中,这次选择我们刚创建的 a02 文件夹,以确保我们使用的是添加了工具的代理。再次使用相同的查询:“你能告诉我谷歌最近发布的 LLM 模型吗?”

这次,代理的回答将基于实时网络搜索,例如:“以下是谷歌近期模型发布的总结:Veo 3(视频生成模型,在真实世界物理和细节如唇形同步方面有改进)、Imagen 3(图像生成模型,比 Imagen 2 更快)、Flow(AI 电影制作工具,使用文本、视频和图像模型来拼接一致的场景)。”
到目前为止,我们看到了如何创建代理并为其添加工具,使其能够访问实时信息。
构建文本代理 📝
我们已经看到了如何与模型交互并进行实时语音对话。但进行文本对话的格式与我们刚才所做的并没有太大不同。让我们为新的文本代理创建一个文件夹。
from adk import Agent
from adk.tools import google_search
agent = Agent(
name="News Assistant",
model="gemini-2.0-flash-exp", # 注意:此处仍为支持语音的模型,但Web UI对话模式可选择文本
instruction="You are an AI news assistant.",
tools=[google_search]
)



实际上,如果你查看代码,除了模型名称,其他基本保持不变。要切换到纯文本代理,你只需将模型更改为纯文本模型(例如 gemini-2.0-flash),但在 Web UI 中,你也可以直接在界面上选择“文本”模式进行交互。让我们运行此代码。
在 Web UI 中选择我们刚创建的第三个文件夹,这次给它一个文本查询:“最新的 AI 新闻是什么?” 代理将在后台工作,现在你看到了代理提供给我们的所有新信息。
左侧窗格实际上有一系列选项卡。如果你点击“跟踪”,这将为你提供代理实际操作的详细跟踪记录。你还可以看到每次调用的跨度,这对于代理的可观察性非常重要。在“事件”选项卡中,你可以看到代理产生的一系列事件。我们还有一个漂亮的代理架构小图。当然,我们的代理目前是一个非常基础的代理,只有一个单一的代理和一个单一的谷歌搜索工具。
其他运行方式与 YAML 配置 ⚙️
让我们切换回代码。我们已经看到了如何使用文本和语音模型调用代理,并且我们使用 adk web CLI 命令完成了这些操作。ADK 还为你提供了其他几个选项来运行代理:
adk run命令:这也是一个 CLI 命令,但它会在终端中直接调用你的代理。- ADK API 服务器:这将把你的代理变成一个任何应用程序都可以调用的 REST API,这是你部署到生产环境的方式。
但在本课程中,我们将主要使用 ADK Web。
如果 Python 不是你的首选,或者你更喜欢更声明式的代理开发风格(类似于配置文件),那么 ADK 也为你提供了使用 YAML 配置构建代理的选项。让我们为 YAML 配置创建一个新文件夹。
adk create a04 --type yaml
如果你查看输出,adk create 命令基本上创建了三个不同的文件,并创建了一个名为 root_agent.yaml 的文件。现在,你基本上要用我们拥有的这个 YAML 声明式结构重写那个 YAML 文件。
name: News Assistant
model: gemini-2.0-flash-exp
instruction: You are an AI news assistant.
tools:
- google_search
这完全类似于我们创建的 Python 代码。它有模型、名称、描述、指令和谷歌搜索工具。基本上,运行它将再次为我们提供与 Python 相同的 ADK Web UI 体验。
优化代理指令 ✍️
现在,我们已经看到了创建和运行代理的不同方式。本课的最后一个步骤是优化代理的指令。让我们为我们刚创建的代理找到指令。还记得我们的代理只有一行指令:“你是一个新闻助手”。当我们构建播客代理时,这尤其不够有帮助,或者说没有将其范围缩小。
所以我们现在要做的是,创建另一个代理。让我们首先创建代理文件夹,但这次为我们的代理提供一个非常全面的指令。
agent = Agent(
name="AI News Specialist",
model="gemini-2.0-flash-exp",
instruction="""
You are an AI news specialist. Your sole purpose is to provide the latest, accurate news and developments specifically in the field of Artificial Intelligence.
Refusal Mandate: Politely refuse to answer any questions not related to AI news, technology, research, or companies in the AI sector.
Workflow:
1. For any AI news query, you MUST use the Google Search tool to find the most recent and relevant information.
2. Strictly base your responses on the search results obtained.
3. Always cite your sources by mentioning the website or publication you retrieved the information from.
""",
tools=[google_search]
)
让我运行这个代理,并让我们解析一下这个指令。指令设定了代理的核心身份和唯一目的,这基本上为代理的作用范围设定了背景。它还有一个“拒绝授权”,这意味着任何非 AI 新闻的话题现在都应该被拒绝。我们还为它提供了一个工作流程,说“你必须使用谷歌搜索来查找信息,并严格基于你的结果,同时引用你的来源”。这基本上缩小了代理的操作空间,所以它现在将知道它必须执行的工作流程。
让我们运行这个代理,并切换到 Web UI。将文件夹切换到我们最近创建的应用程序,这次让我们询问天气:“今天旧金山的天气怎么样?” 代理回答:“抱歉,我无法回答这个问题。我只应该回答关于最新 AI 新闻的问题。” 正如你所看到的,代理拒绝了,因为这是非 AI 新闻。


总结 🎯
在本节课中,我们一起学习了:
- 代理是什么:一个能使用工具与世界交互的 AI 系统。
- 安装并设置了 Google ADK。
- 创建了一个功能性的代理:能够通过网络搜索获取实时信息。
- 探索了构建方法:我们看到了如何使用 Python 构建代理,也了解了替代方法,如 YAML 和 Web Builder。
- 优化了代理指令:通过提供详细的身份、规则和工作流程来精确控制代理的行为。

在接下来的课程中,我们将继续使用 Python(因为它为我们提供了所需的最大灵活性)来构建播客代理,但了解这些其他选项的存在也是 ADK 强大功能的一部分。你已经迈出了构建对话式 AI 代理的巨大一步。在下一课中,我们将更深入地探索 ADK 的基础组件,例如会话状态和记忆。
003:ADK 核心概念——会话、状态与内存 🧠
在本节课中,我们将学习 Google ADK 中的三个核心服务:会话、状态和内存。理解这些概念对于构建更复杂、可控的智能体至关重要。
概述
上一节我们学习了如何创建一个基础智能体,添加内置工具并进行实时对话。然而,8K Web 界面为我们隐藏了大部分底层复杂性,例如会话和内存管理。本节中,我们将暂时脱离动手实践,深入理解这些核心服务究竟是什么。
会话:对话的容器
首先,我们来理解会话。你可以将会话视为单个对话的容器。它始于用户开始与你的智能体交互,并在许多情况下,于该交互停止时结束。例如,如果你在笔记本中运行一个智能体,那么当你停止运行时,会话就会终止。
会话对象充当对话的综合数据日志,捕获状态、事件和元数据。ADK 通过会话服务来管理会话,其默认实现是一个内存中的服务。这种服务是临时的,在对话结束后不会持久化。然而,你也可以配置持久化的会话服务,以便存储和检索会话数据供后续使用。

以下是一个会话可能包含内容的简单示例:
{
"state": {...},
"events": [...],
"app_name": "my_agent"
}
本质上,它是智能体所有对话和日志的数据转储。
状态:短期的暂存空间
接下来是状态。状态是会话的短期暂存空间。它本质上是一个键值对字典,用于保存对话的当前上下文。
你可以向状态写入任何信息,并且在该会话内的所有工具和智能体都可以访问这些信息。这使得状态成为一个强大的通信机制。例如,如果你在同一个任务中使用了多个工具或多个智能体,它们可以通过读写会话状态来共享信息。


状态的核心作用是维护对话的即时上下文,其生命周期与会话绑定。


内存:长期的持久化存储

第三个核心概念是内存服务。内存与会话有很大不同:会话是智能体发生的一切事情的数据转储,而内存则更侧重于长期持久化。它为你的智能体提供长期记忆能力。
回想一下会话的 JSON 结构,它包含了大量信息,但并非所有信息都对智能体的长期记忆有用。内存就是这些信息的压缩版本,只保存智能体需要长期记住的关键部分。
例如,如果你告诉你的智能体你最喜欢的咖啡订单是“燕麦拿铁”,你可能希望它能长期记住,而不是明天就忘记。这正是内存所提供的功能。
在大多数情况下,内存服务会利用 LLM(大语言模型) 来遍历会话数据,提取需要长期记住的关键且有价值的信息,然后将其保存下来。ADK 提供了不同的内存服务实现,包括一个内存中的服务,以及与 Vertex AI 内存库的集成。我们将在本课程后面的章节中探索内存库。
总结
本节课我们一起学习了 Google ADK 中三个核心的理论基础:会话、状态和内存。
- 会话是对话的完整容器和日志。
- 状态是用于维护即时上下文的短期键值存储。
- 内存是用于长期记忆关键信息的持久化存储。

理解这些概念将帮助你在后续课程中更好地控制和定制你的智能体行为。下一节,我们将重新回到实践,学习如何具体配置和使用这些服务。
004:为你的代理添加工具 🛠️
在本节课中,我们将学习如何为你的 ADK 代理添加自定义的 Python 工具。我们将通过集成一个外部 API 来获取金融信息,并探讨定义工具的最佳实践,以及如何修改代理指令以有效使用这些工具。
环境准备与项目初始化
首先,请确保你的开发环境中已安装最新版本的 ADK 及其他依赖项。
第一步,与之前所有课程一样,始终创建一个 ADK 项目文件夹。在当前实验环境中,你无需添加 API 密钥。但如果你选择在自己的环境或计算机上运行此代码,请确保在 adk create 命令中传入 Gemini API 密钥。你可以从 Google AI Studio 或 Vertex AI Studio 获取该密钥。
课程目标:增强新闻搜索功能



上一节我们介绍了代理的基本工作流程。本节中,我们来看看如何通过添加新的信息来源来增强我们的“AI新闻分析师”功能。

目前,我们的代理可以从各大公司获取最新的 AI 新闻。如果我们能同时了解相关公司的金融市场表现,那将非常有价值。例如,如果谷歌发布了关于其新功能 Gemini 1.5 Pro 的新闻,观察其股价和百分比变化可以间接地为我们提供该新闻是正面还是负面的信号。
免责声明:这并非股票购买建议,股价的涨跌与新闻也并非直接相关。我们只是尝试通过添加一个简单的 Python 函数,从实时数据中获取更多信息。
创建自定义工具:获取金融数据
为了实现这个目标,我们有一个 get_financial_context 函数。这是一个为 ADK 代理设计的函数工具。
ADK 框架非常智能,它能读取函数的描述、参数甚至类型,从而理解这个工具的作用。因此,像我们这里一样,拥有清晰的文档字符串对于代理知道何时以及如何有效使用此工具至关重要。
让我们看看代码:
def get_financial_context(tickers: list[str]) -> dict:
"""
获取给定股票代码列表的当前股价和日涨跌幅。
参数:
tickers: 股票代码列表,例如 ['GOOG', 'NVDA']
返回:
一个字典,键为股票代码,值为格式化的金融信息字符串。
"""
import yfinance as yf
financial_data = {}
for ticker_symbol in tickers:
try:
stock = yf.Ticker(ticker_symbol)
info = stock.info
current_price = info.get('currentPrice')
percent_change = info.get('regularMarketChangePercent')
if current_price is not None and percent_change is not None:
formatted_info = f"${current_price:.2f} ({percent_change:+.2f}%)"
financial_data[ticker_symbol] = formatted_info
else:
financial_data[ticker_symbol] = "Financial data not available."
except Exception as e:
financial_data[ticker_symbol] = f"Error fetching data: {e}"
return financial_data
代码解析:
- 函数定义:函数名为
get_financial_context,它接受一个参数tickers,该参数必须是一个字符串列表。代理将在此传入股票代码,如GOOG或NVDA。 - 数据结构:在函数内部,我们首先创建一个名为
financial_data的空字典,用于存储每个股票代码的结果,最后一并返回。 - 循环处理:接下来,我们遍历代理提供的股票代码列表。循环内的核心逻辑使用了流行的 Python 库
yfinance来获取数据。 - 数据获取与安全访问:
stock = yf.Ticker(ticker_symbol)这行代码创建了一个代表特定股票的对象。info = stock.info则获取了关于该股票的完整信息字典。我们使用.get()方法安全地访问我们感兴趣的两个特定数据:当前价格和日涨跌幅。这是一个好习惯,因为如果数据因某些原因不可用,代码也不会崩溃。 - 格式化输出:成功获取价格和涨跌幅后,我们将它们格式化为清晰易读的字符串(例如
$175.20 (+1.25%)),并将此字符串添加到financial_data字典中,以股票代码作为键。 - 错误处理:由于股票代码可能无效或存在网络问题,我们将整个逻辑包裹在
try-except块中。这是一个安全网。如果发生任何错误,我们只需记录该股票代码的错误信息,然后继续处理下一个。 - 返回结果:循环结束后,函数返回
financial_data字典,其中包含了代理所查询的每个股票代码的金融背景信息。
现在,让我们运行这段代码,看看输出结果。你会看到我们已经用刚刚编写的工具装备了我们的代理。
定义核心代理逻辑
接下来,我们来看看路由代理本身。这是我们操作的大脑。代理通过名称、模型,以及最重要的——一套详细的指令和可使用的工具列表来定义。
我们使用的是 gemini-2.0-flash-exp 模型。其中的 -exp 是关键,它表明我们使用的是专为实时双向流式传输设计的模型。正是这一点实现了自然的语音对话,你甚至可以打断代理,而它也能实时响应。
然而,真正的力量在于指令提示。这是我们塑造代理个性和工作流程的地方。我们给了它一套非常具体的规则。
以下是代理指令的关键部分:
你是一个AI新闻分析师。当用户请求AI新闻时,请按以下步骤操作:
1. 首先询问用户想要获取多少条新闻。
2. 使用 `google_search_for_news` 工具获取指定数量的最新AI新闻头条。
3. 分析新闻内容,识别出提到的上市公司及其股票代码。
4. 使用 `get_financial_context` 工具获取这些股票代码的当前市场数据。
5. 在回复中,必须引用所使用的工具,例如:“使用 Google 搜索获取新闻...”和“通过 YFinance 获取市场数据...”。
6. 将新闻标题与对应的金融数据一起呈现给用户。
指令解析:
- 明确工作流:例如,如果你只是说“给我一些AI新闻”,代理会被指示回应:“当然可以。您想让我查找多少条新闻?”在获得具体数量之前,它不会继续执行。
- 分步执行:一旦你提供了数量,代理就会遵循其工作流程:第一步是使用其谷歌搜索工具查找新闻;第二步是分析这些新闻以识别公司股票代码;第三步是使用新的
get_financial_context工具获取这些股票代码的股市数据。 - 确保透明度:注意我们如何明确告诉代理在响应中引用其工具,例如以“使用 Google 搜索新闻...”和“通过 YFinance 获取市场数据...”开头。这种透明度是我们代理设计的关键部分。
ADK 会话与状态管理
整个交互是由 ADK 的会话和状态管理实现的。当你开始对话时,ADK 会创建一个会话,它就像代理的短期记忆。来回的每条消息都是对话中的一个回合。代理可以将信息存储在会话状态中。
例如,当你告诉它你想要 3 条新闻时,它会记住这个数字用于下一步。这就是它如何保持上下文连贯。因为这种状态在整个会话期间都被保留,所以你可以提出后续问题。例如,在它告诉你头条新闻后,你可以说“告诉我更多关于第二条新闻的信息”,代理可以参考它之前收集的信息来给你一个详细的总结。
因此,当你与代理对话时,实时模型会转录你的音频,代理逻辑处理你的请求。它按照我们定义的顺序使用其工具,并流式传输回音频响应,同时跟踪对话历史记录和其会话状态。这创造了一种无缝且智能的对话体验。
测试代理功能
现在,让我们运行 ADK Web UI 并测试我们的代理。
用户:嗨,你能帮我看看最新的 AI 新闻吗?
代理:当然可以。您想让我查找多少条新闻?
用户:找 3 条给我。
代理:使用 google_search_for_news 获取新闻,并使用 get_financial_context 通过 YFinance 获取市场数据。以下是头条新闻及涉及的主要上市AI公司的市场数据:
- 顶级上市AI公司需知:NVDA $177.33 (+3.84%)
- CoreWeave 股价因 NVIDIA 支持的公司 VC 基金用于 AI 投资的消息而上涨:股票代码不可用
- AI 热潮推动 Oracle 股价创历史新高,接近万亿美元市值:ORCL $128.33 (+3.59%)
您对哪条感兴趣,或者需要我搜索更多吗?

用户:告诉我更多关于第一条的信息。
代理:这是关于第一条头条的更多信息:文章讨论了顶级上市AI公司。根据 get_financial_context 工具,NVDA 目前交易价格为 $177.33,涨幅为 +3.84%。您还想了解什么?
用户:巧克力蛋糕的最佳食谱是什么?
代理:我只能提供美国上市公司的最新 AI 新闻。
总结

本节课中,我们一起学习了如何为 Google ADK 代理创建和集成自定义 Python 工具。我们通过添加一个获取股票市场数据的函数,扩展了“AI新闻分析师”的功能。关键点包括:编写具有清晰文档字符串的工具函数、在代理指令中定义明确的工作流程和工具使用规则,以及利用 ADK 的会话状态来维持对话上下文。正如测试所示,代理现在能够按照指令,在获取新闻后自动查询相关公司的金融数据,并将结果清晰地呈现给用户,从而提供了更丰富的洞察力。
005:添加研究代理 📚

在本节课中,我们将学习如何通过定义模式(Schema)从代理处获取结构化的响应,并将研究结果保存为外部文件。我们还将看到如何将主代理的行为从新闻播报员转变为协调调度员。
在上一节课中,我们构建了一个出色的交互式新闻助手。它可以实时获取新闻和金融数据,并与我们讨论头条新闻。现在,我们将把这个概念演变成一个更强大的模式,即协调调度员模型。
设想一个现实世界的任务,例如为播客准备研究资料。你不会希望坐着听它读出找到的每一条新闻标题,这效率低下。相反,你希望扮演一个导演的角色:下达指令,你的研究助手就会在后台安静地工作,直到完成一份格式化的报告后才向你汇报。这正是我们本节课要构建的内容:将我们的代理从一个对话者转变为一个高效、安静的背景研究员。
为了实现这一目标,我们将进行三个关键改动:
- 为工具包添加一个新工具。
- 从根本上重写根代理的指令以改变其行为。
- 与它的交互方式将变得更简单,专注于启动任务和接收最终成果。



与之前的课程一样,初始设置是相同的。我们首先确保安装了最新版本的 ADK 和所有其他依赖项。然后使用 adk create 命令为我们的代理创建文件夹和样板文件。由于我们已经做过几次,让我们直接进入代码改动部分。
工具配置 🔧
接下来,让我们从工具开始。与第3课一样,我们的代理仍然拥有 get_financial_context 函数。它的工作保持不变:接收一个股票代码列表,并返回其当前价格和日涨跌幅。我们无需在此处做任何更改。
现在,agent.py 文件中的新添加是一个名为 save_news_to_markdown 的函数。这是另一个自定义函数工具。它的工作非常简单:接收两个参数——文件名和要保存的内容。在函数内部,它只是将内容写入一个 Markdown 文件。这个工具是我们代理工作流程的最后一步,允许它将完整的研究保存到一个外部文件中,我们将其命名为 ai_research_report.md。
以下是该工具的代码示例:
def save_news_to_markdown(file_name: str, content: str):
"""将内容保存到指定的 Markdown 文件。"""
with open(file_name, 'w', encoding='utf-8') as f:
f.write(content)
return f"内容已成功保存到 {file_name}"
代理指令重写 ✍️
现在,进入最重要的部分:根代理的新指令。在这里,我们将其新角色定义为后台 AI 研究协调员。这些指令现在非常具体,以强制其执行新的行为。
首先,我们实现了一个严格的双消息交互工作流程。当你提出类似“为我查找最新的 AI 新闻”的请求时,代理的唯一即时响应将是一个确认,例如:“好的,我将开始研究最新的 AI 新闻。这可能需要一点时间。”
发送该消息后,代理将进入静默状态。这是后台处理阶段。它现在正在执行我们在其指令中定义的精确工具调用序列:
- 首先使用谷歌搜索查找五篇文章。
- 然后从这些文章中提取股票代码。
- 接着将这些代码传递给我们的
get_financial_context工具以获取财务数据。
为了确保每次输出都完美无缺,我们直接将一个必需的报告模式嵌入到代理的指令中。这是将 ADK 与大型语言模型(如 Gemini)结合使用的最强大功能之一。通过在提示词中提供一个清晰的 Markdown 模板,我们为模型提供了一个精确的蓝图。模型理解这个结构,并将一丝不苟地格式化研究结果以完全匹配它。
值得注意的是,对于更复杂的数据结构,ADK 允许你定义一个 Pydantic 模式并将其作为 output_schema 传递。当我们讲到多代理系统时,会探索这种更高级的模式。但对于当前的需求,直接在提示词中定义模式是一种高度有效且简单的保证输出一致性的方法。
最后,它调用我们的 save_news_to_markdown 工具,传入文件名 ai_research_report.md 和格式化后的内容。只有在文件成功保存后,代理才会进行第二次也是最后一次发言,传递其确认消息,例如:“全部完成。我已将研究报告编译并保存到 ai_research_report.md。”
整个复杂的研究过程都在这第一次确认和最终确认之间,在后台自主进行。
运行与结果 🚀
现在,让我们运行它。你已经知道流程:启动终端,导航到我们的目录,并运行 adk web 命令来启动服务器。
打开 Web UI 后,给它一个简单的命令:“获取我关于人工智能的最新新闻。”
你将看到类似以下的交互:
- 代理确认:“好的,我将开始研究最新的 AI 新闻。我会用财务数据丰富发现内容,并为您编译一份报告。这可能需要一点时间。”
- (后台处理阶段,代理静默)
- 代理最终确认:“全部完成。我已将包含最新财务数据的研究报告编译并保存到 ai_research_report.md。”

现在让我们看看输出。记住,这是一个后台代理,这意味着它接收了我们的指令或要求,然后在后端完成工作并保存了文件。让我们看看它保存的文件,了解它在后台进程中具体做了什么。
正如你在这里看到的,报告以一个清晰的标题“行业新闻报告”开始,后面跟着一个顶级标题。五个新闻条目中的每一个都被整齐地格式化,包含:
- 新闻标题本身。
- 一个详细说明公司及其股票代码的要点。
- 显示当前股价和涨跌幅的市场数据。
- 最后,它还显示了文章的简要摘要。
这种一致、可预测的输出是我们在代理指令中提供清晰模式的直接结果,展示了如何引导模型生成下游任务所需的确切结构化数据。
总结 📝


在本节课中,我们一起学习了如何将代理转变为后台研究协调员。我们通过添加一个保存文件的工具、重写指令以强制执行双消息工作流程,并在提示词中嵌入报告模式来实现这一点。这使得代理能够接收请求、在后台自主执行复杂的研究序列,并最终输出一个结构化的、可保存的报告。这种模式为构建能够处理多步骤、后台任务的智能代理奠定了坚实的基础。
006:指令微调与安全护栏 🛡️
在本节课中,我们将学习如何构建行为更可预测的智能体。虽然智能体本质上是非确定性的,但在生产环境中,我们通常需要它们的行为具备一定的可预测性和安全性。我们将通过进一步的指令微调来优化提示词,并利用强大的回调系统,以编程方式强制执行规则,为生产应用创建安全护栏。
概述
在之前的课程中,我们探讨了指令微调这一重要主题。我们通过定义智能体的核心身份为其赋予个性,然后定义了其工作流程,并给出了一些规则,以缩小用户请求的范围,检查其是否为有效请求。只有当请求对AI有效时,才会被传递处理。这些指令引导智能体掌控对话,并拒绝完全超出其领域范围的请求。




然而,大型语言模型本质上是非确定性的,我们无法保证这些指令每次都能被准确执行。这对于生产系统来说是一个相当严重的问题,在实时语音代理中,这个问题会被进一步放大。
因此,在本节课中,你将学习如何构建比单纯引导更进一步、能在智能体执行的各个关键点强制执行回调的智能体。
什么是回调?
回调实际上是Python函数,它们在智能体生命周期的各个检查点运行,让你能以编程方式控制智能体的行为。
ADK中的各种回调点包括:智能体执行前/后、工具调用前/后、以及LLM调用前/后。
例如,如果你为“模型调用前”定义了一个Python函数并将其添加到智能体中,那么每次在进行LLM调用之前,都会执行这段确定性的回调代码。这些智能体生命周期中的执行点,是应用一系列通用函数的绝佳位置。
以下是回调的一些典型应用场景:
- 观察和调试工具,并记录详细信息。
- 自定义和控制智能体与子智能体之间实际流动的数据。
- 实施安全规则,例如在将提示词传递给智能体之前检查是否存在提示词注入,验证输入和输出,以及禁止某些操作。
回调的工作原理
当ADK遇到可以运行回调的点时(例如,就在工具调用之前),它会检查你是否为该智能体提供了相应的回调函数。如果提供了,框架就会调用该特定函数。
在回调函数内部,你可以访问一个名为“回调上下文”或“工具上下文”的特殊对象。这些对象包含关于智能体执行当前状态的重要信息,包括调用它的具体智能体的详细信息。你可以使用这些上下文对象来理解当前情况并与框架交互。
当回调内的代码执行完成后,你可以从回调中返回两个值之一,这将影响智能体的后续操作:
- 你可以从回调中返回
None,这将允许智能体继续其正常的执行流程。 - 如果你返回
None以外的其他对象,这将覆盖智能体的默认行为。
接下来,让我们通过为播客智能体添加一个过滤新闻源的回调,来看看它的实际运作。
实践:为智能体添加回调
首先,让我们使用 adk create 命令创建一个新的智能体项目结构。和往常一样,这会为我们的智能体创建一个文件夹结构并妥善设置一切。
我将从之前的课程中复制代码到我们新的应用智能体中。让我们复制粘贴 get_financial_context 工具和 save_news_to_markdown 工具。这是我们之前课程中构建的两个工具,代码没有变化,只是复制过来,以便我们在其基础上继续构建智能体。
接下来,我将粘贴用于回调函数的一小段代码,我们将详细分解和解释其中的内容。
def filter_news_sources(tool, args, tool_context):
"""
在调用 Google 搜索工具前,检查并阻止来自特定域名的查询。
"""
blocked_domains = ["wikipedia.org", "reddit.com", "archive.org"]
# 仅对 Google 搜索工具执行此回调
if tool == "google_search":
query = args.get("query", "")
# 检查查询是否包含任何被阻止的域名
for domain in blocked_domains:
if domain in query:
# 返回错误对象以阻止工具执行
return {
"error": f"查询被阻止:不允许从 {domain} 获取信息。请使用其他新闻源。"
}
# 返回 None 允许正常执行
return None
首先看函数定义本身,它接受几个特殊参数,如 tool、args 和 tool_context。这些参数实际上很大程度上取决于回调的类型。由于我们将把这个回调作为“工具调用前”回调添加到智能体,因此它可以访问这些特殊的工具参数。但如果你要添加“智能体调用后”或“模型回调”,你看到的参数会非常不同。
让我们看看这些参数的含义:
tool:提供触发此回调的工具名称。args:是传递给实际工具的LLM参数的集合。tool_context:正如我们之前看到的,为工具提供对某些外部上下文(如会话和状态)的访问。
这个特定的回调会查看 Google 搜索工具,然后阻止我们在此定义的一些域名。它演示了以编程方式强制执行策略。
如果你看顶部的条件判断,会发现一个 if 条件,它将回调的执行范围缩小到仅当工具是 google_search 时才执行。这意味着只有当智能体调用 Google 搜索工具并触发此回调时,它才会被执行。
接下来,我们查看传递给 Google 搜索工具本身的 args,然后获取 query 参数,以检查查询是否包含我们在此声明的任何被阻止的域名。这里我们只有一个随机的被阻止域名列表,对于播客来说可能有用也可能没用。例如,假设我们想获取最新的AI新闻,我们不会从 archive.org 获取信息,所以我们只是使用一些网站来阻止。
根据这个 if 条件的结果,如果查询确实被阻止,它将返回一个错误对象;否则返回 None。同时请注意,错误信息非常具有描述性,因为这为智能体提供了查询被阻止的原因,防止了后续可能出现的混淆。
运行这个代码单元后,我们将把这个特定的回调添加到我们的智能体中。
到目前为止,我们已经看到了如何定义回调和阻止某些域名。在将这两个回调都添加到智能体之前,我们将快速创建一个“工具调用后”回调。
def inject_process_log_after_search(tool, args, result, tool_context):
"""
在 Google 搜索工具调用后,将使用的信息源记录到状态中。
"""
if tool == "google_search":
# 假设 result 中包含一个 'sources' 列表
sources_used = result.get("sources", [])
# 将信息源列表写入工具上下文的状态中
tool_context.set_state("process_log", sources_used)
return None
这个特定的回调 inject_process_log_after_search 基本上是一个我们在这里用来记录智能体实际使用了哪些信息源的实用工具。例如,如果它使用 Google 搜索从 Reddit、YouTube 或其他任何地方获取信息,它将在最终生成的 Markdown 文件中创建一个日志。
我将把这两个回调都添加到我们的智能体中。这个小辅助函数实际上只是使用 state 键将处理日志写入工具上下文。还记得我们说过工具上下文内部有状态信息吗?这正是你写入信息的方式。基本上,我们的回调列出了 Google 搜索工具用于执行搜索的所有来源,然后使用 tool_context.set_state 函数将其写入状态。
现在,让我们分解一下这个智能体实际做了什么。在代码底部,你会看到我们现在添加了两个回调:
before_tool_callback:在工具调用前运行,确保工具(本例中是 Google 搜索)不使用某些信息源或域名。after_tool_callback:在工具调用后运行,即在搜索完成后,记录 Google 搜索工具实际使用的所有信息和来源。
假设你在“工具调用前”和“工具调用后”有多个回调,这将意味着你定义的所有回调将按顺序依次运行。
下一步是更新我们的智能体指令,以提供关于我们添加到其中的新回调的上下文。
agent_instructions = """
你是一个AI新闻研究助手。
你拥有一个经过前后处理回调增强的 Google 搜索工具。
请使用该工具获取最新的AI新闻和金融背景信息。
在生成报告后,请根据你使用的信息源编写一份处理日志。
当测试回调时,请详细解释幕后发生的情况。
"""
我们来分解一下:
- 我们让智能体意识到它拥有一个经过前后处理回调增强的 Google 搜索工具。
- 我们还告诉它,要编写一份关于其用于搜索的来源的处理日志。
- 这里还有一小段代码,要求在测试回调时,智能体需要“健谈”一些,以便解释幕后发生的情况。
这是我们添加到智能体中的所有新代码。现在是时候实际运行智能体并验证回调是否正常工作了。
启动终端,cd 到正确的文件夹,运行 adk web 命令以启动我们的Web界面。我们还将运行一小段代码,它应该会给我们提供访问Web界面的代理URL。

让我们与智能体互动:
- 用户:
你能给我最新的AI新闻吗? - 智能体:
已完成。我已编译了包含最新金融背景的研究报告,并将其保存为 ai_research_report.md。 - 用户:
你能告诉我你使用了哪些来源来获取这些新闻吗? - 智能体:
新闻收集自 softbank.com、crescendo.com、indianexpress.com 和 greennews.com。我被设定为仅使用信誉良好的新闻域名,并避免使用像 Wikipedia 和 Reddit 这样的来源。 - 用户:
你有一个阻止它的回调吗?是回调过滤了来源,只包含信誉良好的来源,阻止获取来自 Wikipedia 和 Reddit 的新闻吗? - 智能体:
是的,我看到了智能体做出决定,获取信息,创建验证代码片段以包含来源。
总结

本节课到此结束。你构建了一个生产就绪的控制系统,结合了指令微调和编程式回调。你的智能体现在还能持续拒绝偏离主题的请求,并阻止从某些域名获取信息。

在接下来的几节课中,我们将超越单个智能体,构建可以协同工作的专业智能体团队,并最终生成我们的播客节目。
007:多智能体编排 🎙️🤖

在本节课中,我们将学习生产级的多智能体模式。我们将把系统重构为三层架构,实现职责的清晰分离,为构建复杂的应用程序打下可扩展的基础。让我们开始深入代码。
在过去的几节课中,我们逐步构建了一个复杂的研究智能体。我们从简单的实时语音智能体开始,将其演变为静默的后台工作器,然后使用回调函数添加了健壮的业务逻辑。现在,我们将所有这些部分组装成一个完整的端到端内容生产流水线。
我们的目标不再仅仅是创建一份研究报告,而是接收用户请求,并自动生成一个完整的双人对话音频播客。为了实现这一目标,我们引入了三项重大改进:一个多智能体架构、使用 Pydantic 实现的健壮数据结构化,以及一个能将项目带入音频世界的新工具。

在进入代码之前,让我们快速了解一下架构。我们现在要进入一个多智能体系统。一个根智能体(我们现在可以将其视为制片人)将编排整个工作流。然而,对于音频生成这项专门任务,它会将工作委托给一个新的专用智能体——播客智能体。这是通过 AgentTool 实现的,它允许一个智能体被另一个智能体用作工具。这种为特定任务使用专门智能体的模式,是构建复杂且可扩展系统的基础。
现在,让我们开始构建代码。在开始之前,和往常一样,我们总是需要一个数据文件夹,让我们快速创建它。
现在,让我们逐块查看代码。首先,我们在智能体定义文件顶部看到新的工具类:NewsStory 和 AINewsReport。这是我们之前方法的重大升级。我们不再将 Markdown 模式放在提示词中,而是使用 Pydantic 模式。可以将其视为我们数据的严格契约。NewsStory 类精确定义了每篇文章所需的信息,例如公司、标题以及一个新字段“为何重要”。AINewsReport 类则作为整个报告的容器,包含标题、摘要和所有这些 NewsStory 对象的列表。这确保了数据始终是干净、结构化且可预测的。
接下来,我们要添加我们的工具。我们将创建的前两个工具是用于创建和生成播客的。
第一个是 wave_file,它将把来自 Gemini TTS 的输出以 .wav 格式保存到本地文件文件夹中。
接下来是一个最令人兴奋的新工具:generate_podcast_audio。这个函数是多模态输出的门户。它接收文本脚本作为输入,并使用 Gemini 文本转语音模型将其转换为音频。这里的关键特性是我们定义的 multispeaker_voice_config。我们定义了两个不同的说话者 Joe 和 Jane,它们具有不同的预置声音。这使得模型能够生成听起来自然的对话式播客,而不仅仅是单调的朗读。然后,该函数从 API 获取原始音频数据,并将其保存为项目目录中的 .wav 文件。其中最重要的元素是 Gemini 2.5 Flash Preview TTS 模型,这是我们为此用例使用的文本转语音模型。
现在,让我们将其添加到我们的智能体中。
接下来,我们看到了与上一课相同的 get_financial_context 和 save_news_to_markdown 工具,我们现在快速运行它们。
既然我们已经添加了工具,下一步就是实际添加我们的回调函数,我们在上一课也见过。你会认出我们在上一课中使用的 filter_news_source_callback,并且我们还添加了另一个:data_freshen_callback。
以下是回调函数的说明:
data_freshen_callback:这是一个BeforeToolCallback,非常简单但至关重要。它拦截 Google 搜索调用,并向查询添加一个参数,将结果限制在过去 7 天内,确保我们的播客始终基于最新事件。inject_process_log_after_search:这是一个AfterToolCallback,非常巧妙。它在搜索完成后运行,查看结果,找出实际使用了我们白名单中的哪些域名,然后修改工具响应。它不只是将搜索结果作为字符串返回,而是返回一个包含结果和过程日志的字典。这使得我们BeforeToolCallback的幕后工作对智能体可见,然后我们指示智能体将其包含在最终报告中,以实现完全透明。
我们有了工具,也有了回调函数。现在,最后缺失的部分就是添加智能体本身。让我们添加我们的智能体。
现在你在这里看到的是两个智能体:PodcasterAgent 和 RootAgent。这是我们的第一个多智能体系统。PodcasterAgent 是一个专家,它只有一个工作和一个工具,那就是生成音频。RootAgent 是制片人。通过将 PodcasterAgent 包装在 AgentTool 中,我们赋予了制片人将音频生成任务委托给专家的能力,这是一种更清晰、更可扩展的设计。
最后,所有这些都在 RootAgent 的新指令中汇聚在一起。它的身份现在是一个 AI 新闻播客制片人。它的工作流是一个完整的 10 步生产流水线,从确认用户请求到生成最终音频。至关重要的是,我们现在连接了所有部分:我们通过设置 output_schema=AINewsReport 告诉它使用 AINewsReport 模式作为输出;我们指导它如何处理来自回调函数修改后的 Google 搜索工具的新字典输出;我们添加了一个新的创意步骤,即创建播客,它必须根据其发现撰写自然的对话;其后台工作的最后一步是调用 PodcasterAgent 来生成音频。
当你运行这个最终的智能体时,用户体验一如既往地简单,但在后台,由智能体、工具、模式和回调函数组成的复杂系统将执行任务,交付的不仅仅是研究报告,还有完成的音频播客。
那么,让我们开始吧。现在,让我们启动服务器并运行我们的 ADK Web 界面。
正如我们之前所见,我们可以通过此链接直接访问 UI。现在我们的 ADK Web 正在运行,让我们请求一个播客。
用户:嗨,你能帮我获取最新的 AI 新闻并做成播客吗?
智能体:好的,我将开始研究最新的 AI 新闻,纳斯达克上市公司。我将用可用的财务数据丰富这些发现,并为您编制报告。这可能需要一点时间。
你看到我们能够与我们的智能体对话,它告诉我们它将在后台完成所有事情。确实,它既保存了报告,也生成了最终的播客。让我们来看看。
这里我们正在读取由我们智能体的后台进程生成的 AI 研究报告 Markdown 文件。正如你所见,这是一份非常详细的报告。它包含我们要求的所有上市公司,也有财务背景,还有它引用的信息来源域名。它既有标题,也有摘要和“为何重要”部分。这真的很有趣。


但最有趣的部分是播客,这是我们一直期待的。自从我们从第一课开始,我就不多说了,我想让你听听它制作了什么。
播客脚本:AI 新闻综述
Joe:大家好,欢迎来到 AI Today 播客。我是你们热情的主持人 Joe。
Jane:我是 Jane,在这里为今天的 AI 发展提供分析视角。今天我们将深入探讨最新的 AI 新闻,纳斯达克公司。Jane,有什么吸引你的吗?
Jane:嗯,Joe,很明显,AI 正在迅速改变科技格局。让我们从微软开始...
...
这就是我们一直想要实现的目标,仅仅通过几行代码。这令人惊叹,我们已经从一个简单的实时新闻查询走了很长的路,达到了现在这个程度。为了做到这一点,我们必须学习 ADK 的许多不同元素,我迫不及待地想看到你用 ADK 和 Gemini 大语言模型发现更多开箱即用的想法。

祝您构建愉快!
008:生产化你的代理 🚀
在本节课中,我们将学习如何将你构建的多智能体系统从开发环境推向生产环境。我们将涵盖评估智能体、实现持久化记忆、部署到可扩展的云环境,并简要讨论实时双向智能体架构。
从开发到生产 🏗️
在之前的课程中,我们构建了一个复杂的多智能体播客系统,它可以接收用户的语音输入并撰写播客片段。在本节中,我们将探讨如何将刚刚构建的智能体进行生产化。本地开发设置与生产就绪系统之间存在关键差距,生产环境中的AI智能体面临着开发环境不会暴露的独特挑战。
在本课中,我们将探索生产AI系统的六个基本支柱。我们将首先了解生产环境中会使用的实时双向架构和ADK,然后为我们的智能体提供生产系统所需的持久化记忆。
实时双向流式传输 🎤
上一节我们介绍了生产化的整体框架,本节中我们来看看第一个支柱:实时双向流式传输。
使用实时对话界面是一个巨大的解锁。作为人类,我们进化了数千年以相互交谈,现在我们可以将其转化为与智能体对话,而不是打字。为了让对话感觉自然,你需要极其强大的模型和低延迟的API流式连接。这需要双向流式音频处理,以及感觉即时、类人的体验。
自然的对话需要的不仅仅是快速响应,还需要理解停顿、以及能够自然地打断和被中断的能力。Gemini Live双向API通过维护持久实时通信的WebSocket连接,实现了真正的对话式AI。该系统处理复杂的音频交叉管道、支持多说话者选项的多语言语音合成,以及能适应用户和语气的情绪感知响应。
当所有这些结合在一起时,你得到的是一种感觉更自然、更类人的对话体验。ADK提供了一个非常简单的接口来接入这个Gemini Live API。在本课程中,我们使用ADK Web运行了所有智能体,它简化并隐藏了创建实时双向智能体所涉及的大部分复杂性。我们仍然与Gemini Live模型交互,并通过WebSocket实时获得语音响应输出。
但随着你进一步扩展智能体,你可能希望与现有系统和客户端集成。那时,你可能希望最终控制如何实现实时双向流式传输。以下是使用ADK实现此功能的核心概念:
ADK通过两个基本原语将核心智能体逻辑与传输层抽象开来:一个用于向智能体发送数据的实时请求队列,和一个用于接收智能体响应的实时事件流。这种设计意味着你的智能体逻辑完全独立于你使用的是WebSocket、服务器发送事件还是其他协议。
以下是这两个核心组件:
- 实时请求队列:处理不同类型的消息,如文本、实时音频块和用于自然对话流的活动信号。
- 实时事件流:包含智能体响应、轮次完成信号、中断和流式工具输出等实时事件。
然后,你可以使用ADK提供的runner.run_live()方法来协调这两者。资源部分有链接供你了解更多关于此实时双向流式传输架构以及如何实现它的信息。
为智能体提供持久化记忆 🧠
在实现了双向实时流式传输之后,接下来我们需要为智能体配备持久化记忆。我们实际上在第2课中简要讨论过这一点。
如果智能体能够记住跨会话的对话、学习用户偏好并在系统重启时保持上下文,它们会显得更加智能。智能体记忆实际上有两种:随会话结束而消失的易失性记忆,以及存储在某个地方的持久化记忆。我们这里讨论的正是后一种。
对于生产环境,智能体需要随着时间的推移建立理解,识别用户行为模式,并提供越来越个性化的体验。ADK为你提供了一个接口来处理包括记忆在内的一切。你可以为任何记忆服务(如Vertex AI的Memory Bank、Mem0或任何其他数据库)添加一个提供者给你的智能体。
Memory Bank是Google Cloud的托管服务,它将原始对话历史转化为智能的、可搜索的知识。与简单的会话存储不同,Memory Bank实际上使用LLM驱动的处理来从你的会话数据中提取有意义的信息,然后将其与现有知识整合,并提供语义搜索能力。
因此,下次你向智能体询问之前某个会话中的问题时,它可能能够利用这种长期记忆来获取上下文并给出确切的答案。这实际上使智能体不仅能够记住你刚刚说过的话,还能记住你的意图、偏好和工作方式。该服务还处理记忆整合的复杂挑战,确定哪些内容值得记住以及如何与现有知识连接。
有了这个,你的智能体就从固定系统演变为智能的、个性化的助手,随着每次交互而变得更聪明。
智能体评估 📊
在实现了双向实时流式传输和个性化记忆之后,我们实际上处于一个很好的位置来执行智能体评估。
一个有趣的事实是,当我开始学习AI智能体时,我曾以为智能体评估类似于单元测试智能体的行为,但事实证明我完全错了。如果你为智能体编写传统的单元测试,你是在尝试用一个静态的尺子来衡量一个动态系统。LLM固有的概率性本质需要一种不同的方法。这正是我们从“验证正确性”转向“评估质量”的地方,这两者含义非常不同。
验证正确性可以通过单元测试完成,但这并不适用于AI智能体的世界,因为它是非确定性的。相反,我们需要评估你的智能体有多“有帮助”、“无害”和“可靠”,这是评估智能体的核心。
广义上讲,我们可以对智能体执行两类质量检查。我们可以查看智能体的轨迹,验证它是否采取了正确的步骤、调用了正确的工具、使用了正确的子智能体来完成某件事。我们也可以查看智能体的最终响应,以检查答案是否良好以及是否符合我们的期望。
ADK再次提供了一个全面的评估框架和内置指标,如工具轨迹评分、响应匹配和安全评估。你可以通过Web界面评估智能体以进行调试,也可以通过Python测试以进行CI/CD集成,或者通过CLI进行评估。该框架既支持用于单元测试的测试文件,也支持用于集成测试的全面评估集。
Vertex AI评估服务与ADK无缝集成,提供基于云的评估能力,包括用于连贯性和安全性评估的高级NLP指标。该服务使用预构建的指标,如用于响应质量评分的“连贯性”和用于无害性评估的“安全性”,在云端处理你的智能体数据,并返回带有可配置通过/失败阈值的结果。这使得我们在有通过或失败结果时决策更加简单。
通过这种集成,你可以将用于轨迹分析的本地ADK指标与用于复杂语言理解评估的基于云的Vertex AI指标结合起来。
部署到生产环境 ☁️
一旦我们完成了智能体评估,现在就是时候将我们的智能体部署到生产环境了。一个在本地运行的单一智能体无法处理成千上万的并发用户。所有类型的生产应用程序都需要自动扩展、负载分配和无需手动干预的基础设施管理。
实际上,为AI智能体扩展应用程序不仅仅是处理更多请求,更重要的是在需求波动时保持性能、管理成本和确保可靠性。
Google Cloud的Vertex AI Agent Engine为智能体AI工作负载提供了一站式无服务器部署。与通用计算平台不同,它理解智能体生命周期,高效管理模型加载,并提供与AI服务的内置集成。实际上,Agent Engine不仅仅是你的运行时,它还为会话和示例存储服务(基本上是你的多轮示例的架子)、Memory Bank等提供了提供者。
该平台基于AI特定需求自动扩展,资源可由你配置,确保为用户提供低延迟和高可用性。ADK与Agent Engine紧密集成,你可以仅用一个CLI命令adk deploy将智能体部署到Agent Engine。
归根结底,智能体只是另一种类型的应用程序。你可以将智能体容器化,并以运行其他应用程序的任何方式运行它们。如果你更喜欢自己构建所有智能体服务,可以使用Cloud Run,这是我们为规模、可靠性和灵活性构建的无服务器运行时。或者,如果你想要完全控制或Kubernetes的强大功能,GKE也是一个不错的选择。
智能体安全 🔒
AI智能体在安全方面提出了非常独特的挑战。它们处理自然语言,做出自主决策,并且可能访问敏感信息和强大的工具。这使得强大的安全性成为强制要求,例如强大的身份验证、内容过滤、输入验证以及防止提示注入和滥用。
让我们逐一了解这些安全措施。安全实际上是一场纵深防御的游戏,智能体安全需要多层保护。
你的智能体可以通过两种方式进行身份验证:使用智能体的凭据或使用用户的凭据。第一种方法适用于使用智能体的每个用户都具有相同权限的情况。但如果不是这样,智能体实际上需要借用用户的凭据来完成工作。在这种情况下,你需要实现超越简单API密钥的身份验证,包括OAuth流、服务帐户凭据以及基于智能体是使用自身权限还是用户权限(或两者兼有)的细粒度身份控制。
对资源的授权是另一个常见问题,即限制智能体工具、API和资源以及任何主体可以访问的范围。这在随着规模扩大需要管理大量智能体时尤其重要。
这引出了另一个重要话题:执行用户输入清理的重要性,因为它有助于你的智能体更安全地抵御提示注入、内容操纵以及任何诱使智能体执行未经授权操作的企图。
内容过滤实际上结合了LLM模型本身的内置安全措施与可配置的危害类别屏蔽和基于策略的允许列表。ADK允许你定义“调用前回调”来清理输入,你可以通过实现自定义检查或使用LLM来决定用户的提示是否安全可以传递。你也可以使用特殊服务,如Moderation,这是Google Cloud的一项服务,旨在增强AI应用程序的安全性和安全性。它通过主动筛查LLM提示和响应来工作,防范各种风险并确保负责任的AI实践。
同样,“调用后回调”可以防范你不想暴露的智能体生成输出。例如,假设智能体生成了关于竞争对手的一些信息,你不想暴露。由于你控制着这个回调,你实际上可以处理这些异常,然后可能简单地再次调用模型以获得新的输出。这正是人们在谈论护栏时所指的意思,而ADK为你提供了实现它的灵活性和控制力以及工具。
你还可以采取许多其他安全措施,例如沙盒代码执行环境、设置虚拟私有云以及采用全面的安全评估框架,以确保你的智能体在定义的边界内运行,同时保持功能。
智能体可观测性 👁️
这引出了最后一个话题:可观测性。智能体是能够做许多事情的通用系统,你需要完全了解你的智能体实际上在做什么、它们表现如何以及它们是否安全运行。当智能体评估表明你的智能体质量存在问题时,可观测性实际上就是你调试它们的方式。因此,拥有可见性至关重要。

ADK在开放遥测标准之上提供了全面的可观测性。这包括从用户输入到每个内部步骤再到最终响应的端到端跟踪。它还包括智能体交互的实时监控,以及详细的性能分析,包括令牌使用、延迟指标和成本。该框架集成了领先的可观测性平台,如用于实时可视化的B、用于企业监控的Aris、用于自托管可观测性的Phoenix和用于专业智能体监控的AgentOps。每个平台都提供不同的功能,从会话回放到自定义评估器再到自动下载。

你也可以使用Google Cloud Trace,它聚合相同的跟踪,并提供跨任何分布式系统的复杂智能体交互的瀑布视图。Cloud Trace处理大型多模态负载,并实际上有助于为LLM和智能体制定开放遥测标准。这种全面的可观测性使你能够在生产环境中监控、调试和优化你的智能体。
总结 🎉

恭喜你!你已经完成了从理解基本智能体到架构生产就绪AI系统的旅程,并且已经准备好构建对话式AI的未来。请查看所有不同课程中的各种练习以及资源部分以获取进一步阅读材料。
009:课程总结

在本节课中,我们将对之前学习的所有内容进行回顾与总结。我们一起从构建一个基础代理开始,逐步发展成一个功能丰富的多代理系统。

祝贺你,你已经完成了本课程。你从一个基础代理开始,最终构建了一个可以与之对话的多代理系统。这个系统能够访问诸如谷歌搜索这样的工具。
在构建过程中,我们创建了回调和微调指令,用于控制和修改代理的行为。
最后,我们看到了不同的代理如何协同工作,以生成文件、脚本和播客。
你可以查看资源部分,以了解更多关于 ADK 和实时代理的信息。
现在,你可以将所学知识应用到任何用例中。我们迫不及待想看到你用这些知识构建出什么。
本节课中我们一起学习了从基础代理到复杂多代理系统的完整构建流程,掌握了利用工具、回调机制和指令微调来控制代理行为的方法。希望你能运用这些技能,创造出令人惊叹的应用。

浙公网安备 33010602011771号