Skip to content

dsh-decision-tools

Verified

dsh-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)。once hit=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。