第 2 章 Google ADKAgentPython

第 2 章 30 分钟搭第一个 Agent

第 2 章 30 分钟搭第一个 Agent

上一章我们花了一整章讲"为什么是 ADK"。这一章不再讲道理,直接上手。我们把上一章吹过的牛——"代码优先、可调试、可测试、不绑 Google"——全部亲手验证一遍。30 分钟后,你会拥有第一个能真正干活的 Agent:一个能查订单、查退款政策的电商客服。

2.1 开工前的准备

2.1.1 你需要什么

写这本书的前提很简单,你只需要:

  • Python 3.10 或更高版本。ADK 官方要求 Python 3.10+。
  • 一个可用的 LLM。任何模型都行——Gemini、Claude、OpenAI、或者本地跑一个 Ollama。这一章开头先用 Gemini(免费额度足够),后面会演示怎么零成本切换到本地模型。
  • 一个终端。装 Python 包、跑命令行 Agent,都在终端里进行。

不需要的东西也值得说清楚:不需要 Docker、不需要 Kubernetes、不需要任何 Google Cloud 账号。ADK 可以在你自己的机器上独立运行,这也是它"生产优先但不绑架你"的体现。

2.1.2 安装 ADK

安装就一条命令:

pip install google-adk

强烈建议先建一个虚拟环境,避免污染系统 Python:

# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate

# Windows
.venv\Scripts\activate.bat

# 激活后安装
pip install google-adk

装完验证一下版本,确认 2.x:

python -c "import google.adk; print(google.adk.__version__)"

如果输出以 2. 开头,恭喜,你已经在用最新的 ADK 2.0 了。本书所有代码都基于 2.0 编写——如果你看到 1.x,先升级。

2.1.3 配置模型:不绑 Google 的第一个证明

ADK 支持非常多的模型。配置方式有两种:

方式一:直接用模型名(适合 Gemini)

如果你有 Google AI Studio 的 API Key,在项目根目录建一个 .env 文件:

GOOGLE_API_KEY="你的_API_KEY"

然后在代码里直接写模型名 'gemini-flash-latest',ADK 的注册表会自动解析到对应的后端客户端。这是"紧密集成"的路径,写起来最省事。

方式二:用模型连接器(适合非 Google 模型)

对于 Google 生态之外的模型,ADK 提供了一组模型连接器(model connector)。最通用的是 LiteLLM 连接器——它能接几百种模型。比如用本地 Ollama:

from google.adk.agents import Agent
from google.adk.models.litellm import LiteLlm

agent = Agent(
    model=LiteLlm(model="ollama_chat/gemma3:latest"),
    name="local_agent",
    instruction="You are a helpful assistant.",
)

跑之前设置环境变量指向本地 Ollama 服务:

export OLLAMA_API_BASE="http://localhost:11434"

文档特别强调:连接 Ollama 要用 ollama_chat 而不是 ollama,否则可能出现无限工具调用循环——这是踩过坑的人留下的经验。

这一章先不纠结模型。为了让例子简单,后面代码默认用 'gemini-flash-latest'。到第 7 章我们会专门讲模型中立,把客服 Agent 从 Gemini 切到 Claude、切到本地 Ollama,体验一把"代码零改动换模型"。

2.2 第一个 Agent:代码优先,不是概念优先

2.2.1 最简 Agent 长什么样

在项目目录里建一个 agent.py,写下这段代码:

from google.adk.agents.llm_agent import Agent


def get_current_time(city: str) -> dict:
    """Returns the current time in a specified city."""
    return {"status": "success", "city": city, "time": "10:30 AM"}


root_agent = Agent(
    model='gemini-flash-latest',
    name='root_agent',
    description="Tells the current time in a specified city.",
    instruction="You are a helpful assistant that tells the current time in cities. Use the 'get_current_time' tool for this purpose.",
    tools=[get_current_time],
)

注意看,这里没有一个 YAML 配置文件,没有一套需要背诵的 DSL,没有几十行初始化模板root_agent 就是一个普通的 Python 对象。

Agent 构造函数的五个关键参数,全部一眼能懂:

参数 作用 类比
model 用哪个模型 你雇的员工的大脑
name Agent 的名字 员工工号
description 这个 Agent 是干嘛的 员工名片上的职位描述
instruction 系统提示词,告诉它怎么干活 员工入职培训手册
tools 它能调用哪些工具 员工桌上的工具包

这就是 ADK 的核心设计哲学——Agent 就是一个 Python 对象。你不需要学习新的"智能体语言",你只需要写 Python。

2.2.2 代码优先意味着什么

这一小节我们展开讲讲,为什么"Agent 是 Python 对象"这件事,比它看起来重要得多。

第一,可调试。

想象一个用配置文件驱动的框架:Agent 跑出问题,你打开一个 YAML,盯着几十行声明式配置,试图找出"哪一步让模型产生了幻觉"。而在 ADK 里,Agent 就是一个对象。你可以在 IDE 里给它打上断点,一步步看它怎么决定调用哪个工具、怎么组装回复。调试体验跟调试普通 Python 函数没有区别。

第二,可测试。

既然 Agent 是普通 Python 对象,你就可以像测普通函数一样测它。不需要 mock 掉整个框架,不需要启动什么运行时。后面第 10 章讲评估时你会看到,这种"可测试"的基因,让 ADK 的 Agent 质量评估天然比其他框架顺畅。

第三,可版本控制。

Agent 的逻辑就是代码,代码就进 Git。每次改动都能 diff,每个版本都能回溯。你可以 git blame 一行 prompt,看看是谁在什么时候改的。这在团队协作里是刚需——没有版本控制的 prompt,是生产环境的定时炸弹。

第四,无隐藏魔法。

配置驱动框架有个通病:框架替你做了一堆"聪明的"默认行为,但你不清楚。而代码优先的 ADK 里,你写的每一步都是显式的。Agent 就是对象,工具就是函数,编排就是代码。出问题时,你总是知道去哪找。

这一节是全书的地基。请务必理解:ADK 不把 Agent 当作一个需要特殊对待的黑盒,它把 Agent 当作一流的、普通的代码公民。这个选择贯穿了 ADK 的所有设计。

2.3 三种运行方式:同一个 Agent,三种玩法

代码写好了,怎么跑?ADK 一个 Agent 给了三种运行方式,对应三种不同场景。

2.3.1 命令行运行:adk run

在项目目录(包含 agent.py 的那个目录)运行:

adk run agent

adk run 会起一个交互式命令行,你可以直接跟 Agent 对话:

You: What time is it in London?
Agent: The current time in London is 10:30 AM.
You: How about Paris?
Agent: The current time in Paris is 10:30 AM.

命令行是最轻量的调试方式——不需要起服务,不需要开浏览器,适合快速验证 Agent 逻辑。

2.3.2 网页界面:adk web

adk web --port 8000

然后浏览器打开 http://localhost:8000。你会看到一个聊天界面,可以跟 Agent 对话,还能看到它的思考过程、工具调用记录。这个网页界面是 ADK 的"调试工作台"——但注意,文档明确警告:ADK Web 只用于开发和调试,不用于生产部署

2.3.3 API Server:adk api-server

当你想把 Agent 暴露成一个 HTTP 接口,给前端或者其他服务调用时:

adk api-server --port 8080

这会起一个 REST API 服务,你的 Agent 变成了一个标准 HTTP 端点。这是走向生产的桥梁——到第 8 章部署时,我们会在服务器上跑它。

2.3.4 三种方式怎么选

方式 命令 场景
命令行 adk run 开发期快速对话调试
网页 adk web 可视化调试、看思考过程
API 服务 adk api-server 接入其他系统、走向生产

同一个 agent.py,三种方式都能跑。这是"Agent 是一等公民"的另一个体现——运行方式只是外壳,Agent 本身是纯粹的。

2.4 主线项目启动:云销客服 v0.1

从这一节开始,我们启动贯穿全书的项目。这个项目会陪我们走完后面每一章,从最简单的单 Agent 一路长成生产级系统。

2.4.1 项目背景:云销

「云销」是一家做线上零售的公司,主营电子产品和家居用品。它的用户经常问客服三个问题:

  1. 我的订单到哪了? —— 需要查订单物流
  2. 这个商品能退吗? —— 需要查退款政策
  3. 我要退款,怎么办? —— 需要走退货流程

现在这些请求都靠人工客服处理。我们要用 ADK 做一个自动客服系统,逐步接管这些工作。

2.4.2 云销客服 v0.1:能查订单、查退款政策

v0.1 的目标很简单:一个 Agent,能处理前两类问题——查订单、查退款政策。至于退款流程,那是后面几章的事。

agent.py

from google.adk.agents.llm_agent import Agent
from google.adk.tools import FunctionTool

# ---------- 工具:模拟查订单 ----------
def get_order_status(order_id: str) -> dict:
    """查询订单当前状态。

    Args:
        order_id: 订单号,例如 'ORD-20260901-001'

    Returns:
        包含订单状态和物流信息的字典。
    """
    # 真实场景这里会查数据库/订单系统,这里用 mock 数据
    mock_orders = {
        "ORD-20260901-001": {"status": "已发货", "logistics": "顺丰 SF1234567890,预计 3 天到达"},
        "ORD-20260901-002": {"status": "待发货", "logistics": "仓库备货中"},
        "ORD-20260903-003": {"status": "已签收", "logistics": "已于 9 月 6 日签收"},
    }
    order = mock_orders.get(order_id)
    if order:
        return {"status": "success", "order_id": order_id, **order}
    return {"status": "error", "order_id": order_id, "message": f"未找到订单 {order_id}"}


# ---------- 工具:模拟查退款政策 ----------
def get_refund_policy(product_category: str) -> dict:
    """查询某类商品的退款政策。

    Args:
        product_category: 商品类别,如 'electronics'(电子产品)、'home'(家居用品)

    Returns:
        包含退款政策的字典。
    """
    policies = {
        "electronics": {
            "window": "7 天",
            "condition": "商品未拆封、不影响二次销售",
            "note": "已激活的电子产品不支持无理由退货",
        },
        "home": {
            "window": "15 天",
            "condition": "商品及包装完好",
            "note": "定制类家居商品不支持无理由退货",
        },
    }
    policy = policies.get(product_category)
    if policy:
        return {"status": "success", "product_category": product_category, **policy}
    return {"status": "error", "message": f"未知商品类别 {product_category}"}


# ---------- 云销客服 Agent ----------
yunxiao_agent = Agent(
    model='gemini-flash-latest',
    name='yunxiao_cs_agent',
    description="云销电商客服助手,负责回答订单状态查询和退款政策咨询。",
    instruction=(
        "你是云销电商的客服助手。你的职责是帮助用户查询订单状态、"
        "解答退款政策相关问题。\n"
        "规则:\n"
        "1. 查订单时,先获取订单号,再调用 get_order_status 工具\n"
        "2. 查退款政策时,先确认商品类别,再调用 get_refund_policy 工具\n"
        "3. 如果工具返回 error,如实告知用户,不要编造信息\n"
        "4. 语气友好、专业\n"
    ),
    tools=[
        FunctionTool(func=get_order_status),
        FunctionTool(func=get_refund_policy),
    ],
)

注意这里我们用了 FunctionTool(func=...) 把普通 Python 函数包装成工具。get_order_statusget_refund_policy 就是最普通的 Python 函数——带类型注解、带 docstring、返回字典。

关键点:ADK 会根据函数的类型注解和 docstring 自动生成工具的 schema(也就是告诉模型"这个工具接收什么参数、是干嘛的")。所以你写工具的规范程度,直接决定了模型调用工具的正确率。这也是为什么工具要写清晰的 docstring。

2.4.3 跑起来

在项目目录运行:

adk run agent

然后试试跟它对话:

You: 帮我查一下 ORD-20260901-001 的订单
Agent: 好的,订单 ORD-20260901-001 当前状态是「已发货」,物流信息:顺丰 SF1234567890,预计 3 天到达。

You: 你们电子产品可以退货吗?
Agent: 可以的。电子产品支持 7 天无理由退货,但需要满足:商品未拆封、不影响二次销售。另外注意,已激活的电子产品不支持无理由退货哦。

You: 查一个不存在的订单
Agent: 很抱歉,未找到订单 ORD-999999-000,请确认订单号是否正确。

看最后一条——当工具返回 error 时,Agent 如实告知,没有编造。这就是工具 + 指令约束带来的可靠性。

2.4.4 这个 v0.1 有什么用

你可能觉得:"这不就是个会调用函数的聊天机器人吗?" 没错,但请注意我们打下的地基:

  • 一个能查数据的 Agent:它不再只会聊天,它能访问"系统"(哪怕是 mock 的)
  • 结构化工具接口:订单、政策都是函数,以后换真实数据库,只改函数内部,Agent 逻辑不动
  • 指令约束生效:模型没有自由发挥去编造订单信息,它学会了"查不到就直说"

这就是 Agent 和聊天机器人的分水岭:聊天机器人只会说话,Agent 会办事。后面的每一章,我们都会让"办事"的能力变强:多 Agent 分工、图工作流、人工审批、部署上云。

2.5 踩坑记录:新手最容易犯的错

这一节分享几个真实会踩的坑,帮你省时间。

2.5.1 忘记 .env 或 API Key 配置错误

如果跑 adk run 时报模型认证错误,九成是 API Key 没配好。检查:

  • .env 文件是不是在项目根目录(跟 agent.py 同级)
  • Key 名是不是 GOOGLE_API_KEY(Gemini)或对应的其他名字
  • echo $GOOGLE_API_KEY 确认环境变量真的加载了

2.5.2 工具 docstring 写得太随意

模型的工具调用质量,直接依赖你写的 schema。一个没有 docstring、参数含糊的函数,模型会频繁传错参数。记住:docstring 就是给模型看的说明书,值得认真写。

2.5.3 指令里没写清楚"查不到怎么办"

这是新手最容易漏的。如果你不告诉模型"查不到就直说",它很可能为了讨好用户而编造订单信息——这就是幻觉的温床。我们 v0.1 的指令里明确写了第 3 条规则"如果工具返回 error,如实告知用户",这一条规则就能挡掉大量幻觉。

2.5.4 用 adk web 当生产服务器

adk web 是调试工具,不是生产服务器。文档原话是"只用于开发和调试"。真上生产,用 adk api-server 配合部署方案(第 8 章细讲)。

「为什么 ADK 这样设计」:代码优先 vs 声明式配置

这一章的每个环节,其实都在回答同一个问题:为什么 ADK 坚持代码优先,而不是像很多框架那样用配置文件/DSL 声明 Agent?

我们对比一下两条路线:

声明式配置路线(YAML/JSON 描述 Agent):

  • 优点:看起来"非程序员友好",配置和代码分离
  • 致命缺点:
    • 不可调试:配置不是可执行代码,没法打断点
    • 不可测试:你没法对一份 YAML 写单元测试
    • 不可版本控制:配置虽然能进 Git,但 diff 和 review 都很痛苦
    • 抽象泄漏:配置文件早晚需要表达逻辑,然后你就会被迫在 YAML 里塞表达式、塞模板——声明式变成了"一种难写的编程语言"

代码优先路线(Agent 就是 Python 对象):

  • Agent 的每个部分都是真代码,天然可调试、可测试、可版本控制
  • 逻辑复杂时,直接写 if/else、写循环,不需要发明 DSL
  • 团队协作时,review 的是代码 diff,不是配置文件

ADK 的选择很清楚:Agent 本质上是一个软件系统,而软件系统最好的表达方式就是代码。声明式配置看似降低门槛,实际上是把复杂度藏起来了——藏起来的复杂度不会消失,它会在生产环境加倍还给你。

这不是 ADK 一家之见。回顾第 1 章的框架版图,你会发现一个规律:越是"生产优先"的框架(ADK、Claude Agent SDK),越倾向代码优先;越是"原型友好"的框架,越依赖配置。而 ADK 把代码优先贯彻到了 Agent 定义的每一个角落。

本章小结

  • 安装 ADK 只需一条命令 pip install google-adk,Python 3.10+,不依赖任何云环境
  • Agent 就是一个 Python 对象,五个核心参数:modelnamedescriptioninstructiontools,全部一眼能懂
  • 代码优先是 ADK 的根基:可调试、可测试、可版本控制、无隐藏魔法——这是它和其他框架的根本区别
  • 三种运行方式adk run(命令行)、adk web(网页调试)、adk api-server(HTTP 接口),同一个 Agent 随便切换
  • 模型不绑 Google:可以用 Gemini(注册表解析),也可以用 LiteLlm 连接器接 Ollama/OpenAI/Claude 等任意模型
  • 工具 = Python 函数FunctionTool 包装普通函数,类型注解 + docstring 自动生成 schema
  • 主线项目启动:「云销」客服 v0.1 能查订单、查退款政策,为后续章节搭好了地基

练习

  1. 改造工具:给云销客服加一个 get_shipping_info 工具(模拟查物流详情),并在指令里告诉 Agent 什么时候该调用它。跑通 adk run 验证。
  2. 换模型:如果你有 Ollama,尝试用 LiteLlm(model="ollama_chat/...") 把客服 Agent 切到本地模型,感受"代码零改动换模型"。
  3. 观察工具调用:用 adk web 打开网页界面,故意问一个模棱两可的问题(比如"我要退货"),观察 Agent 是怎么推理、怎么决定调不调工具的。

下一章预告:第 3 章,我们把"工具"和"上下文"讲透。你会看到 Agent 的能力来源不只是工具,还有 Session(会话)、State(状态)、事件流——而这些,正是生产级 Agent 和玩具 Agent 的分水岭。