ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

像调度Pod一样调度Agent:从K8s到Google AX的声明式编排实践

像调度Pod一样调度Agent:从K8s到Google AX的声明式编排实践

1. 为什么需要让Agent像Pod一样被调度

1.1 一个把协作写崩的真实场景

手上有三四个Agent需要协作,状态同步靠全局变量,失败重试靠try-except套while,谁先跑完靠sleep排序。这种代码在demo里跑得飞快,一旦撑到十来个Agent、几十个步骤,现场就会变得非常尴尬:A给B传数据时B刚好重启了,C等D的结果等了三分钟才发现D在第一个工具调用就抛异常了。我在把Agent项目做大之后,这是最深的感受。

做过一点后端的人很快会意识到:这不是Agent独有的问题,这是分布式编排的问题。容器领域八九年前就遇到过一模一样的困境,然后Kubernetes给出了标准答案——声明式状态加调度循环。所以当Google开源AX(Agent Experience)这个项目时,我第一时间去翻了它的源码。它解决的根本问题,就是让Agent像Pod一样被调度:把Agent当成交付单元去声明、注册、编排、容错,而不是在业务代码里手工维护它们的生命周期。

1.2 AX出现的背景:K8s早已给出的答案

先说清楚一个容易混淆的点:AX不是又一个"写Agent的框架"。它不教你如何写ReAct、如何调工具、如何写System Prompt,这些是LangChain、LlamaIndex这类框架的活。AX做的是更底下的一层——把已经写好的Agent包装成一个标准化的、可以被声明、注册、调度、监控和恢复的运行单元。

这个"标准化运行单元"就是Agent Pod。K8s里Pod是调度的最小单位,你不需要关心容器具体跑在哪个机器上,只需要声明这个Pod长什么样,控制平面负责让它达到期望状态。AX把同样思路搬过来:你不需要在代码里逐个启动Agent、手动处理依赖顺序、写一堆状态同步的胶水代码,只需要声明这个Agent的期望状态,调度器会自动完成实例化、依赖解析、生命周期管理和容错。

如果你用过Helm、Kustomize那套声明式配置,再来看AX的设计会有种天然的熟悉感。

2. AX调度模型逐层拆解:Pod、Registry、Driver、Node

2.1 核心抽象:Agent Pod

Agent Pod是AX最核心的数据结构,用来描述一个Agent的完整运行规格。这里的"规格"比传统框架里的Agent配置要重得多,它包含四块内容:

配置块作用类比K8s
agentAgent本身的定义,包括模型、指令、工具集Pod里的容器镜像与启动参数
serviceAgent对外暴露的HTTP服务配置,包括host、port、响应格式Pod的Service抽象
schedule调度策略,默认立即执行,可选定时/循环CronJob/Job的调度策略
metadata与status元数据和运行状态追踪Deployment的Label与Status

第一眼看到这个结构的时候,我有点意外的是service被提升到了Pod的平级字段。后来想通了:在AX里,Agent不仅是"一个能回答问题的程序",它可以是一个对外提供能力的服务单元。多个Agent之间可以通过HTTP互相调用,这正好对应K8s里Pod与Service的关系——我还是第一次看到有Agent框架认真对待跨Agent的通信问题。

2.2 Graph Registry:注册中心与拓扑定义

单个Agent定义好之后,不能直接跑,必须先注册到一个叫Graph Registry的地方。Graph Registry承担两个职责:一是存储Agent Pod的元信息,相当于K8s里的API Server;二是维护Agent之间的依赖关系图。

关系图是用DAG(有向无环图)来表达的。你可以用run(agent, context)触发一个Agent,用chain(a, b)把两个Agent串成一条流水线,用loop让Agent循环处理某个数据集,也可以用parallel把多个互不依赖的Agent并行布置。DAG的好处是拓扑结构显式化,调度器不用去业务代码里猜"谁先谁后",一眼就能算出哪些节点可以并行、哪些节点必须等待。

我自己用下来的感受是:Graph Registry最大的价值在于把Agent协作关系从代码里「抠」出来了。以前看别人写的多Agent项目,要理清调用关系得逐个文件翻。AX里直接在注册阶段就能看到整张依赖图。

2.3 Driver与Driver Node:谁来跑、跑在哪

有了Pod定义和注册图,接下来就是执行。执行由两部分组成:

  • Driver:调度引擎,负责遍历DAG、解析依赖、触发Agent执行、检测状态漂移,然后执行恢复动作。这就是K8s里Controller Manager的角色。Driver有两种运行模式:synchronization模式是显式地控制流程走完一轮;exploration模式适合自主探索型Agent,循环地对环境作出反应。
  • Driver Node:Agent实际运行的环境,对应K8s的Node节点。AX这个设计很聪明——它把存储和执行环境拆开了,目前支持三种Node实现:InMemoryNode(内存模式,跑demo用)、CentralPackageNode(CP模式,集中的包管理型节点)、PubSubNode(基于发布订阅的异步节点,跨进程通信用)。生产环境下建议优先考虑CP和PubSub,内存模式一重启就什么都不剩了。

2.4 与Kubernetes组件的一一对照

把整个调度模型放到一起,它就是一套缩略版K8s架构:

KubernetesAX职责
PodAgent Pod可部署的Agent运行单元
API ServerGraph RegistryAgent定义与拓扑关系注册
Controller ManagerDriver协调循环,驱动Agent达到期望状态
NodeDriver NodeAgent实际的执行与存储环境
ServiceAgent Pod.ServiceAgent对外暴露的通信入口

这张对照表是我理解AX最快的一条路径。只要摸过K8s,再看AX的源码就非常顺畅。

3. 手把手把第一个Agent挂进调度系统

3.1 环境准备与安装

AX目前以Python包的形式发布,核心安装包叫ax-kybernetes。这个名字起得很直白:kybernetes就是Kubernetes的前缀,意思是"Agent领域的Kubernetes"。安装命令很简单:

pip install ax-kybernetes

建议用Python 3.11以上的版本,低版本在并发场景下有些异步特性支持得不好。装完之后可以顺手确认一下版本视图:

pip list | grep ax

我实际跑下来,这个包对系统依赖很少,不要求你有Docker或者K8s环境,纯Python进程就能把整套调度机制演示出来。

3.2 编写Agent类并定义Agent Pod

第一个Agent我们不搞花活,就做一个带定时调度能力的"问候Agent":

from ax import class_definition, AgentPod @class_definition class HelloAgent: """A minimal agent that can be scheduled like a Pod.""" def build(self) -> AgentPod: agent_pod = AgentPod( name="hello_agent", agent=AgentPod.Agent( name="hello_llm", model="gemini-2.0-flash", instruction="你是一个友好的助手,用一句话问候来访者。", ), service=AgentPod.Service( host="127.0.0.1", port=8001, response_format="JSON", ), schedule=AgentPod.Schedule( kind="every_day", value="10:00", ), ) return agent_pod

这里有个关键点:Agent Pod启动后不仅是"被调用时运行",它本身会挂一个HTTP服务等待别的Agent来请求,同时按schedule的配置定时触发。也就是说,Agent从"被动的函数"变成了"主动的调度单元"——这个变化是理解AX和传统框架差异的分水岭。

3.3 注册入图:让依赖关系可解析

Pod定义完成后,把它注册到Graph Registry里:

from ax.registry import AxGraphRegistry graph = AxGraphRegistry( name="hello_graph", description="First AX demo", ) # 注册并指定这个Pod要处理的目标数据 graph.run( HelloAgent, target="everyone", )

run这个方法名初看容易误解,它不是直接运行Agent,而是"把这个Agent及其触发关系放上调度图"。真正执行由后面的Driver来驱动。多个Agent时,可以用chain、parallel这些方法把依赖关系串起来,调度器会自动解析出可并行执行的节点。

3.4 用Driver跑起来

from ax.driver import AxDriver driver = AxDriver( name="hello_driver", mode="synchronization", ) driver.load_graph(graph) driver.run(max_iterations=5)

max_iterations是协调循环的最大轮数。在synchronization模式下,调度器会先解析整张DAG,按拓扑序执行所有就绪节点,遇到依赖未满足的节点就等待。执行完成后,可以通过Driver内部的状态视图确认每个Agent Pod的当前状态。

我在本地跑这个demo时,最有体感的一件事是:整个Agent没有任何asyncio.run()、没有任何threading.Thread、没有手写while轮询,调度全由Driver的循环接管。代码逻辑一下子干净了很多。

4. 从demo到项目:三个被文档略过的实际问题

如果你只看官方文档,会觉得AX清爽得不像话。但真把它用进一个稍微复杂点的项目,有三个问题文档没细讲,我全踩了一遍,这里展开说一下。

4.1 for循环语义:AgentPod与ReconcileContext别混用

AX的工作流支持for循环,用来对数据集批量执行。初看会觉得它就是Python的for循环,其实不是。它是声明式DAG里的一种展开语法,迭代中真正传递的对象有两个:一个是被调度的Agent Pod,一个是Reconcile上下文。前者是Agent实例元信息,后者才是当前这轮迭代要处理的上下文数据。

我第一版代码犯的错误是,直接把迭代出来的AgentPod实例当成上下文对象塞给下游Agent,导致下游拿到的value永远是一串Pod元数据。正解是:循环变量的作用域只在调度展开阶段,你真正用的应该是ReconcileContext里被解析过的数据字段。

这段经验说明AX的循环和普通的for item in list思维模式不一样,写之前要把"展开调度"和"执行处理"两个阶段在脑子里分开。

4.2 DriverNode缓存的作用域问题

三种DriverNode里,我一开始图省事全部用了InMemoryNode。demo没问题,但多个Agent共用同一个Node时问题来了:内存节点上的缓存是全局共享的。A Agent在缓存里写了一个中间结果,B Agent因为某种原因没有重新计算,直接读到了这个"幽灵缓存",导致下游结果出现难以定位的偏差。

排查了一天之后我才意识到:这不是Agent逻辑的bug,是Node选型的问题。多个团队/多条业务链如果公用一个内存节点,等于多个Pod挤在同一个没有namespace的共享内存里。稍微规划一下就明白了:每条独立业务链应当使用独立的Node,或者用PubSubNode把数据隔离在各自的消息通道里。

4.3 状态归档:不清理的Registry会拖垮驱动循环

AX为了支持容错和状态重建,会持续记录Driver Node的历史状态。默认情况下这些状态不断累积。我在一个长跑实验里跑了两个多小时,中途发现调度循环明显变慢,看代码才发现是Registry里的历史状态太多,每次状态比对都要全量扫描一遍。

处理方式很简单:AX提供归档能力,把已经完成、不需要回放的状态归档到独立目录,让Registry只保留活跃状态。这个动作建议在每次规模任务结束后手动执行一次,或者写在定时任务里。生产环境还要把log_dir和归档路径分开,避免日志挤占调度元数据的空间。

5. 生产化思考:AX真正解决的和尚未解决的

5.1 异步化接入:别把同步API当最终形态

AX底层是异步架构,官方的同步API只是给快速上手用的一层薄壳。一旦涉及到多个Agent并行、长耗时工具调用、或者需要对接外部消息队列,同步API会成为瓶颈。我在一个并发实验中发现,同步模式下多个Agent实际上是串行等待的,只有切到底层异步接口之后,DAG里标记为可并行的节点才真正跑出并行效果。

生产接入的正确姿势是:把AX嵌入到已有的异步事件循环里,用异步API操作Driver,外部通过消息队列或HTTP回调来获取执行结果,而不是让一个长驻进程阻塞式地等Driver跑完。

5.2 调度策略的可配置化

AX内置的调度策略是围绕定时和循环设计的,但真实的业务调度场景要复杂得多——周末不执行、过了截止时间自动跳过、失败后按指数退避重试。这些策略在AX里没有直接等价物,需要自己在schedule字段外面再做一层策略包装。

我在现有项目上的做法是:给每个Agent Pod加一个metadata标注业务级调度属性,比如"容忍延迟级别"、"最大重试次数"、"执行窗口",然后写一个薄薄的中控层,在调度触发前根据这些标注决定是放行、推迟还是丢弃。AX给了底层机制,上层策略还是得自己搭,这点要有预期。

5.3 可观测性与日志体系

K8s之所以在生产环境好用,可观测性是不可或缺的一环。AX目前提供的状态视图和日志,能支撑开发调试,距离生产监控还有一段距离。我的建议是:不要等AX内置监控成熟,尽早把关键事件(Pod创建、调度触发、状态迁移、失败恢复)打成结构化日志推到外部系统。至少要做到"每个Agent Pod每次调度都有一个trace_id贯穿全程",排查问题的时候才不会在多个日志文件之间来回翻。

5.4 我的结论与选型建议

用了一段时间AX,我的结论是:它不是用来替代LangGraph之类的Agent流程框架的,而是给"Agent基础设施"提供了一个更底层的调度抽象。如果你现在的主要痛点是Agent没有清晰的编排边界、生命周期失控、容错靠手工,AX能让你眼前一亮。

如果你现在只是写一个单Agent工具,或者需求里根本不存在"多个Agent协作和调度",那用AX反而是引入不必要的复杂度。这套框架的收益,是在Agent数量变多、依赖变复杂之后才开始真正显现的。等项目的Agent规模开始不好管了,再回头看看这篇里的拆解,你会觉得这套"Pod化调度"的思路,确实是绕不开的那条路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进