15669 字
78 分钟
从 ADK 到 LangChain:深入理解 Google Agents CLI 的扩展机制与 Agent 工程化
  1. 1从 ADK 到 LangChain:深入理解 Google Agents CLI 的扩展机制与 Agent 工程化本文
  2. 2从 Trace 到 Harness:深入拆解 OpenAI Agents SDK 的 Agent 改进闭环

原文:Powering the Agent Development Lifecycle with any framework, from ADK to LangChain and beyond

来源:Google Developers Blog

发布日期:2026 年 10 月 21 日

原文链接:https://developers.googleblog.com/powering-the-agent-development-lifecycle-with-any-framework-from-adk-to-langchain-and-beyond/

Agent 框架的选择和 Agent 工程工具链本不应该互相绑定。Google 在 Agents CLI 中给出的答案是 Extension System:不复制、不重写整个工具链,只在必须适配的边界上做局部替换。本文按原文顺序,从框架锁定问题讲起,依次拆解扩展的声明结构、LangChain 实战流程、自定义扩展的实现方式,以及这套设计对 Agent 工程化的启示。

一、引言#

原文开头回顾了 Google Cloud 发布 Agents CLI 时的设计理念:从一个想法到一个可以在 Google Cloud 上生产运行的 Agent,应该尽量通过统一的命令入口完成。

原先的主要目标用户是使用 Google ADK 开发 Agent 的团队。开发者可以创建项目、编写 Agent 逻辑,再将其部署到 Cloud Run,并接入评测与可观测性能力。

这里的关键术语是 ADLC(Agent Development Lifecycle,Agent 开发生命周期)。它指的不是 Agent 在回答一个问题时经历的推理循环,而是一个 Agent 从开发到上线、再到持续改进的整个工程过程。例如:

  • Build:开发。 编写 Agent、提示词和工具。
  • Test / Eval:测试与评测。 验证任务是否完成、工具是否正确调用。
  • Deploy:部署。 构建服务并发布到运行环境。
  • Monitor:监控。 观察线上表现、发现问题并反馈到下一轮开发。

注意,文章不是说这些环节以前不存在,而是希望把它们组织成一个统一、可重复的工作流程。

1.1 为什么作者认为原来的方案不够用了?#

因为 Agent 框架越来越多。

除了 ADK,团队还可能选择 LangChain、LangGraph、Deep Agents,或者自己编写的内部框架。

问题在于:开发者喜欢的 Agent 框架不一定相同,但团队需要的工程能力往往相似。

举个例子。

某团队已经用 LangGraph 写好了一套 Agent,具备工具调用和状态管理能力。现在他们想使用 Agents CLI 的部署、评测和观测能力。如果 CLI 的所有命令都默认要求 ADK 对象,那么这个团队就可能被迫:

  • 把原有 Agent 改写成 ADK;
  • 自己维护一套替代命令;
  • 或者放弃使用 Agents CLI。

这就是作者希望解决的框架绑定问题。

1.2 企业用户还有另一类需求#

作者还提出了一个容易被忽略的问题:企业不只是希望使用自己喜欢的框架,还希望强制执行统一的工程规范。

例如,公司可能要求:

  • 部署之前必须进行合规检查;
  • 登录必须通过企业 SSO;
  • Agent 评测必须经过模型风险管理流程。

这意味着企业不一定希望所有命令都采用工具的默认行为。有时候,它们需要在某些命令前后增加自己的逻辑,甚至完全替换原有实现。

这就引出了本文的第二个问题:

能不能保留 Agents CLI 的部署、评测和观测能力,同时允许团队替换特定命令,而不必复制整套工具?

作者给出的答案是 Extension System。

文章介绍的版本背景是:Agents CLI 从 v1.5.0 开始提供扩展系统,v1.8.0 则提供了官方 LangChain 扩展。

1.3 这节真正想表达的技术思想#

引言其实提出了两种不同的扩展需求:

需求本质问题对应思路
支持 LangChain 等框架原有命令与特定框架耦合替换框架相关命令
执行企业自定义规范默认工作流不能满足企业要求覆盖或新增命令

这两种需求看起来不同,但作者希望通过同一套扩展机制来解决。

这是值得学习的设计思路:先识别两类需求的共同抽象,再用同一套机制承载不同用途。

当然,这也不是没有代价。扩展机制越灵活,就越需要处理命令冲突、版本兼容、权限边界和失败行为。文章后面会通过具体实现展示它如何处理其中一部分问题。

二、框架锁定问题#

这一节的重点不是介绍新功能,而是解释:为什么原有的 Agent 开发工具链会让团队在更换框架时付出不必要的成本,以及作者为什么需要设计扩展系统。

2.1 问题出在哪里?#

原文指出,目前很多 Agent 开发工具链都有一个共同限制:它们往往围绕某个特定框架设计,周边的部署目标、CI/CD 流水线、评测工具和可观测性配置,也默认开发者已经选择了这个框架。

如果开发者使用的框架恰好符合工具链的假设,一切都会很顺利;如果不符合,就会遇到麻烦。

这里有一个关键概念:Opinionated(有明确约定的、带有设计主张的)。

假设你开发一套 Agent CLI,规定所有项目必须具有这样的结构:

my-agent/
├── agent.py
├── config.yaml
├── tests/
└── Dockerfile

你的 CLI 还规定:

  • agent.py 必须导出某个固定名称的 Agent 对象;
  • 运行命令必须使用指定框架的 Runner;
  • 评测模块必须从指定框架的运行对象读取轨迹;
  • Playground 必须启动该框架自带的调试服务。

这时,工具链就不只是提供通用功能,而是对 Agent 的组织方式作出了明确假设。

这种设计本身并不一定有问题。

它的优点是开发体验统一、约定明确、默认流程简单。对于从零开始、愿意采用统一技术栈的团队来说,这甚至是优势。

真正的问题是:当框架假设不再成立时,原本方便的约定就变成了迁移障碍。

例如,团队已经有一个基于 LangGraph 的生产系统。如果为了使用某套部署工具而必须把 Agent 重写成 ADK 版本,那么迁移成本就可能很高。

需要注意,作者批评的不是 ADK 或 LangChain 本身,而是开发工具链把某个框架当作唯一前提的设计方式。

2.2 框架不匹配时,原文列出的三种选择#

原文把开发者面临的选择归纳为三种,并认为它们都有明显缺点。

方案一:Fork 原项目#

原文主旨:复制 CLI 的代码,维护一份与上游逐渐分叉的版本,并在上游发布新版本时重新同步改动。

先解释一个术语:Fork 指基于现有仓库创建自己的独立分支或副本,之后可以独立修改和发布。

假设原项目的代码仓库是:

google/agents-cli

你希望让它支持 LangChain,于是复制一份:

your-name/agents-cli

然后修改运行逻辑:

# 原项目:假设只支持 ADK
def run_agent(agent):
return adk_runner.run(agent)
# 你修改后的项目
def run_agent(agent):
if framework == "adk":
return adk_runner.run(agent)
elif framework == "langchain":
return langchain_runner.run(agent)

这是一个简化示例,并非原项目的真实代码。

一开始看起来很简单。问题在于,上游项目也在持续发展。

假设上游增加了新的评测功能、修复了部署 Bug、改善了 Trace 采集,而你的 Fork 也修改过同一批代码,那么你就需要判断哪些改动可以合并,哪些会与自己的修改冲突。

上游 Agents CLI 持续更新
上游版本:新增评测功能、修复 Bug
你的 Fork:包含自己的框架适配代码
│
▼
维护者必须处理合并:解决冲突、回归测试、验证兼容性,然后发布自己的版本

原文把这种持续付出的维护成本称为 maintenance tax,可以理解为“维护税”。

这里要注意:Fork 本身不是坏设计。如果你准备长期维护一个有大量独立功能的产品,Fork 可能完全合理。作者反对的是:为了支持一个不同的 Agent 框架,就不得不复制和维护整个 CLI。

方案二:在原 CLI 外面包一层脚本#

第二种方案是保留原来的 CLI,自己再写一套命令入口。

例如:

开发者
│
▼
my-agent-cli
│
├── run ──────► LangChain 执行脚本
├── eval ──────► 自定义评测脚本
└── deploy ────► agents-cli deploy

这个方案的优势是:你不需要修改上游 CLI,也不需要复制整个项目。

但是,原文指出了一个很实际的问题:开发者或编码助手可能绕过包装层,直接调用底层工具。

例如,你给团队规定只能使用:

Terminal window
./my-agent-cli deploy

因为它会先执行安全检查,再调用真正的部署命令。

但某位开发者或 AI 编码助手可能直接运行:

Terminal window
agents-cli deploy

这样就绕过了你的检查。

这说明,包装脚本只能约束主动通过它调用命令的使用者,不能天然保证所有调用都经过它。

如果你真的要强制执行部署策略,就需要让策略处于无法轻易绕过的执行边界上,例如受保护的 CI/CD 部署流程或具有权限控制的部署服务,而不能只依赖开发者遵守约定。

所以,原文并不是说包装脚本不好,而是指出:如果目标是改变原 CLI 的命令行为,单独增加一个平行入口不够可靠。

方案三:从零重写#

第三种方案是放弃原项目,自己重新开发所有需要的能力。

例如:

my-agent-platform/
├── cli/
├── runtime/
├── evaluation/
├── observability/
├── deployment/
└── config/

你可以按照自己的需求设计每个模块,也完全不需要兼容原 CLI 的实现。

问题是,原文指出,这样的工作可能耗费数人月的工程时间,而且最终产品未必比上游项目成熟。

更重要的是,重新开发的不只是一个 run 命令。你还可能需要补齐:

  • 项目脚手架和配置管理;
  • 云端部署与 CI/CD;
  • 评测数据格式、评分和结果比较;
  • Trace、日志和错误诊断;
  • 版本兼容和用户文档。

这些功能中的每一个都可能有大量边界情况。

如果你只是想增加一种 Agent 框架支持,那么重写整个工具链往往不划算。

2.3 为什么这个问题有两种不同的应用场景?#

这一段很重要,因为它解释了为什么作者没有只做一个 LangChain 适配器,而是设计通用的 Extension System。

场景 A:企业希望统一治理#

假设一家公司有 50 名开发者,大家都使用 Agents CLI,但公司有统一的安全要求。

平台团队要求:

  • 每次部署前必须执行合规扫描;
  • 登录必须走公司的 SSO;
  • 评测必须经过模型风险管理流程。

这时,平台团队并不一定想改变 Agent 框架,而是想改变某些命令的执行方式。

例如:

开发者执行 deploy
│
▼
统一部署命令
│
▼
合规检查
│
┌────┴────┐
▼ ▼
通过 不通过
│ │
▼ ▼
部署服务 阻止部署

企业希望这套规则在团队的所有项目中生效,而不是让每个开发者自己复制一份脚本。

这属于工作流治理问题。

场景 B:同一团队只在某个项目使用其他框架#

另一种情况是,团队平时主要使用 ADK,但有一个项目必须使用 LangChain。

例如:

项目Agent 框架希望继续使用的工具链能力
项目 AADK运行、评测、部署
项目 BADK运行、评测、部署
项目 CLangChain运行、评测、部署

原文给出的数字是:当时 Agents CLI 有 6 个命令依赖 ADK 的运行时,另外约 18 个命令与具体框架无关。没有扩展机制时,选择非 ADK 框架可能导致无法继续使用整个 CLI 的 24 个命令。

这里的重点不是数字本身,而是这个设计问题:

因为一个项目的运行框架不同,就失去整个工具链的其他通用能力,是不合理的耦合。

例如,LangChain 项目仍然可以使用通用的部署命令,只需要替换那些必须理解 ADK 对象的运行、调试或评测命令。

2.4 结论:为什么需要 Extension System?#

现在把三种方案和两种场景放在一起看:

原有做法主要问题
Fork 整个 CLI长期维护成本高,需要持续同步上游
包装一层脚本使用者可能绕过包装入口
从零重写工程投入大,容易重复造轮子
Extension System只替换或新增必要的命令,尽量复用其余能力

作者的思路是:不必让每个项目都采用同一套 Agent 框架,也不必允许每个团队随意维护一份 CLI。

可以把 CLI 的命令分为两类:

  • 与框架相关的命令:允许扩展替换。
  • 与框架无关的命令:继续使用原有实现。

此外,同一套扩展机制还允许企业增加部署前检查之类的自定义流程。

这就是文章标题中“any framework”的工程含义:不是让所有框架自动兼容,而是允许通过局部适配,让不同框架共享尽可能多的工程基础设施。

不要为了替换一个有框架依赖的模块,就复制或重写整个工具链;应该识别耦合边界,只替换必要的部分。

三、Agents CLI 扩展是什么#

这一节开始进入真正的实现。前一节解释了为什么 Fork、包装脚本和重写都有局限;这一节则回答:不修改 CLI 核心代码,怎样改变它的命令行为?

3.1 扩展的本质:一个带有声明文件的 Git 目录#

原文定义的核心是:一个 Agents CLI Extension,就是 Git 仓库中的一个目录,其中包含名为 agents-cli-extension.yaml 的文件。

这个文件声明两件事:

  1. 要替换哪些已有命令。
  2. 要新增哪些命令。

例如,一个扩展可能长这样:

my-extension/
├── agents-cli-extension.yaml
└── scripts/
├── run.py
└── deploy.sh

这里有两个不同的角色:

  • agents-cli-extension.yaml:声明 CLI 应该如何使用这个扩展。
  • scripts/:放置真正执行工作的程序。

这是一种常见的声明式配置 + 命令实现的设计。

配置文件不负责真正运行 Agent,也不负责扫描依赖漏洞。它只告诉 CLI:当用户执行某个命令时,应当调用哪个程序。

3.2 扩展有两种形式#

形式 A:覆盖已有命令(Command Override)#

假设原本的命令是:

Terminal window
agents-cli run "分析这份日志"

原生实现使用 ADK 的运行机制。

现在安装了 LangChain 扩展。同样的命令仍然可以执行,但 CLI 将其转交给 LangChain 对应的实现。

用户输入:agents-cli run "分析这份日志"
│
▼
CLI 命令解析与扩展路由
(发现当前项目覆盖了 run)
│
▼
执行扩展指定的程序:uv run python scripts/run.py

这里最重要的细节是:用户传给命令的参数会原样传递给扩展程序。因此,用户不需要记住另一套命令语法,扩展也能保留原命令的使用体验。

但这并不代表新实现自动获得了原生实现的所有功能。扩展必须自己实现它接管的行为,并处理相应参数和错误。

形式 B:新增命令(Command Addition)#

扩展也可以增加原 CLI 没有的功能。

例如:

Terminal window
agents-cli sbom-report

这个命令可以生成软件物料清单(SBOM),而不需要修改原 CLI 的核心代码。

因此,覆盖和新增的区别是:

方式命令原本存在吗?扩展的作用
Override存在替换原有行为
Add不存在注册新命令

一个扩展可以同时包含这两种操作。文章后面的 sbom-deploy 示例就会覆盖 deploy,同时新增 sbom-report。

3.3 为什么扩展通过 Git 仓库分发,而不是专门的插件市场?#

原文接着解释扩展的分发方式:不需要先发布到一个专门的扩展注册中心,Git 仓库就可以充当分发渠道。

例如:

Terminal window
# 使用官方仓库中的扩展
agents-cli extension add langchain
# 使用公司内部仓库
agents-cli extension add acme/platform-extensions
# 使用仓库中的指定扩展
agents-cli extension add acme/platform-extensions#soc2-deploy
# 从本地目录安装开发中的扩展
agents-cli extension add local@../my-extension

这些是文章展示的命令形式。具体哪些名称和参数可用,取决于 CLI 版本及仓库结构。

这里有三个值得理解的设计点。

第一,扩展不需要专门的包格式。

只要仓库包含约定的清单文件和脚本,CLI 就可以根据清单加载它。作者不必为了发布扩展,再开发一套独立的打包和分发系统。

第二,一个仓库可以包含多个扩展。

例如,企业可以维护:

platform-extensions/
├── deploy-policy/
├── login-sso/
└── scaffold-policy/

不同团队使用同一个仓库中的不同扩展,同时共享维护流程和发布节奏。

第三,Git 同时承担版本管理和分发职责。

团队可以使用 Git tag、分支和 commit 指定扩展版本,而不必另建一个专用注册中心。

这降低了基础设施成本,但也意味着团队必须认真管理仓库访问权限、代码审查和版本固定。Git 能分发代码,不等于它自动保证代码安全。

3.4 扩展安装到哪里?Project Scope 与 User Scope#

原文接下来讨论扩展的作用范围(Scope)。

Agents CLI 支持两种安装范围。

Project Scope:项目级。 扩展属于某个项目,相关配置与锁文件可以提交到项目 Git 仓库,队友和 CI 在检出项目后能够恢复同一版本。

Terminal window
agents-cli extension add acme/platform-extensions#soc2-deploy

User Scope:用户级。 扩展安装在用户的配置目录中,供这台机器上的多个项目使用,适合个人习惯或项目创建之前就需要的扩展。

Terminal window
agents-cli extension add acme/platform-extensions#bootstrap --global

这里需要区分两个概念:

  • 安装范围决定扩展作用于哪些项目。
  • 命令覆盖规则决定同一个命令最终采用哪个实现。

文章明确指出,当项目级和用户级扩展定义了同一个命令时,项目级优先。CLI 还会显示生效命令的来源;发生冲突时,会在安装或扩展列表中给出提示。

为什么项目级优先?

假设你在个人电脑上安装了一个全局扩展,让所有项目的 run 命令都使用某种自定义实现。但某个项目明确要求使用另一种运行方式,那么项目自己的配置应该能够覆盖个人的默认设置。

这种规则使项目能够声明自身需要的行为,而不是完全受开发者电脑上的全局配置支配。

3.5 Lockfile:为什么安装扩展还需要锁文件?#

这是本节最值得深入理解的部分之一。

假设你安装了公司扩展:

Terminal window
agents-cli extension add acme/platform-extensions#soc2-deploy

如果扩展引用的是会继续变化的分支,那么明天同一个分支上可能出现新代码。

如果每次安装都直接取最新代码,那么同一份应用代码在不同日期可能执行不同的部署逻辑。

文章采用的方式与 uv、npm 等包管理器的锁定思路类似:安装时把 Git 引用解析为精确的 commit SHA,并将结果记录在锁文件中。

例如:

extensions:
- name: sbom-deploy
source: acme/platform-extensions#sbom-deploy
ref: v1.0.0
sha: a1b2c3d4...
scope: project

这里的 SHA 只是文章中的示意值。

各字段的作用:

字段含义
name扩展名称
source扩展来自哪个 Git 仓库和目录
ref声明使用的版本引用,例如 tag
sha实际固定下来的 Git commit
scope扩展的安装范围

为什么既要 ref 又要 sha?

ref 表示人类使用的版本引用;sha 则把实际代码固定到某个具体提交。即使引用以后指向了别的提交,锁文件中的 SHA 仍然能标识原来的代码。

文章还区分了安装和升级:

  • agents-cli install:恢复锁文件固定的版本,不主动推进版本。
  • agents-cli extension update:重新解析所跟踪的引用,更新固定版本。

这意味着升级是一项明确的操作,而不是每次安装时都可能发生的隐式变化。

但还需要注意:锁文件保证的是扩展代码版本可复现,并不意味着整个系统的所有依赖、远程服务和运行环境都自动实现了完全可复现。

3.6 这一节的完整设计逻辑#

把本节串起来,整个机制可以理解为:

Git 扩展仓库
清单文件 + 执行脚本
│
▼
安装与版本解析
确定来源、范围和精确 commit
│
▼
CLI 加载扩展
检查命令覆盖和新增定义
│
┌────┴────┐
▼ ▼
覆盖命令 新增命令
使用扩展实现 注册新功能

从工程设计角度看,这一节实际上把三个问题放进同一个机制中解决:

  1. 行为扩展:通过清单声明命令替换和新增。
  2. 作用范围:决定扩展是项目专用还是用户通用。
  3. 版本治理:通过锁文件固定扩展代码,并显式升级。

还有一个需要保持警惕的边界:扩展脚本本质上是可执行代码。即使版本固定,也仍然需要审查来源、控制权限并验证脚本行为。版本可复现不等于代码可信。

Agents CLI Extension 是一个由 Git 分发、通过 agents-cli-extension.yaml 声明行为的扩展单元。它既可以覆盖已有命令,也可以新增命令。扩展支持项目级和用户级作用范围,并通过锁文件固定 Git commit,保证安装时恢复相同的扩展代码。其核心价值是局部替换和增量扩展,而不是复制整个 CLI。

四、通过 Agents CLI 运行 LangChain#

前面介绍的 Extension System 到底有没有用,关键就看它能不能让一个不使用 ADK 的项目,仍然复用 Agents CLI 的大部分开发流程。

作者用官方 LangChain 扩展验证了这个想法。

4.1 LangChain 扩展到底提供了什么?#

作者首先介绍了一个官方扩展模板。通过它,开发者可以创建一个 LangChain Agent 项目,并将其接入 Agents CLI 的开发生命周期。

最重要的特点有两个:

  • 新项目不依赖 google-adk。
  • 项目仍然可以使用 Agents CLI 的通用部署流程部署到 Cloud Run。

原文还特别提到 LangChain 1.0 及之后版本的状态管理层由 LangGraph 提供。因此,同一套模板可以面向 LangChain Agent、LangGraph 图以及 Deep Agents。

这里先理解一个区别:

  • LangChain:提供构建模型应用和 Agent 的 API。
  • LangGraph:提供有状态的图式执行与流程管理。
  • Deep Agents:建立在相关生态之上的更高层 Agent 方案。

它们不是完全相同的东西,但可以共享部分底层运行基础。

这也是模板能够采用共同图表示的原因。并不意味着所有框架的 API、工具调用语义和评测行为都完全相同。

4.2 Prerequisites:运行示例需要什么?#

作者列出了以下前置条件:

依赖用途
Python 3.11 或更高版本运行 Agent 和相关脚本
uv管理 Python 依赖与虚拟环境
Google Cloud SDK认证及操作 Google Cloud
已启用计费的 Google Cloud 项目部署 Cloud Run、使用相关云服务

此外,还需要配置应用默认凭证,并启用 Vertex AI、Cloud Run 和 Artifact Registry 等 API。

原文给出的安装方式:

Terminal window
uvx google-agents-cli setup

Google Cloud 认证与 API 配置:

Terminal window
gcloud auth application-default login
gcloud config set project YOUR_PROJECT_ID
gcloud services enable \
aiplatform.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com

这里有个值得注意的工程细节:uvx 可以临时运行 Python 工具,而不要求你先手动创建一个专门的 Python 项目安装环境。

不过,安装 CLI 与准备好云端部署环境是两件事。即使 CLI 已经安装成功,没有正确的云端凭证、项目权限或 API 配置,也不能保证部署成功。

如果只是想学习框架适配,可以先在本地运行 Agent;不必一开始就创建云端资源。

4.3 Step 1:创建项目时,扩展就已经参与进来了#

这是本节第一个核心实现。

原文使用的命令是:

Terminal window
agents-cli create my-agent \
--agent google/agents-cli/extensions/langchain/[email protected] \
-d cloud_run
cd my-agent
agents-cli install

这里有三个值得拆解的地方。

第一,create 不只是创建空目录。

CLI 根据指定模板生成一套可运行的项目骨架,包括 Agent 代码、依赖、部署配置、评测样例和扩展清单。

第二,--agent 指向一个特定的模板。

这里指定了官方 Git 仓库中的 LangChain 模板,并固定到 v1.8.0。这使生成项目时使用的模板版本明确,而不是随意获取某个未来版本。

第三,agents-cli install 恢复项目所需的扩展和相关依赖。

生成的项目根目录已经包含 agents-cli-extension.yaml,因此后续命令可以识别项目自己的扩展定义。开发者不必再手动创建另一份包装 CLI。

原文特别强调:从开发者的角度看,这仍然像一次普通的 agents-cli create 操作。扩展机制被藏在统一入口之后,不要求使用者先掌握所有扩展系统细节。

为什么这个设计重要?

如果没有这种机制,团队可能需要先安装一个定制版 CLI,再运行它专属的项目创建命令。

而文章的做法是:让扩展成为项目创建流程的一部分,而不是要求开发者维护第二套工具入口。

这使团队可以在项目生成阶段就选择框架,同时继续使用统一的工程命令。

4.4 Step 2:生成的项目究竟包含什么?#

文章给出了实际生成的目录结构。我们重点看与框架适配有关的文件。

my-agent/
├── agents-cli-extension.yaml
├── agents-cli-manifest.yaml
├── langgraph.json
├── pyproject.toml
├── Dockerfile
├── app/
│ ├── agent.py
│ ├── fast_api_app.py
│ └── app_utils/
│ ├── a2a.py
│ └── telemetry.py
├── scripts/
│ ├── run.py
│ ├── eval_generate.py
│ └── publish.py
├── tests/
│ └── eval/datasets/basic-dataset.json
├── deployment/
└── uv.lock

这些文件不是简单地堆在一起,每个文件都有自己的职责。

文件作用
agents-cli-extension.yaml声明要覆盖的 CLI 命令
agents-cli-manifest.yaml描述项目及部署相关配置
langgraph.json配置本地 LangGraph 开发服务器
pyproject.toml声明 Python 项目依赖
uv.lock锁定解析后的 Python 依赖版本
app/agent.py定义实际的 LangChain Agent
app/fast_api_app.py创建对外提供服务的 FastAPI 应用
app_utils/a2a.py将 Agent 接入 A2A 协议相关的执行器与路由
app_utils/telemetry.py配置 Trace 和 GenAI 日志相关能力
scripts/run.py替代原生 agents-cli run 的实现
scripts/eval_generate.py替代原生评测数据生成命令
scripts/publish.py处理特定发布目标下的兼容行为

最关键的两个文件#

首先是 agents-cli-extension.yaml。

它告诉 CLI:当前项目的 run、playground 和部分评测命令应该使用 LangChain 对应的实现,而不是原生的 ADK 实现。

其次是 app/agent.py。

它才是真正定义 Agent 行为的地方。扩展清单不会替你实现模型调用,也不会替你编写业务工具;它只负责把统一命令入口连接到合适的程序。

因此,这套结构实际上分成三层:

Agents CLI
统一命令与工程生命周期
│
▼
Extension Scripts
将 CLI 命令适配到 LangChain 的运行方式
│
▼
LangChain Agent + FastAPI
实际执行任务,并对外提供服务

这里要特别区分:scripts/run.py 是命令行层的适配代码;app/agent.py 是 Agent 本身的实现。两者不是同一个东西。

4.5 agent.py:真正的 Agent 是怎样定义的?#

文章使用 LangChain 的 create_agent 创建 Agent,并将最终对象导出为 root_agent。这个对象的类型是 CompiledStateGraph,因此可以由 LangGraph 相关工具使用。

这里不需要先把所有 LangChain API 学完。先看一个简化版的例子,理解代码之间的关系:

from langchain.agents import create_agent
def get_weather(city: str) -> str:
return f"{city} 今天晴天"
root_agent = create_agent(
model="your-model",
tools=[get_weather],
system_prompt="你是一个天气助手。",
)

这段代码说明:

  1. get_weather 是工具的具体实现。
  2. create_agent 把模型、工具和提示词组合成 Agent。
  3. root_agent 是供其他代码调用的 Agent 对象。

这是帮助理解的简化示例,不是文章完整代码。

文章的关键不是天气工具,而是 root_agent 的具体类型。

如果原有 CLI 假定项目一定会导出 ADK 的 Agent 对象,那么它就不能直接用 ADK 的运行器处理 LangChain 的图对象。

因此,作者没有要求 LangChain 伪装成 ADK,而是通过扩展替换运行命令,让新的命令知道如何执行 CompiledStateGraph。

这就是框架适配的核心:不是强迫不同框架使用相同的内部对象,而是在需要接入的边界上适配它们。

4.6 Step 3:执行 agents-cli run 时,究竟发生了什么?#

文章使用下面的命令测试 Agent:

Terminal window
agents-cli run "what's the weather in San Francisco?"

输出示例中,CLI 会先提示有六个扩展命令正在生效,然后显示天气助手的回答。

把这条命令拆开看,实际发生的事情可以理解为:

用户执行 agents-cli run
│
▼
CLI 识别项目中的扩展清单
│
▼
将命令转交给 scripts/run.py
│
▼
加载 root_agent 并执行 LangGraph
│
▼
模型决定调用 get_weather
│
▼
工具返回结果,模型组织最终回答

文章的示例中,天气工具返回的是模拟数据。它演示的是工具调用链路,不是从真实天气服务获取实时天气。

为什么 CLI 要显示覆盖警告?#

因为命令表面上没有变化,但底层实现已经变化。

如果用户执行 agents-cli run,却不知道它正在调用 LangChain 实现,遇到异常时就可能误以为是原生 ADK 命令出了问题。

所以文章展示的 CLI 会明确提示哪些命令被哪个扩展接管。

你可以把它理解为一种运行时透明性:扩展可以改变行为,但这种改变应该能够被开发者观察到。

同样,agents-cli --help 也会在被覆盖的命令后面标注对应扩展的名称,而 agents-cli info 可以列出完整的生效命令和执行向量。

这对编码 Agent 也有价值,因为它读取终端输出时,可以知道当前项目的 run 实际执行的是什么程序,而不是凭默认知识猜测。

playground 又有什么不同?#

文章还将 playground 替换成 LangGraph 的本地开发服务器:

Terminal window
agents-cli playground

该命令启动 LangGraph 开发环境,而不是原来面向 ADK 的 Web UI。

这说明框架适配并不只涉及执行一次 Agent。与框架绑定的开发体验,也可能需要替换。

4.7 Step 4:为什么 deploy 不需要替换?#

这是整篇文章最重要的对照实验。

前面的 run 和 playground 需要替换,是因为它们理解 Agent 框架的内部对象和运行方式。

但 deploy 不需要知道这些细节。

文章中的 LangChain 项目通过 FastAPI 暴露服务,并由 Uvicorn 启动。Dockerfile 使用的启动命令是:

CMD ["uv", "run", "uvicorn", "app.fast_api_app:app", \
"--host", "0.0.0.0", "--port", "8080"]

随后仍然使用原来的命令:

Terminal window
agents-cli deploy

CLI 通过 Google Cloud 的部署流程,将项目发布到 Cloud Run。

这里的 FastAPI 是关键#

想象一下,你写了两个 Agent 服务:

  • 服务 A 内部使用 ADK。
  • 服务 B 内部使用 LangChain。

只要它们都能按照约定启动 HTTP 服务,并提供部署平台需要的接口,部署工具就可以把它们当作普通服务处理。

部署工具不必关心服务内部究竟使用什么框架。

统一的 agents-cli deploy
│
▼
构建 Docker 镜像
(根据项目的 Dockerfile 构建)
│
┌───────────┴───────────┐
▼ ▼
ADK 服务 LangChain 服务
(FastAPI / HTTP) (FastAPI / HTTP)
└───────────┬───────────┘
▼
Google Cloud Run

这里有一个重要的工程原则:

如果两个组件能够遵循相同的外部契约,就没有必要让上层工具理解它们所有的内部差异。

当然,统一 HTTP 启动方式不等于所有 Agent 服务都能无条件部署。环境变量、认证、网络访问、健康检查和请求协议仍然需要符合部署要求。

文章还提到,infra、install、login、lint 等其他通用命令同样可以继续使用,不需要因为换了 Agent 框架而重写。

4.8 Step 5:评测如何跨框架复用?#

这一部分把前面的框架适配思路延伸到了 Agent 评测。

文章展示的流程是:

Terminal window
# 1. 运行 LangChain Agent,生成评测结果
agents-cli eval generate \
--dataset tests/eval/datasets/basic-dataset.json \
-o tests/eval/output/
# 2. 对生成的 Trace 进行评分
agents-cli eval grade \
--traces tests/eval/output/basic-dataset.json \
--config tests/eval/eval_config.yaml
# 3. 比较不同运行结果
agents-cli eval compare \
tests/eval/output/run_1.json \
tests/eval/output/run_2.json

这里的关键是:eval generate 被 LangChain 扩展替换了,但 eval grade、eval compare 和 eval analyze 继续使用原生实现。

为什么可以这样做?

因为这两个阶段解决的是不同的问题。

阶段一:执行 Agent#

eval generate 必须知道如何加载和执行当前框架中的 Agent。

ADK 项目可能通过 ADK 的运行器执行;LangChain 项目则需要调用 LangGraph 图。

所以,这个阶段与框架有关。

阶段二:评估执行结果#

如果不同框架都能把运行结果转换成统一的 EvaluationDataset 格式,那么后面的评分和比较就不必关心 Agent 内部是怎么运行的。

例如,可以用简化的数据结构表示一次测试:

{
"input": "北京天气怎么样?",
"output": "北京今天晴天",
"expected_output": "返回北京的天气信息",
"tool_calls": [
{
"name": "get_weather",
"status": "success"
}
]
}

这只是用于理解的示意结构,并非文章实际使用的完整 Schema。

统一格式相当于为不同框架规定了共同的数据接口。

只要各自的执行适配器能正确生成符合契约的数据,下游评测模块就可以复用。

但是,这里有一个很重要的限制#

统一数据格式,不代表评测一定可靠。

假如一个 Agent 声称自己调用了 get_weather,但实际没有执行工具,那么生成器如果只记录模型的文字描述,评测系统就可能误判。

因此,真正可靠的 Agent 评测还需要明确区分:

  • 模型提出的工具调用请求;
  • 框架实际执行的工具调用;
  • 工具返回的真实结果;
  • 最终任务是否达到预期。

这部分是结合文章设计提出的工程要求。文章主要展示了跨框架复用评测流程的方式,并没有因此自动解决所有工具调用真实性问题。

为什么不支持的命令要明确拒绝?#

文章指出,LangChain 扩展会明确拒绝某些仍依赖 ADK 的评测子命令,例如需要 ADK 用户模拟器的 eval dataset synthesize。

它不会假装执行成功,也不会静默返回一个无效结果,而是解释该功能为什么不受支持。

这一点对 Agent 错误处理的设计同样有启发:

如果某个操作缺少必要的运行环境,系统应该明确报告前置条件不满足,而不是让编码 Agent 反复重试同一个无法成功的命令。

4.9 把整个实战流程串起来#

读完这一节,你应该能解释这个完整的工程过程:

创建 LangChain 项目
通过官方模板生成代码和扩展清单
│
▼
本地运行与调试
替换 run、playground 等框架相关命令
│
▼
生成评测数据
由 LangChain 执行器产生标准格式的结果
│
▼
复用原生评测与部署
评分、比较、部署等通用命令不必重写

这才是 Extension System 在真实项目中的价值:它不是凭空消除框架差异,而是把差异限制在少数必须适配的边界上。

本节要点:

  1. 项目创建:通过指定模板,生成带有扩展清单的 LangChain 项目。
  2. 运行适配:替换 run、playground 等依赖特定框架的命令。
  3. 部署复用:通过 FastAPI、Docker 和 Cloud Run 的统一服务契约,继续使用原生部署流程。
  4. 评测复用:让框架相关的执行阶段输出统一格式,下游评分和比较继续使用通用实现。

最值得思考的问题是:

如果自己开发一套 Agent 观测和评测工具,你会把框架适配放在哪里?是要求所有框架实现同一套内部 API,还是让每个框架通过适配器输出统一的 Trace 和评测数据?

这正是从阅读文章走向自己设计 Agent 基础设施的关键一步。

五、编写你自己的扩展#

前面我们分析了 Google 官方提供的 LangChain 扩展。这一节不再使用现成的 Agent 框架适配器,而是从零实现一个自定义扩展,展示扩展系统如何用于真实的工程流程。

原文选择的例子是 sbom-deploy:在部署应用之前,自动生成软件物料清单(SBOM),检查依赖是否存在严重漏洞,只有检查通过才能继续部署。

5.1 先理解这个扩展要解决什么问题#

假设你的项目原本使用:

Terminal window
agents-cli deploy

直接部署到云端。

现在,团队提出了新的安全要求:

  1. 部署之前,生成项目的依赖清单。
  2. 扫描依赖是否存在严重漏洞。
  3. 发现严重漏洞时,禁止部署。
  4. 检查通过后,继续使用原来的部署流程。

最直接的做法是修改部署脚本,或者要求每个开发人员手动执行扫描命令。

但这两种方式都有问题:前者可能需要侵入原有工具,后者容易因为人为疏忽而跳过检查。

因此,文章采用扩展机制,把原来的 deploy 命令替换成一个安全检查包装器。

开发人员执行 agents-cli deploy
│
▼
自定义 Extension(sbom_then_deploy.sh)
│
▼
生成 SBOM 并扫描漏洞(Syft + Grype)
│
┌─────┴─────┐
▼ ▼
发现严重漏洞 扫描通过
│ │
▼ ▼
终止部署 执行原生 deploy

这里有一个值得注意的设计:扩展并没有重新实现云部署功能,而是只负责增加部署前的安全检查。

这叫作包装原有命令(Wrapper)。扩展负责额外的逻辑,原来的 CLI 继续负责自己擅长的事情。

5.2 第一步:定义扩展清单#

原文首先创建一个 agents-cli-extension.yaml 文件。

这个文件相当于扩展的声明文件,告诉 Agents CLI:扩展叫什么、支持什么版本、要新增哪些命令,以及要覆盖哪些原有命令。

原文示例:

schema: agents-cli-extension/v1alpha1
name: sbom-deploy
description: Scan the project SBOM, then run the native agents-cli deploy.
requires:
agents_cli: ">=1.5,<2"
on_incompatible: warn
commands:
override:
deploy:
run: ["bash", "scripts/sbom_then_deploy.sh"]
description: SBOM scan via syft+grype, then the built-in deploy.
add:
sbom-report:
run: ["bash", "scripts/sbom_report.sh"]
description: Produce an SBOM for the current project (no deploy).

我们逐项分析。

1. schema:清单采用什么格式#

schema: agents-cli-extension/v1alpha1

它声明这个文件遵循哪一个扩展清单规范。

可以把它理解为配置文件的版本标识。CLI 通过这个标识及对应的校验规则判断清单是否合法。

这里的 v1alpha1 也说明扩展接口处于早期阶段,不应该默认它已经是完全稳定的长期 API。

2. name:扩展的身份标识#

name: sbom-deploy

扩展的名字是 sbom-deploy。

它会用于识别扩展、安装扩展,以及执行扩展更新等管理操作。

例如:

Terminal window
agents-cli extension add local@../my-sbom-extension

这里的 local@../my-sbom-extension 指向本地扩展目录,而不是某个已经注册的中央扩展商店。

3. requires:限制兼容的 CLI 版本#

requires:
agents_cli: ">=1.5,<2"
on_incompatible: warn

这表示扩展要求 Agents CLI 版本至少为 1.5,但低于 2.0。

为什么要写这个限制?

假设未来 CLI 2.0 修改了扩展的执行协议,而你的包装脚本依赖旧版本行为。没有版本约束时,用户可能安装成功,却在运行过程中出现错误。

on_incompatible: warn 表示版本不兼容时采用警告策略。它不等同于强制阻止安装,所以如果项目要求严格的兼容性保证,还需要结合具体版本和测试策略处理。

4. commands.override:覆盖已有命令#

commands:
override:
deploy:
run: ["bash", "scripts/sbom_then_deploy.sh"]

这是整份配置中最重要的部分。

它的意思是:当用户执行 agents-cli deploy 时,使用扩展提供的脚本处理这个命令。

注意,run 不是一整段 Shell 字符串,而是一个参数数组(argv vector):

  • bash:要启动的程序。
  • scripts/sbom_then_deploy.sh:传递给 Bash 的脚本路径。

这种设计能够让 CLI 明确知道应该启动哪个程序,以及传递哪些参数。

还有一个细节:文章强调可以覆盖具体子命令,但不能随意覆盖整个命令组。例如,eval.generate 和 eval.optimize 可以分别处理,而不能简单地把整个 eval 命令组当成一个覆盖目标。

5. commands.add:增加全新的命令#

add:
sbom-report:
run: ["bash", "scripts/sbom_report.sh"]

除了替换已有的 deploy,这个扩展还新增了一个 sbom-report 命令。

两者的区别如下:

配置作用示例
override替换已有命令的处理逻辑agents-cli deploy
add添加新的扩展命令agents-cli sbom-report

这意味着扩展既可以修改现有工作流,也可以提供独立功能。

不过,原文这里展示的是清单声明,具体新增命令的脚本实现并没有在这个示例中完整展开。

5.3 第二步:编写包装脚本#

清单只声明了命令的入口,真正的逻辑放在 scripts/sbom_then_deploy.sh 中。

原文示例:

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
echo "[sbom-deploy] generating SBOM..."
syft dir:. -o cyclonedx-json > /tmp/sbom.cdx.json
echo "[sbom-deploy] scanning for criticals..."
grype sbom:/tmp/sbom.cdx.json --fail-on critical
echo "[sbom-deploy] SBOM clean; running native deploy..."
exec agents-cli deploy "$@"

这段脚本值得仔细理解,因为它体现了真实工程中的失败处理、参数传递和命令组合。

1. 为什么使用 set -euo pipefail?#

Terminal window
set -euo pipefail

这是一种常见的 Bash 严格执行模式。

  • -e:命令发生符合规则的非零退出时,通常终止脚本。
  • -u:使用未定义的变量时,报错退出。
  • -o pipefail:管道中的命令失败时,不只看最后一个命令的退出状态。

在这个例子中,最重要的是 -e。

假设 Grype 扫描发现严重漏洞,并返回失败状态,脚本就不会继续执行最后的部署命令。

换句话说,安全检查不是一个只输出报告的旁路任务,而是部署流程的前置条件。

2. 为什么先切换工作目录?#

Terminal window
cd "$(dirname "$0")/.."

脚本从扩展目录下的 scripts/ 运行时,这行命令会将当前目录切换到扩展根目录。

这样可以避免用户从不同目录调用脚本时,相对路径解析出现问题。

例如,用户在项目根目录执行命令,与用户在其他目录通过 CLI 调用命令,可能导致相对路径指向不同的位置。脚本主动确定工作目录,可以减少这种不确定性。

但要注意:这里切换到的是扩展目录,并不必然是用户的业务项目根目录。实际部署时,还需要确认 CLI 的运行约定,以及扫描目标是否确实是当前项目。

3. Syft 和 Grype 分别做什么?#

Terminal window
syft dir:. -o cyclonedx-json > /tmp/sbom.cdx.json

Syft 用来分析指定目录中的软件及其依赖,生成 SBOM(Software Bill of Materials,软件物料清单)。

SBOM 可以理解为项目使用了哪些软件组件和依赖的清单。这里将输出格式指定为 CycloneDX JSON,并写入文件。

接着:

Terminal window
grype sbom:/tmp/sbom.cdx.json --fail-on critical

Grype 根据这份 SBOM 检查已知漏洞,并在达到指定严重程度的漏洞条件时返回失败状态。

两者的职责不同:

  • Syft:识别软件组件,生成清单。
  • Grype:根据清单进行漏洞检测。

这也是文章选择 SBOM 场景的原因:它可以清楚展示扩展如何在原有部署命令前增加一层工程治理逻辑。

4. 为什么最后使用 exec?#

Terminal window
exec agents-cli deploy "$@"

这里包含两个重要知识点。

第一,"$@" 是参数透传。

假设用户执行:

Terminal window
agents-cli deploy --region us-central1

扩展脚本应当尽可能把原始部署参数传给真正的部署命令,而不是只执行一个没有参数的 deploy。

"$@" 可以保留各个参数的边界,避免简单字符串拼接造成参数拆分问题。

第二,exec 会用新的进程映像替换当前 Shell 进程。

它可以减少不必要的进程层级,并让最终部署命令的退出状态更直接地传递给调用方。

因此,脚本完成安全检查后,就把控制权交还给原生部署命令。

5.4 关键问题:怎么避免无限递归?#

现在有一个看似矛盾的问题。

扩展覆盖了 deploy,但脚本最后又执行:

Terminal window
agents-cli deploy "$@"

那么会不会发生下面这种循环?

agents-cli deploy
→ 扩展脚本
→ agents-cli deploy
→ 扩展脚本
→ agents-cli deploy
→ 无限循环

如果 CLI 每次都执行同一个覆盖逻辑,确实会发生递归。

文章给出的机制是:CLI 在执行覆盖脚本时设置环境变量 AGENTS_CLI_DISABLE_OVERRIDES=1。

因此,脚本内部再次调用 agents-cli deploy 时,CLI 会禁用扩展覆盖逻辑,让这次调用落到原生的 deploy 实现上。

第一次调用 agents-cli deploy
│
▼
执行扩展包装脚本(先检查 SBOM 和漏洞)
│
▼
设置了 AGENTS_CLI_DISABLE_OVERRIDES=1
│
▼
再次调用 agents-cli deploy
│
▼
执行原生部署逻辑(不会再次进入包装脚本)

这是一种非常值得记住的扩展设计模式:

当扩展需要增强原有命令,但又希望复用原命令的实现时,需要明确区分“经过扩展的调用”和“直接执行原生命令的调用”。

否则,包装器很容易因为再次调用自身而陷入递归。

5.5 第三步:安装并验证扩展#

文章接下来展示如何安装本地扩展:

Terminal window
agents-cli extension add local@../my-sbom-extension

这里的路径指向本地 Git 扩展仓库。

扩展也可以从远程仓库安装,例如:

Terminal window
agents-cli extension add acme/platform-extensions#sbom-deploy --ref v1.0.0

这表示从指定仓库中安装名为 sbom-deploy 的扩展,并通过 v1.0.0 指定版本引用。

安装后,应该检查以下内容:

  1. 扩展是否成功安装。
  2. agents-cli deploy 是否显示或识别扩展覆盖。
  3. 新增的 agents-cli sbom-report 是否可用。
  4. 扫描失败时,部署是否确实被阻止。
  5. 扫描通过时,原生部署是否能正常执行。

前两项确认扩展是否被 CLI 识别;后三项则是在验证它的实际行为。

仅仅看到扩展安装成功,并不代表安全门禁已经正确工作。对这类扩展而言,失败路径尤其重要:如果漏洞扫描失败,部署必须停止。

5.6 Lockfile Discipline:为什么要提交锁文件?#

文章最后强调了扩展版本管理。

扩展安装后,项目会涉及:

  • agents-cli-extensions.yaml:记录扩展依赖及其锁定信息。
  • extensions/:保存相应的扩展工作副本。

文章建议将这些内容纳入 Git 版本管理,并使用明确的更新命令升级扩展。

为什么需要锁定版本?

假设开发人员 A 今天安装了某个扩展,开发人员 B 明天安装同一个仓库里的扩展。如果仓库内容已经变化,两个人可能运行的并不是同一份代码。

锁定具体的 Git commit SHA,可以让项目恢复到确定的扩展版本。

这里要区分三个动作:

操作目的
agents-cli extension add添加扩展
agents-cli install根据锁定信息恢复项目所需的扩展版本
agents-cli extension update主动更新扩展版本

这和 Java 项目中的依赖锁定有相似之处:不是每次构建都自动追踪依赖仓库的最新状态,而是让项目在明确更新之前保持可复现。

不过,锁定版本不等于保证安全。如果被锁定的 commit 本身存在恶意代码或漏洞,锁文件仍然会忠实地复现那份有问题的代码。因此,安装第三方扩展时仍然需要审查来源、代码和权限。

5.7 从工程角度补充:原文示例还有什么可以改进?#

这部分是额外的工程分析,不是原文直接提出的要求。

原文将 SBOM 写入:

/tmp/sbom.cdx.json

这适合展示基本流程,但实际部署中可以进一步完善。

  • 使用 mktemp 创建临时文件,避免多个任务同时运行时互相覆盖文件。
  • 使用 trap 在脚本退出时清理临时文件。
  • 确认 Syft 和 Grype 已安装,并明确工具缺失时的失败行为。
  • 对漏洞扫描失败采取默认阻断策略,避免扫描服务故障时意外放行。
  • 在 CI/CD 环境中保存扫描报告,便于后续审计和故障排查。

另外,漏洞扫描结果可能受漏洞数据库更新时间、依赖识别能力和配置影响。因此,生产环境应当把扫描器版本、漏洞数据库更新时间和例外审批规则也纳入治理。

这些改进体现了从“实现一个示例扩展”到“构建可用于团队生产环境的安全门禁”之间的差别。

本节实际上展示了三个层次的工程设计:

  1. 扩展声明层:通过 agents-cli-extension.yaml 声明新增命令、覆盖命令和兼容版本。
  2. 执行层:用包装脚本在原生部署之前加入安全检查,并通过退出码控制是否继续。
  3. 依赖治理层:通过锁文件和固定版本,使团队可以重复安装和审查相同的扩展代码。

本节最值得掌握的不是 Syft 或 Grype 的具体命令,而是这种可复用的架构:

通过一个可版本化的扩展,在不重写原有 CLI 功能的情况下,给原有工作流添加新的能力、策略或检查。

六、下一步与上手建议#

6.1 作者真正想推动什么?#

这篇文章表面上是在介绍 Agents CLI 的扩展功能,但更深层的目标是:让 Agent 开发工具链与具体的 Agent 框架解耦。

先回顾一下整个问题。

假设一个团队使用 Google ADK 开发 Agent,另一个团队使用 LangChain,还有一个团队使用自研框架。

如果开发工具链与框架强绑定,就可能出现这样的情况:

  • ADK 团队可以使用 CLI 提供的运行、调试和评估能力。
  • LangChain 团队需要自己寻找替代工具。
  • 使用自研框架的团队可能需要维护一套独立的脚本和部署流程。

这会导致重复建设:不同团队虽然开发的是同一类 Agent 应用,却不得不重复解决运行、评测、部署和工程治理的问题。

文章提出的方向是把问题拆开:

通用开发工具链
项目脚手架、部署、基础设施、认证、工具管理
│
▼
Extension 适配层
将通用 CLI 命令连接到不同框架的具体实现
│
┌───────────┼───────────┐
▼ ▼ ▼
ADK LangChain 自研框架

需要注意,这是一种架构层面的理解,并不意味着所有框架的功能都天然兼容。

例如,某个框架没有统一的评测数据格式,也没有可供外部工具调用的运行接口,那么扩展作者仍然需要实现相应的适配逻辑。有些功能可能无法适配,只能明确拒绝支持。

6.2 这套设计最有价值的地方是什么?#

可以从三个方面理解。

第一,通用能力与框架专属能力分离#

例如,部署一个基于 FastAPI 的 Agent 服务,通常不一定需要知道 Agent 是用 ADK 还是 LangChain 编写的。

只要项目提供了兼容的应用入口、容器配置和部署参数,通用部署工具就可能继续使用。

但运行 Agent、打开专用调试界面、操作框架内部的评测对象等功能,就可能依赖框架自身的接口。

因此,文章没有试图让所有框架共享完全相同的实现,而是允许扩展替换部分命令。

这比强行把所有框架抽象成同一个接口更现实。

第二,允许团队保留自己的技术选择#

框架会随着项目需求变化。

团队可能因为性能、生态、开发体验或者已有基础设施而选择不同框架。如果工程工具链与某个框架深度绑定,迁移成本就会增加。

扩展机制让团队可以保留一部分通用工程能力,而只替换确实依赖框架的部分。

不过,它降低的是工具链迁移和复用的成本,不意味着更换 Agent 框架本身不需要修改代码、提示词、工具定义或评测逻辑。

第三,扩展可以成为团队工程规范的入口#

前一节的 sbom-deploy 是一个很好的例子。

扩展不仅能适配不同的 Agent 框架,也能把团队自己的工程规则放进统一命令中:

  • 部署前进行安全扫描。
  • 运行前检查环境配置。
  • 执行命令前验证项目规范。
  • 在特定命令中接入团队内部系统。

这些都是可以延伸的方向,但需要注意:文章证明的是扩展机制能够覆盖和新增命令,并不代表上述每种功能都已经由官方提供。

6.3 如果现在想上手,应该怎么做?#

结合文章的内容,可以按下面的顺序学习。

  1. 先运行官方 LangChain 模板。 观察生成的目录结构、app/agent.py、FastAPI 入口和部署配置,理解一个 Agent 项目怎样接入 CLI。
  2. 查看命令覆盖情况。 运行 agents-cli info,并查看相应命令的帮助信息,区分哪些命令由原生 CLI 处理,哪些由扩展替换。
  3. 阅读扩展清单。 理解 agents-cli-extension.yaml 中的 override、add 和版本约束。
  4. 编写一个小型扩展。 先尝试增加一个简单的项目检查命令,再尝试包装已有命令,最后验证参数传递、错误处理和锁定版本。

官方模板创建命令如下:

Terminal window
agents-cli create my-agent \
--agent google/agents-cli/extensions/langchain/[email protected] \
-d cloud_run
cd my-agent
agents-cli install

需要注意,它依赖相应版本的 Agents CLI、Python、uv 以及 Google Cloud 环境配置;不能只复制命令就假设所有环境都能立即运行。

七、全文总结#

这篇文章的论证链条其实非常清晰:先指出问题,再证明旧方案不成立,然后给出机制,最后用两个实战案例验证机制。

7.1 问题:工具链被单一框架绑架#

Agent 开发生命周期(ADLC)包含构建、评测、部署、监控四个环节,这些工程能力本应与 Agent 框架无关。但很多工具链从设计之初就假定用户使用某个特定框架,导致框架相关的命令和框架无关的命令被捆在一起。

于是,一个团队只要换了框架,就可能失去整套 CLI 的部署、评测和观测能力。与此同时,企业还有另一类需求:不是换框架,而是要在统一命令中强制执行合规检查、SSO 和风险管理流程。

这两类需求表面不同,本质相同:都希望保留工具的通用能力,只改变部分命令的行为。

7.2 旧方案:都在以”整个工具链”为单位做改动#

原文逐一否定了三种做法:

  • Fork:能改,但从此要持续同步上游,支付长期的 maintenance tax。
  • 包装脚本:不用改上游,但使用者可以绕过包装层直接调用底层命令,无法真正约束行为。
  • 从零重写:完全自由,但需要数人月补齐脚手架、部署、评测、Trace、文档等大量边界情况,且成熟度未必比得上上游。

三者的共同问题不是技术实现,而是改动的粒度太粗:为了替换一个依赖框架的模块,不得不复制或重写整个工具链。

Extension System 的价值就在于此:它把改动单位从”整个 CLI”缩小到”单条命令”。

7.3 机制:声明、实现、范围与版本四件事#

Agents CLI 的扩展机制可以用四个关键词概括:

  • 声明:agents-cli-extension.yaml 描述扩展身份、兼容版本以及要 override 或 add 的命令;真正的逻辑放在脚本里。配置负责”路由”,脚本负责”执行”。
  • 分发:扩展就是一个 Git 目录,不需要专门的包格式或注册中心;一个仓库可以容纳多个扩展,Git tag、分支和 commit 同时承担版本管理职责。
  • 范围:项目级扩展随项目提交,用户级扩展供本机多项目使用;两者冲突时项目级优先,CLI 会显示生效命令的来源。
  • 版本:锁文件同时记录人类可读的 ref 和精确的 commit sha。install 恢复锁定版本,extension update 才重新解析引用,升级因此成为一个显式动作。

这套机制同时解决了行为扩展、作用范围和版本治理三个问题,而代价是需要额外的冲突提示、兼容约束和安全审查。

7.4 验证一:LangChain 项目如何复用工具链#

官方 LangChain 扩展证明了”局部替换”可行:

  • create 通过模板直接生成带扩展清单的项目,开发者仍然只面对一个统一入口。
  • run、playground 和 eval generate 被替换,因为它们必须理解 LangChain 的内部对象——root_agent 是 CompiledStateGraph,ADK 的运行器无法直接处理。
  • deploy 没有被替换,因为部署只依赖 FastAPI、Docker 和 Cloud Run 构成的外部服务契约。
  • eval grade、eval compare 没有被替换,因为只要执行阶段产出统一的 EvaluationDataset,下游评分和比较就与框架无关。
  • 对于仍然依赖 ADK 的子命令,扩展选择明确拒绝并解释原因,而不是假装成功。

这个案例最重要的结论是:跨框架复用的前提是统一外部契约,而不是统一内部实现。

7.5 验证二:自定义扩展如何承载工程规范#

sbom-deploy 展示了扩展的另一种用法:不换框架,只给原有命令加一道安全门禁。

  • 清单覆盖 deploy,新增 sbom-report;安装后开发者仍然执行原来的 agents-cli deploy。
  • 包装脚本先由 Syft 生成 SBOM,再由 Grype 扫描漏洞;set -euo pipefail 保证扫描失败时立即终止,部署不会继续。
  • 最后通过 exec agents-cli deploy "$@" 把控制权交还原生命令,既透传参数,也避免多余的进程层级。
  • CLI 注入 AGENTS_CLI_DISABLE_OVERRIDES=1,让脚本内部的原生调用不会再次进入扩展,从而避免无限递归。

这个案例也暴露了扩展机制的边界:锁文件能固定版本,但不能证明代码可信;示例脚本能演示流程,但生产环境还需要处理临时文件、工具缺失、扫描器与漏洞库版本、失败默认阻断和审计留痕。

7.6 最终结论#

把全文压缩成一句话:

Agent 框架负责”如何构建和运行 Agent”,工程工具链负责”如何开发、评测、部署和治理 Agent”;二者应该解耦,解耦的方式不是统一所有框架的内部接口,而是在少数必要边界上做可版本化的局部适配。

这套思路的真正价值不在于 Agents CLI 本身,而在于它给出了一种可迁移的架构方法:

  • 把与框架无关的通用能力留在核心;
  • 把与框架相关的执行、调试、评测能力放进可替换的扩展层;
  • 用声明文件定义契约,用锁文件保证可复现,用显式提示保证行为透明;
  • 承认扩展机制的能力边界——它能改变默认工作流,但不能替代权限控制、代码审查和 CI/CD 强制门禁。

当一套工具链能够做到”核心稳定、边界清晰、扩展可治理”时,它支持多少种框架就不再是最重要的问题;更重要的是,团队可以在不放弃已有工程能力的前提下,自由选择适合自己的技术栈。

从 ADK 到 LangChain:深入理解 Google Agents CLI 的扩展机制与 Agent 工程化
https://blog.xqcherry.top/posts/ai/agents-cli-extension-system/
作者
xqcherry
发布于
2026-10-24
许可协议
CC BY-NC-SA 4.0