@leonhardthust/dsh-decision-engine 1.9.2 → 1.11.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 +70 -30
- package/docs/releasing.md +4 -4
- package/lib/adapters/agent.js +167 -9
- package/lib/adapters/route.js +42 -10
- package/lib/adapters/tools.js +109 -6
- package/lib/adapters/translate.js +94 -9
- package/lib/client.js +695 -227
- package/lib/decision-log.js +501 -0
- package/lib/decision-store.js +756 -97
- package/lib/engine.js +44 -31
- 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 +330 -15
- package/lib/outcomes.js +3 -1
- package/lib/points/spec.js +86 -5
- package/lib/providers.js +57 -8
- package/lib/tolerance.js +50 -40
- package/package.json +1 -1
package/lib/providers.js
CHANGED
|
@@ -93,9 +93,9 @@ const ALTERNATE_STYLE = { decisions: "systemone", systemone: "decisions" };
|
|
|
93
93
|
/**
|
|
94
94
|
* 合法的思考模式(与 startlux 服务的 `think_mode` 取值一致)。
|
|
95
95
|
*
|
|
96
|
-
* never
|
|
97
|
-
* auto
|
|
98
|
-
* always
|
|
96
|
+
* never 不思考——最省延迟(bamboonode 实测;默认值)
|
|
97
|
+
* auto 由服务端决定——实测比 never 慢(约 7.2s vs 1.9s)
|
|
98
|
+
* always 总是思考——每次多等约 5s;质量收益尚未实测
|
|
99
99
|
*
|
|
100
100
|
* 单独导出是为了让校验、前端下拉、文档三处只有一份取值来源。
|
|
101
101
|
*/
|
|
@@ -131,7 +131,7 @@ export function normalizeThinkMode(raw) {
|
|
|
131
131
|
* 默认模型与三个配置字段名。用户选了预设就用这里的值,不需要在设置界面
|
|
132
132
|
* 重复填写。
|
|
133
133
|
*
|
|
134
|
-
*
|
|
134
|
+
* 三个可选标记(都是「按服务商实测」得出的,不是风格偏好):
|
|
135
135
|
*
|
|
136
136
|
* - `requiresModel: true`:该端点**不接受**省略 model 的请求(实测
|
|
137
137
|
* SiliconFlow 的 /v1/systemone 省略或空串都回 400
|
|
@@ -144,6 +144,14 @@ export function normalizeThinkMode(raw) {
|
|
|
144
144
|
* `supported_endpoint_types`(实测 SiliconFlow 返回 98 个,其中 chat
|
|
145
145
|
* 模型打到 /v1/systemone 是 400 `Model does not exist.`)。
|
|
146
146
|
* 列了这份清单就按它筛,不让下拉里出现「选了就失败」的选项。
|
|
147
|
+
* - `thinkModeSupport`:`think_mode` 这个字段该端点认不认。`supported` =
|
|
148
|
+
* 实测认(bamboonode 的决策端点就是靠它区分 never / auto / always);
|
|
149
|
+
* `unknown` = 未实测(TypeSafe、两个 SiliconFlow 站点与 custom 网关都会
|
|
150
|
+
* 收下请求,但「思考」是否真被兑现没有验证过——SiliconFlow 的实测只覆盖
|
|
151
|
+
* 了路径与 model 必填,没覆盖 think_mode);`unsupported` = 公开 API 是
|
|
152
|
+
* chat 补全形状、没有 think_mode 这个字段(Perplexity / OpenRouter)。
|
|
153
|
+
* 界面据此决定要不要提示「切 always 会更快/更慢」,而不是对每家的行为
|
|
154
|
+
* 都编一套说法。
|
|
147
155
|
*/
|
|
148
156
|
export const PROVIDER_PRESETS = {
|
|
149
157
|
systemone: {
|
|
@@ -160,6 +168,9 @@ export const PROVIDER_PRESETS = {
|
|
|
160
168
|
defaultModel: "jev-latest",
|
|
161
169
|
modelField: "systemoneModel",
|
|
162
170
|
keyEnvField: "systemoneApiKeyEnv",
|
|
171
|
+
// 请求体形状与 bamboonode 相同(见模块头的线协议约定),但该端点对
|
|
172
|
+
// think_mode 是否真被兑现未实测,因此给 unknown 而不是 supported。
|
|
173
|
+
thinkModeSupport: "unknown",
|
|
163
174
|
hint: "TypeSafe / Jev 的 System One 协议(POST /v1/systemone),"
|
|
164
175
|
+ "也是 llama.cpp、vLLM 等自建决策服务通用的线协议",
|
|
165
176
|
hintEn: "TypeSafe / Jev System One protocol (POST /v1/systemone); "
|
|
@@ -181,6 +192,9 @@ export const PROVIDER_PRESETS = {
|
|
|
181
192
|
defaultModel: "startlux-decision-v1.1-27b-autothink",
|
|
182
193
|
modelField: "model",
|
|
183
194
|
keyEnvField: "bamboonodeApiKeyEnv",
|
|
195
|
+
// 这是 think_mode 的原始出处:never / auto / always 三档由该端点兑现
|
|
196
|
+
//(实测 never ≈1.9s、auto ≈7.2s;always 的额外耗时未单独实测)。
|
|
197
|
+
thinkModeSupport: "supported",
|
|
184
198
|
hint: "StartLux 官方端点(POST /v1/decisions),本插件的原有默认值",
|
|
185
199
|
hintEn: "StartLux official endpoint (POST /v1/decisions); the original default",
|
|
186
200
|
},
|
|
@@ -208,6 +222,9 @@ export const PROVIDER_PRESETS = {
|
|
|
208
222
|
// 不带 supported_endpoint_types(实测把 chat 模型打过来是 400
|
|
209
223
|
// `Model does not exist.`),所以按这份清单筛,不让下拉里出现选了就坏的项。
|
|
210
224
|
knownModels: ["Kev-4B", "SemIf"],
|
|
225
|
+
// think_mode 在 SiliconFlow 上没有实测过(实测只覆盖了路径与 model 必填),
|
|
226
|
+
// 因此给 unknown:界面不该声称「切 always 会更准」或「会更快」。
|
|
227
|
+
thinkModeSupport: "unknown",
|
|
211
228
|
modelField: "siliconflowModel",
|
|
212
229
|
keyEnvField: "siliconflowApiKeyEnv",
|
|
213
230
|
hint: "硅基流动(国内站)的 System One 端点(POST /v1/systemone);可用模型 Kev-4B / SemIf,默认 Kev-4B,只填一个 API key 即可使用",
|
|
@@ -228,6 +245,8 @@ export const PROVIDER_PRESETS = {
|
|
|
228
245
|
requiresModel: true,
|
|
229
246
|
defaultModel: "Kev-4B",
|
|
230
247
|
knownModels: ["Kev-4B", "SemIf"],
|
|
248
|
+
// 与国内站同源,同样没有覆盖过 think_mode 的实测。
|
|
249
|
+
thinkModeSupport: "unknown",
|
|
231
250
|
modelField: "siliconflowIntlModel",
|
|
232
251
|
keyEnvField: "siliconflowIntlApiKeyEnv",
|
|
233
252
|
hint: "Silicon Flow(国际站)的 System One 端点(POST /v1/systemone);与国内站账号不通用,可用模型 Kev-4B / SemIf,默认 Kev-4B,只填一个 API key 即可使用",
|
|
@@ -251,6 +270,9 @@ export const PROVIDER_PRESETS = {
|
|
|
251
270
|
// 占位值,未实测。
|
|
252
271
|
requiresModel: true,
|
|
253
272
|
defaultModel: "sonar",
|
|
273
|
+
// Perplexity 的公开 API 是 chat 补全形状,没有 think_mode 这个字段:
|
|
274
|
+
// 带上它只会被忽略或 400,因此明确标 unsupported。
|
|
275
|
+
thinkModeSupport: "unsupported",
|
|
254
276
|
modelField: "perplexityModel",
|
|
255
277
|
keyEnvField: "perplexityApiKeyEnv",
|
|
256
278
|
hint: "Perplexity Decider 端点(api.perplexity.ai);地址与默认模型待实测,接通后建议把端点风格钉死",
|
|
@@ -270,6 +292,8 @@ export const PROVIDER_PRESETS = {
|
|
|
270
292
|
// requiresModel;默认 slug 是占位值,未实测,换模型直接改配置即可。
|
|
271
293
|
requiresModel: true,
|
|
272
294
|
defaultModel: "openai/gpt-4o-mini",
|
|
295
|
+
// 同 Perplexity:OpenRouter 的 /chat/completions 形状里没有 think_mode。
|
|
296
|
+
thinkModeSupport: "unsupported",
|
|
273
297
|
modelField: "openrouterModel",
|
|
274
298
|
keyEnvField: "openrouterApiKeyEnv",
|
|
275
299
|
hint: "OpenRouter 端点(openrouter.ai/api/v1);模型填 OpenRouter 的 slug,地址与默认模型待实测",
|
|
@@ -287,6 +311,9 @@ export const PROVIDER_PRESETS = {
|
|
|
287
311
|
legacyApiKeyEnvs: [],
|
|
288
312
|
keyField: "customApiKey",
|
|
289
313
|
defaultModel: "",
|
|
314
|
+
// 用户自己的服务:认不认 think_mode 只有他知道,因此 unknown 而不是
|
|
315
|
+
// supported——能力位是「已知事实」,不是「希望如此」。
|
|
316
|
+
thinkModeSupport: "unknown",
|
|
290
317
|
modelField: "customModel",
|
|
291
318
|
keyEnvField: "customApiKeyEnv",
|
|
292
319
|
hint: "任意提供决策端点的服务:自建 llama.cpp / vLLM、企业网关、第三方中转",
|
|
@@ -624,15 +651,23 @@ export function parseCustomHeaders(raw) {
|
|
|
624
651
|
*
|
|
625
652
|
* @param cfg - 已合并的配置对象(可能缺字段)。
|
|
626
653
|
* @param options.server - 服务端下发的覆盖值(`{ baseUrl, model, endpointStyle }`)。
|
|
627
|
-
*
|
|
628
|
-
*
|
|
654
|
+
* 下发载荷里**没有** thinkMode 这一项(见 `server-config.js` 的 `parseServerConfig`,
|
|
655
|
+
* 它只返回 baseUrl / model / endpointStyle / key / ttlSeconds)。存在时整段走
|
|
656
|
+
* 覆盖分支,本地配置只保留 `thinkMode` 一项:它是**本机行为偏好**(省延迟还是
|
|
657
|
+
* 判得更充分),与「往哪发、用谁的 key」无关,跟着服务端下发一起丢掉会让用户
|
|
658
|
+
* 配的 `always` 静默退回 `never`。其余本地字段一律不读——服务端下发代表
|
|
659
|
+
* **当前权威配置**,若还让本地字段参与覆盖,用户机器上残留的旧地址就会盖掉
|
|
660
|
+
* 管理员的改动。
|
|
629
661
|
* @returns 生效配置;`valid=false` 时 `error` 是可展示的中文原因。
|
|
630
662
|
*/
|
|
631
663
|
export function resolveProvider(cfg = {}, options = {}) {
|
|
632
664
|
// 只有拿到对象才算「服务端下发了配置」:null / 字符串等一律视为没有,
|
|
633
665
|
// 完全走既有逻辑。既有调用方只传 cfg,因此升级后行为逐字不变。
|
|
634
666
|
const server = options?.server;
|
|
635
|
-
if (server !== null && typeof server === "object")
|
|
667
|
+
if (server !== null && typeof server === "object") {
|
|
668
|
+
// 可选链:这条早退分支过去完全不读 cfg,别为了一项本机偏好引入新的抛错点。
|
|
669
|
+
return buildServerProvider(server, cfg?.thinkMode);
|
|
670
|
+
}
|
|
636
671
|
|
|
637
672
|
const requested = typeof cfg.provider === "string" ? cfg.provider.trim() : "";
|
|
638
673
|
const id = PROVIDER_ID_SET.has(requested) ? requested : DEFAULT_PROVIDER;
|
|
@@ -746,14 +781,21 @@ export function resolveProvider(cfg = {}, options = {}) {
|
|
|
746
781
|
* 因此这里 `explicitKey` 为空、`apiKeyEnv` 也为空。刻意不留本地变量名:
|
|
747
782
|
* 留了就会在下发失效时退回用户自己的 key 打向服务端地址,那是拿错了
|
|
748
783
|
* 账号的 key,而守卫是 fail-open 的,表现为静默失效。宁可「没有 key」。
|
|
784
|
+
* 3. `thinkMode` 仍是**本机**设定(由 `resolveProvider` 从 cfg 传进来):
|
|
785
|
+
* 它是省延迟还是判得更充分的偏好,不属于「往哪发、用谁的 key」这套下发
|
|
786
|
+
* 契约。此前这里整个字段缺失,`buildRequestBody` 的
|
|
787
|
+
* `normalizeThinkMode(payload.thinkMode ?? provider.thinkMode)` 于是收到
|
|
788
|
+
* undefined → 恒为 never,用户配的 `always` 在服务端下发期间静默失效。
|
|
749
789
|
*
|
|
750
790
|
* base URL 非法时不抛错,只把 `valid` 置 false 并给出中文原因:解析点遍布
|
|
751
791
|
* 请求路径,抛错会把一次「配置暂时不可用」放大成决策失败。
|
|
752
792
|
*
|
|
753
793
|
* @param server - 服务端下发的覆盖值,字段可能缺失或类型不对。
|
|
794
|
+
* @param configuredThinkMode - 本机配置里的 `thinkMode`(未归一,交给
|
|
795
|
+
* `normalizeThinkMode` 兜底)。
|
|
754
796
|
* @returns 与预设分支同形状的 provider 对象。
|
|
755
797
|
*/
|
|
756
|
-
function buildServerProvider(server) {
|
|
798
|
+
function buildServerProvider(server, configuredThinkMode) {
|
|
757
799
|
const checked = validateBaseUrl(server.baseUrl);
|
|
758
800
|
// 风格必须是三者之一;缺失或非法回落 auto,避免拼出 `/undefined` 这类路径。
|
|
759
801
|
const endpointStyle = ENDPOINT_STYLES.includes(server.endpointStyle) ? server.endpointStyle : "auto";
|
|
@@ -772,6 +814,8 @@ function buildServerProvider(server) {
|
|
|
772
814
|
candidates: [...plan.candidates],
|
|
773
815
|
endpointStyle,
|
|
774
816
|
model,
|
|
817
|
+
// 与两个本地分支逐字相同:非法/缺失一律归一为 never。
|
|
818
|
+
thinkMode: normalizeThinkMode(configuredThinkMode),
|
|
775
819
|
headers: {},
|
|
776
820
|
apiKeyEnv: "",
|
|
777
821
|
legacyApiKeyEnvs: [],
|
|
@@ -918,6 +962,11 @@ export function providerCatalog() {
|
|
|
918
962
|
// 都由预设表下发,前端不必知道任何一家服务商的脾气。
|
|
919
963
|
requiresModel: preset.requiresModel === true,
|
|
920
964
|
knownModels: [...(preset.knownModels ?? [])],
|
|
965
|
+
// think_mode 能力位:`supported` / `unknown` / `unsupported`。
|
|
966
|
+
// 界面据此决定「思考模式」那一格要不要提示代价、要不要警告可能无效,
|
|
967
|
+
// 而不是对每家服务商各编一套说法。缺省按 unknown 处理——未知不等于
|
|
968
|
+
// 可用,也不等于不可用。
|
|
969
|
+
thinkModeSupport: preset.thinkModeSupport ?? "unknown",
|
|
921
970
|
hint: preset.hint,
|
|
922
971
|
hintEn: preset.hintEn,
|
|
923
972
|
customizable: id === "custom",
|
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.11.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",
|