agentgateway 深潜:AI 原生网关的架构、取舍,与几处宣传落差
一句话结论:agentgateway 的对外说法是「一个进程统一 HTTP/gRPC、LLM、MCP、A2A」。但逐文件读完
v1.7.0的实现后,我认为它真正值得学的有两点,都不在营销词里:一是它清楚自己不该做什么(推理端点选择委托给 EPP、语义缓存委托给 vSR),二是它把 Agent 的会话与身份当成一等公民(会话状态塞进自包含加密令牌、身份栈深到 SPIFFE 和 ID-JAG)。至于「AI 原生」,代码层面的含义比宣传语具体得多。
这篇文章基于对本地仓库(git describe = v1.7.0-71-g134897e1)的逐文件核对写成。凡是能验的我都验了,验不了的会明确标注来源。文末单列一节讲宣传与实现对不上的地方——这不是挑刺,而是因为一个正在快速迭代的项目,官方口径与实际行为的差距本身就是选型时要评估的风险项。
一、问题背景:传统网关为什么接不住 Agent 流量
传统 API 网关(Envoy、Kong、Nginx)的底层假设是短生命周期、无状态、一问一答。这套模型在微服务时代运转良好,但 Agent 流量有三个地方对不上:
MCP 是有状态的长会话协议。 客户端打开连接后服务端可以异步回推消息;一次 tools/list 可能要扇出到多个后端 MCP 服务器,再按会话权限合并结果。这和传统网关的 listener/cluster 模型根本不是一个形状。
LLM 请求需要深度解析请求体。 按模型名、token 数、prompt 内容做路由、限流和审计——只匹配 URL 和 Header 完全不够。
A2A 引入了 Agent 间特有的语义:能力发现、模态协商、任务协作。传统网关对此一无所知。
业界的普遍补丁是给 Envoy 挂一个 Go 写的 ext_proc sidecar 来补会话逻辑。Solo.io 的工程团队最后选择了另一条路:用 Rust 从零写一个数据平面。这个决定的可信度部分来自人——git shortlog 显示提交数第一的是 John Howard(1335 次,断层第一),他是 Istio/ztunnel 的核心作者,而 ztunnel 正是 Istio Ambient Mesh 的 Rust 节点代理。
需要说明:「为什么选 Rust」这件事在仓库里找不到出处。 我在
README.md、CHARTER.md、DEVELOPMENT.md、architecture/全量 grep 过zero-cost/no GC/garbage collector/memory safe,零命中。「无 GC、内存安全、零成本抽象」是外部宣传语,仓库内没有对应的设计文档佐证。这一点在选型时值得注意——它意味着这个技术判断的依据不在代码库里。
二、项目概况
| 项 | 值 | 依据 |
|---|---|---|
| 首次提交 | 2025-03-18 | git log --reverse |
| 当前版本 | v1.7.0(2026-10-27);上一版 v1.6.0(2026-10-02) | git tag |
| 许可证 | Apache-2.0 | LICENSE |
| 治理 | LF Projects, LLC 的 Series,TSC 制 | CHARTER.md |
| 提交集中度 | John Howard 1335 / Eitan Yarmush 205 / Nina Polshakova 111 | git shortlog -sn |
迭代节奏值得单独说一句:v1.6.0 到 v1.7.0 只隔了 25 天。这是典型的早期高速迭代项目——好处是问题修得快,代价是本文后面会讲到的文档滞后。
关于与 kgateway 的关系:代码里还留着一批 legacy 注解,kgateway.dev/a2a、kgateway.dev/mcp-path,xDS 的 TLS secret 名直接叫 kgateway-xds-cert。这些残留佐证了两者是同源演进而非并列的两个项目。
三、架构:一个刻意不复用 Envoy 类型的数据平面
先给一张全局拓扑。注意最下方那个虚线框——它标出的不是 agentgateway 的功能,而是它明确不做的事,这是理解整个设计取向的关键。
图:agentgateway 的拓扑架构。控制平面(Go)通过 xDS 向数据平面(Rust)下发自研资源类型;数据平面在单进程内承载 LLM / MCP / A2A / 传统 HTTP·gRPC 四类流量,并共享同一套策略、身份、观测与成本控制;推理端点选择与语义缓存则委托给外部的 EPP 与 vSR。
3.1 自研 xDS 资源类型,以及为什么
这是整个设计里最有信息量的一条。architecture/configuration.md 写得很直白:
We use the XDS Transport Protocol, but do not use the Envoy types (Listener, Cluster, etc), and instead use purpose-built types.
注意区分传输与资源:传输层复用了 Envoy 的 discovery service proto(crates/xds/src/types.rs 里 pub use protos::envoy::service::discovery::v3),workload/service 复用 Istio 的 istio.workload。不复用的是资源类型本身。
自研的类型定义在 crates/protos/proto/resource.proto:
1 | message Resource { |
这里有个细节值得记下来:正式类型名是单数,而且是十个不是五个。很多二手资料(包括一些对比文章)写成 Binds / Listeners / Routes / Backends / Policies 的复数形式——那是 Go 控制平面里 krt collection 的字段名(controller/pkg/agentgateway/plugins/interfaces.go),不是 xDS 资源类型名。
那么为什么要自研?答案是避免 fanout。同文档给出的设计原则是:
A critical design philosophy for the APIs is to maintain a nearly direct mapping of user facing APIs to XDS to IR.
反例讲得很具体:在 Envoy 模型里,改一个 TLS 密码套件字段,可能要向所有 Cluster 重新推送配置;而 agentgateway 的资源是指向父级而非父级包含子列表(Route.listener_key 就是一个指针),所以只改一个小 protobuf。这个理由在大规模部署里是真实的运维痛点——不是纸上谈兵。
3.2 IR 不是原子替换,是写锁下逐条应用
架构文档说配置统一翻译成「shared internal representation (IR)」。这句话容易让人以为是一个 ArcSwap 整块替换。实际上不是。
运行时 IR 是 Store,由 Stores 聚合(crates/agentgateway/src/store/mod.rs):
1 | pub struct Stores { |
StoreUpdater 内部是 Arc<RwLock<Store>>。xDS handler 拿到写锁后,对每个 XdsUpdate 逐条调 insert_xds(...) / remove_resource(...)。本地文件 reload 走的也是 diff 式增量(sync_local 接收 prev: PreviousState)。
arc_swap 这个 crate 确实在用(crates/core/src/arc.rs 定义了 Atomic<T> = Arc<ArcSwap<T>>),但只服务于局部子结构:负载均衡端点、模型成本目录、日志字段、DNS 缓存。整个 IR 没有做快照级原子替换。
这个区别对使用者有意义:增量应用意味着大批量配置变更不是「一瞬间全切」,而是逐条生效——排查配置问题时的心智模型不一样。
3.3 控制平面:krt 是 Istio 的,不是自研的
Go 控制平面用了响应式集合库 krt。这里要纠正一个常见误解:krt 不是 agentgateway 自研的,是 Istio 的第三方库(istio.io/istio/pkg/kube/krt),仓库里有 85 个 Go 文件引用它。
「某个被 watch 的资源变化时,只有依赖它的集合会被重新计算」这个特性是 krt 固有的响应式依赖图语义,归功于 Istio 的设计而非本项目。
其余两块:
- xDS server(
controller/pkg/syncer/krtxds/xds.go):流式 gRPC ADS +DeltaAggregatedResources增量推送,全部资源走单一 type URL(type.googleapis.com/agentgateway.dev.resource.Resource)。认证用 Kubernetes ServiceAccount Token(tokenreview.ValidateK8sJwt,audience 固定为agentgateway),TLS 自签 CA + 自动轮换。 - Deployer(
controller/pkg/deployer/):watch 使用agentgatewayGatewayClass 的 Gateway,自动 provision 代理工作负载。默认 Deployment,可通过AgentgatewayParameters.spec.workload.kind切成 DaemonSet。
3.4 三种配置来源
| 来源 | 用途 | 是否动态 |
|---|---|---|
| Static | 进程级全局参数(日志、端口) | 启动时一次 |
| Local | YAML/JSON 文件,定义完整功能集 | file watch 动态 reload |
| xDS | 控制平面下发 | 流式增量 |
Local 模式让 agentgateway 可以脱离控制平面独立跑——这对本地开发和边缘部署很实用,也是它能在非 K8s 环境用的原因。
四、LLM Gateway:passthrough 优先,这是它能跟上 provider 的原因
4.1 Provider 覆盖
权威枚举在 crates/agentgateway/src/llm/mod.rs:
1 | pub enum AIProvider { |
7 家内置 + custom。比常见说法多出来的两个:GitHub Copilot 和 custom。而 custom 还带 17 个命名预设(crates/llm/src/custom.rs):Cohere、Ollama、Baseten、Cerebras、Deepinfra、Deepseek、Groq、Huggingface、Mistral、Openrouter、Togetherai、XAI、Fireworks、Meta、Perplexity、Typesafe、Nebius——每个预设内置 base_url() 和声明的 wire format。
接入自托管模型(vLLM 等)的实际路径就是 custom,指向 OpenAI 兼容端点。代码里没有 vLLM/TGI/Triton 的专用后端支持。
4.2 端点:既收 OpenAI,也收各家原生
crates/agentgateway/src/llm/model_router.rs 的 SERVING_ENDPOINTS 里同时挂着:
- OpenAI 兼容:
/v1/chat/completions、/v1/responses、/v1/embeddings、/v1/models、/v1/images/*、/v1/audio/* - Anthropic 原生:
/v1/messages、/v1/messages/count_tokens - Gemini 原生:
/v1beta/models/{model}:generateContent等 - Bedrock 原生:
/model/{model}/converse等
内部用 ChatFormat(OpenAICompletions / OpenAIResponses / AnthropicMessages / BedrockConverse / VertexGemini)做跨格式翻译。所以「客户端用 Anthropic 格式进来、上游是 Bedrock」这种组合是能转的。
4.3 passthrough 设计:为什么它跟得上 provider 的 API 变化
这是我认为最值得学的工程决策。看请求类型定义(crates/llm/src/types/completions.rs):
1 | pub struct Request { |
设计原则写在 crates/agentgateway/src/llm/README.md:
passthrough approach… Only fields we specifically operate on need to be included.
含义是:provider 加一个新参数,网关不需要改代码就能转发。 对比之下,一个把所有字段都建模成 typed struct 的网关,每次上游 API 变动都要发版。对于一个要罩住二十多个 provider 的项目,这个选择几乎是唯一可行的——但它需要克制:只有网关真正要读要改的字段才允许进 typed 部分。
4.4 CacheTokenConvention:一个小而典型的「网关做归一化」
这是最能体现网关价值的一个细节,值得单独讲。
问题:不同 provider 对 input_tokens 的口径不一致。 Anthropic 报告的 input tokens 不含缓存命中的部分;OpenAI 含。如果直接拿来记账,跨 provider 的成本和限流就不可比。
agentgateway 的解法(crates/llm/src/lib.rs):
1 | pub enum CacheTokenConvention { InputIncludesCache, InputExcludesCache } |
cache_convention_for()(crates/agentgateway/src/llm/mod.rs)把 Anthropic / Bedrock 判为 InputExcludesCache,OpenAI / Gemini 判为 InputIncludesCache。
归一化后的值被三个消费点共用:CEL 的 llm.inputTokens、遥测的 gen_ai.usage.input_tokens、以及限流的 token 结算。同时保留了未归一化的 llm.providerInputTokens 供需要原始值的场景。
测试里有直接断言:llm_span_uses_cache_inclusive_input_tokens 构造 input=50 / cached=40 / creation=10,断言 span 值为 100。
两个补充:一是归一化是 provider 相关的,不是无条件相加;二是上游没回 usage 时会回退到网关侧 tokenizer 估算。
这类「把 provider 差异在网关层抹平」的工作,恰恰是单点网关相对「每个应用自己接 SDK」的核心价值——但它不性感,很少被写进宣传材料。
4.5 成本控制:内置定价库 + 按 Key 的预算
内置成本目录(catalog/model-catalog.json,crates/agentgateway/src/llm/catalog/mod.rs 用 include_str! 编进二进制)覆盖 22 个 provider、2116 条模型定价——bedrock 260 条、azure 95、huggingface 82、deepinfra 77、openai 65、vertex 61。有独立的 overrides 文件可以打补丁。
每请求的真实美元成本由 token 用量投影出来,进 CEL 的 llm.cost、遥测 metric、以及日志库的 cost 字段。
预算机制(crates/agentgateway/src/http/budget/mod.rs):
1 | pub enum BudgetLimitUnit { Usd, Tokens } |
配置形如 apiKey.keys[].budgets[].{name, limit.unit, limit.amount, window.rolling, onBudgetExceeded},在响应完成后按真实 token/成本 settle()。
这里要纠正一处宣传口径:预算绑定在 API key 维度,代码里没有独立于 API key 的「团队级」预算对象。API key 可以带任意 metadata(JSON),你把 team/app/user 塞进去再用 CEL 做限流 key 是可以的,但那是你自己组织的约定,不是网关强制的三级模型。
五、MCP Gateway:把会话状态塞进令牌里
5.1 传输:四种上游 target
代码里上游 target 由 McpTargetSpec 枚举定义,共四种(crates/agentgateway/src/types/agent.rs):
| target 类型 | 对应实现文件 |
|---|---|
Mcp(Streamable HTTP) |
mcp/upstream/streamablehttp.rs |
Sse |
mcp/upstream/sse.rs |
Stdio |
mcp/upstream/stdio.rs |
OpenAPI |
mcp/upstream/openapi/mod.rs |
注意:根 README 写的「stdio/HTTP/SSE/Streamable HTTP transports」这四个词,和代码里的四种 target 不能一一对应——「HTTP」和「Streamable HTTP」在实现里是同一个东西,真正的第四种是 OpenAPI。
5.2 工具联邦与命名
tools/list 会扇出到所有后端(handler.rs 的 send_list → send_fanout_to → fanout_open_streams,遍历全部 target 并发调用)。
命名重写规则是 ${target}_${tool},分隔符是下划线:
1 | const DELIMITER: &str = "_"; |
但资源 URI 和 task id 用的是加号,不是下划线(handler.rs 的 resource_uri / task_id):service+http://...、{target}+{task_id}。这个区分很容易被笼统说成一种,实际是两种。
前缀行为由 McpPrefixMode 控制:Conditional(默认,多 target 时才加)、Always、Never。
5.3 会话:一个把问题溶解掉的设计
这是全文我认为最值得讲的一点。
常见的批评是「MCP 有状态会话在多实例网关下有粘性问题」。但 agentgateway 的解法不是 consistent hash 路由,而是让会话状态随会话 ID 一起走。
SessionManager(mcp/session.rs)确实是进程内存储:
1 | pub struct SessionManager { |
内存 HashMap、进程内单例、30 分钟默认空闲 TTL 加后台 reaper。全仓库搜 redis 在 MCP 路径下零命中——没有外部存储后端配置。
但关键在于:会话 ID 本身是一个自包含的加密令牌,携带上游会话状态(http/sessionpersistence.rs 的 MCPSessionState,AES-256-GCM 加密后 base64)。get_or_resume_session 会先 decode(id),然后重建连接。
结果就是:任意副本拿到这个 ID 都能恢复会话。粘性问题不是被 consistent hash 缓解的,是被这个设计从根上绕开了——网关不需要记住你,令牌记得。
这个设计的代价是令牌体积和加解密开销,收益是网关可以完全无状态地水平扩展。对多副本部署来说这是更干净的答案。
5.4 无状态模式已经是配置项
同样需要纠正一处:「团队后续计划把 MCP 改为无状态」——这已经是现成配置了(crates/agentgateway/src/types/local.rs):
1 | pub enum McpStatefulMode { Stateless, Stateful } // Stateful 为默认 |
配置字段 statefulMode,proto 里对应 MCPBackend.StatefulMode { STATEFUL, STATELESS }。代码路径 serve_stateless / create_stateless_session(注释明写 “not registered in session manager”)。
另外,MCP 现代协议(2026-07-28+)本身就没有会话概念,代码里有 uses_sessions 判断和 REMOVED_METHODS_2026_07_28 常量在跟进这个变化。
5.5 OAuth:比「支持 OAuth」深得多
mcp/auth.rs 有 1546 行,实现的不是一个 OAuth 客户端,而是一整套授权服务器代理:
| 能力 | 规范 |
|---|---|
| Protected Resource Metadata | RFC 9728 |
| Authorization Server Metadata | RFC 8414 |
| 动态客户端注册(DCR) | RFC 7591 |
| PKCE | code_challenge_methods_supported: ["S256"] |
| Resource indicator | RFC 8707 |
支持 authorization_code 和 refresh_token 两种授权类型,明确不支持 client_credentials(代码注释直接写明)。
内置六个 IDP 的适配:Auth0、Keycloak、Okta、Descope、Authentik、Entra——每个带自己的 audience 变通和 JWKS URL 推导规则。没有 DCR 能力的 IDP(Entra、Authentik)会短路返回一个 mock 注册响应。
令牌校验模式三档:Strict(默认)/ Optional / Permissive。
5.6 OpenAPI → MCP:三条常见「限制」有两条不成立
这个功能是把 OpenAPI spec 的每个 operation 转成 MCP tool。我核对了流传较广的三条限制:
| 说法 | 核实结果 |
|---|---|
请求体只支持 application/json |
部分属实——还支持 application/octet-stream(base64 编码) |
| 不支持 HTTPS 上游 | 不属实——http/https 都支持,example 里就是 host: https://example.com/api |
| 不支持结构化输出 | 不属实——CallToolResult::structured(...) 已实现,测试里有 structured_content 断言 |
六、A2A Gateway:一个空结构体撑起的协议感知
A2A 的启用方式很简洁——给路由打一个空标记:
1 | policies: |
对应 crates/agentgateway/src/types/agent.rs 里一个空结构体:
1 | pub struct A2aPolicy {} |
空不代表没做事。启用后网关会:
重写 Agent Card(crates/a2a/mod.rs 的 rewrite_agent_card)。识别 GET /.well-known/agent.json(旧版)与 /.well-known/agent-card.json(v0.3.0+),把响应体里的 URL 改写成指向网关自身——v1.0 改 supportedInterfaces[].url,v0.3 改顶层 url。这是让 Agent 能力发现经由网关的关键一步。
解析 JSON-RPC 用于可观测。请求侧取 method;响应侧解析出 outcome(success/error)、error.code、result.kind、task_state、context_id,并且同时兼容 A2A v0.3 的扁平结构和 v1.0 的嵌套结构。
控制平面也能自动识别:Service 上打 agentgateway.dev/a2a 的 AppProtocol 就会生成对应策略。
七、被低估的一节:Agent 的身份与授权
如果只看「LLM/MCP/A2A 三合一」的宣传,会漏掉这个项目投入最重的一块。examples/ 里授权与身份相关的例子排了一整列:
| 例子 | 做的事 |
|---|---|
traffic-spiffe |
从本地 SPIFFE Workload API 取网关的 mTLS 身份 |
traffic-token-exchange |
RFC 8693 令牌交换 / RFC 7523 JWT bearer 授权 |
traffic-cross-app-access |
OAuth ID-JAG,把用户身份换成后端作用域的访问令牌 |
mcp-authzen |
按 OpenID AuthZEN 标准授权 MCP 工具调用 |
traffic-tailscale-auth |
用 Tailscale 身份头做认证 |
traffic-jwt-sign |
每次后端请求签一个短时效 JWT(替代静态凭据) |
microsoft-intune |
校验 Intune 纳管的 Codex / Claude Desktop 配置,上报合规状态 |
再加上 CEL 策略引擎——而 CEL 这块有个细节:crates/cel-fork/ 是 cel-rust 的 vendored fork(crates/cel-fork/README.md 明说 “a vendored fork of cel-rust”),crates/celx/ 在其上追加扩展函数(general / strings / math / cidr / optimize / flatten,分别对齐 cel-go 的 ext 库和 Kubernetes 的 CIDR 库)。CEL 不是拿来即用,是 fork 了语言本体再扩。
我认为这才是 agentgateway 区别于 LiteLLM 类方案的分水岭。LLM 网关解决的是「一个 API 罩住 N 个 provider」;而 Agent 场景真正难的是**「这个 Agent 以什么身份、代表谁、能调哪些工具」**——这是授权问题不是路由问题。项目在这上面投入的深度(SPIFFE、令牌交换、ID-JAG、AuthZEN)说明它的定位确实偏基础设施而非 SDK 封装。
八、性能:数字是厂商自测,方向性可信
Solo.io 官方做过一次代理开销基准(mock 后端剥离模型推理时间,32 并发、1KB 载荷):
| 网关 | 吞吐 | P50 | P99 | 峰值内存 |
|---|---|---|---|---|
| agentgateway | 36,933 QPS | 0.831 ms | 1.970 ms | 29 MB |
| LiteLLM | 3,198 QPS | 7.076 ms | 32.192 ms | 11.8 GB |
约 11.5 倍吞吐、约 10 倍延迟、约 500 倍内存。
必须标注:这组数字不在仓库里(我 grep 过全仓的 LiteLLM / QPS,只有一处无关注释),是 Solo.io 官方博客的厂商自测。我在仓库里也没有找到对应的基准脚本可供复现。
即便打折扣,有一点是独立评测也认同的:Python 网关在高并发下的资源开销会失控。500 倍内存差距里,运行时本身(解释器 + GC + 框架栈)占了大头,这部分差距是结构性的,不是调优能抹平的。
九、横向对比与选型
2026 年的 AI 网关大致两条路线:LLM-request-first(从「一个 API 罩住 100+ provider」起步)和 traffic-infrastructure-first(从 L7 代理向 AI 流量扩展)。
| 维度 | agentgateway | LiteLLM | Kong AI Gateway |
|---|---|---|---|
| 核心抽象 | 统一代理 HTTP/gRPC/LLM/MCP/A2A | LLM 补全请求 | API 请求 + AI 插件 |
| 数据平面 | Rust,自研(ztunnel 血统) | Python | Nginx + Lua |
| MCP 成熟度 | 原生,会话/联邦/授权齐全 | 存在但非设计中心 | 插件式(Enterprise) |
| 身份与授权深度 | 高(SPIFFE / 令牌交换 / ID-JAG / AuthZEN) | 中 | 中高 |
| 许可证 | Apache-2.0 | MIT | 核心开源,高级 AI 插件 Enterprise |
选型上我的判断:
- 场景是「N 个 provider 归一化 + 预算 + fallback」,MCP/A2A 是次要流量 → LiteLLM 仍是最快路径,生态和教程最厚。
- 已经在 K8s/Envoy/Istio 世界,MCP 工具与 Agent 间流量是架构核心,且对 Agent 身份授权有真实要求 → agentgateway 是当前最贴合的工程选择。要接受它更年轻、文档有滞后、自研 xDS 模型有学习曲线。
- 想要产品化仪表盘和开箱 Guardrails → Kong AI Gateway 或 Portkey。
十、宣传与实现的落差
这一节单独列,因为对早期项目来说,官方口径与实际行为的差距是真实的选型风险。
| 宣传/常见说法 | 代码实际情况 |
|---|---|
| 语义缓存 | 本项目没有。 crates/agentgateway/src/llm/ 下 grep semantic 零命中。examples/llm-semantic-routing/ 是把语义路由和响应缓存外包给外部 vLLM Semantic Router,经 ext_proc 接入,缓存键和相似度匹配都在 vSR 侧。本体只有 prompt caching(缓存点标记)和 callout 的精确匹配缓存 |
| 虚拟 Key(virtual key) | 没有这个功能。 全仓唯一命中是迁移工具 controller/pkg/cli/migrate/virtualkeys_to_configmap.go,把旧的 Secret 式 API key 迁移成 ConfigMap。现行机制是 API Key 策略 + 预算 |
| 推理路由基于 GPU 利用率 / KV cache / LoRA / 队列深度 | agentgateway 自己不读这些指标。 它通过 gRPC ext_proc 调用外部 Endpoint Picker (EPP),EPP 返回一个 ip:port,网关直连(http/ext_proc.rs 的 InferenceRequestResult 只含 destination)。GPU/KV/LoRA/队列深度是 EPP 侧(Kubernetes Gateway API Inference Extension)的能力 |
| 对接 vLLM / TGI / Triton | 没有专用后端支持(tgi/triton 全仓零命中)。接 vLLM 的实际路径是 custom provider 指向 OpenAI 兼容端点 |
| Rust 因为无 GC / 内存安全 / 零成本抽象 | 仓库内无任何出处,是外部宣传语 |
| krt collections 是控制平面的心脏 | 属实,但 krt 是 Istio 的第三方库(istio.io/istio/pkg/kube/krt),不是自研 |
| xDS 资源类型是 Binds/Listeners/Routes/Backends/Policies | proto 里的正式类型名是单数 Bind/Listener/Route/Backend/Policy,且共十个(另有 TCPRoute/RouteGroup/ModelRoute/Workload/Service)。复数形式是 Go 侧 krt 字段名 |
| 收到 xDS 更新后原子化应用 | 不是快照替换。Arc<RwLock<Store>> 写锁下逐条增量应用 |
| MCP 默认将改为无状态(未来计划) | 已是现成配置:statefulMode: Stateless |
| JWKS 公钥只在配置解析时拉取一次 | 不属实。resource_manager.rs 有 JWKS_TTL = 15min 周期刷新 |
| 支持 LangChain / CrewAI / Google ADK | 依据薄弱。CrewAI/ADK 只出现在架构图 img/architecture.svg 作为客户端示例;LangChain 仅在两处测试注释里;没有正式支持声明 |
指标 list_calls_total 存在于文档 |
主指标文档 schema/metrics.md 是准的;错的是 examples/mcp-telemetry/README.md 的示例输出——tool_calls_total 和 list_calls_total 在 *.rs 里零命中,代码实际注册的是 agentgateway_mcp_requests_total |
这组落差本身是有信息量的,不是简单的「文档写得差」:
- 语义缓存和推理路由这两条,反映的是项目边界——它选择委托而不是自建。这其实是好设计(见下文),但宣传语把它们算成了自己的功能。
virtual key和「MCP 改无状态」这两条,是版本演进造成的口径滞后——功能变了或早就有,对外材料没跟上。- JWKS 和指标名这两条,说明示例和边角文档的维护滞后于主干代码。25 天一版的迭代速度下这是必然代价。
一个务实的读法:主干的架构设计(xDS 类型、IR、MCP 会话、授权栈)是可信的,边角的宣传描述需要回代码核对。
十一、结语:它真正的取舍
回到开头那个判断。agentgateway 最值得学的不是「一个进程做四件事」,而是两件更朴素的事。
第一,它清楚自己不该做什么。
推理端点选择委托给 EPP,语义缓存委托给 vSR,两者都通过标准协议(gRPC ext_proc)接入。网关只消费结果——EPP 返回 ip:port 它就直连,不试图自己算 GPU 利用率。
这不是能力不足,是架构纪律。一个网关如果既要做路由、又要做向量检索、还要做调度,最后会变成谁都不好用的缝合体。agentgateway 把「AI 流量治理」的边界划在策略、观测、身份三件事上,其余交给专业组件。这个边界感,比它多支持几个协议更有参考价值。
第二,它把 Agent 的会话与身份当成一等公民。
会话状态塞进自包含加密令牌——粘性问题被溶解而不是缓解;身份栈深到 SPIFFE 工作负载身份、RFC 8693 令牌交换、ID-JAG 跨应用访问、AuthZEN 授权。这些不是「顺便支持」,是专门投入。
原因是清楚的:Agent 场景的核心难题从来不是路由,是「这个 Agent 以什么身份、代表谁、能调哪些工具」。 一个只解决路由的网关,在 Agent 架构里会很快遇到天花板。
对正在评估 AI 网关的团队,我的建议是:如果你的 MCP 工具调用和 Agent 间协作已经是核心流量形态,值得在非关键链路上跑一个 PoC,重点验证三件事——多副本下的 MCP 会话恢复(用自包含令牌方案,不要指望 consistent hash)、Guardrails 的实际覆盖度(六种后端里挑你需要的验一遍)、与现有 K8s Gateway API 的集成体验。如果场景仍以 LLM 补全为主,LiteLLM 的成熟生态仍是更稳的起点。
两条路线最终可能在「Agent 网关」这一层汇合。而 agentgateway 目前在这个汇合点上走得最远——但它走的是一条主动划边界的路线,这一点在选型时比功能清单更值得看。
参考链接
- 项目官网与文档:https://agentgateway.dev
- GitHub 仓库:https://github.com/agentgateway/agentgateway
- Linux Foundation 加入公告:https://www.linuxfoundation.org/press/linux-foundation-welcomes-agentgateway-project-to-accelerate-ai-agent-adoption-while-maintaining-security-observability-and-governance
- 独立评测(dev.to/spacewander):https://dev.to/spacewander/agentgateway-review-a-feature-rich-new-ai-gateway-53lm
- 网关选型对比(liquidreply.net):https://liquidreply.net/blog/the-ai-gateway-landscape-agentgateway-litellm
相关阅读
- MCP 沙箱路由框架分析——Agent 工具调用的另一层:沙箱侧的控制面/数据面与句柄机制
- 从冯诺依曼到 Agent 沙箱:虚拟化隔离技术栈拆解——隔离谱系(容器 < MicroVM < VM)与选型依据
- MCP 安全与 Prompt Injection 攻防实战——工具调用的威胁模型,与本文的授权栈互为补充
- Agent 沙盒基础设施实践:从 DSec 与 AgentENV 看调度层的选型与落地——调度层视角的 Agent 执行底座
核实说明:本文所有代码级结论基于本地仓库
v1.7.0-71-g134897e1(HEAD134897e1),核对时间为 2026-11-04。项目迭代很快,具体行为请以你手上的版本为准。性能数据为厂商自测,已在正文标注。
