dsh-decision-tools
Verifieddsh-decision-tools · v0.4.0 · MIT · Web UI
DSH agent tools for local Ollama System One decision models: classify, answer yes/no, and score against a rubric in one batched request.
Install
dsh plugin add dsh-decision-tools Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Readme
dsh-decision-tools
向本地模型要概率,而不是要文字。 两个 DSH 工具,把有界判定变成便宜、可重复的调用:
| 工具 | 回答什么 | 典型用法 |
|---|---|---|
decision |
一份判据判 1–64 项,或一次问最多 64 个不同问题 | 筛列表、分类工单、路由请求、排序候选、「这段日志是不是失败?」 |
monitor |
反复采样同一个源,直到判据成立 | 等构建结束、等某行出现、等作业失败 |
判定跑在你自己的 Ollama(/v1/systemone)上:不经过云服务,材料也不必进入对话上下文。
为什么值得用
- 天然省钱:判据只发一次,一次调用判最多 64 项;逐项循环才是昂贵的反模式。
- 材料不进上下文:传工作区文件、glob 或另一个工具的原始输出,你只读回答案。
- 输出诚实:每个答案是概率 +
confidence;部分完成写进coverage,插件做了什么写在notes。
环境要求
- 已启用本插件的 DSH 桌面版(见「安装」)。
- 本地 Ollama 提供 decision 模型 —— 默认
tev1:0.8b,可用model单次覆盖。 - 无需 API Key;判定本身不出网。
- 图片 material 需要视觉模型(
clef/clef-flash);tev1/nimble会回does not support vision。
安装
npm 发布后:
dsh plugin --profile web add dsh-decision-tools
开发期从工作区链接:
dsh plugin --profile web add link:/absolute/path/to/dsh-decision-tools
然后确认工具可见:
cordis_inspect_query(platform="host", provider="Tool", method="listTools")
随包技能 decision-usage 与工具一起注册,写全了每个参数,按需加载。
换用本机其它模型
默认值(baseUrl = http://localhost:11434、defaultModel = tev1:0.8b、四条 ladder)都只是快照。任何能应答 System One 端点的 Ollama 模型都能用:
- 单次指定 ——
model: "<名称>"(同时关闭容量选择与升档); - 改会话默认 ——
defaultModel; - 换主机/端口 ——
baseUrl; - 调升档阶梯 ——
ladder: [{ model, numCtx }](见 Configuration)。
想知道本机实际装了哪些、并拿到可直接粘贴的 ladder:
node scripts/list-models.mjs # 名称 · 上下文 · 参数量 · 大小
node scripts/list-models.mjs --json # { models, ladder }
numCtx 取自 /api/show 的声明上下文长度(上限),运行时 num_ctx 可能更小。
快速开始
// 1) 筛列表:一份判据判 N 项
{ "material": "<列表或其上下文>", "rubric": "This is a bug report?",
"items": ["app crashes on save", "please add dark mode"], "type": "noul" }
// 2) 对同一个文件问不同的问题
{ "material": { "file": { "path": "build.log", "startLine": 1, "endLine": 200 } },
"questions": [
{ "name": "ok", "type": "noul", "instructions": "Did the build succeed?" },
{ "name": "why", "type": "choice", "instructions": "What failed?",
"criteria": { "compile": "compile error", "test": "a test failed", "env": "missing dependency" } }
] }
// 3) 判另一个工具的输出,而不把它读进上下文
{ "material": { "tool": { "name": "git_bash", "arguments": { "command": "npm test" } } },
"rubric": "Did the test suite pass?", "items": ["the test suite output"], "type": "noul" }
// 4) 判图片(需要视觉模型)
{ "material": { "images": ["screenshots/checkout-error.png"] },
"model": "clef-flash:latest",
"questions": [ { "name": "has_error", "type": "noul", "instructions": "Does this screenshot show an error state?" } ] }
// 5) 等后台作业结束,并让插件通知你
{ "source": { "kind": "job", "id": "<jobId>" },
"rubric": "Did the job finish successfully?", "background": true, "notify": "inbox" }
decision:一次调用,一种模式
给 material,再加恰好一种模式:
| 模式 | 形状 | 含义 |
|---|---|---|
| 批量 | rubric + items + type(+ criteria) |
一份共享判据,逐项独立判定 |
| 混合 | questions |
对同一份材料问最多 64 个不同问题 |
| 参数 | 必填 | 说明 |
|---|---|---|
material |
是 | 内联字符串 / {file} / {glob} / {tool} / {images} / {text, images} |
rubric |
批量 | 唯一共享判据;线格式会替模型重复它 |
items |
批量 | 1–64 个自包含项;直接传字符串也合法 |
questions |
混合 | 1–64 个 {name, type, instructions, criteria?} |
type |
批量 | choice / noul / score(混合模式逐题给) |
criteria |
choice/score | 标签集只写一次:批量走顶层,混合走每题 |
depth |
否 | quick(不升档不分批)、normal(默认)、deep(可切块);图片请求忽略 |
model |
否 | 固定模型;关闭容量选择与升档 |
材料形态:内联文本 · { file: { path, startLine?, endLine? } } · { glob: { pattern, limit? } } · { images: ["a.png"] }(或 { text, images })· { tool: { name, arguments? } }。工具输出不进上下文,全文落盘、路径回传为 material.spillPath;text/file/glob 可与 images 组合,tool 不能与其它形态组合,插件自身工具会被拒。
图片:工作区内 PNG/JPEG/WebP 路径(1–8 张;单张 ≤16 MiB、总量 ≤24 MiB base64 前、请求体 ≤32 MiB)。插件读盘、按 magic 校验并 base64 编码,图片字节不进你的上下文。图片请求需要视觉模型,且只发一次——depth 被忽略(不升档/不分批/不扫描)。输出里会带 material.images(count/bytes/encodedBytes/formats)。
读结果:results[](id/type/value/probabilities?/confidence?)· coverage(requested/answered/batches/truncated/truncatedBy/failed/aborted)· cost(model/inputTokens/bodyBytes/wallMs/escalated/attempts)· notes(首行是选档结论)。
noul 返回布尔 value;P(true) 在 probabilities.true 里(0.475 = 47.5% 是)。score 落在 0..N-1 尺度;choice 返回所选标签。confidence 是分布集中度,不是正确率 —— 当 notes 点名低置信项时,改读 probabilities 而不是标签。
monitor:等一个事件
按间隔采样同一个源、每次问同一个判据、累计命中。once 达到 countThreshold(或 deadline)返回;continuous 跑到 deadline/cancel,返回累积 count。它绝不 kill、重试或取消任何东西。
| 采样源 | 允许的键 | 读什么 |
|---|---|---|
job |
id?、select?(newest/oldest)、jobKind? |
后台作业输出(缺省看本会话最新) |
file |
path、startLine?、endLine? |
工作区相对文件 |
glob |
pattern、limit? |
命中文件拼接 |
command |
argv |
复用插件上游管道重跑宿主命令 |
events |
types、limit? |
作业事件环形缓冲(无缓冲时退到当前作业列表) |
参数:description(必填;作业 label = source.kind + description)· source(必填)· rubric(必填)· type(仅 noul)· criteria · hitThreshold(0.5)· tailBytes(4096,上限 65536)· mode(once|continuous)· countMode(cumulative|consecutive,默认 cumulative)· notifyEvery(continuous,默认 1)· intervalMs(5000)· timeoutMs(120000)· countThreshold(1;仅 once)· background · notify · coalesce · maxNotificationsPerMinute(6)。
- 空样本:
file/glob为空时立即返回(samples: 1、emptySource: true,不调模型);job/events/command的空样本按 P=0 记一次未命中并继续等待事件。 - 后台:
background: true返回jobId;job_output流式读过程行(config/hit/trigger/summary,读一次即消耗)。notify: "inbox"只在 trigger 发一行通知(once 达标 / continuous 每notifyEvery次命中),且必须是后台调用。作业状态:abort ⇒killed;全部探测 errors(无有效 verdict)⇒failed;timeout/有有效 verdict ⇒completed。 - detail:
finished [...]对 once 为hit/P|EP/dur,continuous 为trigger_num/EP/dur;EP:once = Σ(hit P)/N(N=countThreshold,不足补 0;0 hit ⇒ 0);continuous = 各 trigger P 均值(trigger P = 窗口 hit P / notifyEvery;0 trigger ⇒ 0)。oncehit=false取 1-EP;单位 ms。布尔型 verdict 无 P 时按 0.00(detail/通知/流都不出现-);description折叠为单行;永久源错误(描述符/越界/显式作业 id 不存在/文件超限)当场失败,瞬态读取错误继续重试;abort 会打断 in-flight decide/sleep。作业 label =source.kind + description。 command的 argv:argv[0]选解释器(pwsh、git_bash/bash/sh/zsh);解释器标志会被剥掉;带空格的参数要自己加引号。events.types按固定词表校验(job.registered、job.stopping、job.output、job.progress、job.settled、job.completed、job.failed、job.killed、job.removed、job.running、job.unknown),拼错报source_invalid。
Configuration
插件由四行组成:核心行、decision 行、monitor 行与 skill 行。每行在 Plugins 页都有自己的开关;核心/decision/monitor 三行还各有自己的 Configure 页。GUI 可改字段声明为 .volatile(),保存后下一次调用生效,无需重载行。toolName / monitorToolName / ladder / spillDirCap 是注册期或高级 YAML 项:在 cordis.patch.yml 里改,然后重载对应行。
核心行 — decision-tools-plugin(共享模型与运行时)
| Key | Default | Meaning |
|---|---|---|
baseUrl |
http://localhost:11434 | Ollama 地址;路径前缀会被保留;GUI 可改。 |
defaultModel |
tev1:0.8b | 工具行没有 modelOverride 时使用的模型;GUI 可改。 |
timeoutMs |
120000 | 单次请求预算,含冷加载;GUI 可改。 |
keepAlive |
unset | 透传为 keep_alive;未设用服务端默认;GUI 可改。 |
apiKey |
未设 | 可选;作为 Bearer token 发给远程 / System One 兼容接口;GUI 可改。 |
modelsPath |
/api/tags | 模型发现路径;OpenAI 兼容接口可用 /v1/models;GUI 可改。 |
systemOnePath |
v1/systemone | 判定接口路径(相对 baseUrl);GUI 可改。 |
modelOrder |
未设 | 逗号分隔的模型优先级;model@numCtx 可指定上下文;未知模型按 8192;GUI 可改。 |
escalationEnabled |
true | 上下文溢出时允许自动升档;GUI 可改。 |
ladder |
unset (DEFAULT_LADDER) | 有序的 {model,numCtx}[];仅 YAML;改后重载核心行。 |
toolName |
decision | decision 工具的模型可见名;改后重载 decision 行。 |
monitorToolName |
monitor | monitor 工具的模型可见名;改后重载 monitor 行与 decision 行(两行都快照该名)。 |
spillDirCap |
unset (16 MiB) | 插件转储目录的字节上限;仅 YAML。 |
decision 行 — decision-tools-decision
| Key | Default | Meaning |
|---|---|---|
modelOverride |
unset (core default) | 留空使用核心行的 defaultModel;GUI 可改。 |
defaultDepth |
normal | quick / normal / deep;调用省略 depth 时使用;GUI 可改。 |
bodyWarning |
true | false 时不再返回体积/文本形状提示;GUI 可改。 |
softBodyBytes |
unset (4096) | 超过该字节数会在 notes[] 带体积提示;0 表示每次都告警;GUI 可改。 |
minConfidence |
unset (0.25) | confidence 低于该值(或缺失)的 choice/score 项会被点名;0 关闭;GUI 可改。 |
maxChoiceOptions |
unset (255) | choice 选项数上限;GUI 可改。 |
escalationMaxAttempts |
3 | 额外升档次数(不含首次;deep +2;留空 = 3);GUI 可改。 |
scanFirstChunkBytes |
unset (4096) | 首块字节预算;GUI 可改。 |
scanMaxChunks |
unset (32) | 单次扫描的块数上限;GUI 可改。 |
scanMaxTotalBytes |
unset (4 MiB) | 单次扫描的累计字节上限;GUI 可改。 |
scanDeadlineMs |
unset (180000) | 单次扫描的墙钟预算;GUI 可改。 |
scanAggregate |
none | none / any;any 会给跨块答案加一个暂定的最大概率;GUI 可改。 |
monitor 行 — decision-tools-monitor
| Key | Default | Meaning |
|---|---|---|
modelOverride |
unset (core default) | 留空使用核心行的 defaultModel;GUI 可改。 |
minProbeIntervalMs |
unset (1000) | monitor 探测间隔下限;GUI 可改。 |
notifyCoalesceWindowMs |
unset (5000) | 同类通知合并窗口;0 表示不合并;GUI 可改。 |
maxNotificationsPerMinute |
unset (6) | 每分钟通知上限;GUI 可改。 |
覆盖既有行要用裸 id、不带 insert,字段写在 config 键下。config 是整块替换、不做深合并,所以覆盖时必须重述该行用到的全部字段。
- id: decision-tools-plugin
config:
baseUrl: 'http://localhost:11434'
defaultModel: 'tev1:0.8b'
timeoutMs: 180000
keepAlive: '30m'
- id: decision-tools-decision
config:
modelOverride: 'tev1:0.8b'
defaultDepth: normal
- id: decision-tools-monitor
config:
modelOverride: 'nimble:latest'
三条 Config schema 在 lib/configs.js。
常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
context_overflow |
材料 + 问题超出模型窗口 | 用 depth: "deep" 切块、减小材料或减少问题数 |
| 工具列表里没有 | 插件未在 profile 启用 | 链接进 profile 后重载 |
DSH 升级后 ERR_MODULE_NOT_FOUND |
工作区 node_modules 仍指向旧安装 |
在 DSH 内跑 scripts/rebuild-node-modules.mjs,再复跑测试 |
monitor 返回 hit:false 且 errors:0 |
源为空,或 types 没匹配到 |
看 emptySource;notes 会写原因 |
monitor 返回 errors: N > 0 |
读取或判定失败 | 看错误正文,渲染结果里也有 failed probes |
source.kind=events 报错 |
该组合下事件源不可用 | 能回落时会退到本会话当前作业列表 |
invalid_request / mode_invalid |
参数不合法 | 报错列出合法值;mode_invalid = 模式/数量/形态,invalid_request = 字段 |
边界与安全
- 这些是概率,不是证据:不得当作正确性证据,也不得单独作为放行/拒绝的依据(那需要产物级证据)。
- 工具不得决定路由,也不得作为任何 fail-open / fail-closed 策略的依据 —— 那属于确定性规则。
tev1只返回概率、不返回文本;需要可读理由时换生成类模型。- 工具原始输出落到插件自建的临时目录(总上限 16 MiB,
spillDirCap),路径随结果回传。 - 判定全程本地,插件自身不发起云调用。
开发
node --test test/*.test.mjs # 全量套件
node --check lib/*.js # 语法门
改过 lib/ 后,先在 DSH 里重载插件再下结论:宿主跑的是最后一次加载的快照。
Licence
MIT —— 见 LICENSE。
无兼容层
这是一次性结构升级,不是版本过渡。旧行 id(decision-tools-tool)与旧配置键(decisionEnabled / monitorEnabled)不再被接受;使用上面的四行与三张 Config 表。契约见 docs/architecture.md,用例见 docs/use-cases.md。