AI Agent 的潜能究竟有多大?在之前的文章里,我们一起探讨了 ADK 的架构、认知能力,并亲手构建了一个简单的天气智能体。我们知道,一个强大的 AI Agent,绝不能只是“自说自话”的语言模型,它必须能够与真实世界互动,获取最新信息,执行实际操作。而连接外部世界的桥梁,正是 API!
今天,咱们就来一次 ADK Agent 的“超能力注入”!我们将深入探讨如何将五花八门的第三方 API 集成到你的 ADK Agent 中,让它不再是信息孤岛,而是能触达广阔数字世界的“行动派”。我们将不仅仅停留在基础的 API Key 使用,而是要解锁更高级、更标准、更安全、更高效的集成姿势。读完这篇,你对构建一个真正“无所不能”的多功能助手,绝对会有一个全新的理解!
想象一下,你的 ADK Agent 需要知道最新的股票价格,需要帮你发送一封邮件,或者帮你查询附近的餐厅。这些功能,AI 模型本身可做不到,必须依赖外部服务提供的 API。
API 集成的好处,显而易见:
这些优势直接转化为你的 ADK Agent 的竞争力,让它能更快、更便宜、更强大地服务用户。
然而,别以为集成就是儿戏,坑也不少!
把第三方 API 请进门,就像请了一堆有脾气、有规矩的客人。常见的挑战包括:
这些挑战不是孤立的,它们常常相互关联。文档不好可能导致认证错误,进而引发安全问题。所以,对待 API 集成,必须得有个全局观。
成功集成的“武林秘籍”:
为了克服这些挑战,你需要修炼一些“内功”:
很多 API 集成的“最佳实践”,比如错误处理、重试、客户端限流,其本质都是为了构建一个“有韧性”的 API 消费者。ADK Agent 通常在没有人工监督的情况下运行,所以这种韧性至关重要。ADK 的工具设计和开发指南,都应该强调将这些韧性模式构建进 Agent 的逻辑中。这不仅仅是调用 API,更是管理整个交互生命周期及其潜在故障。
在 API 集成这个“巴别塔”中,OpenAPI 规范(OpenAPI Specification, OAS)就像那门统一的“通用语”。对于 ADK 开发者来说,掌握 OpenAPI 是高效、可靠集成第三方 API 的关键。
OpenAPI 规范 (OAS) 是啥?
简单说,OpenAPI 规范就是一套描述 RESTful API 的“蓝图”标准。它用一种大家都能理解(无论是人还是机器)的方式,把 API 的功能、数据格式、请求方法、认证方式等等都给说明白了。
想象一下,你拿到一份详细且标准的 API 蓝图,是不是比对着一份写得乱七八糟的文档或者一点点去“嗅探” API 行为要高效得多?这就是 OpenAPI 的魔力!它能提升清晰度、促进团队协作、还能自动化生成文档和代码!
主要版本和格式:
目前大家用得比较多的是 OpenAPI 3.0.x 版本。文档可以用 JSON 或 YAML 写,YAML 因为更简洁、可读性更好,更受开发者喜爱。
OpenAPI 文档长啥样?
一个标准的 OpenAPI 3.0 文档就像一份 API 的“户口本”,里面包含了各种信息:
字段 (Section) |
描述 |
重要性 |
---|---|---|
|
说明遵循的 OpenAPI 规范版本(比如 3.0.3) |
必须有 |
|
API 的基本信息,比如名字 ( |
必须有 |
|
API 的访问地址 (Base URL) |
可选 |
|
定义 API 的各个具体接口 (Endpoint) 和支持的 HTTP 方法 (GET, POST 等) |
必须有 |
|
放各种可复用的定义,比如数据模型 ( |
可选 |
|
定义全局或某个特定操作需要的安全认证方式 |
可选 |
|
给 API 操作分组,方便生成文档时分类展示 |
可选 |
|
链接到额外的外部文档(用户指南、更详细参考等) |
可选 |
当你跟 ADK 的 APIHubToolset 打交道,或者需要理解第三方 API 时,这张表能帮你快速定位核心信息。
数据类型与 Schema:
OpenAPI 用 JSON Schema 来描述 API 用的数据类型,从简单的整数、字符串、布尔值,到复杂的数组、对象,应有尽有。还能用 format
属性给类型加上更具体的语意,比如 int32
、date-time
、password
等等。
写好 OpenAPI 文档的“心法”:
高质量的 OpenAPI 文档是成功集成的基石。记住这些心法:
OpenAPI 工具生态与代码自动生成:
OpenAPI 的强大之处在于,它不仅能描述 API,还能驱动各种自动化工具,效率瞬间爆炸!
OpenAPI 工具都能干啥?
代码生成工具了解一下:
Swagger Codegen 和 OpenAPI Generator 是这方面的明星工具。它们的核心功能就是“翻译” OpenAPI 文档,生成各种代码。你告诉工具 OpenAPI 文件在哪,想生成哪种语言/框架的代码,它就给你吐出一堆文件。
OpenAPI Generator 是从 Swagger Codegen 分出来的,社区驱动,扩展性更好(支持自定义模板)。知道这些工具,即使 ADK 提供了内置方案,也能帮你理解其原理,或者在 ADK 之外的场景派上用场。这些工具的出现和发展,都说明了减少手动集成工作、提高一致性是业界的强烈需求。ADK 的 APIHubToolset 正是 Google 在自己的生态里满足这个需求的体现。
Google ADK 的 APIHubToolset 与 OpenAPI:
在 Google ADK 里,APIHubToolset 就是那个能把 OpenAPI 规范变成 ADK Agent 可用工具的“魔法师”。它专门用来从 Apigee API Hub(Google 的 API 管理平台)获取 API 的 OpenAPI 规范,然后自动生成 ADK 能理解和调用的工具。
你可以把 APIHubToolset 看作是 ADK 通过 OpenAPI 实现“自动生成工具”的原生路径。它是提升 ADK 开发效率的关键,完全符合业界通过 API 定义自动化客户端接口的最佳实践。生成的工具质量如何,直接取决于输入给 APIHubToolset 的 OpenAPI 规范有多完整、多准确。
让 ADK Agent 调用第三方 API 时,确保通信安全就像给 Agent 的进出通道装上“防盗门”。认证 (Authentication) 是验证“来者是谁”,授权 (Authorization) 是确定“来者能干啥”。
主流 API 认证方法“点将台”:
选择哪种认证方式,取决于 API 的敏感度、用途以及 ADK 的支持程度。
ADK 开发者会遇到各种认证需求的 API,理解这些方法能帮你找到合适的集成方案。ADK 目前支持 API Key 和基于 Token 的方式,并且在积极向 OAuth 2.0 等更安全复杂的方向发展。这也说明 ADK 认识到,如果高安全选项太难用,开发者可能会退而求其次。
API 安全,“十八般武艺”都要会:
选对了认证方法只是第一步,整体安全还得靠多方面配合:
有效的 API 安全是 ADK Agent 和 API 提供商的共同责任。提供商负责服务端安全,但 ADK 开发者必须做好客户端安全,包括凭证管理、安全通信、数据验证,以及优雅处理安全错误(比如 401/403)。如果 Agent 自己在这些方面出了问题,反而可能成为安全隐患。
在 ADK 中,当你配置工具时,特别是使用 APIHubToolset,会涉及到认证参数的配置。ADK 提供了一些辅助函数来帮你处理基于令牌的认证。它也支持使用服务账户,这在生产环境是推荐的方案。ADK 文档里也专门提到构建安全可靠的代理,这和我们讨论的 API 安全原则是一致的。
模型上下文协议(Model Context Protocol, MCP)是 Google ADK 里一个相当酷的创新。你可以把它想象成一个标准化的“适配器”,让 LLM 和它驱动的 AI Agent(比如 ADK Agent)能用一种统一的方式跟各种外部应用、数据甚至工具对话。
理解模型上下文协议 (MCP):
MCP 的核心目标是让 LLM 更容易获取外部信息(资源)、理解如何与用户交互(模板),以及调用真实世界的功能(工具)。它是一种客户端-服务器模式:MCP 服务器暴露能力,MCP 客户端(比如 ADK Agent)消费能力。
ADK 对 MCP 的原生支持是它的亮点之一。这意味着任何符合 MCP 标准的服务器,都可以像“即插即用”一样,将其提供的能力直接注入到 ADK Agent 中,极大地扩展了 Agent 的能力边界。
MCP 代表了一种重要的架构思路:将 Agent 的核心逻辑与具体的工具实现分离开。Agent 只需知道工具的 MCP 接口,而不用关心工具内部是怎么工作的。这比把所有工具逻辑写死在 Agent 里,或者为每个 API 写定制代码要灵活、可维护得多。比如,一个负责复杂数据分析的 MCP 服务器,可以被多个不同的 ADK Agent 复用。
ADK 中 MCPToolset 的角色:
在 ADK 里,MCPToolset
类就是那个连接 MCP 服务器的“桥梁”:
list_tools
MCP 方法)。BaseTool
实例。LlmAgent
,让 Agent 知道“我现在能干这些事了”。MCPToolset
会把这个调用请求转发给 MCP 服务器(通过 call_tool
MCP 方法),然后把结果拿回来给 Agent。MCP 集成“实战”概念:
ADK 文档里通常展示的是 ADK Agent 作为客户端去消费 MCP 服务器提供的工具。这里举几个概念性的例子:
MCPToolset
连接到这个本地服务器进程。把生成的工具加到 Agent 的工具列表里。MCPToolset
连接到这个服务器(可能是远程的)。API 密钥等凭证根据服务器的要求传递。这些例子说明,MCP 通过标准化的接口,让 ADK Agent 能非常方便地接入各种预构建的外部能力。虽然目前 ADK 中使用 MCP 的接口还在完善中,但它的潜力和重要性不言而喻。成功的 MCP 生态需要大家一起努力,不仅要消费 MCP 工具,也要能方便地创建和暴露 MCP 工具。
理论讲了不少,是时候来点实际的!咱们构思一个“智能体工具助手”,看看如何把前面学到的知识串起来,让它同时搞定天气、新闻和空气质量等查询。这次我们模型采用qwen的模型替代gemini模型。
助手的目标与功能:
识别需要的外部服务:
实现认证与安全:
开发 Agent 核心逻辑:
构建这样一个多功能助手,需要的不仅仅是调用 API,更考验 ADK Agent 如何编排这些不同的、可能来自异构平台的外部服务。这凸显了 ADK 灵活的工具架构以及 Agent 内部强大的错误处理和状态管理能力。为每个服务选择合适的集成策略(自定义工具、APIHubToolset、MCPToolset),是 ADK 开发者需要做出的重要架构决策。这个助手实例,恰好能展示 ADK 在整合不同能力时的多面性和适应性。
具体代码如下:
__init__.py文件
#__init__.py
from . import agent
智能体 agent.py文件
import os
import datetime
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools import BaseTool, ToolContext, FunctionTool
from contextlib import AsyncExitStack
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, SseServerParams
# 本地天气API导入
try:
from .weather_api import get_weather_info
except ImportError:
# 当作独立脚本运行时
from weather_api import get_weather_info
# 转换为ADK工具格式
local_weather = FunctionTool(get_weather_info)
# 加载环境变量
load_dotenv()
# 聚合MCP服务Token
DEFAULT_JUHE_MCP_TOKEN = "vu6DADEa78hTp7YehbUidycIOWxk2VKH9ZvfBHLaJ9UHzW"
JUHE_MCP_TOKEN = os.environ.get("JUHE_MCP_TOKEN", DEFAULT_JUHE_MCP_TOKEN)
JUHE_MCP_SSE_URL = f"https://mcp.juhe.cn/sse?token={JUHE_MCP_TOKEN}"
# API密钥设置
DEFAULT_DASHSCOPE_API_KEY = "sk-f227634bb5614dd8b4eb29a95f38d77c" # 请替换为您的实际密钥
DASHSCOPE_API_KEY = os.environ.get("DASHSCOPE_API_KEY", DEFAULT_DASHSCOPE_API_KEY)
async def create_agent():
"""从 MCP 服务器获取工具。"""
common_exit_stack = AsyncExitStack()
# 移除本地MCP工具,只使用远程MCP工具
remote_tools, _ = await MCPToolset.from_server(
connection_params=SseServerParams(
# 重要提示!确保这是你的远程 MCP 服务器路径
url=JUHE_MCP_SSE_URL
),
async_exit_stack=common_exit_stack
)
agent = LlmAgent(
model=LiteLlm(
model="openai/qwen-turbo",
api_key=DASHSCOPE_API_KEY,
api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
),
name='enterprise_assistant',
instruction=(
"""
你是一个功能强大的智能助手,集成了多种工具来提供天气、新闻和空气质量信息。
可用工具:
1. local_weather - 通过本地API查询指定城市的天气信息,参数: {"city": "城市名称"}
2. mcp_weather - 通过MCP服务查询指定城市的天气信息,参数: {"city": "城市名称"}
3. mcp_news_list - 通过MCP服务获取指定类型的新闻列表,参数: {"type": "新闻类型", "page": 页码, "page_size": 每页新闻数量}
新闻类型可选值: top(头条), guonei(国内), guoji(国际), yule(娱乐), tiyu(体育), junshi(军事), keji(科技), caijing(财经), youxi(游戏), qiche(汽车), jiankang(健康)
4. mcp_news_content - 通过MCP服务获取指定ID的新闻详细内容,参数: {"uniquekey": "新闻ID"}
5. mcp_air_quality - 通过MCP服务查询指定城市的空气质量信息,参数: {"city": "城市名称"}
使用指南:
1. 当用户询问天气时:
- 优先使用mcp_weather工具,如果失败则使用local_weather工具
- 参数格式: {"city": "城市名称"},例如 {"city": "北京"}
2. 当用户询问新闻时:
- 先使用mcp_news_list工具获取新闻列表
- 参数格式: {"type": "新闻类型", "page": 页码, "page_size": 每页新闻数量}
- 新闻类型默认为"top"(头条),也可以是"guonei"(国内)、"guoji"(国际)等
- 如果用户想了解某条新闻的详情,使用mcp_news_content工具
- 参数格式: {"uniquekey": "新闻ID"}
3. 当用户询问空气质量时:
- 使用mcp_air_quality工具
- 参数格式: {"city": "城市名称"}
其他注意事项:
- (1) 如果MCP工具调用失败,尝试使用本地工具
- (2) 对用户请求进行准确理解,提取关键信息(如城市名、新闻类型等)
- (3) 提供友好、简洁的回答
- (4) 如遇工具调用错误,向用户解释并提供替代方案
- (5) 不要编造不存在的工具功能
"""
),
tools=[
*remote_tools,
local_weather
],
)
return agent, common_exit_stack
root_agent = create_agent()
API接口文件 weather_api.py
"""
聚合数据实时天气API实现
此模块提供了基于聚合数据的实时天气API接口,用于获取城市天气信息。
"""
import os
import json
import logging
import urllib
import urllib.request as request
import urllib.error as error
from typing import Dict, Any, Optional
from datetime import datetime
from tenacity import retry, stop_after_attempt, wait_exponential
# 设置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("weather_api")
# 从环境变量获取API密钥
#JUHE_WEATHER_API_KEY = os.environ.get("JUHE_WEATHER_API_KEY", "YOUR_API_KEY")
JUHE_WEATHER_API_KEY = "22cfd0c972687aa236d3d8ca148c0232"
class WeatherApiError(Exception):
"""天气API错误的自定义异常"""
pass
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
reraise=True
)
def fetch_real_weather_data(city: str, date: Optional[str] = None) -> Dict[str, Any]:
"""
使用聚合数据API获取指定城市的实时天气数据。
参数:
city: 要获取天气的城市名称
date: YYYY-MM-DD格式的日期(当前版本仅支持实时天气,忽略日期参数)
返回:
包含天气信息的字典
异常:
WeatherApiError: 如果API请求失败
"""
logger.info(f"正在获取{city}的实时天气数据")
# 构建API URL和参数
api_url = 'http://apis.juhe.cn/simpleWeather/query'
params_dict = {
"city": city,
"key": JUHE_WEATHER_API_KEY,
}
params = urllib.parse.urlencode(params_dict)
try:
# 发送API请求
req = request.Request(api_url, params.encode())
response = request.urlopen(req)
content = response.read()
if not content:
raise WeatherApiError("API请求返回空内容")
# 解析JSON响应
result = json.loads(content)
error_code = result.get('error_code')
# 检查API返回的错误码
if error_code != 0:
error_reason = result.get('reason', '未知错误')
raise WeatherApiError(f"天气API错误: {error_code}, 原因: {error_reason}")
# 提取并格式化天气数据
realtime_data = result['result']['realtime']
future_data = result['result'].get('future', [])
# 准备返回数据结构
weather_data = {
"location": {
"name": city,
"country": "中国"
},
"realtime": {
"temperature": realtime_data['temperature'],
"humidity": realtime_data['humidity'],
"info": realtime_data['info'],
"wid": realtime_data['wid'],
"direct": realtime_data['direct'],
"power": realtime_data['power'],
"aqi": realtime_data['aqi']
},
"forecast": []
}
# 如果有预报数据,添加到返回结果中
if future_data:
for day_data in future_data:
weather_data["forecast"].append({
"date": day_data['date'],
"temperature": day_data['temperature'],
"weather": day_data['weather'],
"wind_direction": day_data['direct']
})
logger.info(f"成功获取{city}的天气数据")
return weather_data
except error.HTTPError as e:
logger.error(f"天气API HTTP错误: {str(e)}")
raise WeatherApiError(f"HTTP请求失败: {str(e)}")
except error.URLError as e:
logger.error(f"天气API URL错误: {str(e)}")
raise WeatherApiError(f"URL请求失败: {str(e)}")
except json.JSONDecodeError as e:
logger.error(f"JSON解析错误: {str(e)}")
raise WeatherApiError(f"无法解析API响应: {str(e)}")
except Exception as e:
logger.error(f"获取天气数据时发生意外错误: {str(e)}")
raise WeatherApiError(f"处理天气数据时出错: {str(e)}")
def get_weather_info(city: str, date: Optional[str] = None) -> str:
"""
获取天气信息并格式化为用户友好的字符串。
参数:
city: 要获取天气的城市
date: 要获取天气的日期 (YYYY-MM-DD)
返回:
格式化的天气信息字符串
"""
try:
# 使用真实API获取数据
weather_data = fetch_real_weather_data(city, date)
# 格式化实时天气数据
realtime = weather_data["realtime"]
location = weather_data["location"]["name"]
result = (
f"{location} 实时天气:\n"
f"- 天气状况: {realtime['info']}\n"
f"- 温度: {realtime['temperature']}°C\n"
f"- 湿度: {realtime['humidity']}%\n"
f"- 风向: {realtime['direct']}\n"
f"- 风力: {realtime['power']}\n"
f"- 空气质量: {realtime['aqi']}"
)
# 如果有预报数据,添加到结果中
if weather_data.get("forecast") and len(weather_data["forecast"]) > 0:
future_date = weather_data["forecast"][0]
result += f"\n\n未来天气预报 ({future_date['date']}):\n"
result += f"- 温度: {future_date['temperature']}\n"
result += f"- 天气: {future_date['weather']}\n"
result += f"- 风向: {future_date['wind_direction']}"
return result
except WeatherApiError as e:
return f"错误: {str(e)}"
except Exception as e:
logger.error(f"天气工具发生意外错误: {str(e)}")
return f"抱歉,获取天气信息时发生意外问题: {str(e)}"
if __name__ == "__main__":
# 简单测试
test_city = "北京"
print(f"测试获取{test_city}的天气信息:")
print(get_weather_info(test_city))
# .env
# 不同LLM提供商的API密钥
# 将此文件重命名为.env并根据需要添加你的密钥
JUHE_WEATHER_API_KEY = "**********************"
QWEN_API_KEY = "sk-**********************"
JUHE_WEATHER_API_KEY = "********"
让我们来进行测试一下效果如何:
查询天气
查询新闻
查询新闻内容
说明:MCP服务器采用的是聚合平台的服务。
天聚地合 - 聚合数据_API接口开放平台_API接口大全_免费API数据接口服务
好了,咱们今天给 ADK Agent 来了次彻底的“能力升级”!从第三方 API 集成的基本原理、挑战和最佳实践,到 OpenAPI 这个“通用语”的应用,再到各种 API 认证和安全处理技巧,最后聊了聊 ADK 的“特有武器”——MCP 工具集,并通过一个工具助手实例,将这些知识串联起来。
核心收获:
展望 AI Agent 与 API 集成的未来:
AI Agent 与外部世界的交互只会越来越紧密,API 集成技术也会持续进化。未来我们可能会看到:
LLM 的推理能力、OpenAPI/MCP 这样的标准化协议、以及 ADK 这样的开发框架,这三者的融合,正把我们带向一个令人兴奋的未来:AI Agent 能够越来越自主、越来越智能地发现、集成和编排海量的外部服务。虽然现在很多集成工作还需要我们开发者亲自动手,但这无疑是走向那个动态未来的关键一步。ADK 在这场浪潮中,必将扮演越来越重要的角色。