资讯详情 Agent-Reach:面向CLI的本地智能体工作流调度引擎
📅 2026/10/29 15:38:03
1. 项目概述Agent-Reach 是什么它解决的不是“调用API”而是“调度智能体”的根本问题Agent-Reach 这个名字乍一听像某个大模型API封装库但实际翻遍 GitHub 上 shihabal3amri/diplay 仓库注意不是 diplay是 display原始热词中拼写有误真实仓库名为display而Agent-Reach是其核心子模块或 CLI 工具代号你会发现它压根不提供任何模型服务也不做 token 计费、流式响应、鉴权代理这类 API 网关该干的事。它干的是更底层、更工程化、也更被忽视的一件事在本地终端里把多个异构智能体LLM、工具调用器、记忆模块、规划引擎像乐高积木一样拧在一起让它们能互相“看见”、彼此“委托”、按需“接力”最终完成一个跨步骤、跨能力、跨上下文的复杂任务。这和你用curl调一个 DeepSeek API 或requests.post发个请求完全是两个维度的事——前者是单点射击后者是组建一支能自主协同的特种小队。我第一次在本地跑通agent-reach --plan 帮我查今天北京天气再根据温度推荐三件适合穿的衣服 --verbose这条命令时后背有点发凉。它没去调任何公开 API而是先启动了一个轻量级规划器基于本地运行的 Phi-3-mini把一句话拆成“查天气→解析温度→检索穿搭知识库→生成推荐列表”四个原子动作接着自动加载了weather-cli一个独立 Python 包执行第一环等拿到 JSON 格式结果后又无缝切换到另一个嵌入式向量检索模块在本地clothing-rag.db里匹配关键词最后才调用一个极简的 LLM 模板引擎做语言润色。整个过程没有人工干预没有硬编码的 if-else所有调度逻辑都由Agent-Reach的 runtime 动态解析并执行。这才是它真正的价值锚点它不是 API 客户端而是智能体工作流的本地操作系统。所以如果你正被这些事困扰——写一堆if model deepseek的胶水代码、手动维护十几个脚本之间的输入输出格式、每次加一个新工具就要重写调度逻辑、或者发现 LangChain 的AgentExecutor在 CLI 场景下过于笨重——那Agent-Reach就是为你量身定制的解药。它不追求“支持多少模型”而是专注“如何让模型和工具之间说同一种调度语言”。它的目标用户非常明确需要在终端里快速验证智能体协作逻辑的算法工程师、想把内部工具链一键接入 LLM 的 DevOps 工程师、以及厌倦了 Web UI 交互、坚持用vim tmux CLI构建个人 AI 工作流的技术型产品经理。它不教你怎么安装 Python但会默认你已经能用pipx install管理隔离的 CLI 工具它不解释什么是 RAG但会要求你提前准备好.embedding文件——这种“不废话、只交付”的气质正是它在 GitHub 上 star 数稳步爬升的核心原因。2. 核心架构设计为什么放弃 LangChain / LlamaIndex选择自研 Runtime YAML Schema当我在 2024 年 Q2 第一次看到Agent-Reach的runtime/executor.py时第一反应是“这代码怎么敢这么写”它只有 387 行却撑起了整个调度引擎。没有抽象工厂、没有策略模式、甚至没用asyncio做并发——它用最朴素的subprocess.run()启动每个 Agent 子进程靠标准输入输出stdin/stdout传递结构化 JSON用临时文件做大对象缓存靠signal.signal(signal.SIGINT, ...)实现优雅中断。这种“反模式”设计背后藏着三个被主流框架刻意忽略的现实约束2.1 约束一CLI 工具的不可控性远超 Web APILangChain 的Tool类假设所有工具都是 Python 函数可随时 import、调用、捕获异常。但现实世界里你真正想集成的工具往往是exiftool二进制、ffmpeg命令行参数极其复杂、jq纯 stdin/stdout 流式处理、甚至是你同事写的./data_cleaner.shbash 脚本。它们不遵循 Python 的异常体系不返回dict可能因权限问题静默失败也可能在 stderr 输出关键错误但 stdout 为空。Agent-Reach的设计哲学是不试图驯服 CLI而是给 CLI 一个统一的契约。这个契约就是——所有 Agent 必须接受一个--input参数指向 JSON 文件路径必须将结果写入--output指定的 JSON 文件并在成功时返回exit code 0。至于你是用 Rust 写的还是用 Perl 脚本拼的runtime 一概不管。我实测过用Agent-Reach调度一个用gawk写的 CSV 行数统计工具它比用 LangChain 封装subprocess.Popen稳定得多因为后者总在stdout.read()阻塞时卡死。2.2 约束二YAML 比 Python 代码更适合描述“谁该在什么时候做什么”Agent-Reach的核心配置不是 Python 类而是一个叫workflow.yaml的文件。看一个真实案例name: research-pipeline steps: - id: fetch_paper agent: arxiv-cli input: {query: {{ .user_input }}, max_results: 5} output: papers.json - id: summarize agent: llm-summarizer input: {papers: {{ .fetch_paper.output }} } output: summary.md - id: export agent: md-to-pdf input: {markdown: {{ .summarize.output }} } output: report.pdf这个 YAML 的精妙之处在于{{ .fetch_paper.output }}这种模板语法。它不是 Jinja2而是Agent-Reach自研的轻量解析器只支持点号访问和基础类型转换比如{{ .summarize.output | to_string }}。为什么不用 Python因为 Python 太灵活容易写出无法静态分析的逻辑导致 workflow 无法被 IDE 提示、无法被 CI 检查、无法被非 Python 工程师修改。而 YAML 简单模板让一个前端工程师也能看懂数据流向让 QA 可以直接在 YAML 里 mock 某个 step 的输出来测试下游逻辑。我在团队落地时把workflow.yaml放进 GitLab CI用agent-reach validate --file workflow.yaml做预检CI 失败率从 37% 降到 2%就因为 YAML 语法错误比 Python 语法错误更容易被提前发现。2.3 约束三本地模型推理的资源竞争必须由 Runtime 显式管理热词里反复出现deepseek-official报错根源在于多个 Agent 同时尝试加载同一个 GGUF 模型文件导致显存 OOM。Agent-Reach的runtime/manager.py用一个全局的ModelPool解决了这个问题它维护一个{model_path: (model_ref, ref_count)}字典每个 Agent 启动前先acquire(model_path)用完后release(model_path)。ref_count 归零时才真正卸载模型。这个设计看似简单却避免了 90% 的本地多智能体部署失败场景。我对比过用 Ollama 的ollama run deepseek-coder:6.7b和Agent-Reach的model_pool加载同一模型前者在并发 3 个 Agent 时显存占用飙升到 24GBRTX 4090 直接爆后者稳定在 16.2GB且首次加载后后续 Agent 启动时间从 8.3s 缩短到 0.4s——因为复用了已加载的模型实例。这不是魔法而是把“模型是昂贵资源”这个事实刻进了调度引擎的 DNA。提示不要试图用pip install agent-reach全局安装。它依赖特定版本的llama-cpp-pythonv0.2.72而你的项目可能需要 v0.2.85。正确做法是pipx install --python python3.11 githttps://github.com/shihabal3amri/display.gitmain#subdirectoryagent-reach用 pipx 隔离 Python 环境这是作者在 README 里埋的唯一硬性要求。3. 核心细节解析YAML Schema 设计、Agent 注册机制与安全沙箱实践Agent-Reach的 YAML 不是随意写的它有一套严格校验的 Schema定义在schemas/workflow_schema.json中。这个 Schema 的设计直指 CLI 工具集成的三大痛点输入污染、输出失真、执行失控。我们逐层拆解。3.1 输入注入防护为什么{{ .user_input }}比f{user_input}更安全初学者常犯的错误是把用户输入直接拼进 YAML# 危险永远不要这样写 input: {query: {{ .user_input }}}如果用户输入是; rm -rf /; echo hello而你的 Agent 是一个调用sh -c的 shell 工具这就成了远程命令执行漏洞。Agent-Reach的解决方案是双重过滤首先模板引擎在解析{{ .user_input }}时会强制将其序列化为 JSON 字符串即自动加双引号、转义特殊字符所以实际传给 Agent 的是\; rm -rf /; echo \hello\其次runtime/executor.py在启动子进程前会调用json.dumps()对整个 input 字典做最终序列化确保任何嵌套结构都符合 JSON 规范。这意味着哪怕你的 Agent 是用os.system(fpython tool.py {input_dict})这种野路子写的只要 input_dict 是Agent-Reach生成的就不可能触发 shell 注入。我在审计一个金融客户内部的agent-reach部署时专门构造了包含\n,$(),的恶意输入全部被拦截并记录在logs/security_audit.log中——这是它作为企业级工具的底线。3.2 输出契约强制JSON Schema 校验如何防止“上游改字段下游全崩”每个 Agent 的输出不是随便写个 JSON 就行。Agent-Reach要求你在agents/name/schema.json里定义输出 Schema。比如weather-cli的 schema{ $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { location: {type: string}, temperature_c: {type: number, minimum: -100, maximum: 100}, conditions: {type: string, enum: [sunny, cloudy, rainy, snowy]} }, required: [location, temperature_c, conditions] }当weather-cli执行完毕runtime会用jsonschema.validate()校验输出文件。如果它返回{temp: 25.3}字段名错了或{temperature_c: 200}超出范围整个 workflow 立即中止并抛出清晰错误Step fetch_weather output validation failed: temp is not a valid property for weather-cli schema。这比 Python 的KeyError友好一万倍因为它直接告诉你哪个 Agent、哪个字段、违反了哪条规则。我在重构一个老系统时把原来 17 个手工写的try...except KeyError全部删掉换成 Schema 校验上线后因数据格式导致的故障下降了 63%。3.3 Agent 注册机制agents/目录结构如何实现“零配置发现”Agent-Reach不需要你在主程序里from agents.weather import WeatherAgent。它的发现机制基于文件系统约定agents/ ├── weather-cli/ │ ├── bin/ # 可执行文件shell/python/binary │ ├── schema.json # 输出 Schema │ └── metadata.yaml # 名称、版本、作者、依赖 ├── llm-summarizer/ │ ├── bin/ │ ├── schema.json │ └── metadata.yaml当你运行agent-reach list-agents它会扫描agents/下每个子目录读取metadata.yaml必须包含name和version字段然后把bin/下第一个可执行文件注册为该 Agent。metadata.yaml还支持requires: [python3.9, ffmpeg5.0]agent-reach check-deps会自动检测这些依赖是否满足。这种设计让 Agent 变成真正的“插件”运维同学可以把agents/打包成 tar.gz发给不同环境解压即用安全团队可以定期扫描agents/*/bin/下的二进制哈希值确保没被篡改。我见过最绝的操作是一个团队把agents/目录挂载为 Kubernetes ConfigMap所有 Pod 启动时自动同步最新 Agent实现了“配置即代码”的终极形态。注意agents/目录默认在~/.agent-reach/agents但你可以用AGENT_REACH_AGENTS_DIR/path/to/your/agents agent-reach run ...环境变量覆盖。这是为多租户场景预留的后门别在生产环境乱用。4. 实操过程详解从零构建一个“会议纪要生成器”Agent 工作流现在我们动手做一个真实可用的 Agent把 Zoom 录音转文字、提取关键决策点、生成待办事项列表。整个过程不碰一行 Python 业务代码只写 YAML 和 Shell 脚本这就是Agent-Reach的威力所在。4.1 步骤一准备基础 Agent无需开发直接复用先确认三个基础 Agent 已安装whisper-cli基于 faster-whisper 的 CLI 版本用于语音转文字llm-extractor一个用 Ollama 运行phi3:3.8b的轻量提取器md-formatter一个用pandoc转换 Markdown 格式的工具用agent-reach list-agents检查它们是否存在。如果缺失按官方文档下载对应bin/文件放入agents/目录即可。重点检查whisper-cli的metadata.yaml是否声明了requires: [ffmpeg5.0]因为 Whisper 需要 ffmpeg 提取音频流。4.2 步骤二编写meeting-minutes.yaml工作流name: meeting-minutes-generator description: Convert Zoom audio to structured meeting minutes with decisions and action items steps: - id: extract_audio agent: ffmpeg-cli input: {input_file: {{ .user_input.audio_path }}, output_format: wav} output: audio.wav timeout: 300 # 5分钟超时防卡死 - id: transcribe agent: whisper-cli input: {audio_file: {{ .extract_audio.output }}, language: zh, model: large-v3} output: transcript.json timeout: 600 - id: extract_keypoints agent: llm-extractor input: { text: {{ .transcribe.output | get_field:text }}, prompt: 请从以下会议记录中提取1. 所有明确的决策点Decision格式为 DECISION: 内容2. 所有分配给具体人的待办事项Action Item格式为 ACTION: 内容 (ASSIGNED TO: 人名)。只输出这两类不要解释。 } output: keypoints.txt timeout: 120 - id: format_output agent: md-formatter input: {input_file: {{ .extract_keypoints.output }}, template: meeting_minutes.j2} output: minutes.md timeout: 60这里的关键技巧是{{ .transcribe.output | get_field:text }}。whisper-cli的输出 JSON 结构是{text: ..., segments: [...]}get_field过滤器直接提取text字段避免下游 Agent 因收到完整 JSON 而解析失败。这个过滤器是Agent-Reach内置的不需要额外安装。4.3 步骤三创建agents/md-formatter/bin/md-formatter这是一个纯 Shell 脚本展示如何用最少代码写一个合规 Agent#!/bin/bash # agents/md-formatter/bin/md-formatter set -e # 任何命令失败立即退出 INPUT_FILE OUTPUT_FILE TEMPLATE # 解析 --input --output --template 参数 while [[ $# -gt 0 ]]; do case $1 in --input) INPUT_FILE$2 shift 2 ;; --output) OUTPUT_FILE$2 shift 2 ;; --template) TEMPLATE$2 shift 2 ;; *) echo Unknown option: $1 2 exit 1 ;; esac done # 读取输入 INPUT_JSON$(cat $INPUT_FILE) TEXT$(echo $INPUT_JSON | jq -r .input_file // empty) # 从 JSON 提取 input_file 字段 J2_TEMPLATE$(echo $INPUT_JSON | jq -r .template // default.j2) # 渲染模板用 j2 CLI 工具需提前安装 j2 $J2_TEMPLATE --formatjson $INPUT_JSON $OUTPUT_FILE # 验证输出是否为有效 Markdown简单检查是否以 # 开头 if ! head -1 $OUTPUT_FILE | grep -q ^# ; then echo ERROR: Output file does not start with Markdown header 2 exit 1 fi把这个脚本保存为agents/md-formatter/bin/md-formatterchmod x再放一个meeting_minutes.j2模板文件在同目录。Agent-Reach会自动发现它。4.4 步骤四执行与调试运行命令agent-reach run \ --workflow meeting-minutes.yaml \ --user-input {audio_path: /path/to/zoom_recording.mp4} \ --verbose \ --log-level debug--verbose会打印每个 step 的启动命令、耗时、exit code--log-level debug会把subprocess.run()的完整 stderr/stdout 写入日志。我踩过的最大坑是ffmpeg-cli在某些 CentOS 系统上缺少libopus.so导致extract_audio步骤静默失败。--log-level debug日志里会清晰显示ffmpeg: error while loading shared libraries: libopus.so.0: cannot open shared object file而不是笼统的 “step failed”。实操心得永远用--dry-run先试。它会跳过实际执行只打印每个 step 将要运行的命令。我曾用--dry-run发现llm-extractor被错误地配置为调用ollama run llama3:70b而我的机器根本没有 70B 模型——--dry-run提前暴露了这个配置错误避免了 47 分钟的无谓等待。5. 常见问题与排查技巧实录从“no api key”报错到模型加载失败的全链路诊断网络热词里高频出现的llm-deepseek: no api key for provider route deepseek-official报错其实是Agent-Reach的一个经典误用案例。它根本不是 API 密钥问题而是Agent 配置与 Runtime 环境不匹配的症状。下面是我整理的 7 个最高频问题及其根因诊断法每一条都来自真实客户的工单记录。5.1 问题一“no api key” 报错——真相是 Agent 试图走网络请求但 Runtime 要求本地执行现象运行agent-reach run --workflow deepseek-workflow.yaml时报错llm-deepseek: no api key for provider route deepseek-official即使你确信没配任何 API Key。根因诊断打开agents/llm-deepseek/metadata.yaml检查execution_mode字段。如果它是network说明这个 Agent 被设计为调用 DeepSeek 官方 API但你的AGENT_REACH_MODE环境变量或config.yaml设为了localruntime就会拒绝加载网络型 Agent并抛出这个误导性错误。no api key的真实含义是“我找不到密钥因为我不该走这条路”。解决两种方案方案 A推荐把execution_mode改为local并确保agents/llm-deepseek/bin/下有一个能本地运行 DeepSeek 模型的可执行文件如基于 llama.cpp 的 wrapper。方案 B设置AGENT_REACH_MODEnetwork并在~/.agent-reach/config.yaml中配置api_keys: {deepseek-official: sk-...}。避坑技巧用agent-reach show-agent llm-deepseek查看 Agent 元信息它会明确显示execution_mode: network或local这是诊断的第一步。5.2 问题二Workflow 卡在某一步ps aux | grep agent显示进程存在但无输出现象transcribe步骤执行 10 分钟无响应top显示whisper-cli进程 CPU 占用 100%但logs/step_transcribe.log为空。根因诊断Whisper CLI 默认使用num_workers4但在 Docker 容器或内存受限环境num_workers过大会导致进程假死。Agent-Reach的timeout参数只监控进程是否退出不监控 CPU 是否卡死。解决在agents/whisper-cli/metadata.yaml中添加default_args: [--num-workers, 1]强制单线程运行。或者在 workflow YAML 中覆盖- id: transcribe agent: whisper-cli input: {...} output: transcript.json args: [--num-workers, 1] # 覆盖默认参数避坑技巧对所有 CPU 密集型 AgentFFmpeg、Whisper、Llama.cpp在metadata.yaml中显式声明resource_requirements: {cpu_cores: 2, memory_mb: 4096}Agent-Reach的check-deps会提前预警。5.3 问题三agent-reach validate通过但run时提示Step xxx output validation failed: field is not defined现象YAML 语法检查通过但运行时报 Schema 校验失败提示字段不存在。根因诊断agents/xxx/schema.json中定义的字段名和 Agent 实际输出的 JSON 字段名不一致。常见于Agent 用snake_caseSchema 用camelCase或 Agent 输出了{temperature: 25}但 Schema 要求{temperature_c: 25}。解决用agent-reach debug-step xxx --input {test:data}命令单独运行该 Agent查看其真实输出。这是最高效的诊断方式——它绕过整个 workflow直接暴露 Agent 的原始行为。避坑技巧在agents/xxx/bin/脚本末尾加一行echo DEBUG: final output $(cat $OUTPUT_FILE) 2--verbose模式下会打印出来。5.4 问题四agent-reach list-agents不显示刚放入agents/的新 Agent现象把agents/my-tool/放好list-agents却看不到。根因诊断agents/my-tool/metadata.yaml缺少必需字段。Agent-Reach要求该文件必须包含name: my-tool # 必须和目录名一致 version: 1.0.0 # 语义化版本 description: ... # 描述缺任何一个list-agents就会跳过该目录。解决用agent-reach init-agent my-tool自动生成合规的metadata.yaml模板。避坑技巧agents/目录支持符号链接。你可以把不同项目的 Agent 放在不同 Git 仓库用ln -s /path/to/repo/agents/my-tool ~/.agent-reach/agents/my-tool链接进来实现跨项目复用。5.5 问题五模型加载失败报错OSError: unable to load model from ...现象llm-summarizer步骤报错OSError: unable to load model from /path/to/model.Q4_K_M.gguf。根因诊断GGUF 模型文件损坏或llama.cpp版本与模型不兼容。Agent-Reach的ModelPool在加载失败时会缓存错误状态导致后续所有调用都失败。解决清空模型缓存rm -rf ~/.agent-reach/cache/models/*然后用agent-reach check-model /path/to/model.Q4_K_M.gguf单独验证模型。该命令会调用llama.cpp的main程序做最小化加载测试。避坑技巧在agents/llm-summarizer/metadata.yaml中声明model_compatibility: [llama.cpp0.2.72]check-deps会自动校验。5.6 问题六--user-input传入的 JSON 被解析为字符串而非对象现象--user-input {key:value}但在 workflow 中{{ .user_input.key }}访问不到{{ .user_input }}输出的是字符串{key:value}。根因诊断Shell 的单引号保护了 JSON但agent-reach的解析器期望一个未转义的 JSON 字符串。当--user-input的值被argparse读取时它只是一个普通字符串不是已解析的 dict。解决用双引号并转义内部引号--user-input {\key\:\value\}或更稳妥地从文件读取--user-input input.json前缀表示从文件读取。避坑技巧永远用agent-reach debug-input --user-input {key:value}预览解析结果它会打印出Agent-Reach内部看到的最终数据结构。5.7 问题七agent-reach run成功但生成的minutes.md是空文件现象所有 step 显示exit code 0但最终输出文件为空。根因诊断Agent 的bin/脚本没有正确写入--output指定的文件或写入了但权限不足。Agent-Reach的executor.py会检查--output文件是否存在且非空否则报错。解决在bin/脚本中确保最后一行是echo final content $OUTPUT_FILE而不是echo final content $OUTPUT_FILE追加模式可能导致残留内容。同时用ls -l $OUTPUT_FILE检查文件权限。避坑技巧在agents/xxx/metadata.yaml中添加output_validation: {min_size_bytes: 100}runtime会在写入后检查文件大小小于 100 字节就视为失败。问题现象根本原因诊断命令修复方案no api key for provider routeAgent execution_mode 与 runtime mode 不匹配agent-reach show-agent name修改metadata.yaml的execution_mode或设置AGENT_REACH_MODEWorkflow 卡死无输出CPU 密集型 Agent 的并发参数过高agent-reach debug-step name --input ...在metadata.yaml中设置default_args限制并发Schema 校验失败Agent 实际输出与schema.json定义不符agent-reach debug-step name --input ...用debug-step查看真实输出修正schema.jsonlist-agents不显示新 Agentmetadata.yaml缺少必需字段agent-reach init-agent name用init-agent生成合规模板模型加载 OSErrorGGUF 文件损坏或 llama.cpp 版本不兼容agent-reach check-model path重新下载模型或升级llama.cpp--user-input解析为字符串Shell 引号转义问题agent-reach debug-input --user-input ...用file.json语法或正确转义双引号输出文件为空Agent 脚本未正确写入--output文件ls -l output_file确保脚本用覆盖写入且有写入权限6. 进阶实战用 Agent-Reach 构建一个“自动化竞品分析报告”流水线前面的会议纪要生成器展示了基础能力现在我们挑战一个更复杂的场景每天凌晨自动抓取三家竞品官网的更新日志对比功能迭代节奏生成带图表的 PDF 报告并邮件发送给管理层。这个需求涉及网络爬虫、文本比对、图表生成、邮件发送传统方案需要写 500 行 Python 脚本而用Agent-Reach我们只写 YAML 和 3 个极简 Shell 脚本。6.1 整体架构设计为什么用 Agent-Reach 而不是 Airflow/CronAirflow 适合调度长时间运行的 ETL 任务但对“每个步骤都是短时 CLI 工具”的场景过于沉重。Cron 只能做线性调度无法处理“如果 A 失败则跳过 B执行 C”的分支逻辑。Agent-Reach的优势在于它把调度逻辑从代码里解放出来变成可版本控制、可 Code Review、可 A/B 测试的 YAML。我们的竞品分析流水线最终会变成这样competitor-report.yaml ├── steps: │ ├── fetch_logs: 调用 curl-cli 抓取 HTML │ ├── parse_changelog: 调用 python-cli 运行 BeautifulSoup 脚本 │ ├── compare_versions: 调用 diff-cli 做文本比对 │ ├── generate_chart: 调用 gnuplot-cli 画趋势图 │ └── send_email: 调用 mail-cli 发送 PDF └── triggers: {cron: 0 2 * * *} # 每天凌晨2点所有 Agent 都是独立进程失败互不影响所有配置都在 YAML 里Git 提交即发布所有日志按 step 隔离排查问题时grep fetch_logs logs/*.log一秒定位。6.2 关键 Agent 实现parse_changelog的极简 Python 脚本agents/parse-changelog/bin/parse-changelog#!/usr/bin/env python3 import sys import json import re from bs4 import BeautifulSoup def main(): if len(sys.argv) ! 3 or sys.argv[1] ! --input or sys.argv[2] ! --output: print(Usage: parse-changelog --input input.json --output output.json) sys.exit(1) # 读取 input.json with open(sys.argv[3], r) as f: input_data json.load(f) # 提取 HTML 内容 html_content input_data.get(html, ) # 用 BeautifulSoup 解析这里简化实际会找 ul classchangelog soup BeautifulSoup(html_content, html.parser) versions [] for item in soup.find_all(li): # 匹配 v2.1.0 (2024-05-20) 格式 match re.match(rv(\d\.\d\.\d)\s*\((\d{4}-\d{2}-\d{2})\), item.text.strip()) if match: versions.append({ version: match.group(1), date: match.group(2), features: [p.text.strip() for p in item.find_all(p)] }) # 写入 output.json with open(sys.argv[5], w) as f: json.dump({versions: versions}, f, ensure_asciiFalse, indent2) if __name__ __main__: main()这个脚本只有 32 行但它完成了接收--input指向的 JSON、解析 HTML、提取版本号和日期、生成结构化 JSON 输出。Agent-Reach不关心你用什么语言写只关心你是否遵守--input/--output契约。6.3 YAML 工作流中的高级技巧条件分支与循环