2025年初,谷歌正式开源了自家的智能体开发套件Agent Development Kit(ADK),这个动作直接把不少人的目光从各类通用Agent编排框架上拽了过来。ADK给我的第一印象是,它不是一个强调“编排一切”的重框架,而是一套更贴近工程直觉、强调可观测性和状态管理的轻量级开发工具。它把智能体开发拆得很干净:用Agent类去组织业务逻辑,用Tool去接外部能力,用Session去管状态,用一个统一的事件循环去跑整个会话。如果你正被各种Agent框架搞到选择困难,或者已经上手过一些工具但还是搞不清内部组件怎么协作,这篇内容会帮你建立一套判断标准:一个成熟的智能体框架,核心组件到底应该有哪些,它们之间是怎么配合的。
1. 为什么需要一套“智能体开发套件”:架构设计的出发点
1.1 大模型应用开发的三个老大难
智能体开发跟传统后端开发不太一样。传统后端是“入参-处理-出参”的线性模型,请求进来,逻辑处理,结果返回,一次调用结束。智能体则是一个循环:模型先决定要不要调用工具,工具返回结果,模型再基于结果继续推理,直到它认为任务完成。这个循环本身不难理解,难的是落到工程上,有三个问题绕不开。
第一个是上下文管理。LLM一次请求能接收的token有限,但一个真实对话往往会拉得很长。你不能把整场历史都塞进模型输入,又不能随意丢掉关键信息,否则对话到一半,智能体就像失忆了一样。第二个是工具接入。企业里大部分能力都封装成REST API、数据库查询、甚至命令行脚本,智能体要调用它们,就必须有一套统一机制去描述、注册、调用这些能力,并且能把错误信息准确反馈给模型。第三个是可观测性。模型一步一步做了什么推理、调用了哪个工具、结果是什么,这些过程如果不暴露出来,出问题你连排查的入口都找不到,只能对着黑盒干瞪眼。
ADK的架构正是针对这三个问题设计的:Session负责上下文管理,Tool负责外接能力,事件循环和Runner负责暴露运行全过程。这也是我评价一个Agent框架是否成熟的第一条标准:它有没有把这三个问题当成一等公民来设计,而不是靠开发者自己拼拼凑凑。
1.2 整体架构的分层逻辑
ADK的整体架构可以用一个剧组来类比。Agent是导演,它决定这场戏怎么演:指令怎么写、调用哪些工具、子任务交给谁。Tool是道具组和群演,提供各种能力:查数据库、调接口、读写文件。Session是场记,把每一条指令、每一次工具调用结果、每一个中间状态都记录下来,确保拍摄进度不丢。Runner则是执行导演兼现场监控,它把导演的想法转成具体的执行动作,并且让每个环节的进度都能被外部看到。
这个分层带来的直接好处是解耦。你可以只改Agent的指令,而不动状态存储逻辑;你可以换掉底层模型,而不影响工具层;你甚至可以脱离业务代码,单独查看一个会话的状态流转。我做过几个Demo之后最直观的感受是,调试时间被大幅压缩。不再需要在日志里翻来翻去找哪一步出了问题,直接看事件流就能定位是模型决策错了,还是工具调用挂了,还是状态清理逻辑有问题。
还有一点值得说:ADK的Agent是树状组合,而不是简单的线性链式调用。一个根Agent下面可以挂多个子Agent,根Agent负责整体调度,子Agent负责具体领域的子任务。这种设计符合真实业务里的分工逻辑,也让单个Agent的指令保持精简,避免把几十条复杂业务规则堆到一个Prompt里,最后谁也不知道模型会偏向哪条。
1.3 与通用Agent框架的定位差异:轻量、可观测、可嵌入
拿一些人用过的主流Agent编排框架来对比,ADK的定位差异挺明显。很多通用框架的价值在于调度灵活,你可以把它当作一颗大脑,什么流程都能编排,代价是你要学习它的抽象概念,并且出了问题往往要开着分布式追踪去查。ADK则更偏“默认约定”:它预设了一套标准的运行循环、标准的状态机制、标准的事件流,你在这个约定里写业务,而不是从头搭建一套流程。
这意味着两件事。第一,上手成本低,核心概念就那几样,跑通Demo不用学很多抽象术语。第二,可观测性天生就好,因为所有会话都在框架的统一循环里跑,事件是结构化的,日志天然完整。但这也有代价:如果你需要非常自定义的调度逻辑,比如把多个模型组合成一个非Agent的奇怪管道,ADK反而会显得不那么灵活。所以这个套件更适合的场景,是业务逻辑清晰、需要跟外部系统打交道、并且后续要长期维护的项目。
2. 核心组件逐个拆解:Agent、Tool、Session、Artifact
2.1 Agent类:一切智能体的起点
在ADK里,一个智能体的起点是Agent类。你需要告诉它三件事:它叫什么、它该遵循什么指令、它能用哪些工具。最基本的写法大致如下:
from google.adk.agents import Agent agent = Agent( name="customer_service", instruction=( "你是一名电商平台的客服助手。请根据用户的订单状态和售后政策," "耐心解答问题。如果用户需要查询订单,请调用订单查询工具。" ), model="gemini-2.5-pro", tools=["查询订单工具", "申请售后工具"], )这里的instruction就是Agent的“岗位说明书”,它决定了模型在对话里以什么身份、按什么准则去回复。注意,指令写得越具体,工具的使用边界越清晰,模型就越不容易产生幻觉式调用。
树状组合是让我觉得最有工程价值的设计。你可以创建一个根Agent,它只负责判断用户意图,然后派发给不同子Agent去处理,比如一个负责售前咨询,一个负责售后流程。这种结构有几个好处:每个Agent只关注自己的领域,指令不会越写越臃肿;单个Agent的上下文里不会塞入不相关的工具描述,token占用更可控;团队协作时,不同人维护不同的子Agent,互不干扰。
我实际用下来的经验是,子Agent划分的粒度要以“独立职责”为准,别为了拆而拆。如果一个子Agent只需要一个工具、一段简单指令就能完成任务,那就没必要单独建一个类,直接放在根Agent的工具列表里更省事。拆得细碎不仅增加调度开销,还会让排查问题时得多跳好几层。
2.2 Tool机制:从Python函数到LLM工具
Tool是智能体的手和脚。ADK里定义一个工具,最直接的方式是写一个普通Python函数,然后用装饰器标记一下。关键在于函数的类型注解和文档字符串,因为框架会把这些信息自动组装成模型能理解的工具描述。
from google.adk.tools import tool @tool def get_order_status(order_id: str) -> str: """根据订单号查询订单的当前状态。 Args: order_id: 用户提供的订单编号。 Returns: 订单状态的文字描述,包含物流信息和预计送达时间。 """ status = query_oms(order_id) return f"订单状态:{status['phase']},物流公司:{status['carrier']},预计送达:{status['eta']}"这里面有个细节容易被忽略:工具的返回值一定要是“对模型友好的、结构化的自然语言”。模型看到返回值后,会基于它继续推理并组织给用户的回答。你返回一段JSON没问题,但如果你返回一大坨火星文级别的原始日志,模型很容易理解偏。我通常会在工具内部把关键信息过滤一遍,再拼成一句简短、完整的话返回。这样比直接抛原始对象要稳得多。
错误处理也一样。工具内部如果抛异常,框架会把异常信息返回给模型,模型可能就被错误栈带偏了。更稳妥的做法是在工具内部捕获异常,返回一段人能看懂的错误描述:
@tool def get_order_status(order_id: str) -> str: """...""" try: data = query_oms(order_id) except NotFoundError: return "未找到该订单,请核对订单号。" except TimeoutError: return "订单系统当前响应较慢,请稍后再试。" return f"订单状态:..."这么处理后,模型在遇到错误时还能继续对话,给出安抚话术,而不是直接报错。工具是智能体的能力边界,这个边界上的代码质量,直接决定了整个智能体的可用性。
2.3 Session状态:让智能体“记住”上下文
Session是ADK里容易被新手低估的组件。很多人的第一反应是,会话历史不是模型上下文里有吗?为什么还要单独搞一个Session?
理由很实际。模型上下文里的对话历史,只是Session的一部分。Session还要保存应用状态,比如用户已经选了哪个商品、某个流程进行到哪一步、工具返回过什么关键结果。这些数据不一定适合塞回Prompt,但它们确实是整个任务推进必需的“工作记忆”。Session把这些数据集中管理,才能实现会话中断后恢复、不同入口之间的状态共享、以及多轮对话中的信息沉淀。
ADK里Session的典型使用姿势,是把状态存到一个dict里,框架会自动帮你持久化。比如一个跨轮次收集信息的表单场景:
session.state.setdefault("order_info", {}) session.state["order_info"]["address"] = user_input这里注意一点:会话上下文是有限的,不可能无限增长。当对话轮数多了,早期信息会被压缩或丢弃,所以关键业务数据一定要主动落到Session的state里,而不是依赖模型记得。我踩过的一个坑是,把用户填好的收货地址只写在对话历史里,结果上下文窗口一挤压,模型就把地址忘了,还得再问一遍用户。后来统一改成每次地址信息都写入state,这个问题就消失了。
Session还支持分节和恢复。这就像游戏存档,你可以把一次较长的任务切成多个Session,也可以让用户隔几天回来继续上一次的对话。工程上这意味着,智能体不需要常驻内存,它只需要在每次请求进来时加载对应Session,跑完再存回去。对线上服务来说,这种设计能省下不少成本。
2.4 Artifact与MCP:能力的边界与扩展
除了工具调用,智能体还有一个常见的需求:生成并保存文件。ADK里用Artifact这个概念来管理这类产出物。它不只是一个文件路径,而是一个带元数据的对象,能记录是谁生成的、在什么时候生成的、属于哪个会话。比如让智能体生成一份Excel报表或PDF合同,产出物会被登记成Artifact,后续可以继续被引用、下载或覆盖。
这种抽象对于“边聊边干活”的场景非常有用。用户让智能体写份周报,智能体生成Markdown文件,用户说“帮我改成PDF”,智能体可以直接基于上一个Artifact继续处理,而不是从头再生成一遍。
MCP则是ADK用来接入外部工具生态的协议。简单说,MCP把各种外部系统的能力描述成统一格式,ADK作为客户端去发现、调用这些能力。这意味着你不需要为每个新的外部系统单独写一套适配代码,只要对方提供MCP服务,ADK就能直接接进来。有点像给智能体装了一个标准化的USB接口,什么设备都能插,只要对方遵守协议。
我用MCP接过一个内部数据平台的查询服务,过程确实比手动封装REST API顺畅得多。工具描述、参数结构、调用地址都由服务端发布,ADK这边只需要注册一下端点就能自动获取。对团队来说,这种扩展方式还带来一个额外好处:工具定义离业务方更近,维护成本更低。
3. 从零搭建一个可运行的智能体:实操与代码剖析
3.1 环境准备与项目初始化
实际操作从环境准备开始。假设你有一台能跑Python的机器,版本建议3.10以上。安装ADK只需要一行命令:
pip install google-adk安装完成后,推荐按下面的目录结构组织项目。这个结构不是强制的,但按照职责分层放,后面维护会舒服很多:
agent_project/ ├── agents/ │ └── customer_service.py ├── tools/ │ └── order_tools.py ├── main.py └── requirements.txtagents目录放Agent定义,tools目录放工具函数,main.py作为入口。这种拆分的好处是,Agent定义和工具实现互不干扰。工具层的逻辑往往变化频繁,比如接口换了、字段改了,都在tools目录里解决,不用动Agent定义。
3.2 定义一个带工具的Agent
下面用“订单查询助手”这个小项目把整个流程串起来。先定义工具:
# tools/order_tools.py from google.adk.tools import tool @tool def get_order_status(order_id: str) -> str: """查询订单状态。 Args: order_id: 用户提供的订单编号,通常是纯数字字符串。 """ if not order_id.isdigit(): return "订单号格式不正确,请确认后重试。" # 模拟查询结果,真实项目这里替换成内部接口调用 return f"订单 {order_id} 已发货,预计明天送达。" @tool def cancel_order(order_id: str) -> str: """取消订单。 Args: order_id: 需要取消的订单编号。 """ return f"订单 {order_id} 已取消,退款将在1-3个工作日原路退回。"这段代码里有两个点值得注意。一是每个工具都写了文档字符串,参数含义写得清清楚楚,这直接决定模型能否正确理解工具作用;二是工具内部做了简单的输入校验,避免把明显不合法的情况抛给下游系统。
接下来定义Agent:
# agents/customer_service.py from google.adk.agents import Agent from tools.order_tools import get_order_status, cancel_order agent = Agent( name="customer_service", instruction=( "你是一个电商客服助手。用户可能查询订单状态或申请取消订单。" "查询订单时调用get_order_status,取消订单时调用cancel_order。" "正式回复时语言要友好简洁。" ), model="gemini-2.5-pro", tools=[get_order_status, cancel_order], )这里没有写什么高深语法,但每个字段的取舍都是有讲究的。instruction越具体,模型越不容易乱来。我没有把工具名称写死在指令里让模型死记,而是靠工具自身的文档描述让模型自动发现。这样工具列表即使增加几个,指令也不用频繁改动。
3.3 运行与调试:三种Runner的使用姿势
ADK提供了几种运行方式,适用不同阶段。最常用的是直接跑命令行CLI,适合快速验证单次对话。启动方式很简单:
adk run然后在终端里输入问题,智能体就会开始推理并调用工具。这种方式对快速验证“工具调不通”“指令没生效”这类问题最直接。
如果你需要更直观地观察Agent每一步的输入输出,可以用Web UI模式:
adk webWeb UI的好处是能看到事件流。模型发了什么请求、工具返回了什么结果、Agent在哪个环节做了错误处理,一目了然。这个可视化能力在做复杂多轮对话调试时几乎必不可少,比翻终端日志强太多,是我个人最推荐的调试方式。
还有一种偏生产环境的用法,是把ADK应用包装成OpenAPI接口,通过HTTP对外提供服务。ADK自带相关的服务封装,你只需要启动应用并暴露端口,之后就可以用标准的HTTP客户端去调用。这种模式适合把智能体嵌进已有业务系统,比如用在一个客服后台的工单创建页面里。
三种Runner的选择其实就是一个判断:CLI适合快速冒烟测试,Web UI适合深度调试,OpenAPI模式适合集成上线。我实际项目中常用的节奏是,CLI先跑通核心流程,再用Web UI观察一次完整对话的事件流,确认决策和工具调用都符合预期,最后才切到服务化模式对接业务方。
3.4 部署与扩展思路
当智能体要真正上线,部署层面有几个问题需要提前考虑。第一是模型API的配额和限流,多用户并发访问时,如果模型接口被限流,Agent会频繁重试,体验很差。第二是Session持久化的存储位置。本地文件系统够用,但多实例部署时就要换成共享存储,否则用户请求被路由到不同实例,Session对不上。第三是日志和监控。ADK的事件流是天然的结构化日志,建议在发布环境里把事件全部采集下来,这样线上出问题才能回溯。
扩展方面,一个比较实用的模式是“一个入口,多个子Agent”。根Agent负责判断用户意图,子Agent负责具体领域。比如订单查询、售后申请、优惠券核销各一个子Agent。这样每个子Agent可以独立迭代,甚至可以由不同团队维护。我体验过这种模式的维护体验,改动某个子Agent的指令,完全不影响其他模块,上线回归成本很低。
4. 常见问题与排查技巧实录
4.1 工具调用掉进“死循环”
第一个常见问题是智能体反复调用同一个工具,不往下走。典型的场景是,智能体调用查询工具后,返回结果里说“查不到”,但模型不死心,换个参数再查一遍,来回五六次才放弃。
这个问题根子通常在工具返回值上。返回值里没有给模型明确的“终止条件”。解决办法很简单:在工具返回里加上明确的结论提示,比如“这个订单号不存在,请告知用户并提供下单方式,不要再尝试查询”。模型看到“不要再尝试”的指令,通常会及时收手。另外,可以给Runner设置单次执行的最大轮数限制。ADK里可以通过相关超时参数控制,超过轮数直接强制结束,防止成本失控。
4.2 会话失忆与上下文超限
第二个常见问题是会话失忆。用户前面说到一半的信息,后面再问相关细节,智能体一脸茫然。排查时先确认Session有没有正确加载,再确认关键状态是不是存在了Session的state里,而不是只靠对话历史记忆。
如果Session没问题,但对话一长就失忆,那大概率是上下文窗口碎片化导致的。模型只会保留最近的部分历史,早先的细节被挤掉了。解决办法是把业务关键信息主动抽取到state中,并考虑在长对话中把已完成环节的信息做“摘要化”。具体做法是,每当一个子任务完成,就用模型把结论精简成几句话存起来,后续对话只引用摘要,不翻原始记录。这样既保住了关键信息,又控制了上下文长度。
4.3 调试效率提升的实用技巧
最后聊几个能让调试轻松很多的技巧。
第一,给工具写严格的入参校验。很多“工具调用失败”的问题,根因不是工具逻辑错了,而是模型生成了不合法参数。工具内部做一次类型和取值范围校验,可以省掉大量排查时间。
第二,充分利用事件流。Web UI里一个完整会话的事件顺序是固定的:模型请求发出,工具调用请求,工具返回结果,模型再次请求。你要定位问题,先找事件流里在哪一环节断了,基本能锁定八成问题。第三,写单测。ADK支持针对单个Agent和工具做单元测试,把典型对话场景和典型工具输入固化下来,每次改完代码跑一遍,比每次都用真实模型试错效率高得多。
我个人踩过几次坑之后的体会是,这个套件最舒服的用法是把注意力集中在工具质量和指令设计上,而把循环控制、状态持久化、事件记录这些脏活交给框架。只要Session用对、工具返回规范、调试时多利用事件流,几个核心组件能配合得很顺。对于大多数想落地智能体功能、又不希望被过度复杂的调度抽象拖住的团队,ADK是值得一试的选择。