Replies: 3 comments 1 reply
|
Confirmed against master c291e79 — your report is accurate, including the
Fix direction for maintainers: your proposal is the natural seam — replace the |
|
Evidence chain for the main post — six steps, all read from the published package. Every 1 · A step's blocks are recorded in one ordered list// dsh-llm-deepseek/lib/index.js
:1213 const order = [];
:1216 function open(kind) {
:1217 const block = { index: nextIndex++, kind, text: "" };
:1222 order.push(block) // every opened block lands here
:1223 return block
2 · A reasoning token opens a block:1257 if (typeof reasoning === "string" && reasoning.length > 0) {
:1258 if (!reasoningBlock) {
:1259 reasoningBlock = open("reasoning") // ← opens a blockSo any streamed reasoning — even a single token, even if it is the only thing the model sends — puts at least one entry into 3 · The guard asks about
|
| window | reasoning-only turns | how they ended |
|---|---|---|
| 2026-08-31 → 09-11 01:26 | 18 | all aborted — not one completed |
| 2026-09-11 13:18 → 09-13 | 21 | all completed (aborted still occurs in the same window: 3 + 1) |
The shape itself is therefore not new — it goes back to 08-31 — but being accepted as a normal completion starts 09-11 in my store. Across that boundary it is the same session, the same writer and the same log format, so this is not a logging-format artefact.
One reading of that boundary — offered as an observation, not a diagnosis. Before 09-11 these turns ended aborted: the turn was terminated by a cancellation, which is a larger event than the stream itself, so the question "did this response produce anything?" never had to be answered — the guard was never exercised. From 09-11 they finish normally, so they reach the guard for the first time, and it fails on the first contact. If that reading is right, then what happened is not "a change introduced this defect" but "something upstream stopped aborting these turns, which uncovered a defect that had been latent all along" — consistent with the predicate being byte-identical across the two rc builds. I cannot verify the upstream side from here, so this stays a reading; the two facts above are the observation.
What this is not. It is not attributable to the predicate: the guard and the reasoning path are byte-identical in 0.1.5-rc.1 and 0.1.5-rc.2 (:1239 / :1259 in both). Two environment facts sit near that boundary and neither lands on 09-11: my request headers changed maxTokens from 256000 to 65536 on 09-09 18:48, and my desktop build (2.0.9) was updated 09-10 22:20. I report the boundary as an observation, not as a diagnosis.
Boundary of this evidence. All of it is from my store; blocks are classified by event type, not by re-running the model. It shows that the shape occurs, how it is terminated, and when the termination changed — it does not explain why.
8 · One correction I made to the main post, in the open
The main post's fix section originally recommended assistantStreamHasVisibleContent as the preferred fix, on the grounds that it is the official helper. I had not read its implementation when I wrote that. I have now: it delegates to isVisibleChunk, which counts a non-whitespace reasoning-delta as reader-visible (assistant-stream.js:206-208) and counts a tool-call as not visible (:173). For this guard that is wrong in both directions, so the recommendation was wrong, and I revised the post rather than leaving it. The section now carries the correction and the reasoning behind the mistake, which is the same reasoning this report is about: a name was trusted because it was official. If you read the post before this revision, re-read the fix section.
Authorship note: reproduced and measured by me against my own store; root-cause tracing and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out; I will re-verify against the toolchain and correct.
Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com
中文版 / ZH
主帖的证据链 —— 六步,全部读自已发布包。
下文每个 :行号 都是发布构建产物(dsh-llm-deepseek/lib/index.js)里的行。同样的语句在源码里位于 packages/llm/llm-deepseek/src/translate.ts —— 守卫在 :132-141、reasoning 开块在 :158-164 —— 若你更愿意看源码而不是构建产物,那是更好的核对位置。两边我都读过,说法一致。
1 · 一个 step 的块被记进同一个有序表
// dsh-llm-deepseek/lib/index.js
:1213 const order = [];
:1216 function open(kind) {
:1217 const block = { index: nextIndex++, kind, text: "" };
:1222 order.push(block) // 每开一个块都落进这里
:1223 return blockorder 记的不是"说了话的块",而是"被开过的块"。而空响应守卫检查的,正是这一张表。
2 · 一个 reasoning token 就会开一个块
:1257 if (typeof reasoning === "string" && reasoning.length > 0) {
:1258 if (!reasoningBlock) {
:1259 reasoningBlock = open("reasoning") // ← 开了一个块所以任何流式 reasoning——哪怕只有一个 token、哪怕它是模型唯一吐出的东西——都会往 order 里放进至少一条。
3 · 守卫问的是 order,不是内容
:1236 const reason = pendingFinish ?? { kind: "stop" };
:1239 reason: reason.kind === "stop" && order.length === 0
:1240 ? { kind: "error", failure: {
:1241 message: "model returned a completed response with no content",
:1243 code: EMPTY_RESPONSE_CODE } }
:1245 : reason声明意图(:1206 的注释原文:"…'EMPTY_RESPONSE' error finish instead of a successful empty message")是拦住"完成了但什么都没产出"的响应。而谓词把"什么都没产出"实现成了**"没有开过块"——这跟"没有产出可见内容"是两个不同的问题**。
4 · 于是这个形状上守卫永不触发
只吐 reasoning 的一轮恰好开了一个块 ⇒ order.length === 0 为 false ⇒ 三元落到 reason,即 { kind: "stop" } ⇒ 该轮是正常完成:不报错、不重试,而用户看到的结果是空的。
用户真正能看到的两种块是 text 与 tool-call——在这段判定里一个都没被问到。
5 · 重试机制早已接在"守卫永远不会发出的那个码"上
// dsh-llm/lib/index.js
:235 const DEFAULT_MAX_RETRIES = 5;
:236 const DEFAULT_RETRYABLE_CODES = Object.freeze([
:237 EMPTY_RESPONSE_CODE, "RATE_LIMIT", "SERVER", "TIMEOUT", "TRANSPORT" ]);EMPTY_RESPONSE 是默认可重试集里的第一位。所以把谓词改成问对的问题,不是引入新行为,而是接通一条已经存在、且已经配好重试(最多 5 次)的通路。
而这段代码背后的设计意图,就写在发布源码里、紧挨着代码:
// packages/llm/llm/src/error.ts:28-37
* ... adapters classify it as this failure instead of yielding an empty assistant
* message, because an empty message silently ends the turn with nothing for the
* user or the loop to act on. The attempt produced nothing durable, so retry
* policy treats it as safe to repeat.(译:适配器把它归类为这个 failure,而不是产出一条空的 assistant 消息——因为一条空消息会静默结束这一轮,让用户和循环都没有东西可作用。该次尝试没有留下任何持久产物,所以重试策略认为重复是安全的。)
而"只吐 reasoning 的一轮",恰恰就是结束了一轮、而用户和循环都没有东西可作用——正是这条意图要排除的情形。它的字面措辞是"完全没有内容块",而 reasoning 块就是一个内容块;所以我把这件事读作**"已声明的意图"与"守卫的措辞"之间的落差**,而不是"意图缺失"。这个区分正是下面那条修法收窄条件、而不是放宽意图的原因。
6 · 这个区域里该发布带了两个判据,但两个都不表达这道守卫的意图
在发布构建产物里它们是 dsh-llm/lib/types/assistant-stream.js:282 / :293,并经 index.js:2326 再导出。源码里第一个长这样:
// packages/llm/llm/src/assistant-stream.ts:349
export function assistantStreamHasVisibleContent(stream: readonly AssistantStreamRecord[]): boolean {
return stream.some(record => record.type === 'chunk'
? isVisibleChunk(record.chunk)
: runFirstVisibleTime(record) !== undefined)
}它转调 isVisibleChunk,而后者把"非空白的 reasoning-delta"算作读者可见——正是本报告讲的那个形状;并且 blockIsVisible 对 tool-call 返回 false。所以拿它当修法,对已报缺陷等于零改动,还会额外误判"只调工具"的轮次。它的同族 assistantStreamHasVisibleText 正确排除了 reasoning,但连工具调用一起排除,单独用太窄。
这道守卫真正想要的判据是 可见文本 或 工具调用:
order.some(b => b.kind === "tool-call" || (b.kind === "text" && b.text.trim().length > 0))两个 helper 读的都是持久化紧凑记录(AssistantStreamRecord[]),不是这道守卫检查的那张实时 order ⇒ 在该调用点上都不是可直接替换的。在我读过的包里(dsh-llm、dsh-llm-deepseek、dsh-goal)未发现调用点——我没有全仓扫描,所以这是"我读过的包内无调用点",不是"全仓都没用"。
7 · 我在自己的样本里量到的东西
对我自己的会话日志做了一次扫描 —— 285 个文件、3,973 个 turn,其中 3,320 个以 completed 结束 —— 找到 21 个 turn:其 turn/end 是 completed,而该 turn 内没有任何 step 产出 text 或 tool-call(整个 turn 的 assistant/message 块全是 reasoning):占 completed 的 0.63%。
分母 —— 因为"一个会话"不能当样本。 这 21 例全部在同一个长期会话里;该会话共 1,006 个 turn,其中 890 个 completed ⇒ 在它内部,该形状占 21 / 890 = 2.4%。而且它们不是一整块连续:21 例分成 13 段、散布在 turn 946–1145 之间(最大间隔 48 个 turn)⇒ 这是 21 个独立观察,不是"同一个事件被重复计数"。(该会话在我的迁移副本里也存在;我得到的两组库计数是同样的 21 个 turn,不是 42。)
触发形状是旧的,结局是新的。 统计"只吐 reasoning、别的什么都不吐"的 turn(不论怎么结束):
| 窗口 | 只吐 reasoning 的 turn | 结局 |
|---|---|---|
| 2026-08-31 → 09-11 01:26 | 18 | 全部 aborted —— 一例 completed 都没有 |
| 2026-09-11 13:18 → 09-13 | 21 | 全部 completed(同一窗口里 aborted 仍会发生:3 + 1 例) |
所以形状本身不新——最早可追到 08-31;但"被当作正常完成"是在 09-11 才开始的(在我的样本里)。跨过这个边界的是同一个会话、同一个写作方、同一种日志格式,所以这不是日志格式造成的假象。
对这个边界的一种读法——作为观察提出,不作为诊断。 09-11 之前这些轮次以 aborted 结束:那是被"取消"终止,而取消是一个比流本身更大的事件,于是"这一轮到底有没有产出东西"这个问题从没被问过——守卫从未被检验。09-11 之后它们正常完成,于是第一次走到守卫面前,而守卫在第一次接触时就失效了。如果这个读法成立,那么发生的不是"某次改动引入了这个缺陷",而是"上游不再 abort 这类轮次,顺带把一个一直潜伏的缺陷暴露了出来"——这与"谓词在两个 rc 构建里逐字相同"是一致的。上游那一侧我在这里无法验证,所以它只是一个读法;上面两条是观察。
这不能归因于哪个东西。 它不能归因于该谓词:守卫与 reasoning 写入路径在 0.1.5-rc.1 与 0.1.5-rc.2 之间逐字相同(同为 :1239 / :1259)。边界附近有两件环境事实,但都不落在 09-11:我的请求头在 09-09 18:48 把 maxTokens 从 256000 改成 65536;我的桌面构建(2.0.9)在 09-10 22:20 更新过。我把这个边界当观察报告,不当诊断。
这份证据的边界。 以上全部来自我的样本;块分类依据的是事件类型,不是重跑模型。它能说明"该形状确实发生、如何被收尾、以及收尾方式何时变了"——它不解释为什么。
8 · 我对主帖做过的一处修正(公开说明)
主帖的修法段最初把 assistantStreamHasVisibleContent 作为首选修法推荐,理由是"它是官方 helper"——写那段时我没有读它的实现。现在读过了:它转调 isVisibleChunk,而后者把"非空白的 reasoning-delta"算作读者可见(assistant-stream.js:206-208),并把 tool-call 算作不可见(:173)。对这道守卫而言两个方向都错,所以那条推荐是错的,我改了帖子而不是留着。修法段现在带着这处修正,以及产生那个错误的推理——因为一个名字"是官方的"就免于验证——而这正是本报告在讲的推理。如果你在我修正之前读过主帖,请重读修法段。
声明:本次实测与统计由我针对自己的数据完成;根因追查与文稿撰写由 AI 辅助;核验与发布由我本人负责。我没有工程背景 —— 若任何技术表述有误,请直接指出,我会对照工具链重新核实并更正。
本报告由 OfferKuai(Offer快)团队提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com
|
Thanks — this correction is right, and I verified it against source at c291e79. My earlier endorsement of assistantStreamHasVisibleContent as the fix seam was wrong for this call site, in exactly the two directions you describe:
The shape mismatch is real too: both helpers take persisted AssistantStreamRecord[] (compact records), while the guard inspects the live OpenBlock[] list built in translate.ts:116-124 — record kinds vs block kinds, so neither is a drop-in at :135. Your proposed predicate is the right one at this site: order.some(b => b.kind === 'tool-call' || (b.kind === 'text' && b.text.trim().length > 0)) Two confirmations from source: a tool-call block is only opened when the model actually emits a tool_calls delta (translate.ts:177-183), so it can't false-positive on an empty tool block; and OpenBlock.text accumulates only non-empty deltas for text blocks (:167-175), so the trim() check is doing real work for whitespace-only text. The retry side stands as established earlier in this thread: EMPTY_RESPONSE_CODE is already first in the default retryable set, so the narrowed predicate switches on a path that already retries — no new behavior beyond the question it asks. Your measured 0.63% (2.4% within the long session) and the 09-11 boundary are useful. The "upstream stopped aborting these turns, exposing a latent defect" reading is the most consistent one I can see from here — the predicate being byte-identical across the two rc builds supports it — though like you I can't verify the upstream side. For the record: my earlier comment's fix suggestion should be read with your correction applied — the helper is the wrong tool here; the inline order.some predicate is the seam. Thanks for re-verifying instead of taking the official name on faith. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary.
@deepseek-ai/dsh-llm-deepseek@0.1.5-rc.2has an empty-response guard that is meant to turn a completed response carrying no content into a retryableEMPTY_RESPONSEerror. Its predicate tests whether any block was opened, not whether anything visible was produced — and a streamed reasoning token opens areasoningblock. So a turn that streams only reasoning always satisfies the guard's negative case and is reported as a normal completion. The response is not retried, and the user sees a turn that "finished" with nothing on screen.Where.
A reasoning-only turn opens exactly one block, so
order.length === 0is false and the guard falls through to{ kind: "stop" }.What the release says the guard is for — in its own words. The rationale ships next to the constant:
That rationale is stated as an effect: a turn that ends with nothing for the user or the loop to act on. A reasoning-only turn has exactly that effect. The predicate, though, is faithful to the phrase it was written from — "carried no content blocks at all" — and a reasoning block is a content block. So I am not claiming the guard ignores its own intent. I am claiming that on this shape the stated rationale and the predicate's phrasing part company, and that the phrasing is what reached the code. Two supporting facts, both from the same release:
EMPTY_RESPONSE_CODEis the first entry of the default retryable set —dsh-llm/lib/index.js:236-242(DEFAULT_MAX_RETRIES = 5,DEFAULT_RETRYABLE_CODES = Object.freeze([EMPTY_RESPONSE_CODE, "RATE_LIMIT", "SERVER", "TIMEOUT", "TRANSPORT"])). The guard exists so an attempt that produced nothing durable can be repeated; as written it cannot fire for the one shape where "nothing durable" is true and the user notices.assistantStreamHasVisibleContent/assistantStreamHasVisibleText(dsh-llm/lib/types/assistant-stream.js:282, :293, re-exported atdsh-llm/lib/index.js:2326). In the packages I read (dsh-llm,dsh-llm-deepseek,dsh-goal) I found no call site for either. (Their semantics do not fit this guard as written — see the fix section.)Fix: ask for visible text or a tool call — and note that neither exported helper alone expresses that. Because this guard's own bug is "the predicate asks the wrong question", I read the two exported predicates' implementations instead of assuming their names describe their behaviour. They do not:
For this guard,
assistantStreamHasVisibleContentis therefore wrong in both directions: a reasoning-only turn still counts as carrying visible content (the same failure as the predicate being reported), and a tool-call-only turn would be judged as having nothing visible.assistantStreamHasVisibleText(:293) excludes reasoning correctly but also excludes tool calls, so it is too narrow on its own. The intent here is the union — visible text OR a tool call. (These helpers also read the durable compact stream records, not the liveorderof opened blocks, so they are not drop-in at:1239even where the semantics happen to line up.)The predicate to use at this call site — filter by
kind, not by "is there text". The blocks inorderare built byopen(kind)as{ index, kind, text }(:1216-1223), and a reasoning block'stextis non-empty too:So a predicate such as "some block has non-empty text" would classify a reasoning-only turn as visible and change nothing. The distinguishing field is
kind(orderblocks) /type(message blocks) — the two levels do not share a key name, which is easy to get wrong:Narrow the trigger, don't blanket-retry. The guard should stay narrow: only a completed turn that produced neither
textnortool-callbecomesEMPTY_RESPONSE. Two limits I want to state rather than leave for you to ask:dsh-llm/lib/index.js:235,DEFAULT_MAX_RETRIES = 5). The argument for retrying here is that the retry can only fire on a turn that had nothing visible to lose; whether the terminal state after 5 attempts needs its own message is a product decision I can't answer from the outside.One thing I could not establish. In my environment the trigger shape is old but its ending is new: turns that stream only reasoning occur from 2026-08-31 onward, yet until 2026-09-11 every one of them ended
aborted; from 09-11 13:18 local they endcompleted. The predicate and the reasoning path are byte-identical in0.1.5-rc.1and0.1.5-rc.2(same lines,:1239/:1259), so this guard's logic is not what changed in my environment — I do not attribute the boundary to a specific change, and I am reporting it as an observation, not as a diagnosis.Related but distinct. This is not the same gap as item 2 of the community's unfixed-issues list (#6520), where a reasoning-only turn is serialized as
content: ""and every later turn of that session fails with a gateway 400. That is the serialization side. This one is the termination predicate: the turn is accepted as successfully finished in the first place, no error is raised, and nothing is retried. Same trigger shape, different layer — a fix in this layer may also reduce how often that one is reached, but it does not replace it.Trigger. Any turn where the model streams reasoning but emits no text and no tool call.
Environment.
@deepseek-ai/dsh-llm-deepseek@0.1.5-rc.2,@deepseek-ai/dsh-llm@0.1.5-rc.2. Line numbers are from the published package;:1213/:1216/:1222/:1236/:1239/:1257were read directly and are quoted verbatim.Revision note: an earlier version of this section recommended
assistantStreamHasVisibleContentas the preferred fix on the grounds that it is official — I had recommended it without reading its implementation. I have now read it, it does not fit, and the recommendation is replaced above. Flagging it rather than quietly swapping, since the reasoning that produced the wrong recommendation (trusting a name because it is official) is the same reasoning this report is about.Second revision note: an earlier version of this report carried the heading "why this looks like an oversight rather than intent". Having now read the rationale that ships with
EMPTY_RESPONSE_CODE(quoted in that section), "oversight" is the wrong word — the predicate is faithful to the phrase the rationale itself uses ("carried no content blocks at all"), and a reasoning block is a content block. The claim is now narrower and stated in the code's own terms: the rationale and the phrasing part company on this shape. I would rather hold the smaller claim than the more flattering one.Authorship note: reproduced and measured by me against my own store; root-cause tracing and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out; I will re-verify against the toolchain and correct.
Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com
中文版 / ZH
摘要。
@deepseek-ai/dsh-llm-deepseek@0.1.5-rc.2里有一道空响应守卫:本意是把"完成了但没有任何内容"的响应转成可重试的EMPTY_RESPONSE错误。但它判的是"有没有开过块",而不是"有没有产出可见内容"——而流式吐出的一个 reasoning token 就会开一个reasoning块。于是一轮只吐 reasoning 的响应,永远满足守卫的反面情形,被当成正常完成上报:不重试,用户看到的是"这一轮结束了"但屏幕上什么都没有。位置。
只吐 reasoning 的一轮恰好开了一个块 ⇒
order.length === 0为假 ⇒ 守卫被绕过,落到{ kind: "stop" }。这道守卫「是为了什么」——用发布自己的话。 理由就写在那个常量旁边:
(译:该常量用于"正常完成、但完全没有内容块"的响应。供应商偶尔会产出这种退化完成(一次输出为空的终止 stop);适配器把它归类为这个 failure,而不是产出一条空的 assistant 消息——因为一条空消息会静默结束这一轮,让用户和循环都没有东西可作用。该次尝试没有留下任何持久产物,所以重试策略认为重复是安全的。)
这条理由说的是效果:一轮结束时"用户和循环都没有东西可作用"。而只吐 reasoning 的一轮,恰恰就是这个效果。但那个谓词,是忠于它书写时所依据的措辞——"完全没有内容块"——而 reasoning 块就是一个内容块。所以我并不主张这道守卫违背了它自己的意图;我主张的是:在这个形状上,"已声明的理由"与"谓词的措辞"分岔了,而抵达代码的是措辞。同一份发布里还有两处旁证:
EMPTY_RESPONSE_CODE是默认可重试集里的第一位——dsh-llm/lib/index.js:236-242(DEFAULT_MAX_RETRIES = 5,DEFAULT_RETRYABLE_CODES = Object.freeze([EMPTY_RESPONSE_CODE, "RATE_LIMIT", "SERVER", "TIMEOUT", "TRANSPORT"]))。这道守卫存在的意义,是让"没有留下任何持久产物"的那次尝试可以被重试;而按现在的写法,它恰恰在"确实什么都没留下、且用户看得见"的那个形状上永不触发。assistantStreamHasVisibleContent/assistantStreamHasVisibleText(dsh-llm/lib/types/assistant-stream.js:282, :293,经dsh-llm/lib/index.js:2326再导出)。在我读过的包里(dsh-llm、dsh-llm-deepseek、dsh-goal)未发现调用点(我没有全仓扫描,所以这是"我读过的包内无调用点",不是"全仓都没用")。(但它们的语义并不适配这道守卫,见下"修法"段。)修法:请把判据改为"有可见文本 或 有工具调用"——并且注意:两个已导出的判据单独都表达不了这件事。 因为这道守卫自己的毛病就是"问错了问题",所以这次我读了这两个导出判据的实现,而不是假定函数名描述了它的行为。它们不符合:
所以对这道守卫而言,
assistantStreamHasVisibleContent两个方向都错:只吐 reasoning 的一轮仍被算作有可见内容(即与本报告所指控的失效完全相同),而"只调用工具、不吐文本"的一轮会被判成"什么都没看见"。assistantStreamHasVisibleText(:293)正确地排除了 reasoning,但也排除了工具调用,因而单独用又太窄。这里的意图是两者的并集——有可见文本,或有工具调用。(另外,这两个函数读的是持久化的紧凑 stream 记录,不是守卫检查的那张实时order表,所以即便语义对得上,在:1239处也不是直接替换。)在这个调用点上应使用的谓词——按
kind过滤,不要用"有没有 text"。order里的块由open(kind)构造成{ index, kind, text }(:1216-1223),而 reasoning 块的text同样是非空的:所以像"只要某块 text 非空就算可见"这样的谓词,会把只吐 reasoning 的一轮判成"有可见内容",等于什么都没改。真正的区分字段是
kind(order块)/type(消息级块)—— 这两层不共用同一个键名,很容易写错:把触发条件收窄,不要一巴掌重试。 守卫应当保持窄:只有"既没有
text也没有tool-call"的完成轮才变成EMPTY_RESPONSE。有两点我主动说清、不留给追问:dsh-llm/lib/index.js:235,DEFAULT_MAX_RETRIES = 5)。在这里重试之所以站得住,是因为它只能作用在"本来就没有任何可见内容可丢"的轮次上;至于 5 次之后那个终态是否该有自己的提示,那是产品决策,我从外部答不了。有一件事我没能确定。 在我的环境里,触发形状是旧的,但结局是新的:只吐 reasoning 的轮次从 2026-08-31 起就有,但直到 2026-09-11 之前它们每一例都以
aborted结束;自本地时间 09-11 13:18 起,它们以completed结束。而该谓词与 reasoning 写入路径在0.1.5-rc.1与0.1.5-rc.2中逐字相同(同为:1239/:1259)—— 所以在我这个环境里,变的不是这道守卫的逻辑;我对这个时间边界不做归因,只作为观察报告,不作为诊断。相关但不同。 这不是社区未修清单(#6520)第 2 条那件事——那里是纯推理轮被序列化成
content: "",导致该会话此后每轮都被网关 400。那是序列化侧。本条是终止判据:这类轮次一开始就被当成正常结束,不报错、不重试。触发形状相同、层不同——修好本层可能减少撞上那一层的次数,但不能替代它。触发条件。 任何"模型只吐 reasoning、不吐文本也不调工具"的轮次。
环境。
@deepseek-ai/dsh-llm-deepseek@0.1.5-rc.2、@deepseek-ai/dsh-llm@0.1.5-rc.2。行号取自已发布包,:1213/:1216/:1222/:1236/:1239/:1257均为逐字读取。修正说明:本段早先的版本把
assistantStreamHasVisibleContent作为首选修法推荐,理由是"它是官方的"——我没有读它的实现就推荐了。现已读过:它不适配,上面的分析取代了那条推荐。我选择把这次修正标出来,而不是悄悄替换,因为产生那条错误推荐的推理——"因为是官方的,就免于验证"——正是本报告在讲的那个推理。第二处修正说明:本报告早先有一个小标题写作"为什么更像疏漏而不像有意"。在读过
EMPTY_RESPONSE_CODE随代码一起发布的理由之后(已引在该段)——"疏漏"这个词用错了:那个谓词是忠于理由本身所用的措辞("完全没有内容块")写的,而 reasoning 块就是一个内容块。现在的说法更窄,而且用的是代码自己的术语:理由与措辞在这个形状上分岔。我宁可持一个更小的主张,而不是一个更好看的主张。声明:本次实测与统计由我针对自己的数据完成;根因追查与文稿撰写由 AI 辅助;核验与发布由我本人负责。我没有工程背景 —— 若任何技术表述有误,请直接指出,我会对照工具链重新核实并更正。
本报告由 OfferKuai(Offer快)团队提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com
All reactions