@leonhardthust/dsh-decision-engine 1.9.2 → 1.10.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 +3 -2
- package/README.zh.md +2 -2
- package/config.example.yml +50 -23
- package/docs/releasing.md +4 -4
- package/lib/client.js +695 -227
- package/lib/decision-log.js +501 -0
- package/lib/decision-store.js +756 -97
- package/lib/engine.js +38 -30
- package/lib/guard/effects.js +14 -0
- package/lib/guard/listener.js +28 -3
- package/lib/guard/messages.js +35 -0
- package/lib/index.js +312 -15
- package/lib/outcomes.js +3 -1
- package/lib/providers.js +57 -8
- package/lib/tolerance.js +50 -40
- package/package.json +1 -1
package/lib/tolerance.js
CHANGED
|
@@ -27,21 +27,30 @@
|
|
|
27
27
|
*
|
|
28
28
|
* ## 四档语义
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* 不问 autonomous 软性询问一律不弹窗、不按比例;mandatory deny 照旧,
|
|
31
|
+
* 必须人工批准的操作**仍然询问**(审批不可用时直接拒绝,
|
|
32
|
+
* 不能用模型替代许可)。
|
|
33
|
+
* 少问 relaxed 只按严重度尾部:校准后约最前的 1%,校准前固定分 0.60。
|
|
34
|
+
* 它**替换**默认判断而不是叠加,因此同一次调用既可能
|
|
35
|
+
* 从问变成不问,也可能从放行变成问。
|
|
36
|
+
* 默认 default 沿用决策模型自己的判断提问(**当前的默认行为**,逐字不变)。
|
|
37
|
+
* 多问 cautious 在默认判断之上**再叠加**严重度靠前的一批:校准后约 25%,
|
|
38
|
+
* 校准前固定分 0.30。取并集,所以逐调用一定 ⊇ 默认。
|
|
39
|
+
*
|
|
40
|
+
* 四档之间只有**汇总询问率**单调(少问 ≤ 默认 ≤ 多问;具体数值取决于决策
|
|
41
|
+
* 模型与阈值,DEC-035 那张 22.5% / 41% 的实测表来自已经退役的旧判定路径,
|
|
42
|
+
* 不能当成当前默认档的询问率)。
|
|
43
|
+
* 逐调用并不单调:少问档是替换语义,同一次调用与默认判断可以**双向**不同;
|
|
44
|
+
* 不问档只是不弹软性询问,强制审批那条路径与其它档完全一样。
|
|
38
45
|
*
|
|
39
46
|
* ## 校准不足时怎么办
|
|
40
47
|
*
|
|
41
48
|
* 窗口里还没攒够样本时**算不出可靠的分位数**。此时不猜分位数,改用
|
|
42
|
-
* `fallbackSeverity
|
|
43
|
-
* (见 DEC-035
|
|
44
|
-
*
|
|
49
|
+
* `fallbackSeverity`——这几个绝对值是**在真实数据上量出来的典型分界线**
|
|
50
|
+
* (见 DEC-035 的实测表)。代价要说实话:冷启动比校准后**更安静**——
|
|
51
|
+
* 少问档冷启动阈值覆盖 0.47%(校准后的 P99 ≈0.50 覆盖 1.1%),多问档
|
|
52
|
+
* 冷启动覆盖 19.3%(校准后的 P75 ≈0.28 覆盖 25.1%)。因此攒够 50 个样本的
|
|
53
|
+
* 那一刻是**变吵**一点,而不是变安静。
|
|
45
54
|
*
|
|
46
55
|
* 校准状态会显示在界面上(校准中 n/50),用户不会以为设置没生效。
|
|
47
56
|
*
|
|
@@ -58,7 +67,7 @@ export const DEFAULT_TOLERANCE = "default";
|
|
|
58
67
|
* 滚动窗口容量(每个 provider × model 一份)。
|
|
59
68
|
*
|
|
60
69
|
* 取 200 的理由:分位数要稳,至少要几十个样本;200 个样本能让 P99 落在
|
|
61
|
-
*
|
|
70
|
+
* 「最前的那一两个」之外(插值后是一个连续阈值),同时窗口不必大到让
|
|
62
71
|
* 「最近」失去意义——用户改用模型后,几十次调用之内就该反映新分布。
|
|
63
72
|
*/
|
|
64
73
|
export const TOLERANCE_WINDOW_SIZE = 200;
|
|
@@ -117,44 +126,45 @@ export const HIGH_RISK_FLOOR_SCORE = 0.7;
|
|
|
117
126
|
export const TOLERANCE_PRESETS = {
|
|
118
127
|
autonomous: {
|
|
119
128
|
id: "autonomous",
|
|
120
|
-
label: "
|
|
121
|
-
labelEn: "
|
|
122
|
-
summary: "
|
|
123
|
-
summaryEn: "
|
|
129
|
+
label: "不问",
|
|
130
|
+
labelEn: "Don't ask",
|
|
131
|
+
summary: "软性审查不弹窗、不按比例;权限强制的人工批准与拒绝照旧",
|
|
132
|
+
summaryEn: "Soft checks never prompt and no percentile band applies; permission-mandated approval and denial are unchanged",
|
|
124
133
|
percentile: null,
|
|
125
|
-
//
|
|
134
|
+
// 不问档不走分位数路径,这里的值只是「让通用分支也不会触发」的占位。
|
|
126
135
|
fallbackSeverity: Number.POSITIVE_INFINITY,
|
|
127
136
|
},
|
|
128
137
|
relaxed: {
|
|
129
138
|
id: "relaxed",
|
|
130
|
-
label: "
|
|
131
|
-
labelEn: "
|
|
132
|
-
summary: "
|
|
133
|
-
summaryEn: "
|
|
139
|
+
label: "少问",
|
|
140
|
+
labelEn: "Ask less",
|
|
141
|
+
summary: "只按严重度尾部:校准后约最前 1%,校准前固定分 0.60;替换默认判断,因此可能多问也可能少问",
|
|
142
|
+
summaryEn: "Severity tail only: about the top 1% once calibrated, a fixed score 0.60 before that; replaces the default judgement, so it can ask more or less often",
|
|
134
143
|
percentile: 0.99,
|
|
135
144
|
// 实测(1421 条真实授权决策,见 DEC-035):severity ≥ 0.60 覆盖 0.47%
|
|
136
|
-
//
|
|
137
|
-
//
|
|
145
|
+
// 的调用,而校准后的 P99(≈0.50)覆盖 1.1%。冷启动时用它,所以冷启动
|
|
146
|
+
// 比校准后**更安静**;攒够样本的那一刻是稍微变吵,不是变安静。
|
|
138
147
|
fallbackSeverity: 0.6,
|
|
139
148
|
},
|
|
140
149
|
default: {
|
|
141
150
|
id: "default",
|
|
142
151
|
label: "默认",
|
|
143
152
|
labelEn: "Default",
|
|
144
|
-
summary: "
|
|
145
|
-
summaryEn: "
|
|
153
|
+
summary: "沿用决策模型判断:中/高风险、疑似注入、不可逆且与任务无关会问",
|
|
154
|
+
summaryEn: "Keeps the decision model's own judgement: asks on medium/high risk, suspected injection, or irreversible and off-task calls",
|
|
146
155
|
percentile: null,
|
|
147
156
|
fallbackSeverity: null,
|
|
148
157
|
},
|
|
149
158
|
cautious: {
|
|
150
159
|
id: "cautious",
|
|
151
|
-
label: "
|
|
152
|
-
labelEn: "
|
|
153
|
-
summary: "
|
|
154
|
-
summaryEn: "
|
|
160
|
+
label: "多问",
|
|
161
|
+
labelEn: "Ask more",
|
|
162
|
+
summary: "默认判断之上再加严重度靠前一批:校准后约 25%,校准前固定分 0.30",
|
|
163
|
+
summaryEn: "Adds the top severity band on top of the default judgement: about 25% once calibrated, a fixed score 0.30 before that",
|
|
155
164
|
percentile: 0.75,
|
|
156
|
-
// 实测:severity ≥ 0.30 覆盖 19.3%
|
|
157
|
-
//
|
|
165
|
+
// 实测:severity ≥ 0.30 覆盖 19.3% 的调用;校准后的 P75 分位(≈0.28)
|
|
166
|
+
// 覆盖 25.1%。两个比例都只描述「叠加的那一批」;并入默认判断之后的
|
|
167
|
+
// 总询问率取决于默认判断本身,不是这两个数之和。
|
|
158
168
|
fallbackSeverity: 0.3,
|
|
159
169
|
},
|
|
160
170
|
};
|
|
@@ -389,9 +399,9 @@ export function resolveTolerance(options = {}) {
|
|
|
389
399
|
|
|
390
400
|
// 拦截(deny)不是「打扰用户」,四档一律保留。
|
|
391
401
|
//
|
|
392
|
-
//
|
|
402
|
+
// 这条边界必须守住:不问档的语义是「软性询问不弹窗」,不是「什么都放行」。
|
|
393
403
|
// deny 不会打断用户(它直接告诉模型「这次调用被拒了」),而把它一并
|
|
394
|
-
//
|
|
404
|
+
// 取消等于让不问档变成「关掉安全检查」——那与用户说的「0% 询问」是两件事。
|
|
395
405
|
if (legacyKind === "deny") {
|
|
396
406
|
return { kind: "deny", changed: false, severity, cutoff: null, note: "" };
|
|
397
407
|
}
|
|
@@ -413,7 +423,7 @@ export function resolveTolerance(options = {}) {
|
|
|
413
423
|
changed: true,
|
|
414
424
|
severity,
|
|
415
425
|
cutoff: null,
|
|
416
|
-
note: `tolerance=autonomous
|
|
426
|
+
note: `tolerance=autonomous(不问:软性询问不弹窗,severity=${severity.toFixed(3)})`,
|
|
417
427
|
}
|
|
418
428
|
: { kind: "delegate", changed: false, severity, cutoff: null, note: "" };
|
|
419
429
|
}
|
|
@@ -423,13 +433,13 @@ export function resolveTolerance(options = {}) {
|
|
|
423
433
|
return { kind: legacyKind, changed: false, severity, cutoff: null, note: "" };
|
|
424
434
|
}
|
|
425
435
|
|
|
426
|
-
//
|
|
436
|
+
// 少问 / 多问:按分位数带。
|
|
427
437
|
const cutoff = Number.isFinite(options.cutoff)
|
|
428
438
|
? clamp01(options.cutoff)
|
|
429
439
|
: preset.fallbackSeverity;
|
|
430
440
|
|
|
431
441
|
if (preset.id === "relaxed") {
|
|
432
|
-
//
|
|
442
|
+
// 少问档**替换**默认判断,而不是叠加在它上面——否则「top 1%」会变成
|
|
433
443
|
// 「top 1% ∪ 默认判断」,实测那是 23% 的打扰率,与档位语义不符。
|
|
434
444
|
if (severity >= cutoff) {
|
|
435
445
|
return {
|
|
@@ -437,7 +447,7 @@ export function resolveTolerance(options = {}) {
|
|
|
437
447
|
changed: legacyKind !== "ask",
|
|
438
448
|
severity,
|
|
439
449
|
cutoff,
|
|
440
|
-
note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)}
|
|
450
|
+
note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},落在严重度尾部)`,
|
|
441
451
|
};
|
|
442
452
|
}
|
|
443
453
|
return legacyKind === "ask"
|
|
@@ -446,12 +456,12 @@ export function resolveTolerance(options = {}) {
|
|
|
446
456
|
changed: true,
|
|
447
457
|
severity,
|
|
448
458
|
cutoff,
|
|
449
|
-
note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} < ${cutoff.toFixed(3)}
|
|
459
|
+
note: `tolerance=relaxed(严重度 ${severity.toFixed(3)} < ${cutoff.toFixed(3)},不在严重度尾部)`,
|
|
450
460
|
}
|
|
451
461
|
: { kind: "delegate", changed: false, severity, cutoff, note: "" };
|
|
452
462
|
}
|
|
453
463
|
|
|
454
|
-
//
|
|
464
|
+
// 多问档:默认判断 ∪ 严重度靠前的 25%(**叠加**,因此逐调用一定 ⊇ 默认)。
|
|
455
465
|
if (legacyKind === "ask" || severity >= cutoff) {
|
|
456
466
|
return {
|
|
457
467
|
kind: "ask",
|
|
@@ -461,7 +471,7 @@ export function resolveTolerance(options = {}) {
|
|
|
461
471
|
note:
|
|
462
472
|
legacyKind === "ask"
|
|
463
473
|
? `tolerance=cautious(默认判断已升级询问)`
|
|
464
|
-
: `tolerance=cautious(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)}
|
|
474
|
+
: `tolerance=cautious(严重度 ${severity.toFixed(3)} ≥ ${cutoff.toFixed(3)},落在默认判断之外的严重度带)`,
|
|
465
475
|
};
|
|
466
476
|
}
|
|
467
477
|
return { kind: "delegate", changed: false, severity, cutoff, note: "" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leonhardthust/dsh-decision-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "dsh-decision-engine: a cheap judgement layer for DeepSeek Harness — a small decision model answers structured questions before the main model acts, so calls can be allowed, escalated, rerouted to a cheaper model, or skipped. All eight decision points (tool authorization, model routing, delegation, compaction, turn continuation, retrieval, skill pick, COT translation) are implemented and individually switchable. Works against any service exposing a decision endpoint: built-in presets (system one, bamboonode, SiliconFlow, Silicon Flow) or a custom base URL.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|