一句话结论: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 拓扑架构

图: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
2
3
4
5
6
7
8
9
10
11
12
13
14
message Resource {
oneof kind {
Bind bind = 1;
Listener listener = 2;
Route route = 3;
Backend backend = 4;
Policy policy = 5;
TCPRoute tcp_route = 6;
istio.workload.Workload workload = 7;
istio.workload.Service service = 8;
RouteGroup route_group = 9;
ModelRoute model_route = 10;
}
}

这里有个细节值得记下来:正式类型名是单数,而且是十个不是五个。很多二手资料(包括一些对比文章)写成 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
2
3
4
pub struct Stores {
pub discovery: discovery::StoreUpdater, // workload / service
pub binds: binds::StoreUpdater, // 路由 / 监听 / 策略 / 后端
}

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 使用 agentgateway GatewayClass 的 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
2
3
4
5
6
7
8
9
10
pub enum AIProvider {
OpenAI(openai::Provider),
Gemini(gemini::Provider),
Vertex(vertex::Provider),
Anthropic(anthropic::Provider),
Bedrock(BedrockProvider),
Azure(AzureProvider),
Copilot(copilot::Provider),
Custom(custom::Provider),
}

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
2
3
4
5
6
7
pub struct Request {
pub messages: ...,
pub model: ...,
// ... 显式列出的十几个字段
#[serde(flatten, default)]
pub rest: serde_json::Value, // ← 其余全部原样透传
}

设计原则写在 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
2
3
4
5
6
7
8
9
10
pub enum CacheTokenConvention { InputIncludesCache, InputExcludesCache }

pub fn include_cache_tokens(self, input_tokens, cached_input_tokens, cache_creation_input_tokens) -> u64 {
match self {
Self::InputIncludesCache => input_tokens,
Self::InputExcludesCache => input_tokens
.saturating_add(cached_input_tokens.unwrap_or_default())
.saturating_add(cache_creation_input_tokens.unwrap_or_default()),
}
}

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
2
pub enum BudgetLimitUnit { Usd, Tokens }
pub enum BudgetExceededAction { Audit, Block }

配置形如 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
2
3
4
const DELIMITER: &str = "_";
fn resource_name(prefix_names: bool, target: &str, name: &str) -> String {
if prefix_names { format!("{target}{DELIMITER}{name}") } else { name.to_string() }
}

但资源 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
2
3
4
5
pub struct SessionManager {
encoder: http::sessionpersistence::Encoder,
sessions: Arc<RwLock<HashMap<String, SessionEntry>>>,
idle_reaper: OnceLock<tokio::task::AbortHandle>,
}

内存 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
2
policies:
a2a: {}

对应 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 目前在这个汇合点上走得最远——但它走的是一条主动划边界的路线,这一点在选型时比功能清单更值得看。


参考链接

相关阅读

核实说明:本文所有代码级结论基于本地仓库 v1.7.0-71-g134897e1(HEAD 134897e1),核对时间为 2026-11-04。项目迭代很快,具体行为请以你手上的版本为准。性能数据为厂商自测,已在正文标注。