dsh-decision-engine 0.6.1 → 0.7.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 CHANGED
@@ -104,14 +104,14 @@ Windows 允许点开头的目录。真正与平台相关的是分隔符与家目
104
104
  - 选 `custom` 时展开四个字段:`Base URL`(必填)、`端点风格`、`附加请求头`、`密钥变量名`
105
105
  - 一个 `测试端点` 按钮(回显 provider、实际 POST 的完整地址、HTTP 码、请求 id 与响应体)
106
106
  - **决策模型参与点**列表:每个决策点一行,右侧是 `关闭 / 观察 / 生效` 三态
107
- - `自身 token 预算` 输入框,以及实时的缓存命中率与每个点的 token / 延迟统计
107
+ - 实时的缓存命中率与每个点的 token / 延迟统计
108
108
 
109
109
  **改完即保存,没有「保存」按钮**:开关 / 下拉 / 分段控件点一下就落盘;文本框
110
- (地址、请求头、预算)停手约 0.7 秒或失焦后落盘;API Key 只在**失焦或回车**
110
+ (地址、请求头)停手约 0.7 秒或失焦后落盘;API Key 只在**失焦或回车**
111
111
  时落盘(避免把打字途中的半截 key 写进凭据文件)。底部状态行实时显示
112
112
  `待保存… / 保存中… / 已保存 / 未保存:原因`,失败时附「重试」。落盘即写入
113
113
  `config.yml`,**不需要重启**:监听器常驻注册,provider 与模式在每次调用时现读;
114
- 预算与缓存参数同样是取值函数而非启动期快照。
114
+ 缓存参数同样是取值函数而非启动期快照。
115
115
 
116
116
  一个例外:选 `custom` 但 `Base URL` 还没填或不合法时,配置改动会**整体延后**
117
117
  (状态行给出原因),地址补全后自动落盘;此时决策点改动不受影响,照常保存。
@@ -131,16 +131,26 @@ content-type: application/json
131
131
  响应只需含 `answers`(`model` / `usage` 可选)。因此 provider 只决定三件事:
132
132
  **往哪发、用谁的 key、默认哪个模型**。
133
133
 
134
- ### 三个选项
134
+ ### 五个选项
135
135
 
136
136
  | provider | 默认地址 | 端点 | 密钥名 | 默认模型 |
137
137
  |---|---|---|---|---|
138
138
  | `system one` | `https://api.typesafe.ai/v1` | `/v1/systemone` | `TYPESAFE_API_KEY` | 服务端默认(`jev-latest`) |
139
139
  | `bamboonode` | `https://api.bamboonode.dev/v1` | `/v1/decisions` | `BAMBOO_API_KEY` | 服务端默认(`startlux-decision-27b-autothink`) |
140
+ | `siliconflow`(硅基流动,国内站) | `https://api.siliconflow.cn/v1` | `/v1/systemone` | `SILICONFLOW_API_KEY` | `Kev-4B`(可改 `SemIf`) |
141
+ | `siliconflow-intl`(Silicon Flow,国际站) | `https://api.siliconflow.com/v1` | `/v1/systemone` | `SILICONFLOW_INTL_API_KEY` | `Kev-4B`(可改 `SemIf`) |
140
142
  | `custom` | 用户填写(必填) | 按 `端点风格` 拼 | `customApiKeyEnv`(缺省 `DECISION_ENGINE_CUSTOM_KEY`) | `customModel` |
141
143
 
142
- **每个 provider 有各自的 key 名,各自保存各自的 key。** 三者互不覆盖,
143
- 可以同时配好三套凭据;切换 provider 不会串用别人的 key。
144
+ > 硅基流动两个站点的接入文档:
145
+ > [国内站 api-docs.siliconflow.cn](https://api-docs.siliconflow.cn/docs/api/systemone-post) ·
146
+ > [国际站 docs.siliconflow.com](https://docs.siliconflow.com/en/api-reference/systemone/create-systemone)
147
+
148
+ **开箱即用**:`siliconflow` / `siliconflow-intl` 只需要粘一个 API key——地址、
149
+ 路径(`/systemone`)、默认模型(`Kev-4B`)都已预置。两个站点是**两套账号**,
150
+ key 互不通用,所以各占一格。
151
+
152
+ **每个 provider 有各自的 key 名,各自保存各自的 key。** 五者互不覆盖,
153
+ 可以同时配好五套凭据;切换 provider 不会串用别人的 key。
144
154
 
145
155
  不写 `provider` 时等于 `bamboonode`,**老配置行为完全不变**。
146
156
 
@@ -150,8 +160,20 @@ content-type: application/json
150
160
  |---|---|---|
151
161
  | `bamboonode` | `model` | 与历史一致(留空即服务端默认) |
152
162
  | `system one` | `systemoneModel` | 留空 = 服务端默认(`jev-latest`) |
163
+ | `siliconflow` | `siliconflowModel` | 留空 = 预设默认 `Kev-4B` |
164
+ | `siliconflow-intl` | `siliconflowIntlModel` | 留空 = 预设默认 `Kev-4B` |
153
165
  | `custom` | `customModel` | 留空 = 服务端默认 |
154
166
 
167
+ > ⚠️ 两个 SiliconFlow 端点把 `model` 列为**必填**(实测省略或空串都回
168
+ > `400 {"code":20013,"message":"Model field is required."}`),所以它们的
169
+ > 「留空」不是「让服务端挑」,而是用预设默认 `Kev-4B`;想换模型填 `SemIf`。
170
+ > 官方目前只支持这两个决策模型,界面上的模型下拉也只列这两个——
171
+ > `/v1/models` 会返回 98 个模型,但 chat 模型打到 `/systemone` 会
172
+ > `400 Model does not exist.`,列出来就是「选了就失败」。
173
+ >
174
+ > 模型名**大小写不敏感**:`kev-4b` / `Kev-4b` / `Kev-4B` 实测都 200,
175
+ > 配置里三种写法等价。
176
+
155
177
  ### custom:填写方式与限制
156
178
 
157
179
  在设置界面选 `custom` 后需要填:
@@ -213,7 +235,8 @@ content-type: application/json
213
235
  - 保存后再次打开配置页按服务端返回的状态回显(不缓存、不推断),custom 的字段
214
236
  除密钥明文外全部回显。
215
237
  - 密钥优先级:该 provider 的显式 key(`systemoneApiKey` / `bamboonodeApiKey` /
216
- `customApiKey`)> 密钥存储位置上对应的键 > 环境变量。
238
+ `siliconflowApiKey` / `siliconflowIntlApiKey` / `customApiKey`)>
239
+ 密钥存储位置上对应的键 > 环境变量。
217
240
  - 旧的单一 `apiKey` 字段只对 `bamboonode` 生效(历史兼容)。
218
241
 
219
242
  ### 密钥保存位置
@@ -231,7 +254,7 @@ content-type: application/json
231
254
  写入 `credentials` 的行为约束:
232
255
 
233
256
  - **只改 `refs:` 段**,`records:`(DSH 的授权记录,含嵌套结构)与注释逐字保留;
234
- - 键名就是该 provider 自己的名字,所以三套 key 能同时存在、互不覆盖;
257
+ - 键名就是该 provider 自己的名字,所以五套 key 能同时存在、互不覆盖;
235
258
  - 传空值等于**删除**该键(清空即撤销,不留空壳行);
236
259
  - 文件权限固定 `600`;
237
260
  - 写入失败会整体报错,不会出现"配置说存了、实际没存"。
@@ -314,11 +337,9 @@ content-type: application/json
314
337
  决策点(注册表里标了 `hotPath`,配置页显示「热路径」角标)都带明确的触发闸,
315
338
  不能无条件调用。
316
339
 
317
- 同时有两道护栏兜底:
318
-
319
- - **自身预算**(`budgetTokens`,默认每小时 40 万):超限后所有决策点自动退回
320
- 启发式并放行。省钱的东西不能反过来成为主要消耗方。
321
- - **双层缓存**:见下节。
340
+ 护栏是**双层缓存**(见下节):命中缓存就不发请求,把热路径上的重复调用挡掉。
341
+ 本插件不设每小时的 token 预算——一次决策约 1.9k token,命中缓存后几乎为零,
342
+ 不值得用「超限即停止判断」的方式省,那会让守卫在最需要它的时刻失效。
322
343
 
323
344
  ## 模型选择
324
345
 
@@ -348,12 +369,18 @@ chat 模型,拿去打 `/v1/decisions` 会 400 `model ... is not served by this
348
369
  模型)。因此这两种 provider 改为「有类型就按类型过滤,没类型就全收」,下拉里也
349
370
  仍可手填。
350
371
 
372
+ 对 `siliconflow` / `siliconflow-intl`:它们的 `/v1/models` 返回 **98 个**模型、
373
+ **没有一条**带 `supported_endpoint_types`,而决策端点只认 `Kev-4B` / `SemIf`
374
+ (chat 模型打过去是 400 `Model does not exist.`)。类型过滤拿不到任何线索,
375
+ 全收又等于「96 个选了就失败的选项」,因此按预设里的 `knownModels` 白名单筛,
376
+ 下拉里只列这两个。
377
+
351
378
  ## 超时与自动回退
352
379
 
353
380
  每次请求都带**硬超时**(默认 8000ms),超时不是错误,而是降级放行:
354
381
 
355
382
  ```
356
- AbortSignal.timeout(budget) → 超时 → degrade("timeout") → 钩子 next() 放行
383
+ AbortSignal.timeout(timeoutMs) → 超时 → degrade("timeout") → 钩子 next() 放行
357
384
  ```
358
385
 
359
386
  三层保护,保证守卫永远不会拖住工具执行:
@@ -388,7 +415,7 @@ AbortSignal.timeout(budget) → 超时 → degrade("timeout") → 钩子 next()
388
415
  | 命中失败冷却 | `cooldown` |
389
416
  | 端点 4xx / 5xx | `http-404` / `http-500` 等(附 `endpoint`、`body`) |
390
417
  | 网络不可达 | `network-error` |
391
- | 超时 | `timeout`(附 `budgetMs`) |
418
+ | 超时 | `timeout`(附 `timeoutMs`) |
392
419
  | 返回非 JSON / 非对象 | `invalid-json` / `invalid-response` |
393
420
  | 缺 `answers` 字段 | `missing-answers` |
394
421
  | provider 配置不可用(如 custom 的 base URL 非法) | `invalid-base-url`(附 `provider` 与原因) |
@@ -426,7 +453,7 @@ AbortSignal.timeout(budget) → 超时 → degrade("timeout") → 钩子 next()
426
453
  - **只读工具直接放行**:`read`/`grep`/`glob`/`todo_write` 等不发请求。
427
454
  - **state 截断**:`args` 走头尾摘要,`recent` 只带最近 4 条、每条 240 字符。
428
455
  - **失败冷却**:连续 3 次失败进入 60s 冷却,避免对着故障端点烧钱。
429
- - **自身预算**:`budgetTokens` 限制决策模型每小时的花费,超限自动退回启发式。
456
+ - **双层缓存**:精确命中与同类复用都不发请求,热路径上的重复判断几乎零成本。
430
457
  - **默认只观察**:新决策点先跑 `observe`,确认与人工判断一致后再切 `enforce`。
431
458
 
432
459
  ## 验证链路
@@ -36,14 +36,20 @@ mode: shadow
36
36
  # ============================================================================
37
37
  # 服务提供方(provider)
38
38
  # ============================================================================
39
- # 三选一:
40
- # bamboonode 内置。StartLux 官方端点 POST /v1/decisions,本插件的原有默认值。
41
- # systemone 内置。任意 System One 协议服务 POST /v1/systemone,
42
- # 默认指向 TypeSafe(https://api.typesafe.ai/v1)。
43
- # custom 自己填 base URL,接任何 system-one 风格服务
44
- # (自建 llama.cpp / vLLM、企业网关、第三方中转)。
39
+ # 五选一:
40
+ # bamboonode 内置。StartLux 官方端点 POST /v1/decisions,本插件的原有默认值。
41
+ # systemone 内置。任意 System One 协议服务 POST /v1/systemone,
42
+ # 默认指向 TypeSafe(https://api.typesafe.ai/v1)。
43
+ # siliconflow 内置。硅基流动(国内站)https://api.siliconflow.cn/v1/systemone
44
+ # siliconflow-intl 内置。Silicon Flow 国际站 https://api.siliconflow.com/v1/systemone
45
+ # (与国内站是两套账号,key 互不通用)
46
+ # custom 自己填 base URL,接任何 system-one 风格服务
47
+ # (自建 llama.cpp / vLLM、企业网关、第三方中转)。
45
48
  #
46
49
  # 不写这一项 = bamboonode,老配置行为完全不变。
50
+ #
51
+ # siliconflow / siliconflow-intl 是「开箱即用」的:地址、路径、默认模型都已
52
+ # 预置,只要粘一个 API key 就能跑(详见下方「密钥」一节)。
47
53
  provider: bamboonode
48
54
 
49
55
  # ---- bamboonode 预设的地址覆盖 ----------------------------------------------
@@ -66,6 +72,16 @@ model:
66
72
  # 得到 400 model not served。
67
73
  systemoneModel: ""
68
74
 
75
+ # 硅基流动 / Silicon Flow 的模型。**留空 = 用预设默认 Kev-4B**——
76
+ # 这两个端点把 model 列为必填(实测省略或空串都回 400
77
+ # "Model field is required."),所以这里的「留空」不是「让服务端挑」,
78
+ # 而是用预设默认。想换模型就填 SemIf(官方目前只支持这两个决策模型)。
79
+ #
80
+ # 你写的 kev-4b 与 Kev-4B 是同一个模型:实测该端点对大小写不敏感
81
+ #(三种写法都 200),/v1/models 里的规范写法是 Kev-4B。
82
+ siliconflowModel: ""
83
+ siliconflowIntlModel: ""
84
+
69
85
  # ---- custom 预设(provider=custom 时才生效)--------------------------------
70
86
  # base URL 必填,且必须是 http/https 地址;不带 scheme 会被拒绝(内置预设
71
87
  # 允许省略 scheme,custom 不允许——自定义地址必须显式,避免猜错协议)。
@@ -99,11 +115,15 @@ customHeaders: ""
99
115
  # ============================================================================
100
116
  # 每个 provider 有**自己的** key 名,各自保存各自的 key:
101
117
  #
102
- # system one -> TYPESAFE_API_KEY
103
- # bamboonode -> BAMBOO_API_KEY
104
- # custom -> customApiKeyEnv(没填时用 DECISION_ENGINE_CUSTOM_KEY)
118
+ # system one -> TYPESAFE_API_KEY
119
+ # bamboonode -> BAMBOO_API_KEY
120
+ # 硅基流动(国内站) -> SILICONFLOW_API_KEY
121
+ # Silicon Flow 国际站 -> SILICONFLOW_INTL_API_KEY
122
+ # custom -> customApiKeyEnv(没填时用 DECISION_ENGINE_CUSTOM_KEY)
105
123
  #
106
- # 三者互不覆盖,可以同时配好;切换 provider 不会串用别人的 key。
124
+ # 五者互不覆盖,可以同时配好;切换 provider 不会串用别人的 key。
125
+ # 两个 SiliconFlow 站点是**两套账号**,key 不通用,因此各占一格——
126
+ # 共用一格的话,切过去就会拿错 key 得到 401,而用户看不出是拿错了还是失效了。
107
127
 
108
128
  # 密钥存到哪里:
109
129
  # credentials = ~/.dsh/.credentials.yaml 的 refs 段(默认,与其他服务统一托管)
@@ -113,6 +133,8 @@ keyStorage: credentials
113
133
  # 变量名可以覆盖(留空即用上面的默认名):
114
134
  # systemoneApiKeyEnv: ""
115
135
  # bamboonodeApiKeyEnv: ""
136
+ # siliconflowApiKeyEnv: ""
137
+ # siliconflowIntlApiKeyEnv: ""
116
138
  # customApiKeyEnv: ""
117
139
 
118
140
  # 直接写明文 key 也可以(各 provider 一格,互不影响)。
@@ -120,12 +142,16 @@ keyStorage: credentials
120
142
  # 不会落到这里;只有 keyStorage=config 时下面这些字段才会被写入。
121
143
  # systemoneApiKey: ""
122
144
  # bamboonodeApiKey: ""
145
+ # siliconflowApiKey: ""
146
+ # siliconflowIntlApiKey: ""
123
147
  # customApiKey: ""
124
148
 
125
149
  # .credentials.yaml 里的形态就是 refs 段:
126
150
  # refs:
127
151
  # BAMBOO_API_KEY: sk-你的key
128
152
  # TYPESAFE_API_KEY: sk-另一个key
153
+ # SILICONFLOW_API_KEY: sk-国内站的key
154
+ # SILICONFLOW_INTL_API_KEY: sk-国际站的key
129
155
 
130
156
  # 超时预算(毫秒)。超时自动降级放行,连续失败进入冷却。
131
157
  timeoutMs: 8000
@@ -215,11 +241,8 @@ pointCotTranslate: off
215
241
  # ============================================================================
216
242
  # 自身开销护栏
217
243
  # ============================================================================
218
- # 决策模型自己也烧 token(实测约 1.9k/次,p50 约 0.5s)。
219
- # 这是每小时上限,超出后所有决策点自动退回启发式并放行——
220
- # 省钱的东西不能反过来变成主要消耗方。0 表示不限制。
221
- budgetTokens: 400000
222
- budgetWindowMs: 3600000
244
+ # 决策模型自己也烧 token(实测约 1.9k/次,p50 约 0.5s),靠双层缓存挡住重复调用:
245
+ # 命中缓存就不发请求。本插件不限制每小时的总用量,因此没有预算字段。
223
246
 
224
247
  # ---- 以下为可选调优项 ----
225
248
  # cacheSize: 256 # 缓存条数上限
@@ -174,7 +174,7 @@ async function loadSkillCandidates(ctx, exec, cfg) {
174
174
  } catch {
175
175
  return [];
176
176
  }
177
- // 决策模型每次只有很小的一段 state;候选超过上限就截断,宁可少挑不可超预算。
177
+ // 决策模型每次只有很小的一段 state;候选超过上限就截断,宁可少挑不可超长。
178
178
  const limit = Math.max(2, cfg.skillPickCandidates ?? 8);
179
179
  return summaries.slice(0, limit).map((summary) => ({
180
180
  name: String(summary.name ?? ""),