原文:Powering the Agent Development Lifecycle with any framework, from ADK to LangChain and beyond
来源:Google Developers Blog
发布日期:2026 年 10 月 21 日
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然后修改运行逻辑:
# 原项目:假设只支持 ADKdef 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,也不需要复制整个项目。
但是,原文指出了一个很实际的问题:开发者或编码助手可能绕过包装层,直接调用底层工具。
例如,你给团队规定只能使用:
./my-agent-cli deploy因为它会先执行安全检查,再调用真正的部署命令。
但某位开发者或 AI 编码助手可能直接运行:
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 框架 | 希望继续使用的工具链能力 |
|---|---|---|
| 项目 A | ADK | 运行、评测、部署 |
| 项目 B | ADK | 运行、评测、部署 |
| 项目 C | LangChain | 运行、评测、部署 |
原文给出的数字是:当时 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 的文件。
这个文件声明两件事:
- 要替换哪些已有命令。
- 要新增哪些命令。
例如,一个扩展可能长这样:
my-extension/├── agents-cli-extension.yaml└── scripts/ ├── run.py └── deploy.sh这里有两个不同的角色:
agents-cli-extension.yaml:声明 CLI 应该如何使用这个扩展。scripts/:放置真正执行工作的程序。
这是一种常见的声明式配置 + 命令实现的设计。
配置文件不负责真正运行 Agent,也不负责扫描依赖漏洞。它只告诉 CLI:当用户执行某个命令时,应当调用哪个程序。
3.2 扩展有两种形式
形式 A:覆盖已有命令(Command Override)
假设原本的命令是:
agents-cli run "分析这份日志"原生实现使用 ADK 的运行机制。
现在安装了 LangChain 扩展。同样的命令仍然可以执行,但 CLI 将其转交给 LangChain 对应的实现。
用户输入:agents-cli run "分析这份日志" │ ▼CLI 命令解析与扩展路由(发现当前项目覆盖了 run) │ ▼执行扩展指定的程序:uv run python scripts/run.py这里最重要的细节是:用户传给命令的参数会原样传递给扩展程序。因此,用户不需要记住另一套命令语法,扩展也能保留原命令的使用体验。
但这并不代表新实现自动获得了原生实现的所有功能。扩展必须自己实现它接管的行为,并处理相应参数和错误。
形式 B:新增命令(Command Addition)
扩展也可以增加原 CLI 没有的功能。
例如:
agents-cli sbom-report这个命令可以生成软件物料清单(SBOM),而不需要修改原 CLI 的核心代码。
因此,覆盖和新增的区别是:
| 方式 | 命令原本存在吗? | 扩展的作用 |
|---|---|---|
| Override | 存在 | 替换原有行为 |
| Add | 不存在 | 注册新命令 |
一个扩展可以同时包含这两种操作。文章后面的 sbom-deploy 示例就会覆盖 deploy,同时新增 sbom-report。
3.3 为什么扩展通过 Git 仓库分发,而不是专门的插件市场?
原文接着解释扩展的分发方式:不需要先发布到一个专门的扩展注册中心,Git 仓库就可以充当分发渠道。
例如:
# 使用官方仓库中的扩展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 在检出项目后能够恢复同一版本。
agents-cli extension add acme/platform-extensions#soc2-deployUser Scope:用户级。 扩展安装在用户的配置目录中,供这台机器上的多个项目使用,适合个人习惯或项目创建之前就需要的扩展。
agents-cli extension add acme/platform-extensions#bootstrap --global这里需要区分两个概念:
- 安装范围决定扩展作用于哪些项目。
- 命令覆盖规则决定同一个命令最终采用哪个实现。
文章明确指出,当项目级和用户级扩展定义了同一个命令时,项目级优先。CLI 还会显示生效命令的来源;发生冲突时,会在安装或扩展列表中给出提示。
为什么项目级优先?
假设你在个人电脑上安装了一个全局扩展,让所有项目的 run 命令都使用某种自定义实现。但某个项目明确要求使用另一种运行方式,那么项目自己的配置应该能够覆盖个人的默认设置。
这种规则使项目能够声明自身需要的行为,而不是完全受开发者电脑上的全局配置支配。
3.5 Lockfile:为什么安装扩展还需要锁文件?
这是本节最值得深入理解的部分之一。
假设你安装了公司扩展:
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 加载扩展 检查命令覆盖和新增定义 │ ┌────┴────┐ ▼ ▼覆盖命令 新增命令使用扩展实现 注册新功能从工程设计角度看,这一节实际上把三个问题放进同一个机制中解决:
- 行为扩展:通过清单声明命令替换和新增。
- 作用范围:决定扩展是项目专用还是用户通用。
- 版本治理:通过锁文件固定扩展代码,并显式升级。
还有一个需要保持警惕的边界:扩展脚本本质上是可执行代码。即使版本固定,也仍然需要审查来源、控制权限并验证脚本行为。版本可复现不等于代码可信。
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。
原文给出的安装方式:
uvx google-agents-cli setupGoogle Cloud 认证与 API 配置:
gcloud auth application-default logingcloud 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:创建项目时,扩展就已经参与进来了
这是本节第一个核心实现。
原文使用的命令是:
agents-cli create my-agent \ -d cloud_run
cd my-agentagents-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="你是一个天气助手。",)这段代码说明:
get_weather是工具的具体实现。create_agent把模型、工具和提示词组合成 Agent。root_agent是供其他代码调用的 Agent 对象。
这是帮助理解的简化示例,不是文章完整代码。
文章的关键不是天气工具,而是 root_agent 的具体类型。
如果原有 CLI 假定项目一定会导出 ADK 的 Agent 对象,那么它就不能直接用 ADK 的运行器处理 LangChain 的图对象。
因此,作者没有要求 LangChain 伪装成 ADK,而是通过扩展替换运行命令,让新的命令知道如何执行 CompiledStateGraph。
这就是框架适配的核心:不是强迫不同框架使用相同的内部对象,而是在需要接入的边界上适配它们。
4.6 Step 3:执行 agents-cli run 时,究竟发生了什么?
文章使用下面的命令测试 Agent:
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 的本地开发服务器:
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"]随后仍然使用原来的命令:
agents-cli deployCLI 通过 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 评测。
文章展示的流程是:
# 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 在真实项目中的价值:它不是凭空消除框架差异,而是把差异限制在少数必须适配的边界上。
本节要点:
- 项目创建:通过指定模板,生成带有扩展清单的 LangChain 项目。
- 运行适配:替换
run、playground等依赖特定框架的命令。 - 部署复用:通过 FastAPI、Docker 和 Cloud Run 的统一服务契约,继续使用原生部署流程。
- 评测复用:让框架相关的执行阶段输出统一格式,下游评分和比较继续使用通用实现。
最值得思考的问题是:
如果自己开发一套 Agent 观测和评测工具,你会把框架适配放在哪里?是要求所有框架实现同一套内部 API,还是让每个框架通过适配器输出统一的 Trace 和评测数据?
这正是从阅读文章走向自己设计 Agent 基础设施的关键一步。
五、编写你自己的扩展
前面我们分析了 Google 官方提供的 LangChain 扩展。这一节不再使用现成的 Agent 框架适配器,而是从零实现一个自定义扩展,展示扩展系统如何用于真实的工程流程。
原文选择的例子是 sbom-deploy:在部署应用之前,自动生成软件物料清单(SBOM),检查依赖是否存在严重漏洞,只有检查通过才能继续部署。
5.1 先理解这个扩展要解决什么问题
假设你的项目原本使用:
agents-cli deploy直接部署到云端。
现在,团队提出了新的安全要求:
- 部署之前,生成项目的依赖清单。
- 扫描依赖是否存在严重漏洞。
- 发现严重漏洞时,禁止部署。
- 检查通过后,继续使用原来的部署流程。
最直接的做法是修改部署脚本,或者要求每个开发人员手动执行扫描命令。
但这两种方式都有问题:前者可能需要侵入原有工具,后者容易因为人为疏忽而跳过检查。
因此,文章采用扩展机制,把原来的 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/v1alpha1name: sbom-deploydescription: 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。
它会用于识别扩展、安装扩展,以及执行扩展更新等管理操作。
例如:
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 bashset -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?
set -euo pipefail这是一种常见的 Bash 严格执行模式。
-e:命令发生符合规则的非零退出时,通常终止脚本。-u:使用未定义的变量时,报错退出。-o pipefail:管道中的命令失败时,不只看最后一个命令的退出状态。
在这个例子中,最重要的是 -e。
假设 Grype 扫描发现严重漏洞,并返回失败状态,脚本就不会继续执行最后的部署命令。
换句话说,安全检查不是一个只输出报告的旁路任务,而是部署流程的前置条件。
2. 为什么先切换工作目录?
cd "$(dirname "$0")/.."脚本从扩展目录下的 scripts/ 运行时,这行命令会将当前目录切换到扩展根目录。
这样可以避免用户从不同目录调用脚本时,相对路径解析出现问题。
例如,用户在项目根目录执行命令,与用户在其他目录通过 CLI 调用命令,可能导致相对路径指向不同的位置。脚本主动确定工作目录,可以减少这种不确定性。
但要注意:这里切换到的是扩展目录,并不必然是用户的业务项目根目录。实际部署时,还需要确认 CLI 的运行约定,以及扫描目标是否确实是当前项目。
3. Syft 和 Grype 分别做什么?
syft dir:. -o cyclonedx-json > /tmp/sbom.cdx.jsonSyft 用来分析指定目录中的软件及其依赖,生成 SBOM(Software Bill of Materials,软件物料清单)。
SBOM 可以理解为项目使用了哪些软件组件和依赖的清单。这里将输出格式指定为 CycloneDX JSON,并写入文件。
接着:
grype sbom:/tmp/sbom.cdx.json --fail-on criticalGrype 根据这份 SBOM 检查已知漏洞,并在达到指定严重程度的漏洞条件时返回失败状态。
两者的职责不同:
- Syft:识别软件组件,生成清单。
- Grype:根据清单进行漏洞检测。
这也是文章选择 SBOM 场景的原因:它可以清楚展示扩展如何在原有部署命令前增加一层工程治理逻辑。
4. 为什么最后使用 exec?
exec agents-cli deploy "$@"这里包含两个重要知识点。
第一,"$@" 是参数透传。
假设用户执行:
agents-cli deploy --region us-central1扩展脚本应当尽可能把原始部署参数传给真正的部署命令,而不是只执行一个没有参数的 deploy。
"$@" 可以保留各个参数的边界,避免简单字符串拼接造成参数拆分问题。
第二,exec 会用新的进程映像替换当前 Shell 进程。
它可以减少不必要的进程层级,并让最终部署命令的退出状态更直接地传递给调用方。
因此,脚本完成安全检查后,就把控制权交还给原生部署命令。
5.4 关键问题:怎么避免无限递归?
现在有一个看似矛盾的问题。
扩展覆盖了 deploy,但脚本最后又执行:
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 第三步:安装并验证扩展
文章接下来展示如何安装本地扩展:
agents-cli extension add local@../my-sbom-extension这里的路径指向本地 Git 扩展仓库。
扩展也可以从远程仓库安装,例如:
agents-cli extension add acme/platform-extensions#sbom-deploy --ref v1.0.0这表示从指定仓库中安装名为 sbom-deploy 的扩展,并通过 v1.0.0 指定版本引用。
安装后,应该检查以下内容:
- 扩展是否成功安装。
agents-cli deploy是否显示或识别扩展覆盖。- 新增的
agents-cli sbom-report是否可用。 - 扫描失败时,部署是否确实被阻止。
- 扫描通过时,原生部署是否能正常执行。
前两项确认扩展是否被 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 环境中保存扫描报告,便于后续审计和故障排查。
另外,漏洞扫描结果可能受漏洞数据库更新时间、依赖识别能力和配置影响。因此,生产环境应当把扫描器版本、漏洞数据库更新时间和例外审批规则也纳入治理。
这些改进体现了从“实现一个示例扩展”到“构建可用于团队生产环境的安全门禁”之间的差别。
本节实际上展示了三个层次的工程设计:
- 扩展声明层:通过
agents-cli-extension.yaml声明新增命令、覆盖命令和兼容版本。 - 执行层:用包装脚本在原生部署之前加入安全检查,并通过退出码控制是否继续。
- 依赖治理层:通过锁文件和固定版本,使团队可以重复安装和审查相同的扩展代码。
本节最值得掌握的不是 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 如果现在想上手,应该怎么做?
结合文章的内容,可以按下面的顺序学习。
- 先运行官方 LangChain 模板。 观察生成的目录结构、
app/agent.py、FastAPI 入口和部署配置,理解一个 Agent 项目怎样接入 CLI。 - 查看命令覆盖情况。 运行
agents-cli info,并查看相应命令的帮助信息,区分哪些命令由原生 CLI 处理,哪些由扩展替换。 - 阅读扩展清单。 理解
agents-cli-extension.yaml中的override、add和版本约束。 - 编写一个小型扩展。 先尝试增加一个简单的项目检查命令,再尝试包装已有命令,最后验证参数传递、错误处理和锁定版本。
官方模板创建命令如下:
agents-cli create my-agent \ -d cloud_run
cd my-agentagents-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和精确的 commitsha。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 强制门禁。
当一套工具链能够做到”核心稳定、边界清晰、扩展可治理”时,它支持多少种框架就不再是最重要的问题;更重要的是,团队可以在不放弃已有工程能力的前提下,自由选择适合自己的技术栈。