Skip to content
dsh.fish
Bundle

dsh-repeat-guard

流式输出退化拦截:检测碎片复读,掐断本次生成并提醒模型直接执行工具调用

Source
xiaozhuyuqing
stars
9 stars
License
MIT
Updated
Updated 3 hours ago

Readme

# dsh-repeat-guard

> 解决大模型用你的token唱歌的糟糕问题

我们发现部分模型(尤其是`deepseek v4.1 flash`)很容易在长上下文时进入复读状态。
dsh社区中称呼这种情况为“唱歌”,典型的表现如下所示:
```
Let me do it.
Let me run.
Let me go.
Let me do it.
...
```
如果在预设或者`AGENTS.md`中约束使用中文思考,则会变成如下的场景:
```
好。
做。
开始。
做。
嗯。
做。
我必须开始工作了。
好。
做。
...
```
配合`deepseek v4.1 flash`高达 300tok/s 的恐怖输出速率,模型可能会以极为可观的速度消耗token,而不产生任何有效输出。

这通常是比较隐蔽的,尤其是在用户没有注意模型的思考过程的时候。用户实际上只会看到思考过程快速闪过很短的句子,回答好像很正常。

在上下文长度达到 300k 以上的时候,该现象会变得十分频繁。

不仅如此,我们也观察到`deepseek v4.1 flash`有着在已经思考到需要工具调用的时候输出一个短句,接下来又进入另一个方向的复杂思考过程的倾向。如下所示:
```
我已经收集到了足够的信息,接下来开始操作。先修改xxx文件。
做。
但是我还发现仓库里有其它的内容。注意到用户提到xxxxx
...
```

显然这不是我们想要的行为。既然模型已经清楚下一步的工作是什么,就应该直接开始工具调用。

我们还注意到上述问题只出现于思考过程而不出现于回答内容。
## 此插件的功能

此插件致力于解决上述模型复读和无端长思考的问题。它会持续检测模型的输出,如果思考过程出现了一个**白名单**中的短句且单独成段,且模型在输出这个短句后既然有继续思考的意图,我们就会强行中断对话,并发送一句提醒,让模型直接开始下一轮的输出。

例如
```
[深度思考]
  直接检查服务器状态和崩溃日志。

  关键:先看最新日志和 crash-reports。

  执行。 <--打断

[复读拦截]
  你上一段思考退化成了碎片复读(反复输出"好。"一类的空话),已被系统截断。
  请直接继续执行下一步,不要再输出任何确认语、寒暄或空话。

[深度思考]
  直接调用工具,检查服务器状态和崩溃日志。

[tool calls]
  ssh -o ConnectTimeout=10 -o BatchMode=yes ...
...
```

## 工作原理

逐 chunk 观察 `reasoning-delta`,检查思考文本。

独立段命中白名单时,向模型传递中断请求,掐断模型输出的片段,并补充结束标志,伪装成正常结束的思考过程。

向模型发送一条注入提示词,以恢复`agent-loop`。

用户会通过注入提示词这个步骤看到大模型被中断的过程,因此步骤可能会变得很多。这是不得不付出的代价。

> 虽然我们可以将下一步思考的过程接到上一轮打断的位置以伪装成什么都没发生过的样子,但是一方面这种做法有风险,另一方面不是很有利于缓存命中。更何况,这也能帮助用户判断插件是否在正常工作。

## 可配置选项

在 **设置 → 复读打断** 里查看,保存后生效,无需重启。

| 项 | 含义 |
|---|---|
| 累计拦截 | 掐断次数(有清零按钮) |
| 拦截短句 | 触发打断的白名单,需要加上标点 |
| 连续命中次数\* | 连续命中的阈值,如果为2就需要连续出现两个短句才触发 |
| 行内重复检测\* | 打开后"好。好。好。"这种挤在一行里的重复也算命中。 |
| 续跑指令 | 截断后推给模型的提示词 |
| 折叠行摘要 | 注入段用户可见的简要信息 |

> \* 连续的意思是中间没有长段。如果配置成2就只会拦截循环,默认配置连一个短句后的分支思考过程也会拦下。

> \*deepseek不存在行内重复问题,这个选项是为 qwen 35B 这样的模型准备的。


## 安装到 dsh

```bash
dsh plugin --profile web add dsh-repeat-guard
```

> **兼容性**:`0.2.0` 起要求 dsh **0.1.7 及以上**——0.1.7 起宿主会在装载前校验插件的
> peer 声明,配置表单也换成了 entry Config 模型,旧写法在那上面不再工作。
> 插件用到的宿主接口在 **0.2.0** 上没有变化(`llm/stream` 的 waterfall 形态、
> `agent.steer`、`agent/turn-stopping` 的重读机制、`settings` 与客户端 `configForms`
> 的形状均逐字相同),因此 `0.2.2` 起 peer 范围不再设上限。
> 写法是 `>=0.1.7-0 || >=0.2.0-0` 而不是 `>=0.1.7-0`:按 semver 规则,预发布版本只被
> 「版本号相同的预发布比较器」接受,单写 `>=0.1.7-0` 会漏掉 `0.2.0-rc.x`;`||` 里那一段
> 专门给 0.2 的预发布开口,0.1.7 及更早的正式版仍由前一段覆盖。
> dsh `0.1.0-rc.8` ~ `0.1.6` 请装 `0.1.5`:
>
> ```bash
> dsh plugin --profile web add dsh-repeat-guard@0.1.5
> ```

## 许可

MIT,见 `LICENSE`。

Install

dsh plugin --profile web add github:xiaozhuyuqing/dsh-repeat-guard

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source