DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-collapse-guard

d

dsh-collapse-guard

Identified★ 1

Abort a collapsing LLM stream: trip on repeated short lines and end the step as a provider failure so the garbage never reaches the model's visible history.

塌缩止血 · dsh-collapse-guard

模型吐着吐着开始「好。写。发。好。写。发。」刷屏的时候,把这个流掐掉。

仓库名 dsh-collapse-guard,npm 包名 doudou-collapse-guard(本地开发时挂在 @local/doudou-collapse-guard)。装的时候认包名,搜的时候认仓库名,两个指同一个东西。

兼容性:dsh >= 0.2.1,node >= 20。上限没锁 —— 0.3 的破坏性变更(如果 llm/stream 的签名或 finish 的语义变了)不是声明里能挡住的,装 0.3 之后先看 fires.jsonl 有没有正常记录,再决定要不要留。

一个 DeepSeek Harness 插件。挂在 llm/stream 瀑布上包住上游 chunk 流,扫最近一百多行,发现退化成重复碎念就断流,补一个 terminal finish(error),让这一步以「provider 报错」的样子收场。

它管什么、不管什么

  • 管得住「一直吐到爆」:一次能救回二十几万字符的垃圾吞吐。
  • 管不住「它压根不该塌」:那是模型和参数的事,把 reasoning_effort 降下来才是正解。

为什么要这么断

不是所有断法都一样:

断法后果
补 finish(stop)agent-loop 当正常结束,半截内容组装成 assistant/message 进历史 —— 下一轮模型照着垃圾往下写
agent.cancel()走 signal.aborted 分支,写 assistant/message 且 interrupted:true,照样进历史
补 finish(error)只写 assistant/attempt(没有 surfaceOp,模型看不见),本步不产生 assistant/message

第三种是唯一一条路。本插件走的就是它,failure.code = STREAM_COLLAPSE。

这个码刻意不在 dsh-llm-retry 的默认重试集里(默认只有 EMPTY_RESPONSE / RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT):塌缩是请求本身的属性,重试等于把整个 prompt 再烧一遍。

六种形态

规则码抓的是什么
short-line-tight最近 48 行里不同短句 ≤ 2 种 —— 买的是提前量
short-line-dominant整窗(128 行)里不同短句 ≤ 4 种
short-line-cycle5–12 种短句按固定周期原样绕圈(Write / Execute / Run / Test / Done)
short-line-spread种数更多、但最多那种占一半以上(偏斜型轮转)
short-line-run尾巴上连着 12 行以上短句、这段里不同短句 ≤ 8 种、最多那种占 45% 以上(塌缩挂在正常推理后面时,整窗会被长行稀释,前面几条都进不去)
paragraph-repeat滚动原文里同一段连着重复 ≥ 3 次(累计不算,免得误杀时政稿里重复引用的同一句话)
hard-cap单个通道超过 hardChars(默认 200000)还在继续

段落复读不看分行。模型是一个字一个字吐的,整段复读往往一个换行都没有,凑不齐一行 —— 所以另开了一段 4096 字符的滚动原文,在里面找「连着重复三遍的周期串」(段长 8–400 字),每涨 48 字符扫一次。不换行的 switch switch switch 也走这条路。

围栏(```)内的内容不参与判定;超过 skipLineChars(默认 2000)的长行跳过且不清窗。

形态会变,而且是跟着上下文变的

这一条要单独说:塌缩长什么样,取决于记忆库和上下文里有什么。

它不是固定的乱码,是模型在复制它见过最多的那种形状。你上下文里全是「好。写。发。」,它就吐「好。写。发。」;你写过大量「(输出)」「(写)」「(回答她)」,它就吐「好 + 动作词」交替。换了人、换了历史、换了语言,形状就跟着换。

所以:

  • 上面这张表不是穷举,是目前见过的六种。见到新形状就加一条规则,别指望现有规则能兜住全部。
  • 判据要留余量,宁可多一条窄规则,也别把阈值放宽到会误杀正常写作。
  • 真要少塌,光靠这里掐不够 —— 得从源头减:别让「好」「写」「发」这类动作词当段落写进正文,它们会原样教回来。

命中之后你会看到

界面上是一句出错提示,形如:

模型推理通道进入重复塌缩(尾窗只剩 3 种短句,「发。」出现了 42 次,已读到 4403 字符),守卫掐断了本次流。

这一轮结束,不会自动重试。再发一句就继续。

怎么关

三种,任选:

  1. 建一个空文件:~/.dsh/doudou-collapse-guard/off(下一个请求就生效,不用重启)
  2. 环境变量:DOUDOU_COLLAPSE_GUARD_OFF=1
  3. 在 DSH 设置里把这个插件关掉

怎么改判据

配置优先级:默认值 → cordis.patch.yml 的 config → 环境变量 DOUDOU_COLLAPSE_GUARD_CONFIG(JSON)。

键默认说明
window128主判据窗口行数。这是拐点:64 行误报多,192 行召回掉到 53.7%
distinctMax4主判据允许的不同短句种数
distinctSpreadMax8放宽档:种数到这么多也报(要配合周期或占比)
distinctSpreadShare0.5放宽档要求「最多那种」占全部短句的比例
fenceMaxLines200围栏看门狗:开着这么多行还没闭合,就当它没打开
longTolerance6窗口内允许混几条长行
tightWindow48紧判据窗口
tightDistinctMax2紧判据允许的不同短句种数
tightLongTolerance1紧判据允许的长行数
maxLineChars32超过这个长度算「长行」,不计入短句统计
skipLineChars2000超过这个长度整行跳过(多半是工具输出)
longLineRepeat3同一条长行重复几次算段落复读
minChars400总字符数不到这个数不判
hardChars200000单通道硬上限
watch['reasoning-delta','text-delta']盯哪几条通道;只想管推理就删掉后面那条
enabledtrue设 false 等于不挂
journaltrue设 false 不写交接记录

例:只想管推理通道,跑

DOUDOU_COLLAPSE_GUARD_CONFIG={"watch":["reasoning-delta"]}

交接记录

一次开火一行 JSONL,落在 $DSH_HOME/doudou-collapse-guard/fires.jsonl(DSH_HOME 没设就是 ~/.dsh)。超过 512 KB 自动轮转成 .1。

每行带:时间、规则码、通道、行号、次数、种数、所在字符位置、sessionId、provider、model、首字延迟、耗时,以及当时生效的全部阈值 —— 事后要回校判据,靠的就是这一份。

{"at":"2026-11-10T12:56:40.826Z","rule":"paragraph-repeat","channel":"reasoning-delta","line":null,"count":3,"distinct":20,"atChars":4403,"sessionId":"…","model":"deepseek-flash","thresholds":{…}}

已知边界

  • 段落复读的日志 tail 里长行会显示成 <长行> 占位符,看不出具体在复读哪一段。
  • 只读流,不改判据之外的任何东西,不碰历史,不碰会话文件。

装法

plugin_manager install_bundle target="github:otonemame/dsh-collapse-guard"

或在 cordis.patch.yml 里手动插一条 dsh-mcp-client 那样的条目,name 写 doudou-collapse-guard。

许可

MIT

Comments

Loading…