指导和见解

在 AI API 网关中构建响应 API 兼容层

响应 API 网关不仅仅是具有新路由的聊天完成代理。通过一流的兼容性层保留响应项、状态、工具调用、流、推理连续性、使用归因和降级行为。

不要通过将每个请求转换为 /v1/chat/completions 并希望形状足够接近来实现 /v1/responses。该适配器可能会返回文本,但它可能会默默地丢失开发人员关心的部分:响应项、服务器端状态、工具调用、推理连续性、流生命周期事件、取消语义和项级使用归因。

实际目标是建立一个兼容层,将响应 API 视为更丰富的协议。保持对现有客户端的聊天完成支持,但将响应构建为其自己的网关表面,具有自己的状态模型、流标准化器、工具调用分类帐、功能矩阵和后备规则。

什么是事实,什么是政策,什么是预测?

事实:OpenAI 将 Responses API 描述为统一功能,这些功能以前分散在聊天完成和助手中,包括对网络搜索、文件搜索和计算机使用等工具的支持。该 API 公开诸如 previous_response_id、流式传输、工具选择和内置工具等字段。 SDK 文档显示 previous_response_id 可以提供对话连续性,而之前的指令不会自动结转,必须在仍然适用时重新发送。 OpenAI 的流参考包括不同的响应生命周期和输出事件,而不仅仅是令牌增量。

建议:网关应该保留这些语义,而不是默认扁平化它们。当目标提供程序无法支持所需的行为时,它应该拒绝或显式降级请求。

预测:更多代理工作负载将取决于响应项结构、工具执行跟踪和状态推理上下文。现在,对这些概念进行建模的网关将比将响应视为修饰端点的网关更容易扩展。

为响应定义单独的兼容性契约

第一个实现错误是假设 OpenAI 兼容意味着一个通用的请求和响应模式。实际上,/v1/chat/completions/v1/responses 应该是单独的兼容性协定。

保留共享的身份验证、计费、配额和路由层,但分离协议层:

  • 聊天完成表面:消息、选择、增量、聊天格式的工具调用、旧版客户端行为。
  • 响应表面:输入项、输出项、响应 ID、以前的响应引用、更丰富的工具事件、生命周期流事件、推理相关字段和最终响应状态。

这种划分对于一致性测试很重要。通过聊天测试的提供程序适配器仍可能无法通过响应测试,因为它无法保留 previous_response_id、项目排序、拒绝结构、托管工具元数据或流事件名称。

最低限度的兼容性合同应该回答:

  • 哪些请求字段被接受、拒绝、转换或忽略?
  • 会保留哪些响应项类型?
  • 每个提供商和型号支持哪些工具类型?
  • 提供商能否维护对话状态,还是必须由网关维护?
  • 请求 store=false 时会发生什么?
  • 哪些直播事件是有保证的?
  • 如何记录取消、超时和部分使用?

如果您已有AI API 网关,请将响应支持视为协议扩展,而不是路由别名。

使用规范响应项模型

响应 API 返回多条辅助消息。它可以代表不同的输出项和事件。您的网关在映射到任何提供商之前需要一个内部规范模型。

实用的内部项目架构可以这样开始:

<前><代码>{ "gateway_response_id": "gw_resp_...", "provider_response_id": "resp_...", “tenant_id”:“ten_123”, “key_id”:“key_456”, "model_alias": "代理默认", “提供者”:“openai”, “项目”:[ { “item_id”:“item_1”, “类型”:“文本”, “角色”:“助理”, “内容”:[{“类型”:“output_text”,“文本”:“...”}], “状态”:“已完成” }, { “item_id”:“item_2”, “类型”:“函数调用”, "call_id": "call_abc", “名称”:“查找顺序”, "arguments_json": "{\"order_id\":\"123\"}", “状态”:“已完成” } ], “用法”:{ “输入令牌”:0, “输出令牌”:0, “reasoning_tokens”:空, “工具单位”:[] }, “状态”:“已完成” }

甚至在每个提供商都可以生产物品之前就包含物品类型。有用的类别包括:

  • 文本输出
  • 拒绝
  • 函数调用
  • 应用程序提交的函数输出
  • 推理摘要或推理相关元数据(如果有)
  • 文件参考
  • 网络搜索、文件搜索、计算机使用或其他托管工具事件
  • 最终使用情况和计费元数据

重点不是向用户公开专有架构。关键是要防止网关在审核、计费、流式传输、重播或转换信息之前丢弃信息。

构建网关拥有的状态分类账

previous_response_id 是最能体现无状态聊天代理和响应兼容性之间差异的字段。如果客户端引用先前的响应,网关必须知道该 ID 的含义、是否允许租户使用它以及提供商是否可以继续使用它。

创建一个由租户和响应 ID 键入的状态分类帐:

<前><代码>{ "gateway_response_id": "gw_resp_789", "provider_response_id": "resp_provider_789", "previous_gateway_response_id": "gw_resp_456", “tenant_id”:“ten_123”, “user_id”:“user_999”, “key_id”:“key_456”, "型号": "gpt-...", “提供者”:“openai”, "store_mode": "提供商|网关|无", "retention_policy": "标准|zero_retention|custom_30d", "instructions_hash": "sha256:...", "tool_policy_id": "tools_readonly_v3", "创建于": "...", "expires_at": "...", “deleted_at”:空 }

重要规则:除非租户明确允许保留和成本行为,否则不要通过重播完整聊天历史记录来自动模拟 previous_response_id。重放可能会增加代币成本、改变隐私状况并改变模型行为。返回明确的功能错误比静默发送应用程序不希望您保留或重用的存储对话内容更安全。

状态处理模式

  • 提供商状态:上游提供商存储足够的上下文,网关将网关响应 ID 映射到提供商响应 ID。
  • 网关状态:网关存储必要的优先项并在允许时重建上下文。
  • 无状态:请求使用 store=false 或租户策略禁止保留。 previous_response_id 应被拒绝,除非提供商可以在没有网关保留的情况下满足请求并且策略允许。

另请记住,当客户继续应用之前的说明时,可能需要重新发送。网关不应发明隐藏指令来进行补偿,除非该行为是显式租户策略的一部分。

调度前验证工具

响应使工具的使用更加集中。兼容性层应该处理两大类:

  • 应用工具:由客户端提供的函数定义,在模型提供者外部执行,并将输出提交回 API。
  • 托管提供商工具:网络搜索、文件搜索、计算机使用、代码执行、接地或由提供商或网关控制的基础设施执行的类似工具。

在入口处,在路由之前验证工具架构:

  • 尽早拒绝无效的 JSON 架构。
  • 强制实施最大架构大小和嵌套深度。
  • 检查工具名称的提供商兼容性。
  • 应用租户、密钥、用户和环境范围。
  • 对于写入数据、花钱、访问敏感系统或调用外部连接器的工具需要审批。

对于应用程序函数调用,需要稳定的调用ID。该模型发出带有 call_id 的函数调用;应用程序提交引用该 ID 的工具输出;网关将两者记录在同一跟踪中。如果没有该连接密钥,审核日志和重试就会变得不明确。

对于托管工具,请在调度前预留预算,然后结算成本。托管工具可能会在普通代币核算之外增加费用,因此将工具分类帐连接到统一 AI API 计费,而不是将这些费用隐藏在通用模型调用总额中。

将流式传输标准化为事件,而不是标记文本

聊天代理通常可以逃避转发令牌增量。响应网关不能。流具有生命周期意义:响应可以开始,输出项可以开始和完成,文本可以增量到达,工具调用可以增量组装,使用可以在流结束时或在流期间到达,响应可以失败或取消。

定义网关事件架构,然后将每个提供程序流映射到其中:

事件:response_started
数据:{“response_id”:“gw_resp_123”,“状态”:“in_progress”}

事件:output_item_started数据:{“item_id”:“item_1”,“类型”:“文本”}

事件:text_delta
数据:{“item_id”:“item_1”,“delta”:“你好”}

事件:tool_call_delta
数据:{“item_id”:“item_2”,“call_id”:“call_abc”,“arguments_delta”:“{\”订单“}

事件:usage_delta
数据:{“output_tokens”:12}

事件:已完成
数据:{“response_id”:“gw_resp_123”,“用法”:{...}}

推荐的标准化事件:

  • response_started
  • output_item_started
  • output_item_completed
  • text_delta
  • refusal_delta
  • tool_call_delta
  • tool_result_received
  • usage_delta
  • 已完成
  • 已取消
  • 失败

当客户端断开连接时,如果提供程序支持,则向上游传播取消。无论哪种方式都记录部分响应状态。如果提供者稍后通过延迟回调或最终块返回最终使用情况,请协调分类账。流媒体兼容性与计算和生命周期以及延迟一样重要。

创建提供商能力矩阵

仅当网关了解可以安全路由的内容时,多模型路由才有用。将特定于响应的功能添加到您的模型目录中:

<前><代码>{ "model_alias": "代理默认", “路线”:[ { “提供者”:“openai”, "型号": "...", “supports_responses”:正确, “supports_previous_response_id”:true, “supports_store_false”:正确, “supports_builtin_web_search”:true, “支持函数调用”:true, “supports_stream_lifecycle_events”:true, “supports_reasoning_context_continuity”:true, “最大工具架构字节”:65536 }, { “提供商”:“provider_b”, "型号": "...", “supports_responses”:假, “chat_adapter_available”:正确, “loss_profile”:[“no_previous_response_id”,“no_hosted_tools”,“flattened_stream”] } ] }

后备应该具有丢失意识。如果请求需要内置网络搜索,而后备提供商无法执行该请求,请不要在没有搜索的情况下默默回答。如果请求依赖于保留的推理上下文,并且后备路由无法保留它,则返回客户端明确选择的功能错误或降级响应。

一个有用的请求选项是:

<前><代码>{ "model": "代理默认", “输入”:“...”, “后备政策”:{ “allow_lossy”:假, “允许的损失”:[] } }

对于不太敏感的用例,租户可以允许特定的有损降级:

<前><代码>{ “后备政策”:{ “allow_lossy”:正确, “allowed_losses”:[“flattened_stream”,“no_reasoning_summary”] } }

无论哪种方式,网关都应该记录后备决策。当代理在提供程序中断或模型重新路由后表现不同时,这使得以后的调试成为可能。

响应和项目级别的属性使用

响应调用的成本可能比同等的聊天完成成本更高,因为它们可能包括工具执行、较长的上下文、推理令牌、文件搜索、Web 搜索或重复的指令。对于 AI API 使用情况分析仪表板来说,单个聚合令牌计数是不够的。

记录两个级别的使用情况:

  • 响应级别:租户、密钥、用户、模型、提供商、延迟、最终状态、输入令牌、输出令牌、报告的推理令牌、总成本和后备路线。
  • 项目/工具级别:工具名称、调用 ID、托管工具单元、文件 ID、搜索查询计数(如果可用)、工具延迟、工具成本和审批策略结果。

这可以让开发人员回答具体问题:

  • 成本是否因更长的状态、推理工作、工具调用或回退而增加?
  • 哪个租户或 API 密钥会产生托管工具费用?
  • 哪个响应在工具调用之后但在最终文本之前失败?
  • 哪些已取消的流仍会导致上游使用?

将零保留和删除视为一流行为

服务器端状态很有帮助,但它改变了网关的保留义务。将策略构建到协议层中,而不是将其视为日志记录设置。

对于每个响应请求,解析:

  • 租户保留政策
  • 请求级商店首选项
  • 提供商保留兼容性
  • 是否允许网关重放
  • 是否可以存储工具输入和输出
  • 响应状态的过期和删除行为

如果禁用保留,网关仍可能保留最少的操作元数据:时间戳、ID、状态、令牌计数、成本和策略决策。除非政策允许,否则避免存储原始提示、完整的工具输出或重建的历史记录。

启动前添加的一致性装置

不要依赖快乐路径手动测试。添加装置来验证直接 OpenAI 路由、提供商适应路由和回退场景中的协议行为。

最小测试集

  • 基本响应:返回带有稳定响应 ID 和用法的文本项。
  • 多轮状态:第二个请求引用previous_response_id;网关验证租户所有权和状态模式。
  • 重复指令:验证省略的指令是否不是网关默默发明的。
  • 函数调用往返:模型发出调用 ID;应用程序提交输出;最终响应将两条记录合并起来。
  • 托管工具策略:未经授权的内置工具在分发前被阻止。
  • 流式传输顺序:按有效顺序发出响应开始、项目开始、增量、项目完成、使用情况和完成。
  • 流取消:客户端断开连接会在支持的情况下触发上游取消并记录部分使用情况。
  • 回退拒绝:没有所需响应语义的提供程序返回功能错误。
  • 有损回退选择加入:允许损失的请求会收到明确的降级标记。
  • 零保留模式:阻止状态重放和网关侧提示保留。

推荐的推出顺序

  1. 公开测试版路线。添加 /v1/responses 而不更改现有聊天行为。
  2. 首先为具有本机响应支持的提供商实现传递。保留 ID、项目、流、使用情况和错误。
  3. 添加状态分类账。将网关 ID 映射到提供商 ID 并强制租户所有权。
  4. 添加规范项目。存储审核、计费和流重建所需的项目元数据。
  5. 添加工具治理。验证架构、实施范围并记录工具调用连接。
  6. 添加流标准化。将特定于提供商的流转换为网关生命周期事件。
  7. 添加功能感知路由。默认情况下仅允许安全后备。
  8. 添加分析和计费结算。分别赋予令牌、推理和工具使用情况。
  9. 发布兼容性说明。告诉开发者哪些字段是本机字段、模拟字段、不受支持字段或有损字段。

可行的结论

响应 API 兼容层应保留协议含义,而不仅仅是返回合理的文本。围绕五个持久对象构建它:规范响应项模型、对话状态分类账、工具调用分类账、流事件标准化器和提供者能力矩阵。

最安全的默认设置是严格兼容性:如果路由无法保留所需的状态、工具、推理上下文、流事件或保留行为,则返回明确的功能错误。仅当开发人员了解将删除哪些内容时才添加选择加入有损回退。这种方法可能感觉不如自动扁平化方便,但它可以防止最糟糕的故障模式:应用程序看起来兼容,但同时悄悄地丢失了最初使用 Responses API 的语义。

相关阅读

FAQ

常见问题

网关能否通过将所有内容转换为聊天完成来实现响应 API?
仅适用于狭窄的有损子集。基本文本生成可能有效,但状态、响应项、托管工具、推理相关上下文、拒绝结构、流生命周期事件和项级使用可能会丢失。生产网关应将响应公开为单独的兼容性表面。
网关是否应该重播存储的聊天历史记录以模拟 previous_response_id?
默认情况下不是。重放会改变保留行为、成本,有时甚至会改变模型行为。在网关使用该策略之前,租户应明确允许网关端状态保留和重放。
当后备提供者无法支持响应语义时会发生什么?
最安全的默认值是能力错误。如果租户选择有损回退,则网关应返回显式降级标记并记录哪些语义被丢弃。
为什么要在响应项目级别记录使用情况?
响应调用可能包括工具调用、托管工具费用、推理令牌、部分流和回退行为。项目级使用使计费、调试和租户分析变得可解释。