ADK 集成 AntigravityAgent:把 Google Antigravity SDK 代理封装为原生 ADK Agent 节点
【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python
AntigravityAgent是 ADK(Agent Development Kit)在labs模块中提供的一个BaseAgent子类,它把一个预配置的google.antigravity.AgentConfig(如LocalAgentConfig)包装成标准的 ADK Agent 节点:每一轮对话被委托给 Antigravity SDK 的运行器执行,其轨迹步骤(模型文本、工具调用、工具响应)被流式转换为标准 ADKEvent并写入会话记录。阅读本文后,你将掌握如何用 Antigravity SDK 的本地工作区工具与策略来驱动 ADK 应用,如何通过mode='single_turn'将其作为子代理嵌套进 ADK 父代理,以及如何把 ADKsub_agents反向桥接为 Antigravity 的客户端工具。
为什么需要 AntigravityAgent
Antigravity SDK 擅长在本地工作区中执行任务——它自带工作区(workspace)管理、文件类工具与安全策略(policy),例如限制模型只能在指定目录内读写。但 Antigravity SDK 本身不提供 ADK 那样的代理编排(多代理、工作流)、会话存储与 Web UI 能力。
AntigravityAgent解决了这个组合问题:它让 ADK 应用直接"借用" Antigravity SDK 的本地工作区工具与策略,同时保留 ADK 的编排、会话与 UI 能力。官方指南的原话是:它将 Antigravity SDK 的运行循环(harness)与 ADK 的会话/事件体系缝合在一起(见 官方指南 与 模块文档字符串)。
从源码结构看,该集成位于labs(实验性实验室模块)之下,与labs/openai并列,属于 ADK 提供的非核心、实验性质的集成能力,由以下几个文件组成:
- 入口与主类:
AntigravityAgent主实现 - 事件转换器:把 Antigravity SDK 的
Step翻译为 ADKEvent - 子代理工具桥接:把 ADK 子代理包装为 Antigravity 的客户端工具
- 工具结果捕获:通过钩子(hook)缓冲客户端工具的调用结果
- 包入口:依赖检查与
AntigravityAgent导出
需要特别说明的是:使用本模块前必须先安装google-antigravity包。__init__.py在导入时会import google.antigravity,若缺失会抛出ImportError并提示运行pip install "google-adk[antigravity]"(见 包入口)。
快速开始:包装一个 Antigravity SDK 代理
官方指南给出了最精简的启动代码。第一步先配置 Antigravity SDK 侧的代理,第二步将其包装为 ADK 根代理:
from google.adk.labs.antigravity import AntigravityAgent from google.antigravity import LocalAgentConfig from google.antigravity.hooks import policy # 1. 配置 Antigravity SDK 代理。 # 多轮对话必须设置 save_dir,这样临时目录才能在多轮之间被保留。 sdk_config = LocalAgentConfig( system_instructions="You are a helpful local environment assistant.", workspaces=["./sandbox"], policies=[*policy.workspace_only(["./sandbox"])], save_dir="./trajectories", ) # 2. 将 Antigravity SDK 配置包装为独立的 ADK 根代理。 root_agent = AntigravityAgent( name="antigravity_assistant", description="Runs an Antigravity SDK agent inside ADK.", config=sdk_config, )对这段代码的几个要点说明:
workspaces与policies:policy.workspace_only(["./sandbox"])是一个 workspace 限定策略,允许模型在./sandbox内使用内置文件工具进行创建与编辑,同时把写入限制在该目录内。这是 Antigravity SDK 本地代理的核心安全机制。save_dir的多轮语义:见下文"配置选项"一节的详细解释。name与description:name是 ADK 会话中该代理的标识;description会在该代理被作为子代理/工具暴露给模型时被读取。
完成包装后,root_agent就是一个标准的 ADKBaseAgent,可以直接交给 ADK 的Runner运行,像使用任何其他 ADK 代理一样进行多轮对话、事件监听与 UI 调试。
仓库中有一个完整可运行的真实示例——Game Developer Agent:它把上述模式落地为一个"网页游戏开发者代理",system_instructions要求模型把每个游戏写成单个自包含 HTML 文件(内联 CSS/JavaScript,无外部依赖),并在game_repo工作区内分步增量构建,完成后说明如何游玩。示例还演示了工程细节:用os.makedirs(..., exist_ok=True)预创建工作区与轨迹目录,再传给LocalAgentConfig。
工作原理:每轮重建 SDK 代理,用会话 ID 维持连续性
官方指南明确描述了AntigravityAgent的核心运行时模型:
- 每轮都构建并进入一个全新的 Antigravity SDK
Agent; - 从 ADK 会话状态中读取并恢复会话 ID(conversation ID);
- 把最新用户输入发送进去,并把流式返回的每个
Step转换为标准 ADKEvent(覆盖模型文本响应、函数调用、函数响应三类); - 轮与轮之间不保持任何打开的资源:SDK
Agent实例在退出时被关闭,下一轮重新连接。连续性完全来自会话 ID——包装器把它读写到 ADK 会话状态中。
对应到源码实现(主类):
- 会话 ID 的存储键为
_antigravity_conversation_id_前缀 + 代理名(_conversation_id_state_key,见 L317-L320)。这意味着同一个 ADK 会话中的两个AntigravityAgent实例各自维护独立的 Antigravity 对话,互不干扰。 - 会话 ID 的持久化通过事件携带的
EventActions(state_delta=...)完成(_conversation_id_event,见 L322-L336)。源码注释特别强调:它必须是独立事件而非合并进模型事件,因为部分事件(partial event)不会被追加到会话中,而state_delta正是在会话落盘时才被应用。 - 恢复会话时,配置会被设置为
SessionContinuationMode.CREATE_OR_RESUME(见 L360-L367):这是唯一能在底层存储已不存在时优雅降级为"新建会话"的模式,否则默认模式下"找不到存储"会直接抛硬错误。 - 连接 SDK 代理使用了异步上下文管理器
async with;若__aenter__期间被取消,会显式调用__aexit__以避免孤儿化 harness 子进程(见 L369-L376)。 - 会话 ID 的写入时机经过精心设计:即使某轮步骤不产生任何用户可见事件(例如 compact 压缩步骤),只要对话有历史,就仍要记录 ID,否则下一轮会"孤儿化"这段对话(见 L403-L432)。
Step 到 Event 的映射规则
转换逻辑被独立抽取到 _event_converter.py,以保证映射规则可读、可单独测试。核心入口是convert_step_to_events(L334-L381),每收到一个Step按以下顺序产出事件:
AntigravityStep内容 | 产出的 ADK 事件 |
|---|---|
思考增量thinking_delta | partial=True的模型思考事件(仅 SSE 流式模式) |
文本增量content_delta | partial=True的模型文本事件(仅 SSE 流式模式) |
| 完整模型文本响应 | 一个author=代理名的最终模型文本事件 |
| 模型发起的工具调用 | author=代理名的function_call事件 |
| 工具执行完成/出错 | author=工具名的function_response事件 |
几个值得注意的实现细节(均可从源码确认):
- 最终文本只在
is_complete_response时发出:Antigravity SDK 会在响应增长过程中反复重播累积的content,若每次都发会造成同一消息被记录多次,因此只在完整响应时用最终累积文本生成事件(L99-L131)。 - SSE 流式模式下才生成增量事件:
_run_turn会检查run_config.streaming_mode == StreamingMode.SSE(L452-L454);非流式模式只产出最终事件。 - 工具调用 ID 去重:当 SDK 省略调用 ID 时,会合成
{step_index}-{name}形式的稳定 ID(_build_tool_call_id,L48-L50),并用seen_tool_calls/seen_tool_results集合对跨步骤重播的调用与结果去重。 final_model_text用于把事件中的用户可见文本读出来,它过滤掉 partial、思考(thought)与函数部件,多段文本以换行拼接(L384-L412)。- 已知边界:SDK 在回合取消时发出的
SYSTEM_MESSAGE步骤目前被丢弃,尚未映射为 ADK 事件(见 _event_converter.py 顶部 TODO)。
配置选项
官方指南给出了一张配置参数表:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
config | AgentConfig | (必填) | 描述 Antigravity SDK 代理的google.antigravity.AgentConfig。 |
mode | Literal['single_turn'] \| None | None | 作为子代理使用时的组合模式。 |
两个参数在源码中的定义与细节如下(L161-L178):
config:Antigravity SDK 代理的全部定义
config定义了 Antigravity SDK 的指令(system_instructions)、工作区(workspaces)与策略(policies)。源码中它被标记为Field(exclude=True)——即从序列化中排除,因为它持有运行期接线(例如可调用工具),不是 JSON 可序列化的。
当使用LocalAgentConfig时,多轮连续对话必须提供save_dir。原因(指南与源码双重印证,见 L186-L202):本地配置在每次连接时会新建一个临时目录,如果没有save_dir,每一轮写入的位置都不是下一轮会去查找的位置,即"每轮都是全新对话"。源码会在构造时检查并打印警告日志(_warn_if_local_without_save_dir):若检测到本地配置且未设置save_dir且非单轮模式,会警告"该代理不会跨轮记住任何东西"。注意它检查的是基类BaseLocalAgentConfig(而非默认子类),因为临时目录的创建逻辑在基类里,任何子类缺save_dir都会"失忆"(L265-L271)。
另外,config在每轮运行前会被深拷贝(model_copy(deep=True),见 L296-L299):一方面避免修改调用方的配置对象,另一方面因为 SDKAgent的AsyncExitStack是单次使用的,不能复用同一配置。
mode:子代理组合模式
mode控制AntigravityAgent如何被嵌套在 ADK 父代理之下:
mode=None(默认):作为独立的 ADK 根代理运行。mode='single_turn':允许该代理拥有 ADK 父代理。此时父LlmAgent会把它暴露为一个内联工具,工具签名接收一个request字符串;父代理负责组合请求,会话历史不会被转发,每次单轮调用都是独立对话,前后不继承任何内容。
源码中mode是frozen=True的字段,因为"收养检查"(adoption guard)只在构造时执行一次。该守卫在__setattr__中实现(L240-L248):如果尝试给AntigravityAgent设置非空的parent_agent而mode != 'single_turn',会抛出ValueError,提示文案为:
AntigravityAgent may only be an ADK sub-agent when it sets mode='single_turn'... Otherwise it must run as an ADK root agent.
这与官方指南"Limitations"一节的第一条限制完全对应:AntigravityAgent 运行的是自包含的 Antigravity SDK 对话,因此除非设置mode='single_turn',否则它必须是 ADK 根代理。
高级应用:给 AntigravityAgent 挂 ADK 子代理
官方指南的进阶场景是:AntigravityAgent可以拥有 ADKsub_agents。每个 ADK 子代理会被桥接到 Antigravity SDK 配置上,变成一个以子代理名字命名的客户端工具(client-side tool)。该工具接收一个request字符串,因此每个子代理必须提供非空的description——这是 Antigravity SDK 模型在选择是否调用时唯一能读到的信息。子代理在隔离环境中运行,只返回其最终文本;父会话记录一次工具调用和携带该最终文本的function_response。
from google.adk.agents.llm_agent import Agent def get_current_time(city: str) -> dict: return {"status": "success", "report": f"The time in {city} is 12:00 PM."} time_agent = Agent( name="time_assistant", description=( "Returns the current time. Always call this for time-related queries." ), instruction="Answer time questions by calling get_current_time.", tools=[get_current_time], ) root_agent = AntigravityAgent( name="antigravity_assistant", description="Runs an Antigravity SDK agent inside ADK.", config=sdk_config, sub_agents=[time_agent], )底层机制:客户端工具桥接
桥接实现在 _sub_agent_tools.py 的make_sub_agent_tool(L50-L122):
- 子代理的名字成为工具名(
__name__),子代理的description成为工具描述(__doc__),两者都是模型读取的信息来源。 - 每次调用该工具时,会用
Runner+ 全新的InMemorySessionService以隔离会话运行子代理一次,返回其最后一个用户可见文本;若没有文本则回退到最后一个错误消息,再不行返回''(绝不返回None)。 - 这与 ADK 自带的
AgentTool非常接近,区别是不返回代码执行输出与可执行代码;且子代理抛出的异常会原样传播给调用方(AgentTool则会把失败包装成错误字符串)。 - 工具的
Runner在finally中显式关闭,以保证子代理的 MCP 会话、工具集等资源被正确释放——否则遗留的 MCP 会话会在后续出现 "Attempted to exit cancel scope in a different task" 之类的错误。 - 导入
Runner被刻意延迟到函数内部(L71),因为runners会拉入大半个 ADK,在 labs 模块顶层立即导入容易形成循环依赖。
构造期校验:名字与描述
在_validate_sub_agents(L204-L238)中,构造时会校验三种冲突并抛出ValueError:
- 子代理缺少
description——它作为工具被暴露,描述是模型唯一决策依据; - 子代理名字与
config.tools中已有工具重名——它们会被追加到同一个config.tools,harness 按名字注册工具,重名者会被拒绝; - 两个子代理重名——原因同上。
注意该校验在构造后(例如model_copy或后续修改sub_agents)还会被_build_sdk_config再次调用,防止绕过model_post_init。
客户端工具的结果捕获
一个关键设计点是:客户端工具(ADK 子代理)的执行结果永远不会出现在轨迹(trajectory)里——其终止Step的tool_calls为空,且Step本身没有结果字段。结果只能通过工具钩子到达。因此 _tool_result_capture.py 实现了两个钩子类,共用一个ToolResultBuffer:
ToolResultCapture(PostToolCallHook):成功路径,在post_tool_call时把ToolResult按id存入缓冲;ToolErrorCapture(OnToolErrorHook):失败路径,在on_tool_error时把ToolExecutionError包装为_FailedToolResult存入缓冲。两个钩子互斥——每次调用恰好触发其一(见 _tool_result_capture.py 模块文档)。
缓冲只在存在sub_agents时才注册(因为post_tool_call钩子每次成功调用都会带来一次阻塞往返),且只在 AntigravityAgent 拥有子代理时为真(L355-L359)。回合结束后,_run_turn会调用drain_tool_results把已捕获但尚未答复的调用配对成function_response事件,再清空缓冲(L470-L484)。
运行模式与运行时约束
作为工作流节点(Node)运行
AntigravityAgent同时实现了节点运行路径_run_impl(L486-L521):它把node_input转成用户内容,收集整轮最后一段模型文本作为output产出,从而可以作为 ADK 工作流(workflow)中的节点使用,支持node_input注入与output输出。这从源码结构看是复用run_async主路径的实现。
静默丢失恢复(silent-drop detection)
针对"要求恢复某会话但底层已不存在"的情况,源码对本地连接实现了检测:恢复后若对话历史为空,判定为静默新建了会话,此时会清除存储的 ID 并抛出RuntimeError,提示"已清除存储的 ID,下一轮将开始新对话,但本会话之前的回合不可恢复"(L394-L402)。该检测被限定在本地配置类型内,因为远程后端在陈旧 ID 上静默新建会话的行为无法在不依赖 SDK 支持的前提下被可靠区分(L434-L445)。
已知限制
官方指南明确列出的限制如下(并可与源码互证):
- 嵌套限制:
AntigravityAgent运行的是自包含的 Antigravity SDK 对话,因此除非设置mode='single_turn',否则它必须是 ADK 根代理。这条限制仅适用于该代理被放在 ADK 父代理之下的场景;它自己的 ADKsub_agents是被桥接为客户端工具的,永远不需要设置mode。 - 子代理的 root 解析:
AntigravityAgent的 ADK 子代理的root_agent仍指向最外层 ADK 代理树。在三层结构中(LlmAgent→AntigravityAgent(mode='single_turn')→ 子代理),中间的代理设置mode='single_turn'是因为它有 ADK 父代理,而不是因为它有子代理;此时 ADK 的 transfer 工具会被声明给子代理的模型。建议让AntigravityAgent的子代理保持"叶节点"形态,或者在这些子代理上设置disallow_transfer_to_parent与disallow_transfer_to_peers。 - 并发限制:同一 ADK 会话的两轮并发运行是未定义行为,因为两者会打开同一个已存储的会话。
相关示例与进一步阅读
- Game Developer Agent:一个独立的 Antigravity SDK 代理,在工作区内把浏览器游戏写成自包含 HTML。它同时演示了工作区目录预创建、workspace 限定策略与
save_dir的标准用法,是理解本文全部概念的完整落地样例。 - 模块 README:简短索引,指向本文档。
- AntigravityAgent 单元测试:覆盖会话 ID 持久化、单轮模式、子代理校验与节点运行路径等行为,是理解边界条件的补充材料。
- 事件转换器测试、子代理工具测试、工具结果捕获测试:分别验证 Step→Event 映射、子代理工具包装与钩子缓冲逻辑。
需要提醒的是:labs模块属于实验性集成,其 API 面向的是"把另一套 Agent 运行时嵌入 ADK"这一具体需求;生产使用前请确认你的 ADK 版本与google-antigravity包的兼容性,并严格按pip install "google-adk[antigravity]"安装依赖。
【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考