保真模式把只有思考的 length 截断与带未知 delta 键的回答当成失败重试(dissect3) #30

Closed
opened 2026-10-02 13:36:28 +08:00 by iomgaa · 1 comment
Owner

背景

dissect3 已按 #29 接入 1.3.9 的 chat(response_mode="openai")(pi 经本地代理调用,见 #29)。独立对抗审查读 1.3.9 源码,发现保真模式里有两处会把上游的正常回答当成失败去重试。两处今天都不阻塞试点,记在这里供修。

一:只有思考、没有正文与工具调用的回答被当成空补全

types.py:478-490 与 transports/openai_compat.py:727:保真模式下,无正文、无工具调用的回答一律抛 TransientError 重试。但下面两种是上游的正常结局,不是抖动:

  • finish_reason: "length":模型把 max_tokens 全花在思考上,被截断。dissect3 冒烟实测 MiniMax-M3 在 max_tokens=65536 时 16 趟里有 3 趟如此(每趟首条回复写满 65536 个思考 token)。改动前 pi 把它判成「输出被截断」并结束会话,这是我们要记录的结果;现在会被重抽最多 max_attempts 次、每次都可能写满上限,最后变成失败。
  • 正文只有空白、finish_reason: "stop":少见,但同理是模型的回答。

需要:保真模式下,只要上游给出了明确的 finish_reason(length、stop、content_filter 等),就把这条 completion 原样交回(正文可为空,思考内容照交),不算空补全;只有既无正文、无工具、也无 finish_reason(或流异常结束)时才按瞬时错误处理。

二:delta 里出现未知键就整条拒绝

transports/_openai_completion.py:98、:114:delta 只认 role、content、reasoning_content、tool_calls,多一个键(例如 refusal: null、reasoning、reasoning_details)或 tool_calls: null 即抛 TransientError「未知 delta 字段」。审查员直接调用确认过。

2026-10-02 实测中转站当前对 glm-5.3-flash 与 MiniMax-M3 的流(含工具调用)只出现这四个键,所以今天不触发;但 OpenAI 兼容服务常见 refusal: null,渠道一换就会让 100% 的调用失败,而且是以「瞬时错误、重试到耗尽」的形态出现,很难一眼看出原因。

需要:未知的 delta 键忽略(或原样保留在 completion 的附加字段里),值为 null 的已知键按缺省处理;不因此判失败。

验收

  • 上游流只有 reasoning_content、末块 finish_reason: "length":交回 completion,content 为空、思考内容完整、finish_reason 为 length,attempts=1。
  • delta 带 refusal: null 与 tool_calls: null:正常交回,不重试。
## 背景 dissect3 已按 #29 接入 1.3.9 的 `chat(response_mode="openai")`(pi 经本地代理调用,见 #29)。独立对抗审查读 1.3.9 源码,发现保真模式里有两处会把上游的正常回答当成失败去重试。两处今天都不阻塞试点,记在这里供修。 ## 一:只有思考、没有正文与工具调用的回答被当成空补全 `types.py:478-490` 与 `transports/openai_compat.py:727`:保真模式下,无正文、无工具调用的回答一律抛 TransientError 重试。但下面两种是上游的正常结局,不是抖动: - `finish_reason: "length"`:模型把 `max_tokens` 全花在思考上,被截断。dissect3 冒烟实测 MiniMax-M3 在 `max_tokens=65536` 时 16 趟里有 3 趟如此(每趟首条回复写满 65536 个思考 token)。改动前 pi 把它判成「输出被截断」并结束会话,这是我们要记录的结果;现在会被重抽最多 `max_attempts` 次、每次都可能写满上限,最后变成失败。 - 正文只有空白、`finish_reason: "stop"`:少见,但同理是模型的回答。 需要:保真模式下,只要上游给出了明确的 `finish_reason`(`length`、`stop`、`content_filter` 等),就把这条 completion 原样交回(正文可为空,思考内容照交),不算空补全;只有既无正文、无工具、也无 `finish_reason`(或流异常结束)时才按瞬时错误处理。 ## 二:delta 里出现未知键就整条拒绝 `transports/_openai_completion.py:98`、`:114`:delta 只认 `role`、`content`、`reasoning_content`、`tool_calls`,多一个键(例如 `refusal: null`、`reasoning`、`reasoning_details`)或 `tool_calls: null` 即抛 TransientError「未知 delta 字段」。审查员直接调用确认过。 2026-10-02 实测中转站当前对 glm-5.3-flash 与 MiniMax-M3 的流(含工具调用)只出现这四个键,所以今天不触发;但 OpenAI 兼容服务常见 `refusal: null`,渠道一换就会让 100% 的调用失败,而且是以「瞬时错误、重试到耗尽」的形态出现,很难一眼看出原因。 需要:未知的 delta 键忽略(或原样保留在 completion 的附加字段里),值为 `null` 的已知键按缺省处理;不因此判失败。 ## 验收 - 上游流只有 `reasoning_content`、末块 `finish_reason: "length"`:交回 completion,content 为空、思考内容完整、finish_reason 为 `length`,attempts=1。 - delta 带 `refusal: null` 与 `tool_calls: null`:正常交回,不重试。
Author
Owner

已在 1.3.10 修复并发布(polygateway==1.3.10;Release:https://gitea.iomgaa.online/iomgaa/PolyGateway/releases/tag/v1.3.10),与 #31 同根统一修复。

一(只有思考 / 空白正文):结局改按 finish_reason 分类。保真模式:只有思考/拒答/空白 + 明确 finish(非上游错误)→ 原样交回;文本模式 length/过滤且无正文 → ResultInvalidError(length_without_content / filtered_without_content,不重试、不换源、熔断记成功)。零增量且无明确 finish 仍按 2026-07-20 裁决判瞬时。
二(未知 delta 键):任意层 null 视缺省(refusal: null、tool_calls: null,非流式同样修复);保真模式未知字段按 OpenAI 流式惯例无损合并(字符串拼接、对象递归、带 index 数组按 index、index/type/id/format 同值幂等),冲突删键并按源告警一次;reasoning 字段计入活性并作文本 thinking 别名。

验收:reasoning-only + length → 交回、content 空、思考完整、finish=length、attempts=1;refusal:null + tool_calls:null → 正常交回(流式与非流式)。见 tests/unit/test_completion_transport.py、test_openai_completion.py。

真实中转(magpie glm,max_tokens=50,2026-10-15):

1.3.9 1.3.10
原始线路 finish=length,无 [DONE] 同
保真模式 2/2 AllSourcesExhausted 6/6 原样交回(正文 0 字、思考约 100 字,finish=length)
文本模式 2/2 AllSourcesExhausted 6/6 ResultInvalidError length_without_content,2–5s 返回不重试

后续:文本模式 n>1 → #32;文本"只有思考 + stop"证据收集 → #33。

已在 **1.3.10** 修复并发布(`polygateway==1.3.10`;Release:https://gitea.iomgaa.online/iomgaa/PolyGateway/releases/tag/v1.3.10),与 #31 同根统一修复。 **一(只有思考 / 空白正文)**:结局改按 finish_reason 分类。保真模式:只有思考/拒答/空白 + 明确 finish(非上游错误)→ 原样交回;文本模式 length/过滤且无正文 → `ResultInvalidError`(`length_without_content` / `filtered_without_content`,不重试、不换源、熔断记成功)。零增量且无明确 finish 仍按 2026-07-20 裁决判瞬时。 **二(未知 delta 键)**:任意层 `null` 视缺省(`refusal: null`、`tool_calls: null`,**非流式同样修复**);保真模式未知字段按 OpenAI 流式惯例无损合并(字符串拼接、对象递归、带 index 数组按 index、`index/type/id/format` 同值幂等),冲突删键并按源告警一次;`reasoning` 字段计入活性并作文本 thinking 别名。 **验收**:reasoning-only + length → 交回、content 空、思考完整、finish=length、attempts=1;`refusal:null` + `tool_calls:null` → 正常交回(流式与非流式)。见 `tests/unit/test_completion_transport.py`、`test_openai_completion.py`。 **真实中转(magpie glm,max_tokens=50,2026-10-15)**: | | 1.3.9 | 1.3.10 | |---|---|---| | 原始线路 | finish=length,无 `[DONE]` | 同 | | 保真模式 | 2/2 `AllSourcesExhausted` | 6/6 原样交回(正文 0 字、思考约 100 字,finish=length) | | 文本模式 | 2/2 `AllSourcesExhausted` | 6/6 `ResultInvalidError length_without_content`,2–5s 返回不重试 | 后续:文本模式 n>1 → #32;文本"只有思考 + stop"证据收集 → #33。
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: iomgaa/PolyGateway#30