ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Google AX 声明式编排:YAML 与运行时如何管理数十亿 agent

Google AX 声明式编排:YAML 与运行时如何管理数十亿 agent 1. 从一条吵翻天的帖子说起AX 到底想解决什么问题前几天技术圈被一个开源项目刷屏了Google 放出了一个叫 AX 的东西定位是“声明式编排数十亿 agent 的运行时”。帖子在 HN 上挂了一整天评论区从架构设计吵到工程可行性从 YAML 表达到调度模型几乎每个方向都有人拍砖也有人叫好。我第一时间把仓库拉下来跑了一遍又翻了大半天的讨论越看越觉得这事值得认真聊一聊——不是因为它完美而是因为它踩中了当下 agent 开发最痛的那根神经。先说清楚 AX 是什么。用一句话概括它想让你用声明式的方式主要是 YAML去描述一堆 agent 的协作关系、执行流程和资源约束然后由一个运行时负责把这些描述翻译成实际的调度和执行。你不再需要手写一大堆 Python 胶水代码去串联 agent而是像写 Kubernetes 的 Deployment 一样把“我要什么”写清楚剩下的交给运行时。这个类比不是硬凑的AX 的很多设计思路确实能看到 K8s 的影子——声明式 API、控制器循环、期望状态与实际状态的收敛。那它解决什么问题现在做 agent 开发的人应该都有体会单个 agent 跑通不难难的是让几十上百个 agent 协同工作还要保证可观测、可恢复、可扩展。你写一个 agent 调用另一个 agent再调用工具再根据结果分支这套逻辑用代码写出来很快就变成一团乱麻。更别提失败重试、状态持久化、并发控制这些工程问题。AX 的野心就是把这些脏活累活收进运行时让开发者只关心“业务逻辑长什么样”。适合谁来参考我觉得三类人最该看一是正在做多 agent 系统的工程师二是对声明式编排感兴趣的后端开发者三是想理解 agent 基础设施演进方向的技术决策者。哪怕你最后不用 AX它提出的那套抽象模型也值得琢磨。下面我会从设计思路、核心机制、实操过程到踩坑经验一层层拆开讲。2. 声明式编排的内核为什么是 YAML为什么是运行时2.1 声明式与命令式的本质分歧要理解 AX 为什么这么设计得先回到一个老问题声明式和命令式到底差在哪。命令式是你告诉系统“怎么做”一步步写清楚声明式是你告诉系统“要什么”具体怎么实现由系统决定。Kubernetes 是声明式的典型代表你写一个 YAML 说“我要三个副本”至于怎么调度、怎么拉起那是控制平面的事。AX 把这套思路搬到了 agent 编排上。你写一个 YAML 描述 agent 之间的依赖关系、输入输出、执行条件运行时负责解析这个描述并驱动执行。这样做的好处很直接编排逻辑和业务逻辑解耦了。以前你改一个 agent 的调用顺序可能要动好几处代码现在改 YAML 就行代码本身不用动。但这里有个关键前提声明式系统必须有一个足够强的运行时来兜底。K8s 能做到声明式是因为它背后有一整套控制器、调度器、etcd 存储在支撑。AX 要编排“数十亿 agent”这个量级对运行时的要求只会更高。所以标题里“运行时”三个字才是重点YAML 只是表象。2.2 为什么选 YAML 而不是代码或 DSL评论区吵得最凶的一个点就是为什么用 YAML有人觉得 YAML 表达力太弱复杂逻辑写起来很别扭有人觉得 YAML 门槛低非程序员也能上手。我的看法是选 YAML 是一个权衡后的结果不是因为它最好而是因为它最“通用”。首先YAML 是配置语言里事实上的标准K8s、CI/CD、各种基础设施工具都在用开发者认知成本低。其次YAML 天然适合描述结构化的声明agent 的依赖关系、参数、条件分支用嵌套结构表达很自然。第三YAML 可以被程序生成和消费这意味着上层可以再套一层可视化编辑器或者代码生成器YAML 只是中间表示。但 YAML 的短板也很明显。一旦逻辑复杂到需要循环、递归、动态计算YAML 就会变得非常难写难读。AX 的应对方式是引入表达式和函数允许在 YAML 里嵌入简单的计算逻辑。这招 K8s 也用过比如 Helm 模板。不过说实话任何在 YAML 里写逻辑的方案最后都会走向“图灵完备的配置语言”这个坑AX 能不能避开还得看后续演进。提示如果你打算用 AX 做复杂编排建议把 YAML 控制在“描述结构”的层面真正的业务逻辑还是放在 agent 内部的代码里。YAML 里塞太多逻辑维护起来会很痛苦。2.3 运行时才是真正的战场YAML 只是入口运行时才是决定成败的地方。AX 的运行时要做几件事解析 YAML、构建执行图、调度 agent、管理状态、处理失败、收集指标。这里面每一项都不简单尤其是当 agent 数量上到“数十亿”这个量级时。数十亿这个数字听起来夸张但放在 agent 场景里未必是吹牛。想象一下如果每个用户请求都触发一组 agent每个 agent 又可能派生出子 agent规模确实可能爆炸。这时候运行时的调度能力、状态存储、故障恢复就成了核心瓶颈。AX 在这块的思路是分层调度加状态外置把 agent 的状态存到外部存储比如 Redis运行时本身尽量无状态这样可以水平扩展。这个设计思路和 K8s 把状态存 etcd、调度器无状态化是一脉相承的。好处是扩展性强坏处是引入了外部依赖延迟和一致性都要额外考虑。评论区有人质疑说agent 执行往往是有状态的、长事务的把状态外置会不会导致性能问题。这个质疑很合理AX 的答案是用缓存和批量写入来缓解但具体效果还得看实际负载。3. 核心机制拆解调度、状态与 agent 生命周期3.1 调度模型从 DAG 到动态执行图AX 的调度模型基于执行图。你在 YAML 里描述的 agent 依赖关系会被解析成一张有向无环图DAG运行时按照拓扑顺序调度节点。这是最基础的模式和 Airflow、Argo Workflows 那套很像。但 agent 场景比传统工作流复杂的地方在于执行图可能是动态的——一个 agent 执行完才知道下一步要调用哪些 agent。AX 对动态图的支持是通过“运行时展开”实现的。YAML 里可以声明一个 agent 的输出会决定后续节点的生成运行时在执行到该节点时动态扩展图。这个机制很强大但也带来了调度上的挑战动态生成的节点如何保证资源公平、如何避免无限展开、如何做故障隔离。AX 目前的方案是给动态展开设置配额和深度限制防止失控。调度策略上AX 支持几种模式FIFO、优先级队列、以及基于资源可用性的调度。资源可以是 CPU、内存也可以是自定义的配额比如某个外部 API 的调用次数。这套设计明显借鉴了 K8s 的调度框架连扩展点都类似。如果你熟悉 K8s 的调度器上手 AX 的调度配置会很快。3.2 状态管理Redis 为什么出现在这里热词里出现了 Redis这不是偶然。AX 的状态管理默认支持多种后端Redis 是其中之一而且是最常用的。原因很简单agent 执行需要频繁读写状态Redis 的低延迟和丰富的数据结构正好匹配这个需求。具体来说AX 用 Redis 存几类数据agent 的执行状态pending、running、done、failed、执行图的中间结果、以及调度所需的元数据。用 Redis 的 Hash 存单个 agent 的状态用 List 或 Stream 做事件队列用 Set 做去重和依赖追踪。这套组合拳打下来基本能覆盖 agent 编排的状态需求。但 Redis 做状态存储有个绕不开的问题持久化和一致性。Redis 默认是内存存储虽然支持 RDB 和 AOF 持久化但在极端情况下仍可能丢数据。对于 agent 编排这种要求“至少执行一次”的场景丢状态意味着任务可能重复执行或丢失。AX 的应对是建议在生产环境用 Redis 集群加 AOF 持久化同时运行时层面做幂等设计。这个建议是对的但也意味着运维复杂度上去了。注意如果你用 Redis 做 AX 的状态后端务必开启 AOF 并配置合理的 fsync 策略。我见过有人用默认配置跑生产结果 Redis 重启后状态全丢整个编排图重新执行重复调用了一堆外部接口。3.3 agent 生命周期从创建到销毁的全链路一个 agent 在 AX 里的生命周期大致是这样的运行时从 YAML 解析出 agent 定义创建 agent 实例注入依赖和配置调度到执行器执行上报状态根据结果决定是否重试或触发下游最后销毁或复用。这里面有几个关键设计点。第一是 agent 的隔离性。AX 支持把 agent 跑在独立的进程或容器里避免相互影响。这个隔离级别是可配置的轻量级场景可以同进程重量级场景可以独立容器。第二是 agent 的复用。对于无状态 agent运行时可以维护一个池子重复使用减少创建销毁开销。第三是 agent 的超时和取消。AX 支持给每个 agent 设置超时超时后运行时可以强制终止并触发补偿逻辑。这套生命周期管理和 K8s 的 Pod 生命周期管理非常像连“优雅终止”的概念都搬过来了。如果你做过 K8s 运维理解 AX 的 agent 生命周期会很容易。但 agent 比 Pod 更复杂的地方在于agent 可能有自己的内部状态和外部副作用终止时需要考虑的事情更多。AX 目前对这块的支持还在完善中补偿逻辑需要开发者自己实现。4. 实操过程从零跑通一个 AX 编排4.1 环境准备与依赖安装我这次实操用的环境是 Ubuntu 22.04Python 3.11Redis 7.2Docker 24.0。AX 本身是 Go 写的运行时但提供了 Python SDK 用来定义 agent。安装步骤不复杂但有几个坑我提前说一下。首先装 Redis。如果你只是本地测试用 Docker 跑一个最省事docker run -d --name ax-redis -p 6379:6379 redis:7.2 redis-server --appendonly yes注意我加了--appendonly yes这是开启 AOF 持久化前面说过状态存储不能省这个。然后装 AX 的运行时官方提供了二进制和 Docker 镜像两种方式我选了 Docker因为依赖干净docker pull ghcr.io/google/ax-runtime:latestPython SDK 用 pip 装pip install ax-sdk装完之后验证一下版本确保 SDK 和运行时版本匹配。我一开始用了旧版 SDK 配新版运行时结果 YAML 解析报了一堆莫名其妙的错排查了半天才发现是版本问题。4.2 编写第一个 AX YAMLAX 的 YAML 结构分几块元信息、agent 定义、编排定义、资源配置。我写了一个最简单的例子两个 agent 串联第一个生成数据第二个处理数据。apiVersion: ax/v1 kind: Pipeline metadata: name: demo-pipeline spec: agents: - name: producer image: ax-agent-producer:latest inputs: - name: count type: int default: 10 outputs: - name: data type: json - name: consumer image: ax-agent-consumer:latest inputs: - name: data from: producer.data outputs: - name: result type: json flow: - from: producer to: consumer resources: cpu: 500m memory: 256Mi state: backend: redis address: redis://localhost:6379这个 YAML 里几个关键点值得说。agents下面定义每个 agent 的镜像、输入输出。from: producer.data表示 consumer 的输入来自 producer 的输出运行时会自动做数据传递。flow定义执行顺序。resources是资源约束和 K8s 的写法一样。state指定状态后端。写 YAML 的时候最容易出错的地方是缩进和类型。YAML 对缩进极其敏感多一个空格少一个空格都可能解析失败。类型方面default: 10是整数default: 10是字符串运行时对类型检查挺严格的写错了会直接报错。4.3 启动运行时并提交任务运行时启动命令docker run -d --name ax-runtime \ -p 8080:8080 \ -v $(pwd)/pipelines:/pipelines \ --network host \ ghcr.io/google/ax-runtime:latest \ --config /pipelines/config.yaml这里我用了--network host让容器能直接访问宿主机的 Redis省得配网络。生产环境不建议这么干应该用自定义网络。提交任务用 SDK 或者 CLI 都行我用 CLIax submit -f demo-pipeline.yaml提交后会返回一个执行 ID用这个 ID 查状态ax status execution-id我实测下来这个简单 pipeline 从提交到完成大概 2 秒左右其中大部分时间花在 agent 容器启动上。如果 agent 镜像提前拉好能快不少。4.4 观察执行过程与状态流转AX 提供了一个 Web UI默认在 8080 端口可以可视化看执行图的状态。每个节点会显示当前状态pending、running、done、failed点进去能看到日志和输入输出。这个 UI 做得挺清爽的比看命令行输出直观多了。状态流转方面我观察到一个细节AX 在 agent 执行完成后不会立即销毁而是保留一段时间默认 5 分钟以便复用。这个设计对短任务密集的场景很友好但如果 agent 有内存泄漏长时间保留会出问题。可以通过配置调整保留时间或者干脆关掉复用。另外Redis 里的状态数据我特意去看了看。每个 agent 的状态存在一个 Hash 里key 是ax:agent:execution-id:agent-name里面存了状态、开始时间、结束时间、输入输出等字段。执行图本身存在一个单独的 key 里。这套命名规则挺清晰的排查问题时直接查 Redis 就能定位。5. 常见问题与排查技巧实录5.1 调度卡住不动怎么办这是我在实操中遇到的第一个问题。提交任务后执行图一直停在 pending 状态没有任何进展。排查思路是这样的先看运行时日志发现调度器在等资源再看资源配置发现我给的 CPU 配额是 500m但宿主机上可用的 CPU 不够了。AX 的调度器默认是严格资源匹配的资源不够就排队。解决办法有两个要么降低资源请求要么增加可用资源。我选了降低请求把 CPU 改成 100m内存改成 128Mi立刻就调度上了。这里有个经验本地测试时资源请求尽量写小因为你的开发机可能同时跑着别的东西资源没你想的那么充裕。还有一种卡住的情况是依赖没满足。比如某个 agent 的输入来自上游但上游失败了下游就会一直等。这时候要看执行图里有没有 failed 的节点有的话先解决上游问题。5.2 agent 执行超时与重试配置agent 超时是另一个高频问题。默认超时是 300 秒对于调用外部 API 的 agent 来说可能不够。AX 允许在 YAML 里给每个 agent 单独配超时- name: slow-agent timeout: 600s retry: maxAttempts: 3 backoff: exponential initialDelay: 5s重试策略我建议用指数退避尤其是调用外部服务的时候。固定间隔重试容易把对方打挂指数退避能缓解这个问题。但要注意重试的前提是 agent 幂等否则重复执行会产生副作用。AX 不会帮你判断幂等性这个得自己保证。提示给 agent 配重试之前先问自己一个问题——这个 agent 重复执行会不会出问题如果会要么改成幂等要么别配重试改用补偿逻辑。5.3 状态不一致的排查方法状态不一致是分布式系统的老问题AX 也躲不开。我遇到过一次agent 明明执行完了但状态一直显示 running。排查下来发现是 agent 上报状态时 Redis 连接断了状态没写进去。运行时的健康检查也没及时发现导致状态卡住。这类问题的排查步骤先查 Redis 连接是否正常再查运行时和 Redis 之间的网络最后查运行时的健康检查配置。AX 的健康检查默认是 30 秒一次可以调短一点比如 10 秒能更快发现问题。另外运行时支持配置状态写入的重试建议开启能减少这类问题。5.4 常见问题速查表问题现象可能原因排查方向解决办法调度卡在 pending资源不足或依赖未满足查运行时日志和资源配额降低资源请求或修复上游agent 执行超时默认超时太短或 agent 卡死查 agent 日志和超时配置调大超时或修复 agent 逻辑状态显示 running 但实际已完成状态写入失败查 Redis 连接和健康检查开启状态写入重试YAML 解析报错缩进或类型错误用 YAML 校验工具检查修正缩进和类型agent 重复执行重试配置不当查重试策略和幂等性改幂等或调整重试执行图无限展开动态展开无限制查动态展开配额设置深度和数量限制5.5 几个我踩过的坑第一个坑是 YAML 里的环境变量注入。AX 支持在 YAML 里引用环境变量语法是${VAR}但如果你在 agent 的 command 里用得注意转义否则会被运行时提前解析。我一开始没注意导致 agent 拿到的命令是错的。第二个坑是 agent 镜像的架构。我的开发机是 ARM 的但拉下来的 agent 镜像是 AMD 的跑起来直接报 exec format error。解决办法是构建多架构镜像或者指定平台拉取。这个坑不限于 AX任何容器编排都会遇到。第三个坑是 Redis 的 maxmemory 配置。默认 Redis 不限制内存跑久了可能把机器内存吃满。建议设置 maxmemory 和淘汰策略比如maxmemory-policy allkeys-lru。但要注意如果状态数据不能丢就不能用 LRU 淘汰得用 noeviction 并监控内存。6. 这套东西到底值不值得用我的判断聊了这么多技术和实操最后说说我的真实判断。AX 的思路是对的agent 编排确实需要一层声明式抽象把工程复杂度收进运行时。它借鉴 K8s 的那套设计也经过了大规模验证方向没问题。但现阶段的 AX 还不成熟几个明显的短板YAML 表达力有限复杂逻辑写起来别扭运行时对 agent 生命周期的管理还不够细补偿和回滚机制薄弱状态存储依赖外部组件运维成本不低。所以我的建议是如果你在做多 agent 系统的早期探索AX 值得一试它的抽象模型能帮你理清思路。如果你要上生产建议再等等或者只在对可靠性要求不高的场景用。如果你只是想学习 agent 编排的设计思路那 AX 的源码和文档都值得读尤其是调度和状态管理那部分。这个领域变化很快今天吵翻天的东西明天可能就没人提了。但底层的问题——怎么让一堆 agent 可靠地协同工作——是长期的。AX 是不是最终答案不好说但它提出的问题是对的。我个人的体会是与其纠结用哪个框架不如先把 agent 的幂等性、可观测性、失败处理这些基本功做扎实换框架的时候迁移成本会低很多。
返回列表