@master0071/dsh-systemone 0.0.0-stage → 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 CHANGED
@@ -1,3 +1,543 @@
1
- # Temporary Holding Version
1
+ # @master0071/dsh-systemone
2
+
3
+ [![CI](https://github.com/zerosloney/dsh-system-one/actions/workflows/ci.yml/badge.svg)](https://github.com/zerosloney/dsh-system-one/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@master0071/dsh-systemone)](https://www.npmjs.com/package/@master0071/dsh-systemone)
5
+
6
+ DeepSeek Harness 插件:把 **SystemOne 决策模型**(默认云知声 Unisound `u2-decision`,可切换提供商)接入 Agent 工作流。
7
+
8
+ SystemOne 协议一次请求携带业务状态与**结构化问题**(`choice` / `noul` / `score`),一次前向传播返回带概率分布的判断,没有自由文本幻觉,非常适合需要可解释、可复核的自动化决策。
9
+
10
+ > 文档:[SystemOne | 云知声 MaaS](https://maas.unisound.com/docs/api/text/systemone)
11
+
12
+ ---
13
+
14
+ ## 内置场景(10 大业务域)
15
+
16
+ 每个场景 = 3 个结构化问题,覆盖 30 项业务能力。
17
+
18
+ | 场景 id | 业务域 | 覆盖能力 |
19
+ | --- | --- | --- |
20
+ | `customer_service` | 客服运营 | 工单派单与分流、升级 PO 判定、严重度评分 |
21
+ | `content_moderation` | 内容审核 | 违规与否判定、违规类型归类、放行/复审/拦截 |
22
+ | `agent_routing` | 智能体路由 | 意图识别分流、是否调用工具、是否转人工 |
23
+ | `sales_lead` | 销售线索 | 线索质量评分、线索分配、是否值得跟进 |
24
+ | `risk_control` | 金融风控 | 交易异常评分、风险等级划分、是否人工复核 |
25
+ | `recruiting` | 招聘 HR | 简历匹配度打分、是否进入下一轮、岗位归属 |
26
+ | `data_governance` | 数据治理 | 文档自动打标、数据问题归因、是否敏感数据 |
27
+ | `education` | 教育内容 | 题目知识点归类、难度分级评分、内容合规预检 |
28
+ | `requirements` | 需求与变更 | 优先级评分、变更风险评级、子任务派发 |
29
+ | `software_dev` | **软件开发** | 任务类型(缺陷/功能/重构/评审/测试/文档/构建/性能)、改动复杂度、是否需先探查代码库 |
30
+
31
+ ### `software_dev`:给编码 Agent 用的软件开发场景
32
+
33
+ 对编码 Agent 来说这是最常用的域。`systemone_scenario(action: "run", scenario: "software_dev")` 会返回三项判断:
34
+
35
+ | 问题 | 类型 | 取值 |
36
+ |---|---|---|
37
+ | `task_type` | choice | `bugfix` 缺陷修复 / `feature` 新增功能 / `refactor` 重构整理 / `review` 代码评审 / `test` 测试 / `docs` 文档注释 / `build` 构建依赖 CI 环境 / `perf` 性能优化 / `other` 其他 |
38
+ | `complexity` | score | 0~3:单点改动 → 局部改动 → 跨模块改动 → 系统性改动 |
39
+ | `needs_context` | noul | 动手前是否需要先检索代码库、读实现或跑验证 |
40
+
41
+ 派生字段 `effort` 把复杂度映射为 `S / M / L / XL`,`recommendation` 直接给出一句处置建议,例如:
42
+
43
+ ```
44
+ bugfix 任务(规模 M)。建议先检索代码库并确认相关实现
45
+ ```
46
+
47
+ **典型用法**:把「固定场景」设为 `software_dev`,开启自动决策后,Agent 每一步推理前都会先判断「这是 bug 还是新功能、改动多大、要不要先翻代码」,并把结论注入上下文——用来驱动「先探查再动手」这类工作流非常合适。
48
+
49
+ ## 工具
50
+
51
+ 插件只注册 2 个工具,控制工具 schema 的 token 开销,同时支持无限扩展场景。
52
+
53
+ ### `systemone_scenario` — 统一场景调度
54
+
55
+ | action | 作用 |
56
+ | --- | --- |
57
+ | `list` | 列出全部场景(可用 `keyword` 过滤) |
58
+ | `describe` | 查看某场景的问题定义与可覆盖参数 |
59
+ | `run` | 执行决策,返回概率化答案 + 归一化决策 + 建议 |
60
+
61
+ ```text
62
+ systemone_scenario(action: "run",
63
+ scenario: "customer_service",
64
+ state: "订单支付后超过 24 小时仍未到账,用户无法继续使用核心服务,要求立即处理。")
65
+ ```
66
+
67
+ 返回(摘要节选):
68
+
69
+ ```markdown
70
+ ## 客服运营 · 工单派单与分流
71
+ - **决策服务**:unisound / u2-decision
72
+ - **受理部门**:支付、退款和账单问题(置信度 89.3%|分布 billing=89.3% technical=10.7%)
73
+ - **严重程度**:核心功能不可用,没有替代方案 2.00(置信度 98.9%|分布 2=98.9% ...)
74
+ - **立即升级**:是(96.0%)
75
+ - **建议**:转 支付、退款和账单问题 处理(优先级 P2)。需立即通知值班人员
76
+ - **建议复核**:无需人工复核
77
+ ```
78
+
79
+ 结构化字段:
80
+
81
+ ```json
82
+ {
83
+ "ok": true,
84
+ "scenario": "customer_service",
85
+ "decision": { "department": "billing", "severity": 2, "escalate": true },
86
+ "labels": { "department": "支付、退款和账单问题", "severity": "核心功能不可用,没有替代方案", "escalate": "是" },
87
+ "derived": { "priority": "P2" },
88
+ "confidences": { "department": 0.893, "severity": 0.989 },
89
+ "needs_human_review": false,
90
+ "recommendation": "转 支付、退款和账单问题 处理(优先级 P2)。需立即通知值班人员"
91
+ }
92
+ ```
93
+
94
+ **运行时覆盖问题定义**(`params`):
95
+
96
+ ```jsonc
97
+ {
98
+ "action": "run",
99
+ "scenario": "customer_service",
100
+ "state": "...",
101
+ "params": {
102
+ // criteria 整体替换选项(choice 传对象,score 传标签数组)
103
+ "department": { "criteria": { "vip": "VIP 专属通道", "billing": "账单与支付" } },
104
+ // addCriteria 在现有选项上追加/覆盖单个 key,不丢默认项(仅 choice)
105
+ "severity": { "instructions": "这个问题对业务的严重程度有多高?" }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### `systemone_decide` — 自定义问题
111
+
112
+ 场景库里没有的临时判断,直接提交原始 `questions`:
113
+
114
+ ```text
115
+ systemone_decide(
116
+ state: "订单支付后超过 24 小时仍未到账",
117
+ questions: {
118
+ "department": { "type": "choice", "instructions": "哪个团队处理?", "criteria": { "billing": "账单", "technical": "技术" } },
119
+ "severity": { "type": "score", "instructions": "多严重?", "criteria": ["轻微", "部分", "核心不可用", "严重"] },
120
+ "escalate": { "type": "noul", "instructions": "需要立即升级吗?" }
121
+ }
122
+ )
123
+ ```
124
+
125
+ ## 特性
126
+
127
+ - **提供商无关**:所有工具只依赖统一的 `provider.decide()` 接口。切换厂商只改一行配置,业务工具零改动。
128
+ - **图形配置页**:侧栏「插件 → @master0071/dsh-systemone → systemone」里可直接改提供商、Key、模型与自动决策参数,保存即生效(volatile 热更新,无需重启)。
129
+ - **场景可扩展**:内置 10 大场景(含软件开发),并可在配置页里用 JSON 追加或覆盖场景,**无需改代码、无需重启**
130
+ - **可自动决策**:开启后挂 `agent/pre-step`,每一步推理前自动捕获上下文并注入判断(fail-open、硬超时、可缓存)。
131
+ - **概率化输出**:返回每个选项的概率分布与置信度,而非单一答案。
132
+ - **置信度兜底**:任一答案置信度低于 `minConfidence`(默认 0.6)时标记 `needs_human_review: true`。
133
+ - **可编程调用**:注册 `ctx.systemone` 服务,其他插件可直接 `ctx.systemone.decide({ state, questions })`。
134
+ - **本地可测**:内置 `mock` 提供商,无需 API Key、无网络,输出确定。
135
+
136
+ ---
137
+
138
+ ## 架构
139
+
140
+ ```
141
+ 被动通道(模型主动调用)
142
+ ┌──────────────────────── Agent(模型) ────────────────────────┐
143
+ │ │
144
+ │ systemone_scenario systemone_decide │
145
+ │ (list / describe / run) (自定义 questions) │
146
+ └───────────────────────────┬────────────────────────────────────┘
147
+ │
148
+ 主动通道(开启 autoDecide 后)
149
+ ┌───────────────────────────┴────────────────────────────────────┐
150
+ │ agent/pre-step ◄─ auto.js │
151
+ │ 捕获 context ─→ 路由场景 ─→ 执行决策 ─→ 注入 runtime-context │
152
+ └───────────────────────────┬────────────────────────────────────┘
153
+ │
154
+ ┌────────▼─────────┐
155
+ │ 场景库 │ scenarios.js
156
+ │ 10 内置 + 配置自定义 │ (纯数据,可扩展)
157
+ └────────┬─────────┘
158
+ │ 统一 decide({ state, questions })
159
+ ┌──────────────────┐
160
+ │ provider 抽象层 │ ← 切换厂商只改这里
161
+ │ unisound│http│mock│
162
+ └──────────────────┘
163
+ │
164
+ POST /v1/systemone
165
+
166
+ 浏览器侧(配置界面)
167
+ ┌────────────────────────────────────────────────────────────────┐
168
+ │ client.js ── 注册 plugins.row.config(键 @master0071/dsh-systemone#systemone)│
169
+ │ 折叠卡片表单 ── form.mutate(ops) ──► profile cordis.patch.yml │
170
+ │ ▲ │ │
171
+ │ └────── form.state(volatile 快照)◄── Loader 热更新 ◄──┘ │
172
+ └────────────────────────────────────────────────────────────────┘
173
+ ```
174
+
175
+ ## 安装
176
+
177
+ 以本地 `link:` 依赖接入 profile(与 `dsh-plugin-admin` 等本地插件一致):
178
+
179
+ ```powershell
180
+ # 1. profile package.json 的 dependencies 中加入
181
+ "@master0071/dsh-systemone": "link:D:/code/dsh-system-one"
182
+
183
+ # 2. dsh.profile.bundles 中加入 "@master0071/dsh-systemone"
184
+
185
+ # 3. 在 profile 目录执行 pnpm install
186
+ cd C:\Users\<你>\.dsh\profiles\desktop
187
+ node "C:\Users\<你>\AppData\Local\Programs\DeepSeek Harness\resources\runtime\pnpm\bin\pnpm.cjs" install
188
+ ```
189
+
190
+ 新 bundle 需要刷新运行时模块解析,**首次安装后需完全退出并重启 DeepSeek Harness 才能激活**(只关窗口不算,要从托盘退出)。
191
+
192
+ > 这个坑很常见:桌面端启动时才会构建模块解析表,之后再往 profile 里加 `link:` 包,配置树里能看到条目、但插件导入会失败(`Cannot find package '@deepseek-ai/cordis'`),表现为插件一直 `inactive`、工具不注册、配置页也不出现。重启即可解决。
193
+
194
+ ## 自动决策(可选,默认关闭)
195
+
196
+ 工具是**被动**的:只有模型主动调用才会执行。开启 `autoDecide` 后,插件会在**每一步推理前**自动捕获上下文、执行决策,并把结论注入当步上下文——无需模型记得调用工具。`autoInject: "message"`(默认)同一步生效且**同一决策只注入一次**;`autoInject: "context"` 走宿主动态上下文通道,不写入会话历史。
197
+
198
+ ### 它是怎么挂上去的
199
+
200
+ 关键顺序(摘自宿主 `@deepseek-ai/dsh-agent-loop` 的 `preStep`):
201
+
202
+ ```js
203
+ const claimed = inbox.claim(target, position.turn) // 本轮新进消息
204
+ const assembly = await systemPrompt.assemble(...) // ① 先装配提示词
205
+ const context = runtimeContext.project(...) // ② 渲染运行时上下文
206
+ const decision = await waterfall('agent/pre-step', { // ③ 再跑瀑布
207
+ messages: claimed, ... }, () => ({ kind: 'enter',
208
+ messages: [...claimed, context] }))
209
+ ```
210
+
211
+ 因为**装配在 `pre-step` 之前**,`systemPrompt.context` 的 `text` 是同步求值的,而决策依赖异步 HTTP 调用;宿主在消息入箱后立即同步唤醒 driver,中间没有可等待异步结果的挂载点。因此:
212
+
213
+ - **`autoInject: "message"`(默认)**:在 `pre-step` 里替换 `messages`,**同一步生效**。宿主会把这些消息持久化到会话历史,本插件做了**内容去重**——同一决策只注入一次,不逐条累积。
214
+ - **`autoInject: "context"`**:在 `agent/inbox/inserted`(消息一入箱)就**异步预计算**决策并缓存,通过 `systemPrompt.context` 渲染进宿主动态上下文。不写历史;但第一步装配时决策通常尚未返回,从第二步(工具续步)起稳定可见。
215
+
216
+ ```
217
+ 用户消息到达
218
+ │
219
+ ├─ agent/inbox/inserted ◄── context 通道在这里预计算
220
+ │ └─ 异步执行 SystemOne,结果写入 per-agent 缓存
221
+ ├─ inbox.claim → 本轮新消息
222
+ ├─ systemPrompt.assemble ← 提示词装配(context 通道在这里读取缓存渲染)
223
+ ├─ agent/pre-step ◄── message 通道在这里
224
+ │ ├─ 捕获:本轮消息 + session.log 历史(排除自己注入的)
225
+ │ ├─ 路由:固定场景,或用一次 choice 问句自动选场景
226
+ │ ├─ 决策:执行目标场景(缓存命中则秒回)
227
+ │ ├─ 去重:同一决策只注入一次
228
+ │ └─ 注入:messages + 一条 runtime-context 消息
229
+ └─ 模型推理(已带上决策结论)
230
+ ```
231
+
232
+ 注入的内容形如:
233
+
234
+ ```
235
+ [SystemOne 自动决策 · 仅供参考,如有冲突以用户原始要求为准]
236
+ 场景:金融风控 · 交易异常与风险等级(来源:自动路由,路由置信度 87.0%)
237
+ 结论:异常评分=高度可疑;风险等级=高风险;人工复核=是
238
+ 建议:交易异常评分 3,风险等级「高风险」。需转人工复核
239
+ 注意:本次判断置信度偏低,请人工确认后再执行。
240
+ ```
241
+
242
+ ### 安全约束
243
+
244
+ 自动决策会插入关键路径,因此每个环节都做了 fail-open:
245
+
246
+ | 约束 | 说明 |
247
+ | --- | --- |
248
+ | 默认关闭 | `autoDecide: false`,需显式开启 |
249
+ | 硬超时 | `autoTimeoutMs`(默认 8s)。既给上游传 `AbortSignal`,也用 Promise race 兜底——**即使提供商忽略 signal 也不会阻塞推理** |
250
+ | 失败放行 | 任何错误只记 warning,原样返回宿主决策 |
251
+ | 结果缓存 | 相同 state 命中 `autoCacheTtlMs`(默认 60s)缓存,不重复请求;并发相同 state 自动合并 |
252
+ | 注入去重 | `message` 通道对同一决策文本只注入一次,避免每步重复追加 |
253
+ | 精准触发 | 仅当本轮有新用户消息时触发;工具结果步骤不触发 |
254
+ | 跳过命令 | 以 `/` 开头的斜杠命令不触发 |
255
+ | 不进反馈环 | 从历史里排除自己注入的 `runtime-context` 消息 |
256
+
257
+ ### 上下文捕获
258
+
259
+ `state` 由两部分拼成:
260
+
261
+ - **近期对话**:从 `agent.session.log` 倒序取最近 `autoMaxMessages`(默认 6)条 user/assistant 消息,单条截断 2000 字符;
262
+ - **当前请求**:本轮 `pre-step` 的新用户消息。
263
+
264
+ ### 配置
265
+
266
+ | 配置项 | 默认值 | 说明 |
267
+ | --- | --- | --- |
268
+ | `autoDecide` | `false` | 是否开启自动决策 |
269
+ | `autoScenario` | 空 | 固定场景 id;留空则用一次 choice 问句**自动路由**(多一次请求) |
270
+ | `autoInject` | `message` | `message`=同一步生效并去重;`context`=宿主动态上下文通道(不写历史,入箱预计算,第一步通常来不及、第二步起稳定可见) |
271
+ | `autoTimeoutMs` | `8000` | 自动决策硬超时(毫秒) |
272
+ | `autoMaxMessages` | `6` | 捕获的历史消息条数 |
273
+ | `autoCacheTtlMs` | `60000` | 相同内容的结果缓存时长,`0` 表示不缓存 |
274
+ | `autoMinConfidence` | `0.6` | 自动决策的置信度阈值 |
275
+ | `autoRouteMinConfidence` | `0.35` | 自动路由的置信度门槛,低于该值宁可不注入 |
276
+ | `autoMinChars` | `4` | 低于该长度**且整体是寒暄**才跳过(`退款失败` 这类短请求不会被误伤) |
277
+
278
+ ### 已知取舍与限制
279
+
280
+ | 项 | 说明 |
281
+ | --- | --- |
282
+ | **`message` 通道仍会写入会话历史** | `dsh-agent-loop` 对 `pre-step` 返回的 messages 执行无条件 `session.append('user/message', …, {surfaceOp:'append'})`。插件已做**内容去重**:同一决策文本只注入一次,把「每步一条」降为「每个不同决策一条」;但长对话中决策多次变化时仍会累积。需要绝对零历史写入时请用 `autoInject: "context"`。 |
283
+ | **`context` 通道第一步通常看不到结论** | `systemPrompt.context` 的 `text` 是同步求值,而决策是异步 HTTP 调用,第一步装配时通常尚未返回。插件在 `agent/inbox/inserted` 时预计算,因此**第二步起稳定可见**;单步问答(模型不调工具)时,结论会落到下一轮。 |
284
+ | **自动路由会多一次请求** | 场景数 >1 且 `autoScenario` 为空时,需先路由再决策。固定场景可省掉。 |
285
+ | **延迟直接叠加** | 自动决策在关键路径上同步等待,SystemOne 延迟会加到首字延迟。`autoTimeoutMs` 是硬上限,超时即放行。 |
286
+
287
+ 建议:先用 `autoScenario` 固定场景省掉路由请求;把 `autoTimeoutMs` 设成你能接受的最大延迟;若只是想让模型"知道该用哪个场景",用提示词引导即可,不必开自动决策。
288
+
289
+ ## 配置
290
+
291
+ 插件带**图形配置页**:在 DSH 侧栏打开 **插件 → @master0071/dsh-systemone**,点开 `systemone` 这一行,即可编辑下面的「热更新字段」。保存写入 profile 的 `cordis.patch.yml`,经 Loader 热更新后**立即生效,无需重启**(底层是 schemastery 的 volatile 字段 + 插件的按次读取)。
292
+
293
+ 两个配置入口的分工:
294
+
295
+ | 入口 | 能改什么 | 生效时机 |
296
+ | --- | --- | --- |
297
+ | 插件页(图形表单) | 下表标注「热更新」的字段 | 保存即生效 |
298
+ | profile 的 `cordis.patch.yml` | 全部字段(含结构性字段) | 按 profile 的 HMR 设置,或重启 |
299
+
300
+ ```yaml
301
+ # C:\Users\<你>\.dsh\profiles\desktop\cordis.patch.yml
302
+ - id: systemone
303
+ name: @master0071/dsh-systemone
304
+ config:
305
+ provider: unisound # unisound / http / mock
306
+ apiKey: '' # 留空则读环境变量 UNISOUND_API_KEY / SYSTEMONE_API_KEY
307
+ model: u2-decision
308
+ customScenarios: '' # 结构性:需要重启
309
+ autoDecide: false # 结构性:需要重启
310
+ ```
311
+
312
+ | 配置项 | 默认值 | 生效 | 说明 |
313
+ | --- | --- | --- | --- |
314
+ | `provider` | `unisound` | 热更新 | `unisound`(官方)/ `http`(SystemOne 兼容端点)/ `mock`(本地模拟) |
315
+ | `apiKey` | 空 | 热更新 | Unisound API Key;也可用环境变量 `UNISOUND_API_KEY` / `SYSTEMONE_API_KEY`。声明为 secret,**已保存的值不会回显到界面** |
316
+ | `baseUrl` | `https://maas-api.unisound.com/v1` | 热更新 | Unisound API 基础地址 |
317
+ | `endpoint` | 空 | 热更新 | `provider=http` 时的完整请求端点;留空回退到 `baseUrl` |
318
+ | `model` | `u2-decision` | 热更新 | 决策模型名 |
319
+ | `timeoutMs` | `30000` | 热更新 | 请求超时(毫秒) |
320
+ | `minConfidence` | `0.6` | 热更新 | 置信度阈值,低于则建议人工复核 |
321
+ | `customScenarios` | 空 | 热更新 | 自定义场景 JSON 数组(见下)。保存后**场景库立即重建**,无需重启 |
322
+ | `autoDecide` | `false` | 需重启 | 是否开启自动决策(挂载 `agent/pre-step`) |
323
+ | `auto*` | 见「自动决策」 | 热更新 | 自动决策的运行参数(钩子挂载后实时生效) |
324
+
325
+ > 为什么 `autoDecide` 需要重启:它决定「要不要挂钩子」,属于装配期决定。其余字段每次请求/每次事件都会重新读取,所以可以热更新。
326
+
327
+ ## 添加自定义场景
328
+
329
+ **推荐用图形配置页**:插件 → @master0071/dsh-systemone → systemone → 「自定义场景」卡片,粘贴 JSON 即可。编辑框会实时校验(JSON 语法、`id`/`title`/`questions`、问题类型与选项数量),不通过时保存按钮禁用并给出具体原因;保存后场景库立即重建,新场景的 id 也会自动出现在「固定场景」的候选列表里。
330
+
331
+ 卡片右上角的**「插入意图识别模板」**会填入下面这个通用意图识别场景,改 `criteria` 即可用。
332
+
333
+ 也可以在 profile 的 `cordis.patch.yml` 里配置 `customScenarios`(同样的 JSON 字符串)。同 `id` 会覆盖内置场景,否则新增。
334
+
335
+ ### 用自定义场景做通用意图识别
336
+
337
+ SystemOne 是「结构化问题 → 概率分布」的决策模型,**不是开放式意图分类器**:它的标签空间就是你声明的 `criteria`。所以要「通用意图识别」,就自己定义一个意图场景,把标签集写进去,再把「固定场景」指向它:
338
+
339
+ ```json
340
+ [
341
+ {
342
+ "id": "intent",
343
+ "title": "通用意图识别",
344
+ "description": "把任意输入归类到业务意图。",
345
+ "aliases": ["意图", "intent"],
346
+ "questions": {
347
+ "intent": {
348
+ "type": "choice",
349
+ "label": "意图",
350
+ "instructions": "用户这句话最想做什么?",
351
+ "criteria": {
352
+ "query": "查询/检索信息",
353
+ "action": "执行一个操作",
354
+ "create": "新建内容或文件",
355
+ "modify": "修改已有内容",
356
+ "analyze": "分析、对比、总结",
357
+ "explain": "解释原理或概念",
358
+ "debug": "排查报错或异常",
359
+ "chat": "闲聊、寒暄",
360
+ "other": "以上都不是"
361
+ }
362
+ },
363
+ "urgency": {
364
+ "type": "score",
365
+ "label": "紧急度",
366
+ "instructions": "这件事有多紧急?",
367
+ "criteria": ["不急", "可以等", "尽快", "马上"]
368
+ }
369
+ },
370
+ "derive": { "level": { "question": "urgency", "values": ["P4", "P3", "P2", "P1"] } },
371
+ "recommendation": "意图「{intent}」,紧急度 {level}"
372
+ }
373
+ ]
374
+ ```
375
+
376
+ 配合 `autoScenario: "intent"`,自动决策就不再受内置 10 个业务场景的约束。
377
+
378
+ **容量上限**:每个场景最多 **16 个问题**(延迟随问题数线性增长),`choice` 的选项 **2~26 个**,所以意图标签集实践上限约 26 类——超了只能合并或落到 `other`。
379
+
380
+ > 如果连固定标签集都不够用(想要开放标签、自由文本意图),那超出了这个模型的形态,得换真正的分类器或 LLM 自由文本方案。
381
+
382
+ ### 另一个例子里
383
+
384
+ 下面是一个业务场景的完整写法(法务合同风险预审):
385
+
386
+ ```json
387
+ [
388
+ {
389
+ "id": "legal_review",
390
+ "title": "法务 · 合同风险预审",
391
+ "description": "判断合同风险等级与是否需要法务介入。",
392
+ "aliases": ["合同", "法务"],
393
+ "questions": {
394
+ "risk": {
395
+ "type": "score",
396
+ "label": "风险等级",
397
+ "instructions": "这份合同的风险有多高?",
398
+ "criteria": ["无风险", "低风险", "中风险", "高风险"]
399
+ },
400
+ "clause": {
401
+ "type": "choice",
402
+ "label": "问题条款",
403
+ "instructions": "主要问题出在哪类条款?",
404
+ "criteria": {
405
+ "none": "无问题条款",
406
+ "liability": "责任与赔偿条款",
407
+ "payment": "付款与结算条款",
408
+ "ip": "知识产权条款",
409
+ "other": "其他"
410
+ }
411
+ },
412
+ "lawyer": {
413
+ "type": "noul",
414
+ "label": "法务介入",
415
+ "instructions": "是否需要法务介入?"
416
+ }
417
+ },
418
+ "derive": { "level": { "question": "risk", "values": ["L1", "L2", "L3", "L4"] } },
419
+ "recommendation": "风险等级 {risk}({level}),问题条款「{clause}」。{lawyer?需法务介入|可业务自审}"
420
+ }
421
+ ]
422
+ ```
423
+
424
+ ### 场景字段说明
425
+
426
+ | 字段 | 必填 | 说明 |
427
+ | --- | --- | --- |
428
+ | `id` | ✅ | 唯一标识 |
429
+ | `title` | ✅ | 展示名 |
430
+ | `description` | | 场景说明(`list` 时展示) |
431
+ | `aliases` | | 别名数组,便于按中文名/英文名查找 |
432
+ | `questions` | ✅ | 问题映射,最多 16 个(延迟随问题数线性增长) |
433
+ | `derive` | | 派生字段:`{ name: { question, values } }` 或 `{ name: { question, map } }` |
434
+ | `recommendation` | | 建议模板 |
435
+
436
+ ### 问题类型
437
+
438
+ | type | 定义 | 模型返回 |
439
+ | --- | --- | --- |
440
+ | `choice` | `criteria` 为「选项 key → 说明」对象(建议 ≤26 项) | `choice` + `probabilities` + `confidence` |
441
+ | `noul` | 无 `criteria` | `noul`(0~1,≥0.5 视为是) |
442
+ | `score` | `criteria` 为分级标签数组 | `score`(期望值)+ `legend` + `probabilities` + `confidence` |
443
+
444
+ ### 建议模板语法
445
+
446
+ | 语法 | 含义 |
447
+ | --- | --- |
448
+ | `{qid}` | 该问题的可读标签(choice=选项说明,score=分级说明,noul=是/否) |
449
+ | `{qid.key}` | 原始值(choice=选项 key,score=取整分值,noul=true/false) |
450
+ | `{qid.confidence}` | 置信度百分比 |
451
+ | `{derivedName}` | 派生字段 |
452
+ | `{qid?文案A\|文案B}` | 条件文案(noul 为真 / 分值 ≥1 / choice 非空非 `none` 时取 A;不支持嵌套) |
453
+
454
+ 非法自定义场景会被**跳过并在日志中告警**,不影响内置场景使用。
455
+
456
+ ## 切换提供商
457
+
458
+ 插件与厂商解耦:只要目标服务实现相同协议(`POST {endpoint}`,请求体 `{ model, state, questions }`,响应 `{ answers: { [id]: { type, ... } } }`)即可切换。
459
+
460
+ - **换一家云厂商**:配置 `provider: "http"` + `endpoint` 指向该厂商或自建网关,`apiKey` 填对应凭证。
461
+ - **本地联调**:`provider: "mock"`,无需 Key,确定性输出。
462
+ - **编程扩展**:参考 `lib/provider.js`,实现 `{ name, decide() }` 后注册到 `createProvider()`。
463
+
464
+ ## 开发与测试
465
+
466
+ ```powershell
467
+ npm run check # 语法检查
468
+ npm run test # 59 项测试:
469
+ # - 场景库完整性、10 大场景端到端、自定义场景合并/覆盖/非法跳过
470
+ # - params 覆盖语义、确定性、失败路径(无网络、无 Key)
471
+ # - 插件入口装配(临时桩实例化:工具注册、降级、卸载)
472
+ # - 自动决策(开关、注入形状、fail-open、硬超时、缓存、去重、
473
+ # 跳过规则、历史捕获、自动路由、卸载)
474
+ ```
475
+
476
+ ## 发布(CI / CD)
477
+
478
+ 仓库用 GitHub Actions 做持续集成与发布,两个工作流:
479
+
480
+ | 工作流 | 触发 | 做什么 |
481
+ |---|---|---|
482
+ | [`ci.yml`](.github/workflows/ci.yml) | push / PR 到 `master`、`main`,或手动 | Node **22.19 / 24** 双版本跑 `npm run check` + `npm test`,再跑一次 `npm pack --dry-run` 确认产物完整 |
483
+ | [`release.yml`](.github/workflows/release.yml) | push `v*` tag,或手动(默认演练) | 校验 tag ↔ 版本 → 校验 npm 上未重名 → 测试 → `npm publish --provenance` → 创建 GitHub Release |
484
+
485
+ ### 发一个版本
486
+
487
+ ```powershell
488
+ # 1) 先升版本(semver:patch / minor / major)
489
+ npm version patch # 0.4.0 → 0.4.1,会自动打 v0.4.1 tag 并提交
490
+
491
+ # 2) 推送提交和 tag
492
+ git push && git push --tags
493
+ ```
494
+
495
+ 推送 tag 后 `release.yml` 自动接管。发布前两道闸会拦住常见事故:
496
+
497
+ 1. **tag 必须与 `package.json` 的 `version` 完全一致**(例如 `v0.4.1` ↔ `0.4.1`),否则打错 tag 会静默发错版本;
498
+ 2. **该版本在 npm 上必须不存在**,防止覆盖已发布的版本。
499
+
500
+ ### 首次配置需要的密钥
501
+
502
+ `Settings → Secrets and variables → Actions → New repository secret`:
503
+
504
+ - `NPM_TOKEN` —— npm 的 **Automation** 类型 token([npmjs.com → Access Tokens](https://www.npmjs.com/settings/~/tokens) 生成,勾选 `Automation` 以绕过 2FA)。
505
+
506
+ 发布用的是 `npm publish --provenance`,会带上 npm 的来源证明(Sigstore),因此工作流额外申请了 `id-token: write` 权限——**不需要**额外配置,但需要包在 npm 上与该 GitHub 仓库关联。
507
+
508
+ ### 本地演练
509
+
510
+ 不想真发时,可以在 GitHub 上手动触发 `Release` 工作流,**`dry_run` 输入默认勾选**,只跑校验与 `npm publish --dry-run`。
511
+
512
+ ```powershell
513
+ npm run check && npm test && npm pack --dry-run # 本地等价演练
514
+ ```
515
+
516
+ ## 工具输出结构
517
+
518
+ ```json
519
+ {
520
+ "ok": true,
521
+ "scenario": "risk_control",
522
+ "provider": "unisound",
523
+ "model": "u2-decision",
524
+ "request_id": "4682848d-...",
525
+ "latency_ms": 322,
526
+ "answers": { "...": { "type": "...", "probabilities": {}, "confidence": 0.9 } },
527
+ "decision": { "...": "归一化值(choice=key,score=取整分值,noul=boolean)" },
528
+ "labels": { "...": "可读标签" },
529
+ "derived": { "...": "派生字段" },
530
+ "confidences": { "...": 0.9 },
531
+ "needs_human_review": false,
532
+ "recommendation": "建议文案",
533
+ "summary": "## Markdown 摘要"
534
+ }
535
+ ```
536
+
537
+ ## 许可证
538
+
539
+ MIT
540
+
541
+
542
+
2
543
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
@@ -0,0 +1,7 @@
1
+ # @master0071/dsh-systemone bundle patch:把 SystemOne 决策插件插入任何将本包列入
2
+ # `dsh.profile.bundles` 的 profile。插件依赖 harness 的 `tools`(注册 Agent
3
+ # 工具)与 `logger`;无 Web 面。配置项全部有默认值,可在插件设置面板中覆盖。
4
+ - insert:
5
+ - id: systemone
6
+ name: '@master0071/dsh-systemone'
7
+ config: {}