adk-verify-snippets 实战指南:用 adk-python 仓库自带技能自动验证 Markdown 代码片段的可编译与可运行性

  • 人工智能
  • AI Agent
  • Agent 框架
  • 多智能体
  • 工具调用
  • RAG

【免费下载链接】adk-python

An 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
点击查看 免费下载

导读

在 adk-python 仓库中,README、使用指南与示例文档里散布着大量 python 代码块,一旦 API 演进或配置调整,这些示例很容易悄悄失效。仓库为此提供了 adk-verify-snippets 技能:它会自动提取 Markdown 文件中的每一个 Python 代码块,在隔离子进程中逐一编译、加载并(对含 ADK 组件的片段)真实运行,最后生成一份带行级覆盖率的分片段报告。读完本文,你将掌握该技能的运行前提、命令行用法、三类片段分类机制、报告解读方法,以及如何用 <!-- verify-snippets: ignore --> 注解规避合法片段被误报为损坏的问题。

一、技能定位:验证文档示例,而非测试套件

adk-verify-snippets 位于仓库的 .agents/skills/adk-verify-snippets/SKILL.md,其 frontmatter 明确定义了职责边界:

  • 适用场景:用户要求验证、测试或校验 README、指南或文档页面中的代码示例;想知道某个 Markdown 文件里哪些片段已损坏或过时;或要求生成片段验证报告。
  • 明确不适用:运行项目测试套件(应直接用 pytest);检查代码风格或格式化(应使用 adk-style 技能);创作新的可运行示例 Agent(应使用 adk-sample-creator)。

CONTRIBUTING.md 第 291 行将该技能描述为“从 Markdown 文件中提取 Python 代码块并检查它们是否仍能运行”,并在第 294–298 行说明:这些技能都存放在 .agents/skills/ 目录下,每个技能是一个包含 SKILL.md 及其参考文档的目录,无需安装——从仓库克隆启动的编码 Agent 会自动拾取它们;若要在仓库外使用,把技能目录复制到 Agent 读取的 skills 文件夹(通常为 ~/.agents/skills/)即可。这一设计与 AGENTS.md 中“Skills related to ADK development are in .agents/skills/”的说明一致。

核心能力一句话概括:提取每个 ```python 代码块,通过内置的 run.py 脚手架在各自独立的子进程中执行,并输出覆盖加载状态、运行状态与行覆盖率的分片段报告。

二、只读契约:验证绝不修改文档

技能明确约定:验证一个文档绝不能改变该文档本身。不得创建、修改或删除仓库中的任何文件——包括被验证的 Markdown、其中的代码块以及 SKILL.md 自身。发现问题只负责报告,不负责修复,也不提供补丁。

整个流程中仅有的两次写入行为是:

  1. 临时 .py 文件:写入系统临时目录(位于仓库之外),脚本退出时删除;
  2. 报告文件:写入被验证的 Markdown 源文件旁边。

这两处写入在 verify_md.py 中均有对应实现:第 238 行使用 tempfile.mkdtemp(prefix="verify_snippets_") 创建唯一临时目录,第 273–274 行将每个片段写入其中,第 442–446 行的 finally 块通过 shutil.rmtree(temp_dir, ignore_errors=True) 保证即使遇到 Ctrl+C 或意外错误也会清理临时文件;报告则写入 md_path.parent(第 300 行)。这为在 CI 或文档流水线中安全调用提供了前提。

三、环境准备

运行该技能需要满足三个前置条件:

1. ADK 开发环境。 从仓库根目录运行,并激活 uv 虚拟环境。仓库使用 uv 管理依赖,详细搭建步骤见 adk-setup 技能——其要点是 uv venv --python "python3.11" .venv 创建环境、uv sync --all-extras 安装全部依赖 extras,并支持 Python 3.11–3.14。

2. 可选安装 coverage 包。 它并非项目声明的依赖,需要显式安装;不装的话报告中的 Coverage 列会显示 —:

uv pip install coverage

从源码看,run.py 第 37–42 行通过 try: import coverage 探测其可用性,并将结果保存在 HAS_COVERAGE 中;第 217–223 行只有在它可用时才会以 branch=True、data_file=None(内存中收集、不落盘)且 include=[str(file_path)](仅统计目标片段文件,不计入 ADK 库代码)的方式启动覆盖率采集。

3. Gemini API Key。 仅当片段中构建了 Agent、App 或 Workflow 时才需要——这类片段会对着真实 API 执行:

export GEMINI_API_KEY="{your_key}"
# 或
export GOOGLE_API_KEY="{your_key}"

如果两个变量都被设置,脚手架会丢弃 GOOGLE_API_KEY,即 GEMINI_API_KEY 优先。这一逻辑同样在 run.py 中实现:第 112–114 行复制当前环境后,若同时存在两个 key 就 env.pop("GOOGLE_API_KEY", None)。

四、使用方式

在仓库根目录执行:

uv run --no-sync python .agents/skills/adk-verify-snippets/scripts/verify_md.py {path_to_markdown_file}

脚本会先打印逐片段进度,然后:

  • 在源文件旁边写入报告;
  • 打印报告的完整路径。

报告文件命名规则:取源文件 stem(不含扩展名的文件名),转小写,剔除除 [a-z0-9_] 之外的所有字符,再拼接 _REPORT.md。因此 Workflow-Guide.md 生成的是 workflowguide_REPORT.md 而非 Workflow-Guide_REPORT.md——不要自己脑补路径,直接读取脚本打印出的路径。该规则由 clean_name 函数实现(verify_md.py 第 171–174 行),测试 test_verify_snippets.py 第 59–63 行验证了 clean_name("Hello World 123!") == "hello_world_123" 与 clean_name("Verify-Snippets: Ignore") == "verifysnippets_ignore" 两个用例。若文件名清洗后为空(例如全非 ASCII 文件名),源码第 299 行会退化为基于原 stem 哈希的 report_{hash}_REPORT.md,避免同目录下两个此类文件产生路径冲突。

报告内容结构:

  1. Executive Summary(执行摘要)表:每个片段占一行,列为 Snippet | Preceding Heading | Load Phase | Run Phase | Coverage | Details;
  2. Detailed Snippet Reports(详细片段报告):每个片段一个章节,包含代码块、执行日志(stdout 加 stderr/traceback)以及覆盖率输出。

详细报告中用于包裹代码与日志的围栏并非固定三个反引号,而是由 safe_fence 函数(verify_md.py 第 182–206 行)按 CommonMark 规范动态选取——它找出内容中最长的连续反引号串,再用“严格更长且至少 3 个”的反引号作为外层围栏,从而避免被片段内容本身提前闭合。测试第 70–75 行验证了 safe_fence("x = ```foo```", "python") 会产出四个反引号的围栏。

五、每个片段如何被分类

5.1 Runnable(可运行)——包含模块级 ADK 组件

如果片段把 Workflow、Agent 或 App 赋值给了模块级变量,脚手架就会针对 Gemini API 执行它。关键规则:

  • 变量名无关紧要:脚手架扫描 vars(module)(而不是 inspect.getmembers——后者会触发每个属性的 getter 并静默吞掉损坏的 descriptor/property 抛出的异常,导致含错误 @property 的片段被漏检,见 run.py 第 102–110 行的注释)。
  • 优先级为 Workflow > 根 Agent > App:片段中任何位置的 Workflow 都胜过其中的任何 Agent。
  • 根 Agent 判定:第一个未出现在任何其他 Agent 的 sub_agents 中的 Agent 即为根 Agent。采用两遍扫描(Pass 1 收集全部 Agent,Pass 2 汇总所有 sub-agent 的 id 后再过滤),因此无论 Agent 定义顺序如何,多 Agent 片段都能正确解析根节点。sub_agents 属性只读取一次并快照(第 131–135 行),避免非幂等属性在两次调用间返回不同对象导致根检测静默失灵。
  • App 必须带 root_agent:若 App 构造时未传 root_agent,运行阶段会抛出 AttributeError 并判定失败(第 154–160 行)。
  • 默认测试输入:发送的提示词是 "Test input topic",可在片段中定义模块级 test_input 字符串覆盖(第 283 行 getattr(module, "test_input", "Test input topic"))。

执行路径上,run.py 会创建 InMemorySessionService 与 Runner,然后通过 runner.run_async() 流式消费事件并打印 author 与输出文本(第 164–187 行),与仓库 runners.py 暴露的 Runner 接口一致。

5.2 Load-only(仅加载)——无 ADK 组件

脚手架只确认片段能编译、能导入,不发起任何 API 调用。报告显示 ➖ NO ADK COMPONENT。其详情文案为:“未找到模块级 Workflow、Agent 或 App 实例。请将一个赋值给顶层变量以启用可运行性测试。”此时 Coverage 反映的仅是加载期(模块级语句)的执行情况。

5.3 Skipped(跳过)——带 ignore 注解

把 <!-- verify-snippets: ignore --> 单独放在目标代码块的起始围栏(```python)前一行,即可将该块排除。适用于伪代码、示意性片段以及需要外部环境才能运行的片段。报告显示 ⏭️ SKIPPED。示例:

<!-- verify-snippets: ignore -->
```python
# pseudo-code — not runnable as-is
my_agent = Agent(model="gemini-ultra-hypothetical", ...)
```

注意示例中为了在文档里嵌套展示代码块,外层使用了四个反引号的围栏——这正是上文 safe_fence 机制的体现。

六、分类机制的底层实现:结构化退出码

片段分类不是靠匹配字符串或 emoji,而是依赖 run.py 与 verify_md.py 两端同步维护的结构化退出码常量(两个文件头部均有“keep in sync”注释):

退出码常量含义报告呈现
0EXIT_SUCCESS所有阶段通过Load ✅ PASS / Run ✅ PASS
1EXIT_LOAD_FAILURE编译/加载失败Load ❌ FAIL / Run ➖ SKIPPED
2EXIT_RUN_FAILURE加载成功但运行期失败Run ❌ FAIL
3EXIT_NO_COMPONENT加载成功但无可运行组件Run ➖ NO ADK COMPONENT

run.py 的 main() 依序执行四个阶段:Phase 1 加载与编译(load_target_module 用 importlib.util.spec_from_file_location 动态加载,可捕获 SyntaxError、ImportError、ValidationError 等,并在失败时把半初始化模块从 sys.modules 移除以免污染进程,见第 53–80 行);Phase 2 组件发现;Phase 3 执行组件;Phase 4 覆盖率报告。覆盖率报告一定在 finally 块中执行(第 330–347 行),保证任何路径(正常、加载失败、无组件、运行失败)退出前都会输出,并且非零退出码统一通过 sys.exit(exit_code[0]) 返回。

verify_md.py 侧则在收到子进程结果后,依据退出码直接映射状态,并在加载失败时调用 extract_error_detail(第 139–168 行)从 stderr/stdout 中提取最相关的异常行——按“先 stderr 后 stdout”的顺序反向扫描 ^[A-Za-z]\w*(?:Error|Exception|Warning):\s*.+ 模式,避免把脚手架自己的提示文案(如 ❌ Run Failure: ...)误当异常。测试第 111–123 行覆盖了从 stderr 与 stdout 提取 ValueError: invalid value 两条路径。

七、让正确片段被误报为损坏的六种限制

技能文档明确列出以下限制,并建议用 <!-- verify-snippets: ignore --> 注解绕过,而不是去改动文档:

  1. 片段之间没有共享状态。 每个片段运行在全新的子进程中,因此依赖前一个代码块中导入或变量的片段会以 NameError 或 ImportError 失败。
  2. 每个片段 120 秒超时。 超时后进程被杀死并报告为运行失败。该值对应 verify_md.py 第 28 行的 SNIPPET_TIMEOUT = 120,其注释提醒“若片段确实需要更长时间可调整”;subprocess.run(..., timeout=SNIPPET_TIMEOUT) 触发 TimeoutExpired 时同样映射为运行失败(第 125–136 行)。
  3. 注解位置敏感。 注解作用于下一个 ```python 围栏;两者之间允许空行,但任何散文行或标题行都会取消它。这一点在 verify_md.py 第 60–102 行的解析器中实现:遇到 # 开头的标题行或任意非空行都会把 skip_next_block 重置为 False。
  4. **裸 会关闭代码块。** 解析器在遇到第一个不带语言标签的围栏时即关闭 Python 块,所以片段内部用作内容的裸围栏会截断片段;带语言标签的围栏(如 `bash `)会被当作字面内容保留,是安全的。
  5. 模块级 asyncio.run() 与脚手架自身的事件循环冲突,会报告为运行失败。片段应把顶层异步调用放在 if __name__ == "__main__": 之后。run.py 第 298–316 行专门捕获这类 RuntimeError(含 "event loop" 字样),输出包含“事件循环冲突”的明确诊断并给出两种解决建议(加 __main__ 保护或加 ignore 注解);其注释还说明这是为了规避 Python 3.12+ 中已移除的 asyncio.get_event_loop() 旧 API。
  6. 此外还有两条仅存在于源码注释中的隐含说明:加载失败时不会执行 Phase 2–3,但覆盖率仍会输出(Phase 1 已执行的部分);App 缺少 root_agent 会直接触发运行失败(见 5.1 节)。

八、向用户回传报告的规范

技能要求:读取生成的报告,并把 Executive Summary 表格原样复制给用户——六列、顺序不变、不重命名、不删减:Snippet | Preceding Heading | Load Phase | Run Phase | Coverage | Details,然后停止。

报告生成侧还有一些值得注意的细节:

  • 瞬态 503 处理:当运行失败且输出中同时出现 503 与 UNAVAILABLE 时,详情会显示“⚠️ Transient 503 from Gemini API (overloaded)。代码结构是正确的。”而不是笼统报错(verify_md.py 第 362–369 行)。
  • 覆盖率解析:通过正则 ^TOTAL(?:\s+\d+)+\s+(\d+)% 从 stdout 中提取 TOTAL 行百分比,同时兼容分支(5 个数字列)与非分支(3 个数字列)两种格式,且仅在加载通过时取值(第 344–350 行)。
  • Coverage 章节切分:run.py 输出中的 📊 Phase 4: Code Coverage Report 章节头(COV_SECTION_HEADER 常量)会被剥离出主日志,单独放进“Coverage Report”小节。
  • 表格单元格转义:md_cell 函数把 | 转义为 \|,防止片段文本破坏 Markdown 表格结构(测试第 66–68 行验证了 md_cell("col1 | col2") 的输出)。

九、测试与维护:技能自身的质量保障

该技能并非孤立脚本,仓库为其提供了专门的单元测试 tests/unittests/test_verify_snippets.py。测试以“按路径加载独立脚本”的方式直接导入 verify_md.py 与 run.py(第 24–56 行,同时兼容仓库根目录与 open_source_workspace/ 两种目录布局),覆盖了:

  • clean_name 的文件名清洗(第 59–63 行);
  • md_cell 的管道符转义(第 66–68 行);
  • safe_fence 的动态围栏选取(第 70–75 行);
  • extract_snippets 的提取逻辑——包括标题追踪、skip 标记的命中与取消(第 78–108 行);
  • extract_error_detail 的异常提取优先级(第 111–123 行);
  • discover_adk_component 的空模块返回 (None, None)(第 126–136 行)。

这些测试与技能脚本同步维护,是“报告分类不依赖脆弱字符串匹配、而是依赖两端同步的结构化退出码”这一设计的有力佐证——即使未来 run.py 的人类可读文案改变,verify_md.py 的分类依然稳定。

十、端到端工作流小结

将以上内容串起来,一次完整的验证流程是:

  1. 按 adk-setup 技能 准备 uv 虚拟环境,可选执行 uv pip install coverage;
  2. 若文档含 Agent/App/Workflow 示例,设置 GEMINI_API_KEY(或 GOOGLE_API_KEY,前者优先);
  3. 运行 uv run --no-sync python .agents/skills/adk-verify-snippets/scripts/verify_md.py {path};
  4. 脚本在临时目录中逐片段生成 .py 文件、以结构化退出码驱动分类、汇总执行摘要与详细报告,并在 finally 中清理临时目录;
  5. 读取脚本打印的报告路径,核对 Executive Summary 六列表格与各片段的日志、traceback 与覆盖率,判断哪些片段需要加 <!-- verify-snippets: ignore --> 注解或修订文档。

该技能把“文档示例过期”这一高发的隐性质量问题变成了可量化、可回归的自动化检查项,与 adk-style、adk-review 等技能一起构成了 adk-python 仓库面向 Agent 的文档质量保障体系。无论是人工维护 README,还是让编码 Agent 批量巡检指南页面,adk-verify-snippets 都是可以直接复用的现成工具。

  • 人工智能
  • AI Agent
  • Agent 框架
  • 多智能体
  • 工具调用
  • RAG

【免费下载链接】adk-python

An 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),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值