chat() 无法传递采样参数:temperature / seed / max_tokens 都够不着,下游无法固定解码 #4

Closed
opened 2026-07-31 16:24:17 +08:00 by iomgaa · 1 comment
Owner

问题

GatewayClient.chat() 没有任何途径可以设置采样参数。全库检索 temperature 零命中;
ChatRequest.overlay(types.py:46)虽然会被 transport 并进请求体
transports/openai_compat.py:243payload.update(overlay)),但它只被
结构化输出中间件填充(middleware/structured.py:98),chat() 的签名里没有它,
调用方够不着。

结果是下游无法固定解码行为。

为什么这对 dissect 是阻塞性的

该项目是一组受控实验,它的公共设定里写明「解码固定为 temperature 0」,Phase-0 的
交付物也写明「temperature 0、关闭 thinking 档」。原因是实验要在每格配置上跑 5 个
seed 并报标准差,而这个标准差必须只反映被研究的那个变量,不能混进解码温度带来的
随机性。拿不到 temperature 控制,就等于所有实验条件都在一个未知且可能随供应商默认
值变化的温度下跑——这批数据的可复现性无从谈起。

同理还需要 seed:实验代码的随机性必须显式受 seed 控制并记录进 run 快照。

两个层次的需求,建议都支持

调用级,用于逐次变化的参数(seed 每个 rollout 不同):给 chat() 增加一个
带默认值的 keyword-only 参数,并入 ChatRequest.overlay

async def chat(
    self,
    messages: list[dict[str, Any]],
    *,
    ...,
    overlay: Mapping[str, Any] | None = None,   # 新增
) -> LLMResponse:

带默认值的 keyword-only 参数不破坏 chat() 的既有调用方,与 docstring 里
「签名冻结」的兼容承诺不冲突。合并顺序建议让调用方的 overlay 覆盖配置级的,
但让结构化中间件的注入优先级最高(它关系到响应能否被解析)。

配置级,用于全局固定的参数(temperature=0 对本项目恒定):SourceConfig
增一个 extra_body 字段,从 .env 以 JSON 串配置,装配期解析。好处是不必让每个
调用点都记得传 temperature——漏传一次就是一格实验作废,而这种错误不会报错,只会
让数字悄悄变得不可比。

顺带一提

providers.py 的 minimax profile 里 thinking_off 是空字典,也就是
SourceConfig.enable_thinking=False 对 MiniMax 源不产生任何效果。如果 MiniMax 有
关闭推理的请求参数,补进 profile 会更合适;如果没有,建议在 docstring 里写明这一档
对该 provider 无效,免得调用方以为自己关掉了。这一条与上面的 overlay 需求相关但独立
——有了 extra_body 也能绕过去,只是语义不如 profile 清晰。

## 问题 `GatewayClient.chat()` 没有任何途径可以设置采样参数。全库检索 `temperature` 零命中; `ChatRequest.overlay`(types.py:46)虽然会被 transport 并进请求体 (`transports/openai_compat.py:243` 的 `payload.update(overlay)`),但它只被 结构化输出中间件填充(`middleware/structured.py:98`),`chat()` 的签名里没有它, 调用方够不着。 结果是下游无法固定解码行为。 ## 为什么这对 dissect 是阻塞性的 该项目是一组受控实验,它的公共设定里写明「解码固定为 temperature 0」,Phase-0 的 交付物也写明「temperature 0、关闭 thinking 档」。原因是实验要在每格配置上跑 5 个 seed 并报标准差,而这个标准差必须只反映被研究的那个变量,不能混进解码温度带来的 随机性。拿不到 temperature 控制,就等于所有实验条件都在一个未知且可能随供应商默认 值变化的温度下跑——这批数据的可复现性无从谈起。 同理还需要 `seed`:实验代码的随机性必须显式受 seed 控制并记录进 run 快照。 ## 两个层次的需求,建议都支持 **调用级**,用于逐次变化的参数(`seed` 每个 rollout 不同):给 `chat()` 增加一个 带默认值的 keyword-only 参数,并入 `ChatRequest.overlay`。 ```python async def chat( self, messages: list[dict[str, Any]], *, ..., overlay: Mapping[str, Any] | None = None, # 新增 ) -> LLMResponse: ``` 带默认值的 keyword-only 参数不破坏 `chat()` 的既有调用方,与 docstring 里 「签名冻结」的兼容承诺不冲突。合并顺序建议让调用方的 overlay 覆盖配置级的, 但让结构化中间件的注入优先级最高(它关系到响应能否被解析)。 **配置级**,用于全局固定的参数(`temperature=0` 对本项目恒定):`SourceConfig` 增一个 `extra_body` 字段,从 `.env` 以 JSON 串配置,装配期解析。好处是不必让每个 调用点都记得传 temperature——漏传一次就是一格实验作废,而这种错误不会报错,只会 让数字悄悄变得不可比。 ## 顺带一提 `providers.py` 的 minimax profile 里 `thinking_off` 是空字典,也就是 `SourceConfig.enable_thinking=False` 对 MiniMax 源不产生任何效果。如果 MiniMax 有 关闭推理的请求参数,补进 profile 会更合适;如果没有,建议在 docstring 里写明这一档 对该 provider 无效,免得调用方以为自己关掉了。这一条与上面的 overlay 需求相关但独立 ——有了 extra_body 也能绕过去,只是语义不如 profile 清晰。
Author
Owner

已在 v1.0.5 实现,两个层次都支持(ce630a3 合入 main,包已发到 Gitea PyPI)。

实现

层次 用法 场景
调用级 chat(messages, overlay={"seed": 42}) 逐次变化的参数
配置级 {SCOPE}__{PROVIDER}__{N}__EXTRA_BODY={"temperature":0} 全局恒定的参数

优先级 结构化注入 > 调用级 overlay > 源级 extra_body,由现有洋葱层序天然给出,没有引入新机制。overlay 是带默认值的 keyword-only 参数,既有调用点零改动,与"签名冻结"不冲突——这一点按 issue 里的判断采纳了。

issue 未提、但一并处理的四点

设计过程中发现四个不做就会静默出错的地方,正是 issue 所担心的那种失败形态(不报错,只是数字悄悄不可比):

  1. 采样参数进缓存 key。不进的话,同 messages 跑 5 个 seed 会全部命中第一次的响应,标准差恒为 0 且不报错,整批实验静默作废。代价是逐 rollout 变化的 seed 天然全部 miss(正确语义,但缓存对这条路径不再省钱)。不传采样参数时 key 与旧版逐字相同,存量缓存不受影响。
  2. 保护键黑名单 {model, messages, stream, stream_options},配了直接 ValueError。它们被覆盖会让成本按错单价算、绕过流式看门狗、丢 usage 帧。不可 JSON 序列化的值(numpy 标量)同样在进洋葱前报错——否则会在缓存层降级保护之外抛裸 TypeError,连一行遥测都留不下。
  3. 遥测新增 sampling(端口 20 → 21)。"实验可复现"的另一半是参数进快照,不记的话事后无法证明某批数据跑在什么温度下。列语义是「调用方意图 ⊎ 生效源 extra_body」,不含结构化注入的 response_format
  4. OCR / embedding 路径剥离 extra_body 并 warning。那两条路径的 transport 根本不发它(embed payload 硬编码 {model, input}、MonkeyOCR 只走 multipart),不剥的话遥测会记录一个从未发出的参数——那是数据造假,比参数失效更坏。

另外 SourceConfig 加 mapping 字段后不再 hashable,asdict()/deepcopy() 亦不适用(加任何 mapping 字段的固有代价)。已核实库内无调用点会踩,并加了锁定测试防止有人当 bug "修"回去。

关于 minimax 那条

属实。openaiminimax 的 thinking profile 两档皆空,enable_thinking=False 对它们不产生任何效果。查代码时发现 providers.py 那条"OpenAI 兼容基线"的注释在词法上其实属于 minimax 条目,openai 条目反倒没有注释——两者都只解释了"为何为空",没点明后果。已按你的建议补 docstring 说明(覆盖两个 provider),不改行为:真需要控制时用 extra_body 绕过。

文档

wiki 新增 指南-采样参数,并同步了 参考-公共API / 参考-配置键 / 指南-响应缓存 / 指南-遥测与成本

顺带补了两处历史欠账:v1.0.1/v1.0.2 当初只 bump 了版本号、实质内容(装配守卫在所有构造路径生效、scope 规范化为小写会切 Redis key 命名空间)一行没进 wiki;另经独立审查修正了 6 处 wiki 与代码不符之处,其中一条是教程说改错 API_KEY 会得到 AllSourcesExhausted,实测是 CircuitOpenError——照着写的下游会捕不到。

已在 **v1.0.5** 实现,两个层次都支持(`ce630a3` 合入 main,包已发到 Gitea PyPI)。 ## 实现 | 层次 | 用法 | 场景 | |---|---|---| | 调用级 | `chat(messages, overlay={"seed": 42})` | 逐次变化的参数 | | 配置级 | `{SCOPE}__{PROVIDER}__{N}__EXTRA_BODY={"temperature":0}` | 全局恒定的参数 | 优先级 **结构化注入 > 调用级 overlay > 源级 extra_body**,由现有洋葱层序天然给出,没有引入新机制。`overlay` 是带默认值的 keyword-only 参数,既有调用点零改动,与"签名冻结"不冲突——这一点按 issue 里的判断采纳了。 ## issue 未提、但一并处理的四点 设计过程中发现四个不做就会**静默出错**的地方,正是 issue 所担心的那种失败形态(不报错,只是数字悄悄不可比): 1. **采样参数进缓存 key**。不进的话,同 messages 跑 5 个 seed 会全部命中第一次的响应,**标准差恒为 0 且不报错**,整批实验静默作废。代价是逐 rollout 变化的 `seed` 天然全部 miss(正确语义,但缓存对这条路径不再省钱)。不传采样参数时 key 与旧版逐字相同,存量缓存不受影响。 2. **保护键黑名单** `{model, messages, stream, stream_options}`,配了直接 `ValueError`。它们被覆盖会让成本按错单价算、绕过流式看门狗、丢 usage 帧。不可 JSON 序列化的值(numpy 标量)同样在进洋葱前报错——否则会在缓存层降级保护之外抛裸 `TypeError`,连一行遥测都留不下。 3. **遥测新增 `sampling` 列**(端口 20 → 21)。"实验可复现"的另一半是参数进快照,不记的话事后无法证明某批数据跑在什么温度下。列语义是「调用方意图 ⊎ 生效源 extra_body」,不含结构化注入的 `response_format`。 4. **OCR / embedding 路径剥离 `extra_body` 并 warning**。那两条路径的 transport 根本不发它(embed payload 硬编码 `{model, input}`、MonkeyOCR 只走 multipart),不剥的话遥测会记录**一个从未发出的参数**——那是数据造假,比参数失效更坏。 另外 `SourceConfig` 加 mapping 字段后不再 hashable,`asdict()`/`deepcopy()` 亦不适用(加任何 mapping 字段的固有代价)。已核实库内无调用点会踩,并加了锁定测试防止有人当 bug "修"回去。 ## 关于 minimax 那条 属实。`openai` 与 `minimax` 的 thinking profile 两档皆空,`enable_thinking=False` 对它们**不产生任何效果**。查代码时发现 `providers.py` 那条"OpenAI 兼容基线"的注释在词法上其实属于 minimax 条目,openai 条目反倒没有注释——两者都只解释了"为何为空",没点明后果。已按你的建议补 docstring 说明(覆盖两个 provider),不改行为:真需要控制时用 `extra_body` 绕过。 ## 文档 wiki 新增 [指南-采样参数](https://gitea.iomgaa.online/iomgaa/PolyGateway/wiki/指南-采样参数),并同步了 `参考-公共API` / `参考-配置键` / `指南-响应缓存` / `指南-遥测与成本`。 顺带补了两处历史欠账:v1.0.1/v1.0.2 当初只 bump 了版本号、实质内容(装配守卫在所有构造路径生效、scope 规范化为小写会切 Redis key 命名空间)一行没进 wiki;另经独立审查修正了 6 处 wiki 与代码不符之处,其中一条是教程说改错 API_KEY 会得到 `AllSourcesExhausted`,实测是 `CircuitOpenError`——照着写的下游会捕不到。
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: iomgaa/PolyGateway#4