Google ADK实战:AI Agent工程化开发完全指南

发布时间:2026/9/17 6:08:34
Google ADK实战:AI Agent工程化开发完全指南
AI Agent 开发在过去两年里已经从“调 API 包装一下”进化到了需要工程化体系支撑的阶段。我见过太多团队用脚本攒 Agent前期跑得飞快一旦要加记忆、加多轮工具调用、加多 Agent 协作代码就开始失控。Google Agent Development KitADK的出现算是一个分水岭它把 Agent 运行时的通用复杂度收进了框架里开发者只需要用 Python 对象和函数去描述业务本身。这篇文章不是翻译官方文档而是我从踩坑到上线的一线使用总结。适合理清 Agent 基本概念的入门读者也适合所有在技术选型阶段纠结的工程师参考。1. 先搞清楚 ADK 在 Agent 开发里的生态位1.1 为什么需要一套专门的 Agent 开发工具很多人第一次接触 Agent 开发时第一反应是直接用大模型 API。确实调用一次 Gemini 或者 GPT 的接口并不复杂把 Prompt 塞进请求里返回的 JSON 里就有答案。但一旦你想让模型真正去“做事”比如查数据库、调内部接口、操作文件事情就完全变样了。你需要自己管理工具列表、处理函数调用循环、把多轮对话的状态存下来、还要在模型连续犯傻时做兜底。这些逻辑分散在代码里每个项目都要重写一遍而且每次重写都会踩一遍同样的坑。ADK 解决的正是这个问题。它把 Agent 运行时的通用部分——模型交互、工具调用循环、上下文存储、事件分发——全部内置了。你在代码里只需要关注业务逻辑这个 Agent 要做什么、需要哪些工具、有哪些子 Agent。这是典型的“框架吸收复杂度”的思路和当年 Web 开发从裸写 Servlet 到 Spring Boot 的演进路径几乎一模一样。框架的价值不在于让你少写几行代码而在于把容易出错的部分标准化、可测试化。用我的话说ADK 更像是一个“Agent 应用的操作系统”。它不替你做业务决策但把你跑业务需要的进程管理、资源调度、日志监控这些基础设施全部管起来。这样你的精力就能集中在真正有价值的部分而不是一遍遍调试“为什么工具返回了但模型没理解结果”这种烂问题。1.2 代码优先的设计哲学现在市面上有不少 Agent 构建平台走的是低代码路线拖拽节点、配置画布看起来很美。但真到了要上生产、要接入企业内部的鉴权体系和数据源时低代码平台就非常别扭。ADK 从第一版就明确了代码优先的定位Agent 就是一个普通的 Python 对象通过类、函数和装饰器组合来定义。这种方式有一个非常实际的好处——所有东西都是 Git 可追踪的。Agent 的行为变更、工具函数的修改、提示词的调整全部可以走代码评审流程。我在团队里推行 ADK 时最打动同事的一点就是它能放进现有的 CI/CD 流水线里而不像某些平台那样只能“在线调试”。做技术选型的时候这一点往往是决定性的。代码优先还意味着你可以用 Python 生态里的所有工具pytest 做单元测试、black 做格式化、mypy 做类型检查这些成熟工具链不用重新发明。代码优先的另一个优势是重构成本低。在低代码平台上一个复杂的 Agent 流程改起来基本等于推倒重来。但用 ADK改写一个工具函数、调整一个子 Agent 的 instruction都只是普通的代码修改。对于快速迭代的创业团队来说这个优势在第三周之后就会体现得非常明显。1.3 ADK 与通用编排框架的差异提到 Agent 开发框架很多人会想到 LangChain 或者 LlamaIndex。这些框架确实是先驱但它们的核心问题是把 Agent 的开发模式抽象得过于宽泛什么都能接最后用户自己得拼装一大堆组件才能跑通一个稳定的 Agent。ADK 的格局完全不一样它的底层深度绑定 Google 的 Gemini 模型和 Vertex AI 平台官方支持的开箱即用场景基本覆盖了从原型到生产的所有环节。这种“偏科”反而成了优势。因为模型、框架、部署平台是一条龙设计很多跨层的问题在源头就规避了。比如工具调用的 schema 生成、函数返回结果的结构化解析、多轮对话中的状态传递这些在 LangChain 里经常需要手工拧螺丝的地方ADK 默认就处理好了。如果你已经确定用 Gemini 系列模型ADK 几乎没有理由不用。当然ADK 也不是万能药。它目前对非 Google 模型的支持相对有限如果你的团队深度依赖 OpenAI 或者其他模型需要做一些额外的适配工作。但从我个人的工程经验来看与其追求一个“啥都能接”的框架不如选择一个垂直整合度高的工具链尤其在 Agent 这种复杂度高的场景里深度整合的价值远大于广度兼容。2. 搭建开发环境从安装到跑通第一个 Agent2.1 环境准备与项目骨架先说一下环境要求。ADK 本质是一个 Python 包所以只要你的开发机能跑 Python 3.10 以上版本基本都没有安装障碍。我习惯用虚拟环境隔离依赖避免和系统 Python 打架。python -m venv .venv source .venv/bin/activate pip install google-adk安装完成后可以通过一行命令验证是否成功python -c import google.adk; print(google.adk.__file__)如果能看到输出路径说明安装没问题。接下来你还需要一个 Google AI Studio 的 API Key在环境变量里配置好export GOOGLE_API_KEY你的_API_Key建议把 Key 放进项目根目录的.env里管理不要硬编码在代码中。项目结构我一般是这么组织的my_agent/ ├── agent.py # Agent 定义入口 ├── tools/ │ ├── __init__.py │ └── weather_tool.py # 自定义工具 ├── .env # 环境变量 ├── requirements.txt └── README.md这个结构看起来简单但足够支撑到中型项目。等 Agent 数量变多以后我会在tools/下按业务域拆分子目录比如tools/payment/、tools/order/。保持工具和 Agent 分离有一个好处工具函数可以被多个 Agent 复用不至于出现“同一个功能在十几个文件里各写一份”的惨状。2.2 手写一个最小可运行的 Agent官方文档里的例子会稍微复杂我这里直接从最小可运行版本开始。先写一个最简天气 Agentfrom google.adk.agents import Agent def get_weather(city: str) - dict: 查询指定城市的天气信息。 Args: city: 城市名称比如上海。 Returns: 包含天气、温度的字典。 if city 上海: return {city: 上海, weather: 多云, temperature: 24} return {city: city, weather: 晴, temperature: 26} agent Agent( nameweather_agent, modelgemini-2.0-flash, instruction( 你是一个天气助手。当用户询问天气时 必须调用 get_weather 工具并根据工具返回结果组织回答。 ), tools[get_weather], )然后直接运行adk run weather_agent启动后你会在终端看到一个交互式对话界面。输入“上海天气怎么样”模型就会调用工具并返回结果。第一次跑通的时候那种“模型自己决定调我的函数”的感觉还是很奇妙的。这里有几个细节值得展开说一下。第一model参数直接决定了推理能力。日常调试我常用gemini-2.0-flash速度快、成本低跑通逻辑足够用。等到要上线了再根据业务复杂度换更强的版本。第二instruction不是装饰品。它其实是这个 Agent 的“岗位说明书”写得越清晰模型的意图判断越准。我见过不少同学随手写一句“你是一个助手”结果模型在多种工具之间反复横跳本质就是指令太模糊。第三点也是新手最容易踩的坑——工具函数的 docstring。在 ADK 里工具函数的 docstring 会被拼接到模型的上下文里模型是靠这段文本来理解“这个函数是干嘛的、参数是什么意思”的。所以 docstring 一定要写清楚参数含义和返回结构否则模型就可能传错参数甚至压根不调用工具。2.3 运行时日志与调试技巧跑通第一个 Agent 之后我最推荐你立刻做的一件事情是打开日志。ADK 默认有日志输出如果你跑完没有看到任何过程信息大概率是日志级别设置得比较高。我一般这样调整import logging logging.basicConfig(levellogging.INFO)加了这一行之后你就能看到模型调用前后的完整链路模型请求是什么、工具调用参数是什么、返回结果被怎么处理了。这在后面的复杂 Agent 调试中几乎是救命级别的信息。另外一个调试技巧是手动构造事件。当你怀疑某个环节有 bug但又不确定是哪一层时可以直接在代码里触发调试事件而不是一遍遍模拟用户输入。这样能把调试范围缩小到具体的生命周期阶段效率高很多。3. 核心概念拆解Agent、生命周期与上下文3.1 Agent 的生命周期像写回调一样写业务逻辑很多人第一次用 ADK 时以为 Agent 就是一个“输入输出黑盒”。实际上Agent 处理每个请求都会走过一条清晰的生命周期链路。ADK 把这个过程抽象成了几个可插入的钩子函数before_model_call在调用模型前触发可以在这里修改请求参数after_model_call在模型返回后触发可以在这里拦结果before_tool_call在调用工具前触发可以在这里做参数校验after_tool_call在工具返回后触发可以在这里做后处理举个实际的例子我想知道每次工具调用的消耗情况可以这样写from google.adk.agents import Agent def log_tool_call(callback_context, tool_call, tool_response): print(f调用工具: {tool_call.tool_name}) print(f入参: {tool_call.args}) print(f返回: {tool_response}) return tool_response agent Agent( nametracked_agent, modelgemini-2.0-flash, tools[get_weather], before_tool_calllog_tool_call, )这些钩子看起来只是“日志打印”但实际价值远不止于此。你完全可以在 before_tool_call 里做权限校验在 after_tool_call 里做结果清洗和脱敏。这就像在框架给的关键路径上埋了一堆扩展点让你不用改动 Agent 主逻辑就能插入横切关注点。关于生命周期我必须提醒一个容易忽略的细节钩子函数是有返回值的。before_tool_call返回的 tool_response 可以替换原始的工具响应这意味着你可以在中间层做缓存。比如某个价格查询接口调用很昂贵你完全可以在钩子里检查缓存命中就返回缓存值不再触发真实调用。这个技巧对控制成本极其有效。3.2 上下文对象贯穿全局的数据管道Agent 每次处理请求时都会带上一个上下文对象。你可以把它理解成“这次请求的档案袋”里面装着所有运行需要的数据。最常用的几个字段session_id会话 ID用于标识对同一用户的多轮对话user_id用户 IDstate可变状态字典存放本次会话中的临时数据event_actions事件处理器集合用于和外部环境交互使用 state 的一个典型场景是记住用户在对话中透露的信息。比如用户先说“我在上海”后面又问“明天天气怎么样”Agent 需要知道“明天天气”默认指的是上海。这时可以存进 statecallback_context.state[location] 上海下一次请求时无论用户在哪个分支里只要同一个会话就能读到这个值。重点是你不能把业务核心数据只存在代码的全局变量里因为 Agent 的请求可能是分散到多个进程处理的。ADK 通过上下文对象把状态和单次请求绑定天然规避了分布式场景下的状态漂移问题这一点在部署到云端之后显得尤为重要。3.3 记忆机制分清临时状态和长期记忆记忆功能是我在 ADK 里比较欣赏的一部分。它把“记东西”划分成了不同的层次会话状态session state存在上下文中生命周期跟随会话长期记忆long-term memory跨会话持久化存储需要长期记住的事实这两个很容易被人混在一起。我见过一个项目把用户历次对话全部塞进长期记忆结果每个请求的 token 消耗飙到离谱模型每次都像在翻一座山。正确的做法是临时变量、分页游标、中间计算结果放 state用户偏好、基本资料、历史关键决策放长期记忆。具体到 API 层面读写状态非常直接。访问当前会话的 state 用callback_context.state长期记忆则需要通过官方的记忆模块接口来处理。在代码里保持“临时和长期分离”的习惯你的 Agent 会同时拥有过目不忘的能力和轻巧的响应速度。4. 工具Tool的设计与进阶玩法4.1 工具函数不是普通函数工具函数虽然形式上就是普通 Python 函数但在 Agent 体系里它多了一层身份模型的外部动作接口。所以它的设计规范比普通函数要严苛得多。我总结了几条实践经验参数必须有类型注解最好加上默认值和取值范围说明函数必须写 docstring模型要靠它理解函数用途返回结构必须 JSON 可序列化不要返回自定义对象单个工具的执行时间尽量控制在几秒以内太慢会拖垮整个 Agent 的响应为什么返回结构必须固定因为模型的工具调用是一个严格的结构化过程。工具返回后模型要解析你的返回内容来决定下一步动作。如果返回结构不稳定模型很容易在解析时出岔子。一个比较典型的坏味道是让工具返回“给用户看的话”比如“查询成功天气是多云温度24度”。更好的做法是返回一个结构化字典再让模型根据对话语境组织语言{status: success, data: {weather: cloudy, temperature: 24}}这样模型既可以从中提取关键信息也能结合上下文给用户一个自然的回答而不是生硬地念工具返回的字符串。4.2 用 tool 装饰器做更复杂的控制简单的纯函数工具已经能覆盖相当多场景但有些工具需要访问上下文信息比如读取当前的用户 ID 来做数据隔离。这个时候就可以用tool装饰器from google.adk.tools import tool tool def get_user_orders(callback_context) - dict: 查询当前用户的订单列表。 Returns: {orders: [...]} 结构。 user_id callback_context.user_id orders query_db(user_id) return {orders: orders}在这个例子里装饰器让工具函数可以直接访问上下文参数。这带来一个很大的好处工具不再是一个孤立的函数它可以感知整个 Agent 运行环境。你可以在工具里读取用户身份、获取请求 ID、写入状态值这使得工具具备非常强的扩展能力。但这里也有个容易踩雷的地方如果你用tool装饰器又同时给函数添加了 docstring那么这个 docstring 会被当作“工具描述”传给模型。也就是说你可以利用这一点来向模型解释工具的行为边界和适用条件模型会据此判断是否要调用这个工具。实践下来一个写清楚“什么时候不要用”的 docstring比写十行“什么时候用”更能减少模型的误调用。4.3 工具编排的常见坑工具多了以后Agent 容易出现工具选择混乱的问题。我遇到过最常见的三种情况第一种多个工具功能重叠。比如有两个工具都能查天气一个按城市查一个按坐标查模型经常选错。解决办法是合并工具减少选择空间或者在 docstring 里明确区分适用场景。第二种工具调用参数依赖前置调用结果。比如先要查用户 ID再查订单信息。这时候不要让模型去猜 ID而是设计成第一步工具直接把 ID 写入 state第二步工具从 state 里读取。第三种工具返回数据过大。你让工具从一个接口拉了几千条记录返回给模型后不仅 token 消耗巨大模型也容易“消化不良”。最好在工具里做一次数据瘦身只返回模型决策所需的关键字段。工具设计的本质是“降低模型的决策成本”。一个优秀的工具列表应该像一份很清晰的菜单每道菜名都让人一目了然而不是放了满页的复杂工序。5. 多代理编排从单兵作战到团队协作5.1 父子代理主管负责分派下属负责执行如果你只有一个简单任务单 Agent 已经足够。但现实中的业务往往需要多个专业角色配合比如一个负责检索资料一个负责数据分析一个负责生成报告。逐个串起来做太僵硬ADK 的多代理机制就是来解决这个问题的。父子代理是最清晰的一种模式。父代理负责理解用户意图然后把任务分派给对应的子代理。子代理拥有自己的模型和工具集执行完再把结果汇报给父代理。代码结构大概是这样的from google.adk.agents import Agent def get_weather(city: str) - dict: 获取天气。 ... def get_stock_price(symbol: str) - dict: 获取股价。 ... weather_agent Agent( nameweather_sub_agent, modelgemini-2.0-flash, tools[get_weather], ) stock_agent Agent( namestock_sub_agent, modelgemini-2.0-flash, tools[get_stock_price], ) root_agent Agent( nameroot, modelgemini-2.5-pro, instruction( 你是总负责人根据用户问题选择子代理处理。 天气类问题交给 weather_sub_agent 股票类问题交给 stock_sub_agent。 ), sub_agents[weather_agent, stock_agent], )我特别建议在instruction里明确每个子代理的职责边界。父模型虽然有推理能力但指令越清晰它的路由准确率越高不会把股票问题丢给天气代理。父子代理还有一种隐藏的调试优势你可以查看父代理的子代理调用链路精准判断是路由错了还是子代理执行出错了。这在单 Agent 里是看不到的。生产环境中我会把这条链路日志打到独立的追踪系统里方便随时复盘。5.2 工作流代理固定工序用流水线父子代理是“模型决定谁来做”但有些场景其实是固定工序不能用模型做决策。比如一个数据处理流程先清洗、再转换、最后汇总。这种流程如果用父子代理每一步都要等模型“思考”既慢又贵。ADK 提供了工作流代理模式你可以在里面按顺序定义阶段每一步调用固定的处理逻辑。工作流模式的最大优点是可预测。每一步做什么、输入输出是什么、从哪里失败全部是确定的非常容易排查问题。我在做企业内部的数据报表 Agent 时清洗和转换环节就全部走工作流只有当需要根据用户自然语言生成新查询时才切换回普通 Agent。混合使用两种模式能在一个应用里同时拿到“灵活”和“稳定”。5.3 协作代理多角色群聊式讨论协作代理是多代理中最复杂也最接近“多智能体系统”的一种。多个代理拥有各自独立的角色他们可以围绕同一个目标进行多轮讨论、提出不同观点、最终达成一致。可以想象成一个专家评审团每个人从自己的专业角度提意见最后汇总。这种模式适合目标模糊、需要多角度分析的场景。但我要诚实说一句它的工程复杂度不低你需要设计好讨论的轮次上限、终止条件、防跑题机制。在我的实践里协作代理的收益并不是每个任务都能体现它更适合“分析型任务”而不是“执行型任务”。新手可以把父子代理跑熟之后再尝试协作模式步子太大会很难收场。6. 评估 Agent 能力不能靠“感觉还行”6.1 建立评估集比想象中重要写 Agent 和写普通程序最大的不同是普通程序是确定性的输入相同输出就一定相同而 Agent 每次都可能有细微差异。如果没有一套客观评估机制你根本不知道一次代码修改到底是让 Agent 更聪明了还是更笨了。ADK 提供了 Agent Evaluation 能力来支撑这件事。你可以预先准备一组测试数据每条数据里包含模拟的用户输入和期望的正确行为然后批量运行 Agent用预设标准评判是否通过。我的建议是从项目第一天就建一个测试集哪怕只有十几条用例。之后每改一次代码就全量跑一遍这是成本最低的回归测试方式。测试数据的核心是覆盖关键场景。至少要有以下几种正常场景用户问题明确工具调用正常边界场景用户问题含糊看 Agent 是否合理追问工具失败场景工具返回异常看 Agent 能否优雅处理恶意输入场景看 Agent 是否会被 Prompt 注入带偏6.2 评估方法与指标怎么选常用的评估方法有三类我按性价比排个序规则判定检查输出是否包含关键字段或者工具调用顺序是否符合预期。成本最低适合初期。模型判定用一个强模型比如 Gemini 2.5 Pro作为裁判给 Agent 的回答打分。比规则灵活适合复杂任务。人工评估用抽样方式让人来判断结果最准确但成本最高。实际使用中我建议组合起来。快速回归用规则判定关键变更用模型判定重大版本上线前加一轮人工抽检。指标方面初期盯两个就够了工具调用成功率、意图识别准确率。这两个指标能帮你快速定位大部分问题。如果工具调用成功率低大概率是工具定义或指令有问题如果意图识别差则需要优化 instruction。还有一个容易被忽视的视角评估的不仅是最终回答还要评估中间过程。比如 Agent 是否在调用一个工具前反复空转、是否莫名其妙地调用了无关工具。这些中间行为虽然不直接影响单次结果但预示着后续规模化后的可靠性风险最好在评估时一并采样检查。7. 部署落地与常见问题排查实录7.1 从本地脚本到线上服务写代码和上线是两码事Agent 应用也不例外。在本地用adk run调试没问题但生产环境需要一个稳定的 HTTP 服务入口。ADK 官方提供了服务化方案你可以把你的 Agent 包装成一个标准的 API 服务然后用容器平台跑起来。我常用的部署方式比较简单务实。第一步把 Agent 实例化代码抽成一个独立的模块。第二步用官方提供的 Web 服务入口把它封装成 HTTP 接口。第三步写一个 Dockerfile把项目打进镜像。之后部署到支持 Docker 的云平台上。这套流程最大的优势是 Agent 代码和服务化代码完全解耦Agent 本身的改动能快速迭代上线。部署时有两件事一定要提前处理。一是环境变量API Key 这类敏感信息务必通过平台的安全机制注入不要打包进镜像。二是启动探针因为 Agent 服务首次加载模型可能比较慢需要配置足够长的健康检查超时时间否则一启动就被平台重启永远起不来。7.2 常见问题速查我把这段时间踩过的坑整理成一个速查表方便你在遇到问题时直接对照。现象可能原因解决办法模型一直不调用工具instruction 没有明确要求或工具 docstring 信息不足在 instruction 中写明“必须调用XX工具”完善 docstring工具被调用但参数传错参数类型注解不清晰或函数名有歧义补充参数说明必要时把多个参数合并为字典结构Agent 陷入反复调用工具的死循环缺少最大迭代限制或工具异常被模型误判为需重试设置迭代上限在 after_tool_call 中对异常结果做终止处理状态在多次请求后丢失用了代码全局变量而不是 ADK 的上下文状态改用callback_context.state存储会话临时数据部署后工具访问不了内网服务容器网络策略限制或工具接口地址未配置在部署环境配置网络策略检查环境变量里的接口地址响应速度越来越慢长期记忆或会话状态过于膨胀定期清理过期状态长期记忆只存必要事实7.3 成本控制与性能优化最后聊一个大家迟早要面对的问题成本。Agent 应用的成本大头在于 token 消耗尤其是多轮工具调用时每多一次循环都会把整个上下文再发送一遍。优化体验很明显的一个手段是精简工具返回内容只返回模型真实需要的字段。另一个手段是控制指令长度instruction 不是越长越好冗余的规则描述反而会占用上下文窗口。还有一个比较实用的方法给请求设置合理的超时和重试策略。Agent 服务面对流量突增时与其无限重试让模型把预算打空不如快速失败让上层去做降级处理。这些虽然不是 ADK 独有的话题但在 Agent 应用的工程化落地中它们的重要性比在普通 Web 服务里更高。我在实际项目里的体会是Agent 开发最迷人的地方在于它既是“代码工程”又是“提示词艺术”。ADK 给了一条相对顺畅的路但真正让 Agent 好用的还是你对业务流程的理解和对细节的打磨。工具函数的每一行 docstring、指令里的每一个决策边界都会在真实用户使用时被放大。把这些基础功夫做扎实Agent 的“智能”才会真正可靠。