Skip to content

feat(transport): 为模型客户端提供可选宿主策略 - #151

Open
zhuowp wants to merge 2 commits into
pinvou3-cleanfrom
codex/issue-1389-model-http-policy
Open

zhuowp wants to merge 2 commits into
pinvou3-cleanfrom
codex/issue-1389-model-http-policy

Conversation

@zhuowp

@zhuowp zhuowp commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

做什么

嵌入方需要在现有模型传输 builder 上应用内存策略,同时覆盖主客户端、HTTP/1 回退和独立视觉客户端。增加可选的 ModelHttpPolicy;没有策略时保持原有行为。策略不从配置文件反序列化,并随配置 clone/视觉继承传递,拒绝错误在客户端构造时传播。

基于公开维护头 11cba403dc6bd6ca5c4dca55042785b03449ac8e,候选 55393015d8a4905d47ca1a81ad41aead8c60962f。通用接口还在最新官方 main 上单独准备了移植,避免把维护分支的历史差异提交上游。

上游贡献:codewhale-hq/Codewhale #7251。上游候选与本维护底座候选分别固定版本;不把上游提交当作当前应用已运行依赖。

验证

  • 实际维护底座结果测试 2 项通过,覆盖主客户端、HTTP/1、独立视觉请求和策略拒绝传播;回环 HTTP 夹具要求实际策略 callback 标记。
  • 消费方实际原生相关测试 28 项通过,另有隔离 TLS/代理/重定向和 DNS 生命周期证据;这些不是本仓完整测试或真实公网模型调用。
  • 维护底座候选未更新受保护分支或固定标签。合入仍需适用 CI、外审和明确维护分支更新授权。

审核与边界

本候选四文件及主模型、HTTP/1、视觉消费者已通过独立 V3 代码与相称功能证据验收,绑定上述候选。维护分支、固定标签、CI、外审和发布资格分别判断;此次只推送候选分支供审核。

No-Issue: 从 Pinvou/pinvou3#1389 提取的通用传输策略接口;本仓未启用 Issues,应用端完整验收仍由原 issue 跟踪,本底座 PR 不关闭应用 issue。

本批修复复核

提交 16e8a8d,tree a7236fffc33e2b6f465d8c0e2f2c020bba9eb1a8。仅按 rustfmt 将 transport_policy_tests 的 cfg(test)/path/mod 完整三行块排序,模块路径、内容和属性归属不变。cargo fmt --all -- --check 通过;本批未重跑 Rust 功能测试或全工作区 Clippy。PR 已补 No-Issue 说明其来自应用 issue 的通用接口提取,本仓 Issues 未启用;不关闭应用验收 issue,不更新公开维护分支或自动合入。 本批增量通过 v3 五角色独立首审及未参与实现者裁决。新 SHA 的适用 CI、外审及合并资格分别验收。

Signed-off-by: zhuowp <9426526+zhuowp@users.noreply.github.com>
@zhuowp
zhuowp requested a review from asto18089 November 8, 2026 07:52
Signed-off-by: zhuowp <9426526+zhuowp@users.noreply.github.com>

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全新 11 分片复审(rebase 到 6f7a3a7 后重放,本地头 09c7bc690)

结论:REQUEST_CHANGES — 接缝本身设计得当、测试经突变证实真实承重、零夹带,但有 3 个 MAJOR 需先处理(其一是落地即破坏下游宿主编译)。

复审设置与证据

  • 原 PR 头 16e8a8d(2 提交)基于旧底座 11cba40;pinvou3-clean 已前移 5 提交至 6f7a3a7(含 #150/#142 对 client.rs 的改动,均在 PR hunk 之外)。评审 worktree 中将两提交 cherry-pick 重放到新底座:自动合并零冲突,逐 hunk 比对确认与原 PR 字节等价(仅 #142 的 +6 行导致锚点平移,hunk 体逐字节一致)。本地 rebase 仅用于评审;未推送、未改写作者分支;PR 当前对最新底座 MERGEABLE。
  • 上游移植声明已核实:codewhale-hq/Codewhale codewhale-hq#7251 存在(closed,同作者同特性)。
  • 本地门(09c7bc690):cargo fmt --all -- --check 通过;cargo check -p codewhale-tui --lib --tests 干净;transport_policy 滤镜 2 passed; 0 failed;vision::tools 19 passed; 0 failed;build_http_client 1 passed; 0 failed。CI 在旧头全绿(早于底座前移,头更新后自动重跑)。
  • 突变验证(实测,非预测):
    • 基线:2 passed; 0 failed
    • M-突变1 删除 client.rs:1366 策略应用 → 0 passed; 2 failed
    • M-突变2 删除 config.rs:7361 视觉继承 → 1 passed; 1 failed(继承恰由测试1独占钉住)
    • M-突变3 删除 vision/tools.rs:67 独立客户端策略 → 0 passed; 2 failed
  • 夹带检查:两提交仅触及声明内 4 文件;提交 2 纯 mod 块 rustfmt 重排(3+/3-);DCO 在位;无 Cargo.lock/CI/无关修改。

值得肯定

  • 按本仓 ponytail 判据这是"能用的最小实现":复用 request_idempotency_header 的 host-owned 模式(serde-skip + or-merge + 视觉继承),单方法无默认体的 trait 是注入缝的固有形状(boxed closure 反而要为 Debug 手写 newtype,更大)。
  • serde 双向封死:Config/VisionModelConfig 均无 Serialize derive,#[serde(skip)] 保证 TOML/JSON 注不进,测试钉住注入拒绝。信任边界审计通过:模型/工具/会话代码不可达该字段。
  • 零策略时行为与底座逐行等价;构造期拒绝传播 fail-closed(测试钉 calls == 1,HTTP/1 兄弟客户端不再构建)。

MAJOR

M1 — trait 文档承诺 "every inference HTTP client",但同 crate 内两条带钥推理路径不受策略约束。
config.rs:2964-2965 写明 "applied to every inference HTTP client";但:

  • crates/tui/src/commands/groups/core/voice.rs:320 post_chat_completions:裸 tls::reqwest_client() POST 带钥 {base_url}/chat/completions,且自己调用 acquire_remote_control_inference_participant()——crate 自己的词汇将其归为 inference;
  • crates/tui/src/tui/prompt_suggestion.rs:396-401 suggestion_client():进程级 OnceLock 静态客户端发带钥 chat/completions(静态初始化意味着即使日后接入策略,进程启动后也无法生效)。
    (另 client.rs:1973-2002 verify_provider_api_key 的 /models 探针带 provider 鉴权头自建客户端,属边界判断,请一并表态。)
    两处在底座就存在、本 PR 未触碰——不是回归;但合同句是本 PR 写下的。修法二选一:(a) 文档收窄到实际交付面("model client 与独立视觉路由构建的每个推理客户端"),并在正文列出仍不受约束的模型侧出口供宿主验收 issue 跟踪;(b) 把两条路径接进策略(小改动;注意 suggestion 的静态初始化时序)。倾向 (a)。

M2 — 配置重载/供应商切换会静默丢掉宿主策略。
runtime_threads.rs:3375 reload_config 最终 *guard = new_config(3435-3437)整体替换;两个入口:POST /v1/config/reload(runtime_api.rs:6978)与 switch_provider(6560)。重载产物 model_http_policy: None,此后新建的所有客户端(client.rs:1366)与视觉路由都不再受约束。仓库已有保留先例:重载会保留 route 身份(reload_config_preserves_profile_selected_named_custom_route,runtime_api/tests.rs:9765)。修法:跨重载保留该字段(建议顺带审视同构的 request_idempotency_header 是否同样被冲掉——那是既有缺口),或至少在字段/trait 文档写明重载交互并挂跟踪项。评 MAJOR 而非 BLOCKER:仓内无生产 setter,与 sibling 缺口同构。

M3 — 落地即破坏 pinvou3 宿主编译,需同批协调。
宿主以路径依赖消费本 crate(pinvou3-app/src-tauri/Cargo.toml:102);VisionModelConfig 仅 derive Debug, Clone, Deserialize(无 Default、非 non_exhaustive),新增必填字段后,宿主 4 处字段完整字面量将编译失败:platform/bridge.rs:1398、1405、1418(生产代码)与 product_runtime/eval_tool_policy.rs:143(测试)。PR 已补齐仓内全部 9 处字面量,说明作者清楚这个破坏类,但宿主侧无法由本 PR 修补。修法:正文注明所需宿主侧一行补丁(model_http_policy: None, ×4)并绑定落地批次,或与宿主侧补丁同批落地。

MINOR

  • m1 vision/tools.rs:20,79:Result<reqwest::Client, ()> + .map_err(|_| ()) 丢掉原因,execute 只回泛化 "Vision HTTP transport unavailable"(旧 .expect 至少带原因)。字段需 Clone,存 String 即可;模型面消息保持泛化是对的(宿主内部细节不该到模型可见面),但请在构造期记一条宿主侧日志。
  • m2 config.rs:2999 Config.model_http_policy 字段无 doc 注释;sibling request_idempotency_header(2988-2998)有 9 行写明 host-only/never-persisted 合同。宿主触摸的面应自带合同。
  • m3 merge_config 的策略优先级(config.rs:10566)无红测试覆盖(保留单侧的编译变体全绿)。
  • m4 VisionModelConfig 的 serde-skip 无注入拒绝测试(仅 Config 侧有,transport_policy_tests.rs:109-112)。
  • m5 顺序脚枪未记录:策略在 build_default_headers 之后运行,而 reqwest default_headers() 是整体替换——策略一旦调用它就清掉鉴权头(fixture 演示的正是这种 clobber)。trait 文档应写明"策略最后运行、需有意识保留鉴权头"。
  • m6 fleet worker 子进程从盘上 roster 配置重建客户端(fleet/exact.rs:311-323),策略不出宿主进程;文档应限定适用范围。

NIT(不阻塞)

  • client.rs:1098-1099 陈旧名称 "Used by build_http_client"(实际消费者已更名 build_http_builder_with_auth_mode)。
  • 测试名 ..._denial_fails_primary_and_vision_closed 的 "closed" 描述了从未执行的端口拒绝路径(fixture 守卫先于拨号触发,127.0.0.1:9 不会被连)。
  • 测试1 assert!(result.success) 在 Ok 路径恒真(ToolResult::json 硬置 true)。
  • vision/tools.rs 构造期 IIFE 与 client.rs 具名闭包风格不一致。
  • "https://api.openai.com/v1" 字面量两处(tools.rs:73 新增 / :185 既有)。

判据对照

  1. 有用且根因完整:是——宿主确需传输约束接缝;交付面(主/HTTP1/独立视觉)完整且经突变证实;缺口在文档合同句(M1)而非交付本身。
  2. 优雅:是——最小实现,复用既有 host-knob 模式,未重复造轮。
  3. 夹带:无——逐提交字节级核验,两提交均干净。
  4. 无功能/性能 bug:交付路径未发现;M2/M3 属生命周期与协调缺口,非代码错误。

M1-M3 解决后可合并;MINOR 可随后续批次。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants