dsh-dsml-rescue 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/README_zh.md +9 -5
- package/lib/index.js +65 -46
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<h1 align="center">dsh-dsml-rescue</h1>
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
|
-
<p><strong>A stalled
|
|
9
|
-
<p><em
|
|
8
|
+
<p><strong>A turn stalled by leaked DSML markup gets another one</strong></p>
|
|
9
|
+
<p><em>DSML 泄漏导致的空转,替你续上</em></p>
|
|
10
10
|
|
|
11
11
|
<p>
|
|
12
12
|
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
## Does this look familiar?
|
|
33
33
|
|
|
34
34
|
You asked for something. The model reasoned for a while. Then the turn simply ended — no answer, no
|
|
35
|
-
tool call, nothing.
|
|
35
|
+
tool call, nothing. What it produced ended with lines like these:
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
</||DSML|| parameter>
|
|
@@ -40,16 +40,21 @@ tool call, nothing. The reasoning output ended with lines like these:
|
|
|
40
40
|
</||DSML|| calls>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img src="assets/dsml-leak-rescue.png" width="900" alt="The web UI after a stalled turn: what the model produced ends with leaked DSML markup, then the turn stops with no answer">
|
|
45
|
+
</p>
|
|
46
|
+
|
|
43
47
|
DSML (DeepSeek Markup Language) is how DeepSeek formats a tool call. Some provider routes hand that
|
|
44
|
-
markup back as
|
|
45
|
-
turn closes with no output, and everything
|
|
48
|
+
markup back as ordinary message content instead of an actual call — in the reasoning, or in the
|
|
49
|
+
visible reply. Either way the call never happens, the turn closes with no output, and everything
|
|
50
|
+
waits for someone to type "continue".
|
|
46
51
|
|
|
47
52
|
This plugin types it.
|
|
48
53
|
|
|
49
54
|
## What it does
|
|
50
55
|
|
|
51
|
-
- Continues a stalled turn: DSML markup
|
|
52
|
-
|
|
56
|
+
- Continues a stalled turn: DSML markup where the message ends, and no tool call. Both halves are
|
|
57
|
+
needed — an empty turn without the markup is a different failure.
|
|
53
58
|
- Sends one short, neutral line: take the next step — never an offer to declare the work finished.
|
|
54
59
|
- Backs off while you are typing.
|
|
55
60
|
- Gives up after a few attempts instead of burning requests.
|
package/README_zh.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<h1 align="center">dsh-dsml-rescue</h1>
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
|
-
<p><strong
|
|
9
|
-
<p><em>A stalled
|
|
8
|
+
<p><strong>DSML 泄漏导致的空转,替你续上</strong></p>
|
|
9
|
+
<p><em>A turn stalled by leaked DSML markup gets another one</em></p>
|
|
10
10
|
|
|
11
11
|
<p>
|
|
12
12
|
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
## 这场景眼熟吗
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
你提了要求,模型推理了一会儿,然后这一轮就这么结束了——没有回复、没有工具调用,什么都没有。它产出的内容,最后几行是这样:
|
|
35
35
|
|
|
36
36
|
```
|
|
37
37
|
</||DSML|| parameter>
|
|
@@ -39,13 +39,17 @@
|
|
|
39
39
|
</||DSML|| calls>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
<p align="center">
|
|
43
|
+
<img src="assets/dsml-leak-rescue.png" width="900" alt="网页界面里的一次空转:模型产出的内容末尾是泄漏的 DSML 标记,这一轮就此停下,没有回答">
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
DSML(DeepSeek Markup Language)是 DeepSeek 表示工具调用的协议标记。某些 provider 路由会把这段标记当成普通消息内容返回——可能落在推理里,也可能落在正文里——而不是真的发出调用;于是调用没有发生,这一轮空着结束,所有人都在等一句「继续」。
|
|
43
47
|
|
|
44
48
|
这个插件替你把这句「继续」发出去。
|
|
45
49
|
|
|
46
50
|
## 它做什么
|
|
47
51
|
|
|
48
|
-
-
|
|
52
|
+
- 救空转的轮次:标记落在消息末尾,而且没有工具调用。两个条件缺一不可——空转但没有标记,是另一种失败。
|
|
49
53
|
- 发一句很短、很中性的话:接着做下一步,不给「已经做完了」这种台阶。
|
|
50
54
|
- 你正在打字时它让开。
|
|
51
55
|
- 连着几次没救回来就收手,不会一直烧请求。
|
package/lib/index.js
CHANGED
|
@@ -2,13 +2,14 @@ import z from "@deepseek-ai/schemastery";
|
|
|
2
2
|
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
// src/protocol.ts
|
|
4
4
|
/**
|
|
5
|
-
* Protocol-mark detection over one
|
|
5
|
+
* Protocol-mark detection over one message's content.
|
|
6
6
|
*
|
|
7
7
|
* Some provider routes deliver the model's tool-call dialect as ordinary
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* content instead of a structured tool call. The dialect's closing tokens then
|
|
9
|
+
* sit at the very end of the message, and the turn ends having done nothing.
|
|
10
|
+
* This module answers one question about that text: does it end with such a
|
|
11
|
+
* token? It knows nothing about turns, agents, or sessions, and nothing about
|
|
12
|
+
* which content kind the route chose to put it in.
|
|
12
13
|
*
|
|
13
14
|
* @module dsh-dsml-rescue/protocol
|
|
14
15
|
*/
|
|
@@ -19,11 +20,32 @@ import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
|
19
20
|
*/
|
|
20
21
|
const MARKER = /\|\|\s*dsml\s*\|\|/i;
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Remove fenced code blocks, keeping a fence opener's line prefix.
|
|
24
|
+
*
|
|
25
|
+
* A fence is quotation: a model that transcribes the call it meant to make puts
|
|
26
|
+
* the transcription inside one, and that transcription is not what the message
|
|
27
|
+
* says. Fences can open mid-line, so this cuts at the delimiter rather than at
|
|
28
|
+
* the start of the line.
|
|
29
|
+
* @param text - one message's content.
|
|
30
|
+
* @returns the same text with fenced spans blanked out.
|
|
25
31
|
*/
|
|
26
|
-
|
|
32
|
+
function withoutFences(text) {
|
|
33
|
+
const kept = [];
|
|
34
|
+
let fenced = false;
|
|
35
|
+
for (const line of text.split(/\r?\n/)) {
|
|
36
|
+
let rest = line;
|
|
37
|
+
let visible = "";
|
|
38
|
+
for (;;) {
|
|
39
|
+
const at = rest.search(/```|~~~/);
|
|
40
|
+
if (at < 0) break;
|
|
41
|
+
if (!fenced) visible += rest.slice(0, at);
|
|
42
|
+
rest = rest.slice(at + 3);
|
|
43
|
+
fenced = !fenced;
|
|
44
|
+
}
|
|
45
|
+
kept.push(fenced ? visible : visible + rest);
|
|
46
|
+
}
|
|
47
|
+
return kept.join("\n");
|
|
48
|
+
}
|
|
27
49
|
/**
|
|
28
50
|
* A line that is protocol markup rather than a sentence.
|
|
29
51
|
*
|
|
@@ -37,22 +59,23 @@ function isMarkupLine(line) {
|
|
|
37
59
|
return line.startsWith("<") && MARKER.test(line);
|
|
38
60
|
}
|
|
39
61
|
/**
|
|
40
|
-
* Whether a
|
|
62
|
+
* Whether a message's content ends with leaked protocol markup.
|
|
41
63
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
64
|
+
* Only the last non-blank line is read. A leak is what the message stops on, so
|
|
65
|
+
* anything said after the markup is output, and a turn that produced output is
|
|
66
|
+
* not a stall. Fences are read per stream: a fence cannot span two blocks, and
|
|
67
|
+
* carrying one block's fence state into the next would flip the reading of a
|
|
68
|
+
* block that never opened one.
|
|
69
|
+
* @param streams - one text per text-bearing block, in stream order.
|
|
70
|
+
* @returns whether the last non-blank line of the content is protocol markup.
|
|
46
71
|
*/
|
|
47
|
-
function hasLeakedProtocolTail(
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
let
|
|
51
|
-
for (let index = lines.length - 1; index >= 0 && scanned < TAIL_LINES; index -= 1) {
|
|
72
|
+
function hasLeakedProtocolTail(streams) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
for (const stream of streams) for (const line of withoutFences(stream).normalize("NFKC").split(/\r?\n/)) lines.push(line);
|
|
75
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
52
76
|
const line = (lines[index] ?? "").trim();
|
|
53
77
|
if (line === "") continue;
|
|
54
|
-
|
|
55
|
-
if (isMarkupLine(line)) return true;
|
|
78
|
+
return isMarkupLine(line);
|
|
56
79
|
}
|
|
57
80
|
return false;
|
|
58
81
|
}
|
|
@@ -60,10 +83,10 @@ function hasLeakedProtocolTail(reasoning) {
|
|
|
60
83
|
/**
|
|
61
84
|
* The one judgment this plugin makes: does a finished turn deserve a rescue?
|
|
62
85
|
*
|
|
63
|
-
* It deserves one when the turn ended normally
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
86
|
+
* It deserves one when the turn ended normally and its closing message ends in
|
|
87
|
+
* leaked protocol markup. Reasoning and visible text are both content: which of
|
|
88
|
+
* them the route put the markup in says nothing about whether the work happened,
|
|
89
|
+
* and the message's last line says everything.
|
|
67
90
|
*
|
|
68
91
|
* Pure: no host types, no events, no clock. The caller adapts real session
|
|
69
92
|
* events into {@link TurnObservation}.
|
|
@@ -71,38 +94,34 @@ function hasLeakedProtocolTail(reasoning) {
|
|
|
71
94
|
* @module dsh-dsml-rescue/detect
|
|
72
95
|
*/
|
|
73
96
|
/**
|
|
74
|
-
* Whether
|
|
97
|
+
* Whether a block carries message content rather than an effect.
|
|
75
98
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* content kind this plugin has never seen is far likelier to have produced
|
|
79
|
-
* output than to have produced nothing, and a false rescue costs a request.
|
|
99
|
+
* Everything else — a tool call, or a content kind this plugin has never seen —
|
|
100
|
+
* is output the turn produced, so it ends the question.
|
|
80
101
|
* @param block - one content block of the closing message.
|
|
81
|
-
* @returns whether the block is
|
|
102
|
+
* @returns whether the block is reasoning or visible text.
|
|
82
103
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
if (block.type !== "text") return true;
|
|
86
|
-
return block.text !== void 0 && /\S/.test(block.text);
|
|
104
|
+
function isTextBearing(block) {
|
|
105
|
+
return block.type === "reasoning" || block.type === "text";
|
|
87
106
|
}
|
|
88
107
|
/**
|
|
89
|
-
*
|
|
108
|
+
* The text of every text-bearing block, in stream order.
|
|
90
109
|
* @param blocks - the turn's closing content blocks.
|
|
91
|
-
* @returns
|
|
110
|
+
* @returns one entry per block that carries text, empty entries included.
|
|
92
111
|
*/
|
|
93
|
-
function
|
|
94
|
-
return blocks.filter((block) => block
|
|
112
|
+
function streams(blocks) {
|
|
113
|
+
return blocks.filter((block) => isTextBearing(block) && block.text !== void 0).map((block) => block.text ?? "");
|
|
95
114
|
}
|
|
96
115
|
/**
|
|
97
116
|
* Decide whether one finished turn is a leaking stall.
|
|
98
117
|
* @param observation - the turn's end reason and its closing message blocks.
|
|
99
|
-
* @returns true only when the turn completed, produced
|
|
100
|
-
* its
|
|
118
|
+
* @returns true only when the turn completed, produced no non-text block, and
|
|
119
|
+
* its content ends in protocol markup.
|
|
101
120
|
*/
|
|
102
121
|
function isRescueCandidate(observation) {
|
|
103
122
|
if (!observation.completed) return false;
|
|
104
|
-
if (observation.blocks.some(
|
|
105
|
-
return hasLeakedProtocolTail(
|
|
123
|
+
if (observation.blocks.some((block) => !isTextBearing(block))) return false;
|
|
124
|
+
return hasLeakedProtocolTail(streams(observation.blocks));
|
|
106
125
|
}
|
|
107
126
|
// src/policy.ts
|
|
108
127
|
/**
|
|
@@ -241,9 +260,9 @@ function reduce(state, event, limits) {
|
|
|
241
260
|
*
|
|
242
261
|
* The rescued turn ended with a tool call that never landed, so the next step
|
|
243
262
|
* has not happened yet; a "say you are done" clause has nothing true to be right
|
|
244
|
-
* about. Taking one is not free either: a prose answer is
|
|
245
|
-
*
|
|
246
|
-
* undone.
|
|
263
|
+
* about. Taking one is not free either: a prose answer is what detection reads as
|
|
264
|
+
* the message's end, so the turn stops counting as a stall and the run ends with
|
|
265
|
+
* the work undone.
|
|
247
266
|
*/
|
|
248
267
|
const CONTINUE_PROMPT = "Continue with the task. Take the next concrete step now, using a tool if needed.";
|
|
249
268
|
/**
|
package/locale/en.json
CHANGED
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dsml-rescue",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Auto-continues turns stalled by DSML tool-call markup
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Auto-continues turns stalled by DSML tool-call markup, wherever the route leaks it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
7
7
|
"deepseek-harness",
|