- API网关
- LLM 网关
- 后端
【免费下载链接】ccx
Claude / Codex / Gemini API Proxy - CCX
本文是 CCX(Claude / Codex / Gemini API Proxy)竞速模式(Racing)的深度技术解析。竞速模式在首字明显慢的渠道(如中转站排队)触发跨渠道影子请求(hedging):主渠道慢时并行派出影子请求,谁先交付有效响应,客户端就用谁的。读完本文,你将掌握竞速模式的两个配置开关、CostPreference 策略表、自适应阈值推导、影子候选五元组选取、claim-once 提交闸门与分支写出隔离的裁决原理,以及延迟负反馈学习的完整闭环。
一、机制概述:从"排队等待"到"谁快用谁"
在真实的多渠道网关场景中,渠道质量波动是常态:某个模型家族的首字延迟可能因为上游中转站排队而急剧恶化,而客户端无法感知、只能干等。CCX 竞速模式的解决思路来自对冲思想——参照兄弟项目 kiro.rs 的对冲竞速(hedging):主请求等待超过自适应阈值时,向候选渠道并行派发影子请求,谁先交付有效响应,客户端就透传谁的。
竞速模式复用 CCX preflight 缓冲的"未提交可弃"窗口作为裁决点:在响应真正写入客户端之前,各分支的响应都处于可丢弃状态,只有胜者完成提交。从源码结构看,其核心实现分布在:
| 模块 | 职责 |
|---|---|
| backend-go/internal/racing/ | 阈值注册表、Gate 提交闸门、全局并发信号量、策略表、ErrRacingSuperseded |
| backend-go/internal/config/racing_config.go | 全局/渠道级开关 +ResolveRacingPolicy+SetRacingEnabled |
| backend-go/internal/handlers/common/racing.go | 编排器RunRacingAttempt(武装检查/定时器/影子派发/裁决归并/样本记录) |
| backend-go/internal/handlers/common/racing_writer.go | 分支 writer:pre-commit 私有缓冲、赢家 Commit 桥接真实 writer、败者 Discard |
| backend-go/internal/autopilot/racing_candidates.go | 五元组影子候选选取 |
| backend-go/main.go | RacingHub 注入 +/api/racing/config |
二、用户可见面(极简设计):只有两个开关
竞速模式的核心理念是极简配置面:用户只控制开关,其余行为参数(影子数、触发下限、候选成本过滤)全部由策略自动推导,不暴露为配置项。这一点在 backend-go/internal/config/racing_config.go 的源码注释中明确写明。
2.1 全局开关racing.enabled
| 开关 | 位置 | 语义 |
|---|---|---|
racing.enabled(全局) | 智能路由面板「竞速模式」开关(随PUT /smart-routing/config整卡保存,字段名racingEnabled;另有独立端点PUT /api/racing/config供脚本直调) | 竞速总开关,默认关 |
全局开关的底层实现由GlobalRacingConfig.Enabled承载(nil 视为默认开启),管理接口通过SetRacingEnabled更新并持久化,同时fireConfigChangeCallbacks通知运行时刷新(backend-go/internal/config/racing_config.go)。
2.2 渠道级开关racing.enabled
| 开关 | 位置 | 语义 |
|---|---|---|
racing.enabled(渠道级) | 渠道编辑 → 自定义参数 | 渠道参与开关:关闭 = 既不做主触发,其候选也不进影子池 |
渠道级配置由ChannelRacingConfig.Enabled承载(nil 时继承全局)。最终生效策略由ResolveRacingPolicy按渠道级字段 > 全局字段 > 默认开启的优先级解析(backend-go/internal/config/racing_config.go)。该解析在武装检查(shouldArmRacing)与影子候选构建(buildSelectionFromCandidate、调度器回退路径)三处被调用,保证关闭渠道既不会触发竞速、其候选也不会被选中。
2.3 武装条件(何时才会进入竞速流程)
从 backend-go/internal/handlers/common/racing.go 的shouldArmRacing可以确认竞速只武装四类对话协议(messages/chat/responses/gemini),并有三类请求明确不竞速:
- 显式渠道 pin:请求头
X-Channel非空时,渠道选择是用户意图,不做竞速; - 含图请求:影子需要整包重传图片,v1 不竞速;
- 竞速未武装时:编排器
RunRacingAttempt退化为直接调用渠道尝试闭包,路径零开销。
三、策略表:CostPreference 决定影子数与触发时机
竞速行为参数由请求生效的CostPreference推导,其解析优先级为:请求头X-Cost-Preference> 场景预设(X-Routing-Scenario)> 全局配置 Mode,未知值回退balanced(backend-go/internal/handlers/common/racing.go)。
策略表为代码内常量,定义于 backend-go/internal/racing/registry.go 的BehaviorForCostPreference:
| CostPreference | 影子数 | 流式触发 floor | 影子候选过滤 |
|---|---|---|---|
quality_first | 3 | 4s | 无限制 |
balanced(默认) | 1 | 8s | 无限制 |
cost_first | 1 | 8s | 仅综合倍率 ≤ 主候选一半(渠道 CostMultiplier × key GroupMultiplier);无合适候选不派 |
从源码注释可以看到 floor 档位的演进脉络:早期 2s/3s/5s 会对"慢而真"的渠道(如 ark kimi-k3 真实首字 2-4s)系统性误判慢,且家族分位数窗口混入快而差的中转假模型首字后 p90 被拉低、2s 档形同虚设,成为抢闸交付伪工具标记文本的根因之一;floor 上调后 clamp 兜底两类污染。行为推导的正确性由 backend-go/internal/racing/registry_test.go 的TestBehaviorForCostPreference固化(如quality_first → 3 影子/4s floor、cost_first → 1 影子/8s floor/cheap-only、空值与未知值按balanced)。
其余内部常量同样定义在 backend-go/internal/racing/registry.go:
| 常量 | 值 | 语义 |
|---|---|---|
| 分位数目标 | p90 | percentileTarget = 90 |
| 样本门槛 | 20 | 低于该数回退 floor(sampleFloor) |
| 观测窗口 | 15 分钟 | windowRetention,滚动淘汰过期样本 |
| 窗口容量 | 512 | maxSamplesPerWindow,超限淘汰最旧 |
| 全局并发影子信号量 | 12 | maxConcurrentShadows,防主渠道系统性事故时影子堆积放大 |
| 非流式 floor | 10s | NonStreamFloorMs,全策略统一 |
3.1 隐藏推理阶段的保护下限
带隐藏推理阶段的流式请求(请求画像ReasoningNeed=true)使用更高的首内容保护下限,避免在模型正常思考期间过早误判"慢渠道":
- 未声明 effort:24s;
low/minimal:16s;medium:24s;high/xhigh/max/ultra:30s;none/off:保持普通请求 floor。
该逻辑实现于 backend-go/internal/racing/registry.go 的StreamFloorForReasoning,并且该下限仍受渠道StreamFirstContentTimeoutMsceiling 裁剪(if floorMs > ceilingMs { floorMs = ceilingMs },见编排器),避免把正常推理等待误判为慢渠道。测试 backend-go/internal/racing/registry_test.go 验证了各 effort 档位的下限值,以及"已有更高 floor 时不应降低"的语义(StreamFloorForReasoning(35_000, "low") = 35_000)。
四、触发阈值:p90 自适应 + floor/ceiling 双裁剪
竞速触发的核心公式:
threshold = p90(近 15 分钟同家族×阶段成功样本, n≥20) 否则 floor, 再 clamp[floor, ceiling]- 维度:模型家族(claude/gpt/gemini/other)× 阶段(流式首字 / 非流式完成时长),家族全局共用一个窗口。家族归类实现于
FamilyForModel(backend-go/internal/racing/registry.go):claude/sonnet/opus/haiku/fable归 Claude 家族,gpt/o1/o3/o4/codex归 GPT 家族,gemini前缀归 Gemini 家族,其余(含 deepseek、kimi)归 other。同家族首字延迟特征接近,按家族分窗可加速样本积累;这也意味着慢性慢渠道持续超 p90 会被持续竞速。 - ceiling:流式 = 渠道
StreamFirstContentTimeoutMs解析值;非流式 = 渠道ResponseHeaderTimeout(同源超时,等过它请求已失败)。实现上流式 ceiling 由racingStreamCeilingMs取渠道与全局熔断配置中的首字超时(backend-go/internal/handlers/common/racing.go),非流式 ceiling 由racingNonStreamCeilingMs取渠道有效响应头超时。 - 样本消赢家偏差:流式分支出过首字即记(含被取消的败者);非流式仅记赢家完成耗时。对应实现
recordRacingStreamSample与recordRacingNonStreamSample(backend-go/internal/handlers/common/racing.go)。 - 计时起点= 外壳
attemptStartedAt(含渠道内 key 轮转等待,符合用户体感;与 kiro 的 attempt 级重置刻意不同)。
阈值注册表实现于 backend-go/internal/racing/registry.go 的Registry:每个(family, stage)键维护一个 15 分钟滚动环形样本窗口(sampleRing,按时间升序、超容量淘汰最旧、过期样本 prune 淘汰),ThresholdMs在样本不足时回退 floor,否则取排序后 p90,再 clamp 到[floorMs, ceilingMs]。测试 backend-go/internal/racing/registry_test.go 完整验证了四类边界:样本不足回退 floor、p90 低于 floor 向上 clamp、p90 超过 ceiling 向下 clamp、窗口过期后重新回退 floor,以及家族×阶段窗口隔离与容量封顶。
五、影子候选:五元组粒度选取
影子候选以五元组(channelUID + keyIdentity + actualModel)为粒度,选取分两条路径:
主源(SmartRouter 排名缓存):
autopilot.RacingShadowCandidates从可行集(Selected=true)中选取,并排除与主尝试相同的五元组——同渠道不同 key / 不同执行模型可作影子。选取纪律在 backend-go/internal/autopilot/racing_candidates.go 中实现:- 主 key 一律不作影子:同一明文 key 跨渠道重复配置时,与主分支并发同账号请求是放大器(限速/空流);
- 异渠道候选优先:同渠道行一律不作影子(同 provider 同队列的重复消耗,key 级对冲已由 attempt 内部轮转覆盖);
- 多影子按 keyIdentity 去重:同 key 只取一行,保持排名原序。
回退:缓存为空或无可选时,
SelectChannelWithOptions按FailedRoutes(已用路由)重选(纯跨渠道)。该回退同样应用渠道去重、竞速开关、限流热渠道过滤与成本过滤(backend-go/internal/handlers/common/racing.go)。执行:候选反查渠道构造
SelectionResult+ pin(WithExecutionPin经WithSelectionTrace透传),reason 记racing_shadow(backend-go/internal/handlers/common/racing.go)。
影子构建出口还有两道关键约束:
- 工具调用白名单路由排他:带工具定义的请求,影子不从非白名单路由派(伪工具标记方言的根治约束);该协议无白名单时 fail-open,但"连续伪标记 miss 达阈值"的已知劣化组合仍不派(
toolWhitelistAllows)。 - 限流热渠道不派影子:渠道级冷却中或全部 key scope 被限速延迟时该候选被跳过——排名缓存路径绕过调度器冷却过滤,候选构造处兜底,防止 429 风暴中 shadow 放大真实上游消耗(
IsChannelRateLimitHot检查)。
此外,影子使用自己的完整 ceiling 超时窗口(从影子发出起算),不共享主链剩余 deadline。
六、裁决与分支治理:claim-once 闸门 + 单写者分支隔离
6.1 提交闸门racing.Gate(claim-once)
裁决点是竞速正确性的核心。claim 点在 preflight 首字确认后 / 非流式完整响应校验后、写客户端之前;胜者 claim 即取消其余分支(ctx 级),败者 claim 失败以ErrRacingSuperseded收尾。
Gate实现于 backend-go/internal/racing/gate.go:claimed/winner用 atomic 存储保证并发安全查询,ClaimBy(ownerID)通过 CAS 实现首次调用返回 true 并取消其余分支的 claim-once 语义;CancelExcept(ownerID)则主动取消除赢家外的全部分支。测试 backend-go/internal/racing/registry_test.go 验证了串行 claim-once 与 32 个并发分支下唯一赢家的不变量。ErrRacingSuperseded在失败分类链中必须先于isClientSideError拦截:不计渠道失败、不触发熔断/Key 拉黑/自学习,仅完成渠道日志终态。
主分支返回后的编排器止血只取消非赢家分支(CancelExcept(ClaimedBy())):影子已 claim 时必须保留赢家透传(固定CancelExcept(0)会把赢家一并取消、客户端流被切断 context canceled——这是 248deb01 的回归修复);无 claim(主真实失败)时保留在飞影子,由结算路径兜底接管。
6.2 分支写出隔离racingBranchWriter
主/影子分支各挂独立分支 writer(backend-go/internal/handlers/common/racing_writer.go):
- pre-commit 阶段:头/状态码/体写私有缓冲(
Write返回len(p), nil,不干扰上游流读取); - claim 赢家
Commit:一次性回放私有响应头与已缓冲状态/体到真实客户端 writer,之后全部操作转为透传; - 败者
Discard:丢弃缓冲,后续写出操作静默无操作。
其不变量由构造保证:真实 writer 只被 claim 赢家触碰,分支间不共享响应头 map——取代早期"影子回填主 Writer + meta 锁串行化 echo 头"的约定式模型(后者是 2026-09-08 双 panic 事故的临时修复形态)。主分支同样经分支 writer 写出(编排器中c.Writer = newRacingBranchWriter(origWriter)),保证真实客户端 writer 全程只被赢家触碰。
6.3 败者治理:静默失败,不污染健康度
败者不计失败指标、不熔断、不拉黑、不标 URL 失败、不参与自学习(工具调用/严重度/上下文棘轮)。唯一例外是质量闸门让出——伪标记已在败者分支自身首包缓冲实测命中(已完成观察,非部分流推断),让出时单独计一次白名单负反馈(notePseudoToolCallYield,见 docs/specs/tool-call-capability.md)。影子真实上游错误(超时/500/拉黑)仍照常记账。
渠道日志终态:败者racing_lost+racingStatus=lost,赢家racingStatus=won。主/影子尝试共享请求 correlation ID,渠道日志按用户请求折叠为一行(「N 次尝试」徽章,见 docs/specs/web-ui-dialogs.md)。
6.4 防误判赢家
被取消的影子可能以空流 EOF → 内部轮转 →context.Canceled + Handled=true收尾,pickWinner判据为Handled && LastError == nil(backend-go/internal/handlers/common/racing.go);cancel 连带的空流响应直接按败出终止(racingSupersededOrCanceledEmptyStream判定)。
6.5 防放大机制汇总
- 每请求最多
maxShadows条(策略表); - 全局并发信号量 12(
TryAcquire不阻塞主流程,超容量直接放弃派影子;backend-go/internal/racing/gate.go); - 影子分支内禁递归竞速(影子分支执行
trySelectedChannel,不再套RunRacingAttempt); - X-Channel pin / 含图请求 / 非四对话协议不竞速;
- 限流热渠道不派影子。
6.6 影子赢时的状态归并
影子赢时分支 gin keys 回拷主 context(copyBranchKeysBack,跳过ccx.racing.*内部 key,保证responseText/lastUserMessage等后处理读到赢家数据);primary selection 补记 trace 终态(notifyPrimarySuperseded,防悬空)。
七、后端结构总览
| 位置 | 职责 |
|---|---|
| backend-go/internal/racing/ | 阈值注册表、Gate、信号量、策略表、ErrRacingSuperseded |
| backend-go/internal/config/racing_config.go | 全局/渠道级开关 +ResolveRacingPolicy+SetRacingEnabled |
| backend-go/internal/handlers/common/racing.go | 编排器RunRacingAttempt(武装检查/定时器/影子派发/裁决归并/样本记录) |
| backend-go/internal/handlers/common/multi_channel_failover.go | 外壳接线(TrySelectedChannelFunc增加 gin context 参数;AlsoFailedRoutes并入 failedRoutes) |
| backend-go/internal/handlers/common/upstream_failover.go | echo 头块写出(分支 writer 隔离)、败者分类豁免(先于 isClientSideError)、成功路径 won 标记、日志角色 |
| backend-go/internal/handlers/common/racing_writer.go | 分支 writer:pre-commit 私有缓冲、赢家 Commit 桥接真实 writer、败者 Discard |
| backend-go/internal/handlers/common/stream_processor.go + 4 协议 handler | 流式/非流式 claim 点 |
| backend-go/internal/autopilot/racing_candidates.go | 五元组候选选取 |
| backend-go/main.go | RacingHub 注入 +/api/racing/config |
八、延迟负反馈学习(竞速姊妹机制)
竞速样本不只驱动阈值,还回流为组合级学习,构成"竞速触发 → 慢组合标记 → 影子排除 + 软降权 → 快样本恢复"的完整闭环。
8.1 键粒度与存储
学习键为渠道 × keyHash × 实际出站模型 × 任务类(TaskClass 7 值,未分类落unknown全量桶)。存储在 ChannelCompatCache 第四分区latencyPenalties(.config/channel_compat.json,TTL 24h,GET/DELETE /api/compat-cache可查/可清?section=latency-penalty)。
8.2 三个慢信号
- 竞速 primary 被影子击败(upstream_failover 败者豁免点,非 shadow 分支才记);
- 竞速触发本身(派影子时,主组合记一次——
recordPrimaryRacingTriggerEvidence,见编排器startShadow); - 普通流式成功请求首字超同家族 p90 阈值(
MaybeLearnLatencyDegradation)。
竞速败出/被取消分支不学习。
8.3 判定与恢复
连续慢证据streak ≥ 3判劣化(单次是抖动);任一快样本(首字 < 阈值一半)乐观翻转清零。观测日志为[Latency-Learn](首次达阈值)/[Latency-Recover](翻转)各一行。
8.4 消费:软降权,非硬排除
延迟差不是能力缺失,因此采用软降权而非硬排除:ScoringCandidate.LatencyDegraded→calcPenalty叠加 −15(介于 degraded −5 与 limited −20 之间);竞速影子候选排除学习过的慢组合(racingShadowExcludesLookup→learnedLatencyDegradedLookup,见 backend-go/internal/autopilot/racing_candidates.go,由 backend-go/internal/autopilot/latency_memory.go 的查询入口接入)。taskClass精确桶优先,unknown桶对任意任务类生效(全量记录)。排除逻辑的测试见 backend-go/internal/autopilot/racing_candidates_test.go。
九、与 kiro.rs 的刻意偏离
竞速机制参照 kiro.rs,但结合 CCX 的架构做了四处刻意偏离:
- 只做首字/完成阶段竞速,无响应头阶段——CCX preflight 闸门已覆盖主要收益;
- 影子用独立完整超时窗口——CCX preflight 超时是 per-attempt 口径,共享剩余窗口会让 5s 短超时渠道竞速失效;
- 候选是五元组绑定(跨渠道 + 同渠道换绑定),非 kiro 的同模型换凭据;
- 无 SQLite 分钟桶预热(重启后 20 样本内走 floor);样本记流式败者(消赢家偏差)。
十、快速上手:如何开启与验证
竞速模式开启后无需额外运维动作,行为参数自动推导,可通过以下途径验证其工作状态:
- 开启竞速:在智能路由面板打开「竞速模式」,或脚本直调
PUT /api/racing/config(全局)与渠道编辑页自定义参数(渠道级); - 观察阈值窗口:样本积累与触发行为由
Registry维护,阈值随 p90 自适应浮动,可通过源码中SampleCount/ThresholdMs的测试用例理解边界行为(backend-go/internal/racing/registry_test.go); - 观察日志终态:竞速发生时渠道日志按请求折叠为一行,带「N 次尝试」徽章,终态标记
racingStatus=won/racingStatus=lost;影子派发时有[Racing] 首字等待超阈值(...ms),向候选 ... 派出影子分支 #N日志; - 清理延迟负反馈:需要时通过
DELETE /api/compat-cache?section=latency-penalty清除 24h TTL 的学习结论。
十一、适用前提与限制
- 竞速以冗余流量换延迟,默认关闭,按需开启;
cost_first通过仅派更便宜候选(综合倍率 ≤ 主候选一半)控制成本; - 竞速只对四类对话协议(messages/chat/responses/gemini)生效,显式渠道 pin、含图请求不竞速;
- 重启后样本窗口为空,前 20 个样本内走静态 floor,需等待窗口积累后阈值才能自适应;
- 延迟负反馈是软降权(−15),不参与硬排除,任务类精确桶优先于
unknown全量桶。
- API网关
- LLM 网关
- 后端
【免费下载链接】ccx
Claude / Codex / Gemini API Proxy - CCX
相关推荐
CCX 渠道 Vision Fallback 限制剖析:为什么 `visionFallbackModel` 对 Gemini 渠道不生效
CCX 渠道 Vision Fallback 限制剖析:为什么 visionFallbackModel 对 Gemini 渠道不生效 本文以 CCX(Claud
API网关LLM 网关后端Grafana Tempo 中的 Hedged HTTP:用 hedgedhttp 对冲请求消除对象存储长尾延迟
Grafana Tempo 中的 Hedged HTTP:用 hedgedhttp 对冲请求消除对象存储长尾延迟 Grafana Tempo 是一个高吞吐、低依
后端可观测性链路追踪IP2Region.xdb使用指南:基于gh_mirrors/ipd/IP_database项目
IP2Region.xdb使用指南:基于gh_mirrors/ipd/IP_database项目 IP2Region.xdb是一款高效精准的IP地址定位数据库,
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考