Skip to content

feat: 为 Responses prompt_cache_key 增加安全的软亲和路由与可观测性 #629

Description

@DabengBa

例行检查 / Checklist

  • 我已确认目前没有相同的 issue (I have confirmed that there is no duplicate issue)
  • 我已确认当前主版本线的最新补丁版本仍无法满足需求 (I have confirmed that the latest patch release does not meet this need)
  • 我已查看对应版本线的 README 和发布说明 (I have read the README and release notes for the 2.x line)
  • 我不会在 issue 中提交任何凭据或令牌 (I will not include any credentials or tokens in this issue)
  • 我理解并愿意跟进此 issue,协助测试和提供反馈 (I am willing to follow up, test, and provide feedback)

目标版本线 / Target Release Line

2.x


功能描述 / Feature Description

希望为 OpenAI Responses API 增加 prompt_cache_key 的安全软亲和支持。

当客户端在 Responses create 请求中提供有效的顶层 prompt_cache_key 时,GPT-Load 可以将其作为受限的软亲和信号,优先复用此前成功使用的凭据,从而提高同一 prompt cache 分组的凭据复用概率。

该能力不应把 prompt_cache_key 解释为严格的 session ID、认证凭据、机器绑定标识或跨实例会话状态。

同时,希望在请求日志和监控界面中区分:

  • 软亲和目标实际命中;
  • previous_response_id 或 WebSocket binding 的硬续接命中;
  • 软亲和未命中、目标不可用或缓存不可用等有限状态。

应用场景 / Use Case

当多个请求属于同一个 prompt cache 分组时,客户端会重复使用相同的 prompt_cache_key。如果每次请求都完全随机选择凭据,可能降低上游 prompt cache 的复用效率。

目标场景包括:

  • 同一 AccessKey、协议、客户端模型和操作下,重复使用相同的 prompt_cache_key;
  • 在没有显式 key 时,继续使用现有的 prompt-prefix 亲和逻辑;
  • 使用 previous_response_id 时,仍严格复用原 response 绑定的凭据;
  • 当软亲和目标不可用、分组禁用或本地缓存不可用时,安全回退到现有调度流程;
  • 让运维人员能够在请求日志中区分软亲和和硬续接,而不是将两者都解释为 affinity_hit。

期望方案 / Desired Behavior

1. 请求信号

仅在 Responses create 请求的 JSON 顶层读取 prompt_cache_key。

有效值必须满足:

  • 非空字符串;
  • 有效 UTF-8;
  • 首尾无空白;
  • 不包含控制字符;
  • 不超过 256 字节。

无效值应被静默忽略,不阻断原请求,也不改变原有请求转发行为。

有效的显式 prompt_cache_key 应优先于 prompt-prefix 派生信号。prompt-prefix 仅在没有有效显式 key 时作为 fallback 使用,来源包括:

  • instructions;
  • system;
  • developer;
  • 首个 user 或 input_text。

显式 key 与 prompt-prefix 应使用独立 namespace,避免同值信号意外共享。

2. 软亲和路由

软亲和应遵循以下边界:

  • 不扩大现有候选凭据集合;
  • 不绕过 AccessKey、分组、凭据身份或响应绑定安全检查;
  • 不改变现有重试策略;
  • 在现有候选范围内提供凭据优先级提示;
  • 软亲和目标不可用时继续走现有降级路径;
  • 仅在成功终态后学习软亲和目标;
  • 失败、取消、不完整流和硬续接请求不应学习软亲和。

当前方案还会将同一受限信号用于已有的 provider-private replay scope。该 scope 不对外暴露,也不等同于严格 session 身份、机器绑定或认证凭据。希望在本 Issue 中确认是否保留这一行为,或是否应将软亲和限制为候选优先级提示。

3. 硬续接优先

previous_response_id 和 WebSocket binding 继续作为硬绑定信号,优先于软亲和:

  • continuity_hit:表示已找到并采用硬续接绑定;
  • affinity_hit:仅表示首轮实际选择了已有软亲和目标;
  • 两者不应混用;
  • 硬续接失败时保持现有拒绝或失败语义,不扩大允许使用的凭据集合。

4. 请求日志和监控

请求日志、控制面 API 和监控界面可以提供以下有界观测:

  • affinity_source
  • affinity_state
  • continuity_hit

建议的 affinity_source:

  • none
  • prompt_cache_key
  • prompt_prefix

建议的 affinity_state:

  • no_signal
  • cache_miss
  • hit
  • group_disabled
  • target_unavailable
  • cache_unavailable

这些字段只表示 GPT-Load 本地的亲和决策:

  • affinity_state=hit 不表示上游 prompt cache 一定命中;
  • continuity_hit 不表示上游一定成功恢复响应状态。

原始 prompt_cache_key、派生 key 和 HMAC 输入不应写入:

  • 请求日志;
  • API 响应;
  • 错误信息;
  • 监控 UI;
  • 调试输出;
  • 持久化数据。

5. 缓存和派生 key

亲和缓存可以继续使用进程内缓存:

  • 不要求重启后共享;
  • 不承诺多实例之间共享;
  • 不新增数据库缓存;
  • 使用现有稳定 Hash 能力生成 opaque key;
  • 派生 key 应按 AccessKey、协议、客户端模型、操作和信号类型隔离;
  • 显式 key 与 prefix-derived key 不应共用 namespace。

兼容性、数据与安全影响 / Compatibility, Data, and Security Impact

API 兼容性

这是对 Responses create 顶层可选字段的非阻断式支持。

现有客户端请求格式保持兼容:

  • 有效 prompt_cache_key 被内部用作软亲和提示;
  • 无效或未知格式的值被忽略;
  • 无效值不会导致请求失败;
  • 请求原始 body 不因本地亲和处理而被改写。

路由兼容性

软亲和不会:

  • 扩大客户端已有的凭据访问权限;
  • 扩大候选凭据集合;
  • 覆盖 previous_response_id 硬续接;
  • 改变现有重试策略;
  • 将 prompt_cache_key 变成严格的 session 或机器绑定标识。

数据库影响

请求日志需要增加以下字段:

  • continuity_hit
  • affinity_source
  • affinity_state

数据库变更应使用下一个连续的 additive migration,不应固定依赖某个未来迁移编号。历史记录使用安全默认值,例如:

  • continuity_hit=false
  • affinity_source=none
  • affinity_state=no_signal

历史数据不应回填任何原始 prompt cache key、派生 key 或 HMAC 输入。

SQLite 应覆盖本地迁移回归;MySQL/PostgreSQL 的升级、默认值读取和中断恢复应由 CI 或维护者数据库矩阵补充验证。

安全和隐私

不新增依赖、密钥配置或 X-Session-ID。

原始 key、派生 key 和 HMAC 输入不得进入:

  • durable storage;
  • 请求日志;
  • API 响应;
  • 错误文本;
  • 调试捕获;
  • 监控 UI。

调查结果 / Investigation

已对上游仓库现有 Issues 和 Responses 相关实现进行检索。

精确重复检索

搜索:

  • prompt_cache_key
  • prompt cache affinity
  • Responses prompt_cache_key
  • previous_response_id

结果:

  • 未发现针对 prompt_cache_key 软亲和的相同 Issue;
  • 未发现已经讨论该字段校验、软亲和 namespace 或 affinity/continuity 观测拆分的 Issue。

搜索链接:

相关 Issue:#365

#365 添加密钥轮询间隔配置以优化 KV 缓存利用率

该 Issue 讨论:

  • 当前轮询会导致同一用户/会话跨 API key;
  • 跨 key 可能降低上游 KV/prompt cache 利用率;
  • 是否增加密钥轮询间隔;
  • 是否使用会话粘性或一致性哈希。

该 Issue 与本提案有相同的用户价值背景:减少多凭据轮换导致的上游缓存浪费。

但两者不是同一个方案:

相关 Responses 实现:PR #609

PR #609 feat(responses): 实现响应状态续接路由

该 PR 已实现 previous_response_id 的硬续接路由,明确了以下边界:

  • response ID 归属应按 AccessKey 隔离;
  • 续接只使用当前路由仍允许的原凭据;
  • ID 路由与提示词软亲和互斥;
  • 硬续接不应被普通软亲和逻辑覆盖。

本提案沿用并扩展这一边界,不试图替代或放宽 previous_response_id 的硬绑定。


备选方案 / Alternatives Considered

1. 新增 X-Session-ID

不采用。它会引入新的客户端协议契约,并容易被误解为严格 session 绑定。

2. 将 prompt_cache_key 作为严格 session 或机器绑定

不采用。该字段只能作为软亲和提示,不能改变现有凭据权限、候选集合或 response binding 规则。

3. 固定密钥轮询间隔

相关方案见 #365。

不作为本提案的主要方案,因为固定轮询间隔无法识别不同的 prompt cache 分组,也无法表达客户端明确提供的缓存分组信号。

4. 持久化原始 prompt cache key 或亲和缓存

不采用。原始 key 和派生 key 不应进入 durable storage;进程内缓存足以支持当前目标。

5. 将显式 key 与 prompt-prefix 合并到同一个 namespace

不采用。两类信号语义不同,合并可能造成意外共享或碰撞。

6. 将软亲和限制为纯候选排序

这是需要维护者确认的设计选择。

当前原型除了候选优先级,还将受限派生信号用于已有的 provider-private replay scope。该 scope 不对外暴露,但会影响执行层行为。需要确认:

  • 是否保留该 scope 影响;
  • 是否仅允许它作用于已有需要 replay scope 的 Responses 渠道;
  • 或者是否将实现收窄为只影响候选优先级。

当前状态 / Current Status

该方案已在独立 worktree 和分支中完成原型实现与验证,尚未提交正式 PR:

当前原型已覆盖:

  • Responses 顶层 prompt_cache_key 解析;
  • 显式 key 校验和 prefix fallback;
  • 显式 key 与 prefix namespace 隔离;
  • HTTP 和 Responses WebSocket 软亲和;
  • affinity_hit 与 continuity_hit 分离;
  • 请求日志、API、数据库迁移和监控 UI 投影;
  • 三份 README;
  • Go、race、vet、build、前端 lint/type-check/format/build 验证。

已通过的主要验证包括:

make PNPM=pnpm check
go test -race -count=1 ./internal/affinity ./internal/gateway ./internal/requestlog

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions