Skip to content

fix(proxy): handle optional params in Responses routing - #7630

Open
Lingbou wants to merge 2 commits into
farion1231:mainfrom
Lingbou:fix/6394-responses-optional-params
Open

Lingbou wants to merge 2 commits into
farion1231:mainfrom
Lingbou:fix/6394-responses-optional-params

Conversation

@Lingbou

@Lingbou Lingbou commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Map Anthropic output_config.format to Responses text.format, including the format name and strict JSON Schema semantics required by the Responses API.
  • For structured 400/422 responses whose error.param is a safely omittable sampling field (temperature or top_p), retry on the same provider, endpoint, model, and API format without that field.
  • Cache negotiated omissions per route so later requests do not repeat known unsupported parameters. Errors outside the whitelist or status boundary are returned unchanged.

Tests

  • cargo test --manifest-path src-tauri/Cargo.toml --lib: 2911 passed, 0 failed, 9 ignored
  • cargo fmt --all --manifest-path src-tauri/Cargo.toml -- --check
  • cargo clippy --manifest-path src-tauri/Cargo.toml --lib --tests: completed successfully with one pre-existing, unrelated warning outside this change

Fixes #6394

@Lingbou
Lingbou requested a review from farion1231 as a code owner September 23, 2026 10:13
@github-actions github-actions Bot added backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding labels Sep 23, 2026
@farion1231

Copy link
Copy Markdown
Owner

GPT-6 Astra 自动代码审查

审查提交:7a4af4d21e13b790ac1d12fedaa59542f8620e28
Base:f2537fdf6b709082b52886d52ce6914bae690699

由 Codex 定时任务使用 GPT-6 Astra 子 agent 审查,并经主 agent 复核。

[P2] 不要将含可选属性的 Anthropic schema 原样设为 Responses 严格模式

位置:src-tauri/src/proxy/providers/transform_responses.rs:65

当 output_config.format.schema 含有未列入 required 的可选属性时,这里直接复制 schema 并默认 strict=true,会生成 OpenAI Responses 不接受的结构化输出配置。Anthropic 明确支持可选属性,而 OpenAI 严格模式要求所有属性都在 required 中。因此合法的 Anthropic 请求会因本次新增的 text.format 被上游拒绝;新增测试仅覆盖全部属性必填的 schema,未覆盖这一协议差异。Anthropic 文档;OpenAI 文档。

证据:离线测试 review_repros::optional_anthropic_property_is_emitted_as_invalid_strict_schema 直接运行从 HEAD 原样提取的映射函数。输入为 properties={name:string,notes:string}、required=[name]、additionalProperties=false,输出仍为 required=[name] 且 strict=true。OpenAI 官方文档明确说明不符合严格模式约束的 schema 会报错。调用方 anthropic_to_responses 在 1896-1898 行直接发送该映射结果;BASE 对应位置未生成 text.format。

建议:转换前检查两套 schema 支持子集的差异,对可选属性实施能保留客户端语义的兼容处理,不要只添加 strict=true;对无法等价转换的 schema 明确处理。增加顶层及嵌套可选属性的回归测试。

[P2] 不要把请求级采样参数拒绝永久缓存为整条路由的能力

位置:src-tauri/src/proxy/responses_compat.rs:160

这里把任何匹配 error.param 的 400/422 都缓存为后续请求必须删除该字段,但拒绝可能仅与本次推理模式或参数值有关。例如同一 GPT-5.2 路由先发送 high 推理请求并触发 temperature 回退,再发送关闭 thinking 的请求,第二次本来支持的 temperature 仍会被静默删除,直到进程重启。官方文档说明 GPT-5.2 的 temperature/top_p 支持取决于 reasoning.effort 是否为 none,而非仅取决于模型。OpenAI 参数兼容性文档。

证据:离线测试 review_repros::reasoning_rejection_leaks_into_non_reasoning_request 使用原始缓存模块和原样提取的路由键、reasoning 解析函数:首个请求 output_config.effort=high 的模拟上游返回 400/unsupported_parameter/temperature;第二个请求 thinking.type=disabled、temperature=0.2 的实际发送体变成 {model:claude-sonnet-4-6,thinking:{type:disabled}}。transform.rs:108-154 对 disabled 返回 None,transform_responses.rs:1885-1890 因而不注入 reasoning,使用 GPT-5.2 默认 none。另一个复现 value_validation_error_poisons_later_valid_temperature 确认 invalid_value 错误也被当作能力拒绝,随后合法的 0.2 同样被删除;optional_param_from_error 在 127-128 行仅检查 param,不检查错误原因。

建议:将当前请求的降级重试与跨请求能力缓存分开。只对明确、无条件不支持字段的错误记录路由能力;请求条件导致的拒绝应按最终推理配置等条件隔离,普通参数值校验错误应原样返回。增加同模型 high→disabled 及非法值→合法值的连续请求测试。

[P2] 使用实际出站模型和目标地址隔离兼容缓存

位置:src-tauri/src/proxy/forwarder.rs:1184

缓存键在模型映射和 URL 解析之前构建,记录的是客户端别名及 /v1/messages,而不是实际出站模型和上游地址。用户在同一供应商卡片中修改模型映射或上游地址后,provider.id 和客户端别名不变,旧上游的采样字段禁用记录仍会应用到新上游。例如先把 claude-sonnet-4-6 映射到不支持 temperature 的模型,再改映射到支持该参数的模型,后者的 temperature 会在首次请求前就被删除。进程级缓存没有配置更新失效机制,因而这不是一次额外重试,而是持续改变新路由的请求语义。

证据:离线测试 review_repros::upstream_model_change_reuses_old_model_omissions 原样提取这里的键构造代码,并调用完整的 HEAD model_mapper 模块。相同 provider.id、入站 model=claude-sonnet-4-6,先后设置 ANTHROPIC_DEFAULT_SONNET_MODEL=o3 和 gpt-4o,两个键完全相等;首个模拟上游协商删除 temperature 后,第二次实际映射结果为 {model:gpt-4o},丢失传入的 temperature=0.2。实际 forward 在 1297-1305 行才应用模型映射,在 1555 行附近才生成目标 URL。ProviderService::update 在 5535 行保存同一 ID 的新配置,未清理本模块的 OnceLock 缓存。

建议:在最终模型、实际 API 格式及上游目标解析完成后生成能力缓存键,并在供应商相关配置变化时隔离或失效旧记录。增加同一供应商修改模型映射和 base URL 后保留新路由采样参数的回归测试。

验证与范围

已完整审查指定 BASE...HEAD 的全部四个文件 diff,以及参数兼容缓存、schema 转换、模型映射、协议/URL 解析、请求覆写、四处重试调用、错误分类和供应商更新的关键上下文与相关测试。确认 3 条 P2 发现:结构化 schema 的严格模式兼容性、请求级拒绝污染跨请求能力缓存、入站路由键导致上游配置变更后复用旧删除记录。离线 29 项测试完成,审查 worktree 保持干净。

  • 完整读取 cat /tmp/cc-switch-astra-review-20260924.g96peR/prs/7630.json:确认精确 BASE/HEAD、review_workspace;comments、reviews、inline 均为空,无已有实质意见需要去重。
  • 在 /private/tmp/cc-switch-astra-review-20260924.g96peR/worktrees/7630 执行 git rev-parse HEAD:返回 7a4af4d。git merge-base f2537fd 7a4af4d 返回指定 BASE。
  • 完整读取 git diff f2537fd...7a4af4d --:4 个文件,647 行新增、4 行删除。git diff --check 同一范围退出码 0。
  • 通过 sed、rg、nl 阅读四处重试调用、forward 请求准备和错误返回、handler 调用、Claude 协议选择/转换、完整 model_mapper、供应商更新路径及相关测试;通过 git show BASE:path | sed 定点对比旧请求转发和 schema 转换行为。
  • 将 responses_compat.rs、model_mapper.rs、transform_responses.rs、forwarder.rs、transform.rs 只读复制到 validation-7630/snapshot;使用 apply_patch 创建最小 Rust 测试工程。原始缓存及模型映射模块直接编译,schema/路由键/reasoning 函数由构建脚本从复制文件原样提取。
  • 执行 cargo test --offline --manifest-path /tmp/cc-switch-astra-review-20260924.g96peR/validation-7630/Cargo.toml -- --nocapture:退出码 0,29 项通过、0 项失败;包含原缓存模块 7 项测试、原模型映射模块 18 项测试及 4 项针对性复现。全部依赖使用本机已有 Cargo 缓存,未访问真实上游。测试工程有一条不影响测试的未使用导入警告。
  • 4 项复现断言确认当前缺陷行为:可选属性仍与 strict=true 一起发出;参数值错误使下一次合法 temperature 消失;high 推理时协商的删除跨到 disabled 请求;修改同一供应商模型映射后仍继承旧删除记录。测试名称以 review_repros:: 开头,位于 validation-7630/src/lib.rs。
  • 实际搜索并打开 OpenAI Structured Outputs、GPT-5.2 参数兼容性及 Anthropic Structured Outputs 官方文档,核对可选属性与推理模式相关限制;文档链接已置于对应发现中。
  • 审查前后在独立 worktree 执行 git status --short:均为空。未修改审查 worktree 或用户工作目录,未提交、推送、发布评论或操作 CI。

未验证或受限范围:

  • 未运行完整 Tauri 应用构建、全库测试或 HTTP 端到端测试;本次按限制仅执行离线小型 Rust 验证工程。
  • 小型工程仅对不参与目标逻辑的 Provider/ProxyError 数据类型使用最小替身;未编译完整 RequestForwarder。调用顺序及配置更新影响通过精确快照的静态代码审查确认。
  • 上游错误由内存闭包模拟,复现证明本地参数删除与缓存行为,不声称实际请求过 OpenAI 或其他供应商;外部协议约束依据已打开的官方主文档。
  • 4 项新增复现测试刻意断言 HEAD 当前缺陷表现,因此测试通过表示复现成立,而非修复后的正确性验证。

本评论仅针对以上提交的代码审查结果,不构成合并批准。

@Lingbou

Lingbou commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

已在 3f890271 更新。

  • 结构定义严格模式:仅当结构定义递归满足严格模式约束时才启用 strict=true;包含顶层或嵌套可选属性时会降级为 strict=false。
  • 兼容降级:仅在明确表示“参数不支持”时重试;参数值非法及其他 400/422 错误均原样返回。
  • 缓存范围:缓存键在最终模型和 URL 解析完成后生成,并包含推理模式;模型映射、base URL 或推理模式变化后不会复用旧记录。

@Lingbou

Lingbou commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231

1 similar comment
@Lingbou

Lingbou commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231

@Lingbou

Lingbou commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231 已同步变基至最新 v4.0.4 主线。修复 Responses 路由在不支持可选参数(如 temperature/top_p)报错时的过滤重试与 output_config 映射,分支无冲突且 CI 全绿,方便时麻烦审阅一下,谢谢!

@Lingbou

Lingbou commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231 顺便跟最新 main 合过了,CI 检查全绿。等手头发版节奏放缓了再看看哈!

Map Anthropic output_config.format to Responses text.format and negotiate unsupported temperature/top_p omissions through bounded, same-route retries. Cache negotiated omissions by provider, endpoint, model, and API format.

Signed-off-by: Xingqiu Xu <hilbertanjou83@gmail.com>
Enable strict Responses schemas only when eligible, retry only explicit unsupported-parameter errors, and key negotiated omissions by the final endpoint, model, and reasoning mode.

Signed-off-by: Xingqiu Xu <hilbertanjou83@gmail.com>
@Lingbou
Lingbou force-pushed the fix/6394-responses-optional-params branch from 3f89027 to 3a56b8e Compare October 16, 2026 03:35

This branch has not been deployed

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

Labels

backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Claude Code Auto Mode fails through generic Responses routing when optional parameters are rejected

2 participants