

在《谷歌 AI 智能体白皮书》第一章中,谷歌团队为大家梳理 AI 智能体的定义,即 AI 智能体并非仅仅是大语言模型的改进,而更是一种全新的业务工具,能够在复杂环境中独立完成任务,并将显著提升企业的自动化与创新能力。
那 AI 智能体是如何独立完成复杂任务的呢?这也就引出了第二章关于「工具」的内容。
为了让 AI 智能体能够具备实时交互、上下文感知,并与外部系统互动,谷歌团队介绍了三种 AI 智能体工具:扩展(Extensions)、函数(Functions)和数据存储(Data Stores)。
今天淼翰实验室就为读者们分享《谷歌 AI 智能体白皮书》的核心内容,如何通过工具,让 AI 智能体完成多样化任务。
工具:我们通往外部世界的钥匙
尽管语言模型在处理信息方面表现出色,但它们无法直接感知或影响现实世界。这限制了它们在需要与外部系统或数据交互的情况下的实用性。从某种意义上说,语言模型的能力仅限于其训练数据中所学的内容。但无论我们向模型提供多少数据,它们仍然缺乏与外部世界实时交互的基本能力。那么,我们如何赋予模型实时、上下文感知的能力,以便与外部系统进行交互?
Functions(函数)、Extensions(扩展)、Data Stores(数据存储)和 Plugins(插件) 是为模型提供这种关键能力的几种方式。
这些工具的名称各不相同,但它们的本质是为基础模型与外部世界之间建立联系。通过与外部系统和数据建立这种联系,我们的智能体能够执行更多样化的任务,并以更高的准确性和可靠性完成任务。例如,工具可以使智能体调整智能家居设置、更新日历、从数据库中获取用户信息,或根据特定指令发送电子邮件。
截至本白皮书发布之日,Google 模型能够交互的主要工具类型有三种:Extensions(扩展)、Functions(函数) 和 Data Stores(数据存储)。通过为智能体配备工具,我们可以释放其潜力,使其不仅能理解世界,还能对其采取行动,从而为各种新应用和可能性打开大门。
扩展(Extensions)
理解扩展的最简单方式是将其视为一种以标准化方式在智能体与 API 之间建立桥梁的工具,使得智能体能够无缝调用 API,而无需关心其底层实现。例如,假设你构建了一个帮助用户预订航班的智能体,并计划使用 Google Flights API 来检索航班信息,但你不确定如何使智能体调用该 API 端点。

图 3:智能体如何与外部 API 交互?
一种方法是实现自定义代码,该代码负责接收用户查询,解析查询以提取相关信息,然后调用 API。
例如,在航班预订场景中,用户可能会说:「我想预订从奥斯汀到苏黎世的航班。」在这种情况下,我们的自定义代码需要从用户查询中提取「奥斯汀」和「苏黎世」作为相关实体,然后尝试调用 API。但如果用户仅仅说:「我想预订到苏黎世的航班」,而未提供出发城市,API 调用将因缺少必要数据而失败,这就需要额外的代码来捕获这些边缘和极端情况。这种方法不可扩展,且很容易在未涵盖的场景中失效。
一种更稳健的解决方案是使用扩展。扩展通过以下方式在智能体与 API 之间建立桥梁:
1. 通过示例教会智能体如何使用 API 端点。
2. 教会智能体成功调用 API 端点所需的参数或参数格式。

图 4:扩展将智能体连接到外部 API
扩展可以独立于智能体构建,但应作为智能体配置的一部分提供。智能体在运行时使用模型和示例来决定是否需要使用扩展,以及选择哪个扩展来解决用户的查询。这凸显了扩展的一个核心优势:其内置的示例类型,使得智能体可以动态选择最适合任务的扩展。

图 5:智能体、扩展和 API 之间「一对多」的关系
与软件开发人员在解决用户问题时选择使用哪个 API 端点类似,智能体/模型堆栈使用一组已知的扩展来决定哪个最符合用户的查询。例如,如果用户希望预订航班,开发者可能会使用 Google Flights API;如果用户想知道离他们最近的咖啡店在哪里,开发者可能会使用 Google Maps API。同样,智能体可以根据用户查询和任务动态选择适当的扩展。
如果你想体验扩展的实际应用,可以在 Gemini 应用中通过路径 Settings > Extensions 启用相关扩展。例如,可以启用 Google Flights 扩展,然后向 Gemini 询问:「显示下周五从奥斯汀到苏黎世的航班。」
示例扩展
为了简化扩展的使用,Google 提供了一些开箱即用的扩展,可以快速导入到项目中并以最少配置使用。例如,Code Interpreter(代码解释器)扩展允许你基于自然语言描述生成并运行 Python 代码。
Pythonimport vertexaiimport pprintPROJECT_ID = "YOUR_PROJECT_ID"REGION = "us-central1"vertexai.init(project=PROJECT_ID, location=REGION)from vertexai.preview.extensions import Extensionextension_code_interpreter = Extension.from_hub("code_interpreter")CODE_QUERY = """Write a python method to invert a binary tree in O(n) time."""response = extension_code_interpreter.execute(operation_id = "generate_and_execute",operation_params = {"query": CODE_QUERY})print("Generated Code:")pprint.pprint({response['generated_code']})# The above snippet will generate the following code.```Generated Code:class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef invert_binary_tree(root):"""Inverts a binary tree.Args:root: The root of the binary tree.Returns:The root of the inverted binary tree."""if not root:return None# Swap the left and right children recursivelyroot.left, root.right =invert_binary_tree(root.right), invert_binary_tree(root.left)return root# Example usage:# Construct a sample binary treeroot = TreeNode(4)root.left = TreeNode(2)root.right = TreeNode(7)root.left.left = TreeNode(1)root.left.right = TreeNode(3)root.right.left = TreeNode(6)root.right.right = TreeNode(9)# Invert the binary treeinverted_root = invert_binary_tree(root)```
代码片段 1:代码解释器扩展可以生成和运行 Python 代码
总结来说,扩展为智能体提供了一种能够感知、交互和影响外部世界的方式。扩展的选择和调用由示例定义,并作为扩展配置的一部分。
函数(Functions)
在软件工程领域,函数被定义为实现特定任务的独立模块,可以根据需要重复使用。当软件开发人员编写程序时,他们通常会创建许多函数来完成不同的任务,并定义何时调用某个函数(如 function_a 或 function_b),以及函数的预期输入和输出。
在智能体的世界中,函数的工作原理非常相似,但此时可以将软件开发人员的角色替换为模型。模型可以根据一组已知函数来决定何时使用每个函数,以及基于函数的规格(Specification)提供所需的参数。函数与扩展的主要区别在于:
1. 模型输出一个函数及其参数,但不会直接调用 API。
2. 函数在客户端执行,而扩展在智能体端执行。
继续以 Google Flights 的示例为例,函数的简单设置可能如图 7 所示:

图 7:智能体如何与外部 API 交互?
函数与扩展的主要区别在于,函数和智能体不会直接与 Google Flights API 交互。那么,API 调用究竟是如何实现的?
对于函数,调用 API 端点的逻辑和执行被转移到智能体的客户端应用程序中,如下面图 8 与图 9 所示。这为开发人员提供了对应用程序中数据流的更细颗粒度的控制。开发人员选择使用函数而非扩展的原因可能包括以下几点:
API调用需要在应用程序堆栈的其他层次完成:例如,调用可能需要通过中间件系统或前端框架,而不是直接由智能体架构处理。
安全性或身份验证限制:API可能未对互联网公开,或者智能体无法访问相关基础设施。
时间或操作顺序限制:某些情况下,智能体无法实时调用API,例如需要批量操作或涉及人为审查的场景。
需要对API响应应用额外的数据转换逻辑:因为这些逻辑是智能体无法执行的。举例来说,某些API端口可能不具备过滤机制,无法限制返回结果的数量。在客户端使用函数可以为开发者提供额外的机会来完成这些数据转换。
开发人员希望在不部署额外API基础设施的情况下迭代开发智能体:函数调用可以模拟API调用的行为,而无需额外的部署。
虽然如图 8 所示两者的内部架构区别较为微妙,但函数调用提供了更强的控制能力,并减少了对外部基础设施的依赖性,因而对于开发者来说是一个有吸引力的选择。

图 8:区别扩展与函数调用的关键是客户端与智能体端的控制权
使用场景
模型可以通过调用函数来处理复杂的客户端执行流程,在这些情况下,智能体开发人员可能不希望语言模型直接管理 API 的执行(如扩展所做的一样)。以下是一个具体示例:
假设一个智能体被训练为旅行规划助手,与用户互动以帮助规划度假行程。目标是让智能体输出一个城市列表,这些城市可以进一步用于中间件应用程序下载图像、数据等内容,以供用户进行旅行计划。用户可能会说:
「我想和家人一起去滑雪,但不知道去哪儿。」
在普通的模型提示下,输出可能如下:
「以下是一些适合家庭滑雪旅行的城市:
克雷斯特德比特(科罗拉多,美国)
惠斯勒(不列颠哥伦比亚,加拿大)
采尔马特(瑞士)」
虽然上述输出包含了所需的数据(城市名称),但其格式不便于解析。通过函数调用,我们可以教模型将输出格式化为更方便系统解析的结构化样式(如 JSON)。给定同样的用户输入,函数的 JSON 输出示例可能如片段 5 所示:
Unsetfunction_call {name: "display_cities"args: {"cities": ["Crested Butte", "Whistler", "Zermatt"],"preferences": "skiing"}}
代码片段 5:用于显示城市列表和用户偏好的函数调用有效负载示例
上述 JSON 是由模型生成的,随后被发送到客户端服务器,用以执行开发者希望的操作。例如,我们可以调用 Google Places API,将模型提供的城市信息用于查找图像,然后将其作为格式化的丰富内容返回给用户。

图 9:显示函数调用生命周期的序列图
图 9 中的示例结果是,模型被用来「填补空白」,为客户端 UI 提供调用 Google Places API 所需的参数。客户端 UI 使用模型在返回的函数中提供的参数来管理实际的 API 调用。这只是函数调用的一个用例,但还有许多其他场景需要考虑,例如:
你希望语言模型建议一个可以在代码中使用的函数,但又不希望在代码中包含凭据信息。由于函数调用并不会实际运行函数,因此你不需要在代码中与函数信息一起包含凭据。
如果你正在运行需要几秒或更长时间的异步操作,这些场景非常适合使用函数调用,因为函数调用本身是一个异步操作。
如果你希望在生成函数调用及其参数的系统之外的设备上运行这些函数,函数调用也非常适用。
关于函数调用,需要记住的一个关键点是:它旨在为开发者提供更大的控制,不仅是对 API 调用的执行,还包括整个应用程序中的数据流。在图 9 的示例中,开发者选择不将 API 信息返回给智能体,因为这些信息对于智能体可能采取的后续行动并不相关。然而,根据应用程序的架构,可能需要将外部 API 调用的数据返回给智能体,以便影响其未来的推理、逻辑和行动选择。
最终,决定权在于应用程序开发者,他们需要根据特定应用的需求选择最适合的实现方法。
函数示例代码
为了实现我们滑雪度假场景中的上述输出,让我们构建每个组件,使其能够与我们的 gemini-1.5-flash-001 模型一起工作。
首先,我们将定义 display_cities 函数,将其作为一个简单的 Python 方法。
Pythondef display_cities(cities: list[str], preferences: Optional[str] = None):"""Provides a list of cities based on the user's search query and preferences.Args:preferences (str): The user's preferences for the search, like skiing,beach, restaurants, bbq, etc.cities (list[str]): The list of cities being recommended to the user.Returns:list[str]: The list of cities being recommended to the user."""return cities
代码片段 6:用于显示城市列表的函数的示例 Python 方法
接下来,我们将实例化模型,构建工具(Tool),然后将用户的查询和工具传递给模型。执行以下代码将生成代码片段底部所示的输出结果。
Pythonfrom vertexai.generative_models import GenerativeModel, Tool, FunctionDeclarationmodel = GenerativeModel("gemini-1.5-flash-001")display_cities_function = FunctionDeclaration.from_func(display_cities)tool = Tool(function_declarations=[display_cities_function])message = "I’d like to take a ski trip with my family but I’m not sure whereto go."res = model.generate_content(message, tools=[tool])print(f"Function Name: {res.candidates[0].content.parts[0].function_call.name}")print(f"Function Args: {res.candidates[0].content.parts[0].function_call.args}")> Function Name: display_cities> Function Args: {'preferences': 'skiing', 'cities': ['Aspen', 'Vail','Park City']}
代码片段 7:构建工具,将其与用户查询一起发送到模型,并允许函数调用执行
总结来说,函数提供了一个简单的框架,使应用程序开发者能够对数据流和系统执行进行精细控制,同时有效地利用智能体/模型生成关键输入。开发者可以选择是否让智能体「参与循环」,通过返回外部数据来影响后续的推理、逻辑和行动选择。这种灵活性使函数成为智能体开发中的强大工具。
数据存储(Data Stores)
将语言模型想象成一个庞大的图书馆,其中存储着它的训练数据。但与不断增加新书的传统图书馆不同,这个图书馆的内容是静态的,仅包含最初训练时获取的知识。这种静态性带来了一个挑战:现实世界的知识是不断变化的。数据存储(Data Stores) 通过为模型提供动态和最新的信息,解决了这一限制,确保模型的回答始终基于事实并保持相关性。
举一个常见场景:开发人员可能需要向模型提供少量额外数据,例如电子表格或 PDF 文件。

图 10:智能体如何与结构化和非结构化数据进行交互?
数据存储允许开发人员以原始格式(如文档、表格等)向智能体提供附加数据,而无需耗费时间进行数据转换、模型重新训练或微调。数据存储将传入的文档转换为一组向量数据库嵌入(vector database embeddings),智能体可以使用这些嵌入提取所需信息,以补充其对用户请求的下一步回答或行动。

图 11:数据存储将智能体连接到各种类型的新实时数据源
实现与应用
在生成式 AI 智能体的上下文中,数据存储通常实现为开发人员希望智能体在运行时访问的向量数据库。虽然这里不会深入探讨向量数据库的细节,但需要理解的是,向量数据库以向量嵌入的形式存储数据,这是一种高维向量或数学表示形式,用于描述提供的数据。
一个广泛应用的数据存储案例是基于检索增强生成(Retrieval-Augmented Generation,RAG) 的应用。这类应用旨在通过为模型提供对各种格式数据的访问,扩展模型知识的广度和深度。这些数据格式包括但不限于:
网站内容
结构化数据:如PDF、Word文档、CSV文件、电子表格等
非结构化数据:如HTML、PDF、TXT文件等

图 12:智能体与数据存储之间的「一对多」关系,可表示多种类型的预索引数据
每个用户请求和智能体响应循环的底层过程通常按照图 13 所示建模:
用户查询被发送到嵌入模型中,为查询生成嵌入向量。
查询嵌入随后通过匹配算法(如 SCaNN)与向量数据库的内容进行匹配。
匹配到的内容以文本格式从向量数据库中检索,并返回给智能体。
智能体接收用户查询和检索到的内容,然后制定响应或执行某项操作。
最终响应被发送回用户。

图 13:基于 RAG 应用中用户请求和智能体响应的生命周期
最终的结果是,应用程序允许智能体通过向量检索,将用户查询与已知的数据存储内容匹配,检索原始内容,并将其提供给编排层和模型以进行进一步处理。下一步操作可能是向用户提供最终答案,或执行额外的向量检索以进一步优化结果。
一个实现 RAG 与 ReAct 推理/规划的智能体的示例交互见图 14。

图 14:基于 RAG 的应用结合 ReAct 推理/规划的示例
工具总结
扩展(Extensions)、函数(Functions)和数据存储(Data Stores)构成了智能体在运行时可用的几种工具类型。每种工具都有其特定的用途,开发者可以根据具体需求选择单独使用或组合使用这些工具。

未完待续…

编译:刘嘉禾
审核:Flora
视觉设计:Luna
责任编辑:刘嘉禾
推荐阅读:









