首页
/ Headroom Phase F 深度解析:PAYG / OAuth / Subscription 三模式认证分类与逐模式压缩策略门控

Headroom Phase F 深度解析:PAYG / OAuth / Subscription 三模式认证分类与逐模式压缩策略门控

2026-09-06 23:14:11作者:董宙帆

Headroom 的 Phase F 将「认证模式」提升为一等策略维度:在每个请求进入代理入口时,先通过一个纯函数分类器判定调用方属于按量付费(PAYG)、OAuth 固定额度(OAuth)还是订阅制 CLI(Subscription)三类中的哪一种,再据此门控压缩行为、请求头注入与 TOIN 遥测聚合。读完本文,你将掌握三模式分类器的完整判定规则与源码实现、逐模式压缩策略矩阵的设计原理(为何订阅用户只允许无损压缩、为何要跳过 X-Forwarded-* 头),以及支撑该机制的 Rust/Python 双端一致性测试体系,从而理解一个 LLM 压缩代理如何在不破坏订阅客户端指纹的前提下安全省钱。

一、Phase F 的设计目标:为什么认证模式必须成为策略轴

Headroom 是一个压缩工具输出、日志、文件与 RAG 片段的中间层代理,典型部署方式是让 Claude Code、Codex、Cursor 等编码代理经由它转发到上游模型 API。不同计费方式下的调用方,对代理「能动什么」的容忍度截然不同:

  • PAYG(按量付费):调用方按 token 付费,激进压缩直接省钱,可以放开所有优化;
  • OAuth(固定额度):调用方使用订阅/IAM 计划(Claude Pro OAuth、Codex Enterprise、Cursor Pro、Bedrock IAM 签名、Vertex ADC),单 token 成本对其不透明,缓存安全性优先——OAuth 作用域绑定 (account, model, session),beta 头漂移会导致凭证失效;
  • Subscription(订阅 CLI):调用方是 UX 绑定的 CLI/IDE(Claude Code、Cursor、Copilot、Antigravity 等),提供方按请求数限流,且会做程序化指纹检测——代理必须「看起来像」上游代理本身:保留 User-Agent、绝不注入 X-Headroom-* 内部头、绝不剥掉 accept-encoding

Phase F 的目标文档见 REALIGNMENT/08-phase-F-auth-mode.md,规划为 1 周、4 个 PR:F1 先行落地分类器,F2/F3/F4 在其后并行推进,目标是消除 P5-52~P5-56 五个已知问题,显著降低订阅 CLI 用户的指纹风险。

二、PR-F1:classify_auth_mode 分类器——三模式的判定规则

2.1 判定顺序:最具体信号优先

分类器是一个纯函数:输入 http::HeaderMap(或 Python 侧的 header 映射),输出 AuthMode = Payg | OAuth | Subscription。Rust 实现位于 crates/headroom-core/src/auth_mode.rs,判定顺序(越具体的信号越先命中):

  1. Subscription UA 前缀Subscription。这是最具体信号:同一个 sk-ant-oat* OAuth token 既出现在 Claude Pro(Web)也出现在 Claude Code(CLI)中,只有 User-Agent 能区分二者。CLI 自身的认证模式优先于它碰巧携带的 bearer token 形态;
  2. Authorization: Bearer sk-ant-oat*OAuth(Claude Pro / Max OAuth)。必须排在更宽泛的 sk- PAYG 规则之前,因为 sk-ant-oatsk-ant-api 共享 sk- 前缀;
  3. Bearer sk-ant-api* 或任意 Bearer sk-*Payg(Anthropic / OpenAI API key);
  4. Bearer <jwt>(三段式,以 . 分隔)OAuth(Codex / Cursor / Copilot OAuth)。实现只数分段、不校验 JWT;
  5. 存在 Authorization 但非 Bearer ...OAuth(Bedrock 的 AWS SigV4 AWS4-HMAC-SHA256 ... 即属此类;任何其他非 Bearer 方案也按透传优先处理);
  6. x-api-key 存在Payg(Anthropic API key 风格);
  7. x-goog-api-key 存在Payg(Gemini key);
  8. 默认Payg。默认选择 PAYG 的理由是:误把非 PAYG 客户端当 PAYG,最多多压缩一次;而把 PAYG 客户端漏判成保守模式,则是白白少省钱——对开源默认用户后者更糟。

Subscription 的 UA 前缀表在 crates/headroom-core/src/auth_mode.rs#L78-L87 中作为模块级常量维护(claude-cli/claude-code/codex-cli/cursor/claude-vscode/github-copilot/anthropic-cli/antigravity/)。从源码结构看,把它放在模块作用域而非函数内部有三重考虑:编译器可将切片常量折叠进只读段;后续 PR 可把它换成可配置列表而无需改函数体;新增客户端只需一行编辑。匹配用 str::contains 而非前缀匹配,因为部分 Anthropic CLI 会在自己的 UA 前拼接父代理的 UA。Python 侧的实现 headroom/proxy/auth_policy.py 在同一前缀表基础上还多了 grok/ 一项,并额外提供 CLIENT_UA_MAP 用于识别客户端 harness(claude-code、codex、cursor、zed、aider、droid、opencode、copilot、antigravity、strands 等)。

2.2 Rust 实现的健壮性细节

crates/headroom-core/src/auth_mode.rs#L121-L210classify 函数有若干值得注意的工程决策:

  • 从不 panic:非 UTF-8 的 User-AgentAuthorization 头只触发一条 tracing::warn! 事件(auth_mode_classify_unparseable_user_agent / auth_mode_classify_unparseable_authorization),随后落入安全默认 Payg,运维可从日志流中发现异常客户端而不影响代理存活;
  • 不记录头值to_str 返回与 HeaderMap 同生命周期的 &str,Authorization 值零拷贝读取且绝不进日志;
  • 性能预算:仅一次 UA 小写化的 String 分配,其余全部是零分配的 starts_with / contains / split('.').count(),验收标准要求单次调用 <10us,基准测试位于 crates/headroom-core/benches/auth_mode.rs
  • 稳定字符串格式AuthMode::as_str() 输出 "payg" / "oauth" / "subscription",这是与 Python 端共享的 wire 格式——Python 一致性测试直接断言这些字符串(见 tests/test_auth_mode.py#L161-L165)。

枚举本身派生 Copy + Hash + Eq,注释解释了动机:Copy 是因为该值会在每个请求中穿过几十个压缩决策点,克隆一个 1 字节枚举比跨 await 点持有引用更便宜;Hash 则是为了 Phase F PR-F3 中按租户划分 TOIN 聚合 map。

2.3 双端一致性测试矩阵

Python 移植版位于 headroom/proxy/auth_mode.pyclassify_auth_mode 委托给 auth_policy.py 中的纯函数 classify_auth_signals。测试矩阵 tests/test_auth_mode.py 逐条镜像 Rust 侧用例,两端的约定是「必须对每个被覆盖的 header 集合给出相同分类,任何分歧都是 bug」。核心用例包括:

场景 输入示例 期望结果
Anthropic PAYG Authorization: Bearer sk-ant-api03-... PAYG
Codex/Cursor OAuth JWT Bearer eyJ...xxx.signature(三段式) OAUTH
Claude Pro OAuth Bearer sk-ant-oat-01-... OAUTH
真实 OAuth token 形态 Bearer sk-ant-oat01-...(无 oat 后的连字符) OAUTH
Claude Code CLI User-Agent: claude-code/1.2.3 (darwin; arm64) SUBSCRIPTION
Cursor CLI User-Agent: cursor/1.0 SUBSCRIPTION
空 header {} PAYG(安全默认)
Bedrock SigV4 Authorization: AWS4-HMAC-SHA256 Credential=... OAUTH

其中两个用例暴露过真实缺陷,值得注意:

  • test_oauth_real_sk_ant_oat01_classified_oauth:真实 Anthropic OAuth access token 是 sk-ant-oat01-...(版本号紧跟、oat 后无连字符)。早期只匹配 sk-ant-oat- 的写法会让它们漏过、落入 PAYG,从而在订阅请求上启用激进有损压缩;
  • test_subscription_takes_precedence_over_oauth_token:Claude Code 同时携带 claude-code/... UA 和 Bearer sk-ant-oat-... token,UA 必须胜出——限流/指纹策略绑定在 CLI 上,而非 token 形态。同一 token 走非 CLI UA 时则应分类为 OAuth。

此外还有防御性用例:非 UTF-8 的 bytes 值 Authorization 头不抛异常且落回 PAYG、header 名大小写不敏感匹配、对每个 UA 前缀参数化的独立分类、以及单次调用 <100us 的 Python 侧性能冒烟(Rust 预算为 10us,Python 因字符串开销放宽一个数量级)。

2.4 请求入口接线

Rust 代理在 crates/headroom-proxy/src/proxy.rs#L419-L480 完成接线:请求进入时调用一次 classify_auth_mode(req.headers()),把结果插入 axum request extensions 供下游各 handler 读取而无需重复分类;同时按 CompressionPolicy::for_mode(auth_mode) 派生策略对象并存入 extensions。入口日志事件 auth_mode_classified 携带 auth_mode 字段与 request_id,可与后续各阶段的结构化日志按 auth_mode + request_id 关联。Bedrock 路径则通过 crate::bedrock::classify_and_attach_auth_mode 单独附着(Bedrock 客户端不带 Authorization 头、签名发生在上游,见 crates/headroom-proxy/src/bedrock/auth_mode_layer.rs)。Python 侧在 headroom/proxy/handlers/anthropic.pyopenai.py 的请求入口做同样的调用。

三、PR-F2:逐模式压缩策略门控——从分类到策略结构体

3.1 策略矩阵

Phase F 的策略矩阵(见 REALIGNMENT/02-architecture.md §2.4)规定:

  • PAYG = aggressive(当前默认行为):全部压缩能力放开;
  • OAuth = passthrough-prefer:不自动放置 cache_control 断点、不自动注入 prompt_cache_key、不使用有损压缩器,只允许无损压缩;
  • Subscription = stealth:OAuth 的一切约束,外加保留 accept-encoding(绝不剥离)、绝不注入 X-Headroom-*、绝不改动 User-Agent

3.2 CompressionPolicy 结构体:集中式策略而非散落的 match

策略的落地实现是 crates/headroom-core/src/compression_policy.rs 中的 CompressionPolicy::for_mode(AuthMode) -> CompressionPolicy。源码注释明确解释了为什么用结构体而不是在每处门控点写 match auth_mode:一是集中化——没有结构体时,逐模式判定会在 E3 cache_control、E4 prompt_cache_key、live-zone 门控、cache_aligner 门控等处重复,调整策略要满仓库找调用点;二是测试面——for_mode 是纯函数,可以直接对结构体做属性测试,而不用搭完整请求 fixture。

当前各模式取值(源码注释中标注为 CONSERVATIVE 默认值,待 bake 遥测后调优):

模式 live_zone_only cache_aligner_enabled volatile_token_threshold max_lossy_ratio toin_read_only
Payg false true 128 0.45 false
OAuth false true(同 PAYG) 128(同 PAYG) 0.45(同 PAYG) false(同 PAYG)
Subscription true false 32 0.25 true

各字段语义(见 crates/headroom-core/src/compression_policy.rs#L27-L78 的模块文档):

  • live_zone_only:为 true 时下游变换不得改动缓存标记之后的 live zone 之外的字节。Rust 调度器本身按构造就是 live-zone-only,该字段主要约束 Python TransformPipelineCacheAligner / ContentRouter 门控,同时保证跨语言一致性测试对字段映射的诚实性;
  • cache_aligner_enabled:为 false 时 Python CacheAlignershould_apply 必须返回 False。注释指出 CacheAligner 是缓存不稳定投诉的负载级修复点——它历史上会改动已缓存前缀并按 pipeline 实例写 _previous_prefix_hash,正是这一点破坏了订阅用户的 prompt cache。对 Subscription 禁用它是 F2.1 的用户可见收益;
  • volatile_token_threshold:低于该 token 数的内容视为缓存稳定。Subscription 保守(32,更早标记 volatile → 保持 prompt 稳定),PAYG 激进(128,容忍更多噪音)。源码明确说明该字段在 F2.2 处于「已接线但未被消费」状态——当前 cache_aligner.py 里的 volatile 检测器是基于形状而非 token 计数,接线需要超出本次范围的检测器重构;
  • max_lossy_ratio:有损压缩可丢弃原始 token 的比例上限(0.0 为禁止、1.0 为不限制)。PAYG 上限 0.45,Subscription 上限 0.25。同样标注为已接线未消费,区别于 Python ContentRouter 中调用方驱动的 target_ratio 参数;
  • toin_read_only:为 true 时 TOIN 只提供缓存建议、不从此请求写新的模式观察。订阅流量为 prompt-cache 稳定性买单,其压缩事件不应污染全局学习池;PAYG/OAuth 继续写入以维持网络效应。

结构体上还实现了净收益公式(#856):net_mutation_gain(delta_t, suffix_tokens, expected_reads, p_alive) 用 Anthropic 缓存定价常量(写 1.25×、1 小时档 2.0×、读 0.1×,见 crates/headroom-core/src/compression_policy.rs#L133-L144)计算「改动某条消息、使其后所有缓存失效」的期望净收益,配套 break_even_reads 给出盈亏平衡读次数 R = ((w−r)/r)·S/ΔT = 11.5·S/ΔT(5 分钟档)。例如 2K 剪裁落在 50K 热后缀下需要 287.5 次剩余读取才回本(几乎不划算),而 50K 剪裁落在 10K 后缀下 2.3 次读取即回本。tests/test_compression_policy.py 以相同数值断言 Python 镜像,两端任何漂移都会同时触发一致性测试对。

门控测试以 #[test] oauth_matches_payg_today 作为金丝雀:一旦未来 PR 让 OAuth 与 PAYG 分叉,该断言立即失败,强制显式更新——这正是设计意图。订阅侧的门控测试则断言:live_zone_only 为真、cache_aligner_enabled 为假,但 live_zone_compression_enabled() 仍为真——源码注释强调「关闭缓存投诉绝不等于零压缩」,订阅用户依然保留 live-zone 压缩。

3.3 门控的具体落点

按 Phase F 文档,F2 需要修改的位置包括:Rust 的 live_zone_anthropic.rsauto_place_breakpoints 仅在 auth_mode == Payg 时执行)、live_zone_openai.rsinject_prompt_cache_key 同门控)、live_zone.rs(有损压缩器仅在 PAYG 下启用,OAuth/Subscription 只走无损路径)、headers.rs(见下文 PR-F4)、以及 proxy.rsaccept-encoding 剥离改为条件化(Subscription 除外);Python 侧 headroom/proxy/handlers/anthropic.pyopenai.py 做镜像门控。crates/headroom-proxy/src/compression/live_zone_anthropic.rslive_zone_openai.rslive_zone_responses.rs 均引用 AuthMode,可确认三端 live-zone 模块都已接入模式参数。验收标准包含一条人工冒烟:真实 Claude Code 会话穿过代理后,上游请求不含任何 X-Forwarded-*,且 accept-encoding 被保留。

四、PR-F3:TOIN 按租户聚合键与订阅凭证加固

TOIN(Token Optimization INsights,headroom/telemetry/toin.py)原本按 structure_hash 做全局聚合。F3 将聚合键扩展为 (auth_mode, model_family, structure_hash) 三元组:

  • Pattern 数据结构新增 auth_mode: str = "unknown"model_family: str = "unknown" 字段;
  • 聚合键改为元组,内存字典按新键重排;
  • 旧观察数据在 legacy/ 前缀下保留并迁移到 ("unknown", "unknown", structure_hash),在 30 天内逐步废弃,实现优雅降级;
  • 聚合键变更会使早期推荐失效,需通过部署 CLI 重新发布;
  • 推荐文件结构化:按 (auth_mode, model_family) 分节的 recommendations.toml

与认证模式直接相关的另一个加固点在 headroom/subscription/tracker.py:把内存中的 _current_token: str(原始 OAuth bearer 存储)替换为 _current_token_id: str(单向哈希 + 末 4 位用于调试),轮询代码改为每次请求使用真实的 Authorization 头而非存储副本,消除 token 泄漏面。对应测试:tests/test_toin_per_tenant.py::test_aggregation_key_includes_auth_mode_modeltest_legacy_observations_preserved_under_unknowntest_publish_per_auth_mode_writes_separate_recommendations,以及 tests/test_subscription_tracker_token_hardening.py::test_raw_token_not_stored_in_memory(仓库测试目录中 tests/test_subscription_tracker.py 等订阅跟踪测试覆盖了 tracker 主体行为)。

从聚合维度看,这条改动的意义在于:不同认证模式下同一请求结构的压缩收益/风险特征不同(订阅流量必须保缓存稳定,PAYG 流量追求最大节省),混在一起聚合会让推荐值互相污染——这正是 Phase F 把 auth mode 作为策略轴的同一逻辑在遥测层的延伸。

五、PR-F4:X-Forwarded-* 注入按模式条件化

Rust 代理的转发行头构建逻辑位于 crates/headroom-proxy/src/headers.rsbuild_forward_request_headerscrates/headroom-proxy/src/headers.rs#L132-L169)负责:剥离 hop-by-hop 头(RFC 7230 §6.1)、移除 Connection: 列出的头与客户端托管头(hostcontent-length)、追加 X-Forwarded-For、设置 X-Forwarded-Proto / X-Forwarded-Host、确保 X-Request-Id,并在 strip_internal == true 时剥掉 x-headroom-* 前缀头(INTERNAL_HEADER_PREFIX,大小写不敏感匹配)。

F4 的改动是把这个「总是注入」行为按模式条件化:PAYG → 注入;OAuth → 注入;Subscription → 跳过。风险在于程序化指纹检测——上游提供方一旦在订阅流量上看到代理附加的 X-Forwarded-* 痕迹,就可能识别出非官方客户端路径。相关单测已覆盖内部头剥离(strip_internal_headers_removes_only_internal_prefixbuild_forward_strips_internal_when_enabled / build_forward_keeps_internal_when_disabled,见 crates/headroom-proxy/src/headers.rs#L241-L287),模式条件化的集成测试规划在 crates/headroom-proxy/tests/integration_x_forwarded_authmode.rspayg_adds_xfwdoauth_adds_xfwdsubscription_no_xfwd)。Python 侧对应策略模块 headroom/proxy/forwarded_policy.py 与测试 tests/test_forwarded_policy.py 覆盖同源行为。

六、阶段验收清单与回滚策略

四个 PR 全部落地后,Phase F 的验收清单(源自 REALIGNMENT/08-phase-F-auth-mode.md 的 acceptance summary):

  • classify_auth_mode 辅助函数可检测 PAYG / OAuth / Subscription;
  • 逐模式压缩策略门控就位(auto-cache_controlprompt_cache_key、有损压缩器);
  • TOIN 聚合键按 (auth_mode, model_family, structure_hash) 划分;
  • 订阅 tracker 不再存储原始 OAuth bearer;
  • Subscription 模式跳过 X-Forwarded-*
  • Subscription 模式保留 accept-encoding

每个 PR 的回滚策略都是 git revert 且语义清晰:F1/F2/F4 回滚后所有请求按 PAYG 处理(即 Phase F 之前的现状,PAYG 用户无功能回归,OAuth/Subscription 用户可能看到 scope 拒绝或吊销风险回升);F3 回滚后 TOIN 退回全局聚合,无功能破坏。

七、小结:模式分类作为策略原语的设计模式

Phase F 的核心价值在于把一个「请求元数据判定」沉淀成了贯穿压缩、缓存、头处理与遥测的策略原语,其实现有三个可复用的工程范式:

  1. 纯函数分类 + 最具体信号优先 + 保守默认:分类器无 I/O、不 panic、<10us,把判定复杂度集中在一个可穷举测试的函数里,而不是散落在各 handler 中;
  2. 策略结构体替代散落 matchCompressionPolicy::for_mode 让逐模式调参收敛到单点,配合金丝雀测试(如 oauth_matches_payg_today)使未来有意的策略分叉无法「静默发生」;
  3. 双端一致性契约:Rust 与 Python 实现共享同一测试矩阵和 wire 字符串格式(payg / oauth / subscription),在 Python 代理路径尚未退役(Phase H)之前保证行为等价。

对运维者的实际意义:走 API key 的按量客户端获得最大压缩收益;走 OAuth/Bedrock/Vertex 的客户端得到缓存安全优先的透传保守行为;跑 Claude Code / Cursor 等订阅 CLI 的用户则在几乎不可感知的「隐身」模式下工作——压缩继续发生(live-zone 无损路径),但前缀不被改写、内部头不外泄、指纹风险被系统性消除。

登录后查看全文
热门项目推荐
相关项目推荐