dsh-effort-lens · 思考强度镜 v0.3.1
输入框左侧的大脑按钮:手动触发判定,设置页管理 JEV 供应商,用户确认后真正改当前会话的推理档位。
档位集合永远来自 DSH 运行时(客户端读当前模型的
reasoning.efforts),插件不发明档位、不做 clamp。
1. v0.3.1 本次改动
v0.3.1 是 v0.3.0 的打包与工程化补丁,功能与判定行为与 v0.3.0 一致。 v0.3.1 补齐了 v0.3.0 发布包中缺失的
LICENSE,并重建了随包发布的测试套件(v0.3.0 的 tgz 用files白名单排除了test/,导致npm test指向不存在的文件)。 下表记录的是 v0.3.0 引入的功能改动,保留作为变更历史。
| # | 改动 | 位置 | 说明 |
|---|---|---|---|
| 1 | E1 判定引擎重写 | lib/client.js · >>> E1-BEGIN / <<< E1-END 区块 |
由 maxRank 聚合改为「按证据家族取最强证据」;ASCII 规则全部加词边界;同名异构词固定口径;置信度改为三因子;稀疏档位映射平局取低档并标注「近似」 |
| 2 | off 语义纠正 |
lib/core.js TIER_SEMANTICS.off、lib/client.js panel.offNote |
off = 显式关闭思考,不是「不发送 effort / 网关默认档」;off:null 与 off:none 发出的 payload 相同 |
| 3 | 配置热更新 | lib/index.js · apply / liveConfig() |
每请求重新 resolveConfig(rawConfig) 读 volatile 最新值,并监听 loader/volatile-update 清缓存;设置页保存后不用重启 |
| 4 | 设置页修复 | lib/client.js · EffortLensSettings |
字段补齐(含 engine / 凭据引用 / 鉴权头 / 鉴权前缀 / 超时);只在引用真的改动时写 credentialRef;配置与密钥分离提示;保存后不再整页 loading;优先走宿主 props.form.mutate |
| 5 | 超时链路 | lib/client.js · 客户端预算 |
客户端超时不再是固定 45s,按 health 的 limits.timeoutMs 取 2×timeoutMs + 5s(宿主最多重试 1 次);timeoutMs 可配 2–120s |
| 6 | 429 / Retry-After | lib/index.js |
宿主读上游 retry-after 并在内部重试时按它等待(上限 30s);对外 429 返回真实秒数而不是固定 5 |
| 7 | 串档修复 | lib/client.js · abortRef / judgeKeyRef |
编辑指令会 abort 在途 JEV 请求,并用 judgeKeyRef 丢弃过期响应,旧结果不会合并进新文本的行 |
| 8 | UI 小修 | lib/client.js · renderRow / 结果区 |
每行都有「应用」按钮;panel.apply.note 会渲染;jevState.warnings 走提示条;50 条截断提示走 i18n;置信度为 null 时进度条宽 0、数值显示「—」;engine=rules 时禁用「精细判定(JEV)」并提示 |
| 9 | 宿主侧加固 | lib/index.js、lib/core.js |
档位校验改用 hasOwnProperty(constructor / toString 不再被当成合法档位);legend 缺失标 approximate 并告警;百分制概率自动归一;缓存键纳入 version / credentialRef / 档位三元组并用双 32 位哈希;openai 协议解析失败返回 502;Host 头缺失拒绝(403);限流改为进入时记账;engine: rules 时宿主直接拒绝 JEV 调用;openai max_tokens 上限提到 8000;可选 context(≤2000 字)可透传进网关请求 |
| 10 | 测试与基准 | test/ |
npm test 全过(v0.3.1 重建的 43 项套件,零依赖、无需 npm install;详见 §10) |
2. 快速上手
2.1 安装
从发布包安装(把 dsh-effort-lens-0.3.1.tgz 的路径换成你本地的实际路径):
dsh plugin install ./dsh-effort-lens-0.3.1.tgz
从源码目录安装(开发时用,改完即时生效):
dsh plugin install .
安装后在 设置 → 插件管理 里应能看到「思考强度镜」,并在输入框左侧出现大脑按钮。
2.2 入口
- 大脑按钮:输入框左侧(槽位
conversation.input.left,ideffort-lens,图标 Lucidebrain)。 - 设置页:
设置 → 插件管理 → 思考强度镜 → 配置(槽位plugins.row.config,keydsh-effort-lens#effort-lens)。
2.3 判定流程
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 点大脑按钮打开面板 | 只有指令框和「尚未判定」空态,不自动判定 |
| 2 | 点底部「开始判定」 | 跑本地规则引擎 E1,立刻出结果表(档位 / 置信度 / 依据) |
| 3 | 需要更精细时点「精细判定(JEV)」 | 独立按钮,POST /effort-lens/judge;JEV 结果合并回同一张表,JEV 一律胜出,差 ≥2 档标「引擎分歧较大」 |
| 4 | 在行内下拉手工改档 | 该行标「人工指定」,置信度记 100% |
| 5 | 应用 | 每行都有「应用」按钮(多行也能逐条应用);底部「确认应用」只在单条结果时可用,多条时禁用并提示「多条指令时请用行内『应用』逐个确认」 |
编辑指令文本会作废上一次结果(清空表格、退回「尚未判定」),并 abort 在途的 JEV 请求;旧响应被 judgeKeyRef 丢弃,不会串到新文本的行里。
2.4 面板上的复制与详情
| 位置 | 按钮 | 行为 |
|---|---|---|
| 行内 | ⧉ |
复制该行档位名 |
| 行内 | 「详情」/「收起」 | 展开该行的完整指令 + JEV 概率分布 |
| 行内 | 下拉 | 手工指定档位(标「人工指定」) |
| 底部 | 「复制报告」 | 复制 Markdown 表格(模型 / 档位集合 / 引擎 / 每条的档位·置信度·依据) |
底部只保留「复制报告」;旧的「复制档位名」「复制操作步骤」两个底部按钮已不存在。
v0.3.0 的行内「应用」按钮与结果区提示条尚未截图,需要在 DSH 里实际打开一次面板复核(见 §11)。
3. 判定引擎 E1
E1 是纯本地规则引擎,不联网、不消耗额度。代码位于 lib/client.js 的哨兵区块:
>>> E1-BEGIN ... <<< E1-END
test/e1.test.mjs 把这个区块原样切片出来执行,所以基准跑的就是发布代码本身,不存在「测试用副本漂移」。
3.1 证据家族
扫描命中多个家族时,取目标档位最高的那一个家族作为结论,其余家族只降低置信度、不再抬高档位(旧版是 maxRank 聚合,一个高档词就能压过全部证据)。
| 家族 | 含义 | 阶梯目标 | 对应标准档位 |
|---|---|---|---|
root |
根因 / 架构权衡 / 竞态 | 5 | xhigh |
archDesign |
架构设计 / 高并发 | 5 | xhigh |
math |
数学 / 算法推导 | 4 | high |
arch |
架构 / 选型 / 一致性 | 4 | high |
debug |
调试 / 定位 / 跨文件 | 4 | high |
review |
设计评审 | 4 | high |
perf |
性能诊断 | 4 | high |
explain |
解释 / 分析 | 3 | medium |
impl |
实现 / 重构 | 3 | medium |
common |
常规问题排查 | 3 | medium |
small |
单点小改 | 2 | low |
mechanical |
机械改写 / 格式转换 | 1 | minimal |
lookup |
单点查询 | 0 | off |
阶梯是标准档位序:off 0 / minimal 1 / low 2 / medium 3 / high 4 / xhigh 5 / max 6。
3.2 词边界与同名异构词
- 所有 ASCII 规则都加了
\b:\brace\b不再命中gracefully、braces、traceability。 - 同名异构词在
OVERRIDES名单里钉死口径,优先级高于家族扫描:
| 词形 | 固定目标 | 说明 |
|---|---|---|
design token |
low | 不是「架构设计」 |
naming consistency / braces / batch insert |
minimal | 机械改动 |
derived state / 目录结构 / 异常处理 / why not |
medium | 常规排查 |
is this real / correct / valid |
high | 真伪判断 |
prove |
high | 数学推导 |
报错…啥意思 |
low | 读不懂报错 |
怎么查看 / how to |
low | 用法查询 |
查一下 / 版本号 / check the latest |
minimal | 单点查询 |
列出文件 / 目录 |
minimal | 列举 |
回调改 promise |
medium | 实现 |
合理吗 / 能行吗 |
medium | 方案设计 |
| 寒暄 / 现在几点 | off | 无任务 |
3.3 置信度与 needsConfirm
置信度不再是常数表(v0.2 单命中恒 0.85),改为三因子:
base = 证据量:无命中 0.42–0.50 / 单命中 0.70–0.78 / 目标≥4 且多命中或显式加码 0.84
score = base − 冲突惩罚(0.12) − brevity 惩罚(0.04)
confidence = score × 映射距离折扣(1 档 ×0.86,≥2 档 ×0.68),夹在 [0.05, 0.95]
- 证据量:同档命中多家族、或命中显式加码词(
仔细想/think hard/step by step)都会提高置信度; - 冲突惩罚:家族之间目标档位相差 ≥2 档记为证据冲突,扣 0.12;
- 映射距离:目标档位与本模型档位集对不上时按距离打折(相差 1 档 ×0.86,≥2 档 ×0.68)。
需要人工确认(行内标「建议人工确认」)的条件:
needsConfirm = 映射距离 >= 2 或 置信度 < 0.55 或 证据冲突 或 高档任务被降档
无命中兜底按文本长度 / 代码名词 / 纯 brevity 指令分档,兜底置信度 0.42–0.5(不再是恒定 medium)。
3.4 稀疏档位集映射
目标档位不一定在本模型里存在(例如模型只有 low/high/max,目标却是 high 与 xhigh 之间):
- 平局一律取较低档(
mapTier(..., preferHigher = false)),避免系统性超支; - 每个有损映射都标「近似」(
approximate),并在依据里写明「本模型无该档,落至 X」; - 丢失 ≥2 档、或目标档位 ≥4 却发生了映射的行,一律要求人工确认,不再静默上报。
3.5 简短输出不降推理
「别解释 / 一句话 / 简单说 / just answer / briefly」这类指令只约束输出长度,不降低推理档位:
- 扫描前先把这些从句剥离(
BREVITY_ALL_RE),避免「不要解释」反过来命中「解释」类证据; - 「别解释,这个 bug 在哪」仍然判成一次 bug 排查;
- 纯 brevity、没有任何任务残留时才落到 minimal。
显式加码只剩一类:UP_RE(仔细想 / 深入 / think hard / step by step)加一档(上限 5)。旧的「显式减码」规则已废弃。
4. off 的语义:显式关闭,不是网关默认
这一点 v0.3.0 专门纠正过,写反了会误导用户选错档位。
- 本机 6 个模型都配了
compat.thinkingFormat:pi-ai 在 deepseek / zai 分支发thinking:{type:"disabled"},qwen 分支发enable_thinking:false; - 因此
off: null(deepseek-v4.1-flash)与off: none(deepseek-flash)发出的 payload 完全相同; off的含义是显式关闭思考,不是「不发送 effort / 网关默认档」。
这条语义同时写进了两处用户可见文案:
- JEV criteria 文案(
lib/core.jsTIER_SEMANTICS.off):「显式关闭内部推理(向网关发送禁用思考的参数,不是不传参数)」; - 面板上
off档 chip 的 tooltip(panel.offNote):说明各分支发的具体参数与off:null/off:none等价。
因此插件不禁止选
off。只有当模型本身没声明off档时,面板才提示「该模型不提供 off 档:无法关闭思考,最低只能到 X」。
5. 设置页
5.1 字段
| 字段 | 配置键 | 取值 / 校验 |
|---|---|---|
| 供应商名称 | providerName |
非空 |
| 供应商 URL | baseURL |
必须是合法 http(s) |
| 供应商模型 | model |
非空 |
| 协议 | protocol |
systemone / decision / openai |
| 判定引擎 | engine |
rules = 只跑本地规则(宿主拒绝 JEV 调用、面板禁用「精细判定」);auto 与 jev 都允许 JEV,仅作偏好标记 |
| 凭据引用 | credentialRef |
非空,默认 JEV_API_KEY |
| 鉴权头 | authHeader |
默认 authorization |
| 鉴权前缀 | authScheme |
默认 Bearer;留空则不带前缀 |
| 超时(秒) | timeoutMs |
2–120 秒,默认 30 |
| 供应商密钥 | 凭据中心 | 写入 DSH 凭据中心;输入框留空表示「保持现有 Key」 |
5.2 保存语义
- 只在用户真的改了凭据引用时才写
credentialRef。旧版每次保存都硬写JEV_API_KEY,会覆盖用户自定义的引用名。 - 配置先落盘、密钥后写。密钥写失败时提示「配置已保存,但密钥写入失败:…」,不再把整次保存误报成失败。
- 保存后只刷新密钥状态,不再整页
loading。 - 优先走宿主给的
plugins.row.config的props.form.mutate(由宿主自己管 revision 与冲突);宿主没提供时才退回remote.settings.mutate。 - 清除密钥用的是当前配置的引用(
keyInfo.ref→form.credentialRef→JEV_API_KEY),不是硬编码;确认文案与结果文案都显示实际引用名。
5.3 读写路径
| 读 | 写 |
|---|---|
ctx.remote.settings.describe() → effort-lens 命名空间的扁平字段 |
配置:settings.mutate('effort-lens', ops, revision)(或宿主 props.form.mutate) |
GET /effort-lens/health → 宿主报告的实际生效后端与能解析到的凭据引用 |
密钥:ctx.remote.credentials.set(ref, key) → ~/.dsh/.credentials.yaml |
读到的字段优先取配置;配置缺省时用 health 回填(health 反映的是宿主真正在用的值)。
宿主解析凭据的顺序:credentialRef → JEV_API_KEY。
6. 配置热更新:保存后立即生效,不用重启
Config(lib/index.js)声明 15 个字段,全部.volatile():engine/providerName/baseURL/model/protocol/credentialRef/authHeader/authScheme/timeoutMs/maxItems/maxInputChars/rateLimitPerMinute/maxConcurrent/cacheTtlMs/cacheMaxEntries设置页写进去的每一个字段都过 schema,不会作为未知键被丢弃。
apply()启动时的那次resolveConfig只做校验(配置非法就记一条 warn 并让插件保持未激活),不缓存结果。之后每次
judge/health/ 限流判定 / 缓存清理,都调用liveConfig()→resolveConfig(rawConfig)重新读 volatile 最新值。同时监听
ctx.on('loader/volatile-update'):设置页保存会触发它,插件据此清空判定缓存(旧缓存是在旧设置下产生的,不能复用)。
所以「设置页保存 → 立刻生效」这句话在 v0.3.0 是成立的:既不需要重启插件,也不需要 reload。
7. 超时、重试、429 与限流
| 项 | 行为 | 可配 |
|---|---|---|
| 网关单次超时 | 宿主 config.jev.timeoutMs |
timeoutMs,2–120 秒,默认 30000 |
| 宿主重试 | 最多 1 次(共 2 次尝试),仅对可重试错误 | — |
| 内部重试等待 | 上游 429 时按 retry-after 等待,上限 30 秒;其它可重试错误退避 350ms |
— |
| 浏览器端等待预算 | 2 × timeoutMs + 5s(下限 20s,上限 600s),取自 health 的 limits.timeoutMs |
随 timeoutMs 联动 |
| 对外 429 | 返回真实计算的 retry-after 秒数(不是固定 5) |
— |
| 本地限流 | 进入时记账,默认 20 次/分钟 | rateLimitPerMinute,1–120 |
| 并发 | 默认 2 | maxConcurrent,1–8 |
| 缓存 | 默认 10 分钟、最多 100 条;volatile-update 时清空 |
cacheTtlMs / cacheMaxEntries |
| 请求体 | 上限 256 KiB,超出返回 413 | — |
| 条数 | items ≤ 50 |
maxItems |
| 指令总长 | ≤ 12000 字符 | maxInputChars(200–60000) |
限流与并发都用同一套错误码返回给客户端(rate-limit / concurrency-limit),并带 retry-after 头。
8. 宿主路由与安全边界
8.1 路由
| 方法 | 路径 | 作用 |
|---|---|---|
GET |
/effort-lens/health |
当前生效的后端、凭据状态、执行上限 |
POST |
/effort-lens/judge |
对浏览器送来的档位集合做 JEV 判定 |
路由注册在 DSH 共享 webserver 的 /effort-lens 前缀下,实际端口由 DSH 实例决定(本机当前是 4006 / 1412,README 不写死端口)。
8.2 信任边界
| 检查 | 不通过时 |
|---|---|
| socket 必须是 loopback(127.0.0.1 / ::1 / ::ffff:127.0.0.1) | 403 |
Host 头必须是本机名(localhost / 127.0.0.1 / ::1),缺失即拒 |
403 |
不得带 x-forwarded-for / forwarded 代理头 |
403 |
Origin(若存在)必须是本机且同端口 |
403 |
8.3 已知安全限制(未修,如实写)
- 没有本地 token。上述 fence 挡的是浏览器侧的跨站调用;本机任意进程仍可直接调用
/effort-lens/judge,会消耗用户自己配置的 JEV 额度。加 token 需要宿主与客户端共享秘密,本版不做。 - 响应里不含密钥:错误信息中的密钥会被替换成
***,describe只返回configured/ref,不返回值。 - 凭据值只存在于宿主进程内与 DSH 凭据中心。
9. 判定引擎 E2:JEV 网关
- 「精细判定(JEV)」把浏览器读到的档位集合原样送给宿主,宿主不猜档位;
- criteria 按模型内相对档位描述(
high(第 3/4 档:…)),不用全局 L 序号,避免四档模型把L4读成「七档里的第四档」; - 支持三种协议:
systemone(JEV TypeSafe)、decision(JEV decision)、openai(chat/completions JSON); openai协议max_tokens = min(8000, 400 + items × 220),避免多指令时被截断;- 宿主支持可选
context(≤2000 字),并入 state / system prompt —— 面板 UI 尚未提供输入入口,其它客户端可以直接传; - 归一化时:未知档位按
hasOwnProperty丢弃并告警;legend 缺失时按档位顺序推断并标approximate+ 告警;概率是百分制(45/54/1)时自动归一;openai 解析失败返回 502(不是 500); - 缓存键包含
version/ 协议 / 端点 / 模型 /credentialRef/ 档位三元组[id, name, rank]/ 指令 / state,用双 32 位累加器哈希。
10. 测试与基准
10.1 测试
npm test
等价于:
node test/run-tests.mjs
43 项全过,且零依赖:lib/core.js 不 import 任何模块,所以干净检出后不装 node_modules 也能直接跑。CI 在 Node 20 / 22 / 24 上验证这一点。
| 文件 | 项数 | 覆盖 |
|---|---|---|
test/core.test.mjs |
33 | 档位阶梯不变量、hasOwnProperty 档位校验(constructor / toString 不被当成合法档位)、legend 解析、百分制归一、legend 越界与缺失的近似标注、score/argmax 分歧标记、openai 两种响应形状与解析失败、缓存键敏感度 |
test/package.test.mjs |
10 | 打包契约:dsh 清单字段、exports 目标全部可解析、files 白名单逐项存在、README 标题版本与 package.json 一致、LICENSE 含 MIT 正文、locale 可解析、icon 是合法 SVG、patch 文件引用包名 |
test/package.test.mjs 就是用来防「发布包和仓库不一致」这类问题的:v0.3.0 的 tgz 里 npm test 指向 5 个不存在的文件,正是这组断言现在会直接拦下的情况。
跨平台注意事项:
node --test默认给每个测试文件开子进程;在 Windows 受限令牌下该 spawn 会失败(EPERM),需要--test-isolation=none,但该参数并非每个 Node 大版本都有(Node 22 上会直接报node: bad option)。因此npm test走test/run-tests.mjs,只在当前 Node 认识该参数时才追加,其余情况用默认隔离。
10.2 基准(历史记录,夹具未随包发布)
v0.3.0 的 E1 基准是 85 条人工标注指令 × 5 个真实档位集,引擎由 lib/client.js 的 >>> E1-BEGIN / <<< E1-END 哨兵区块切片执行,所以基准跑的就是发布代码本身。
⚠️ 该基准的夹具 test/fixtures/e1-cases.json 与 test/e1.test.mjs 没有随 0.3.1 的 tgz 发布,因此本仓库当前无法复跑基准;下表是 v0.3.0 的实测存档,保留作为对照基线。
v0.3.0 实测:
| 集 | 模型 | 档位集 | 精确命中 | 超支率 | 结构性超支下限 |
|---|---|---|---|---|---|
| A | deepseek-v4.1-flash-1017 | off/low/high/max | 45.9% | 0% | 0% |
| B | deepseek-flash | off/low/high/max | 45.9% | 0% | 0% |
| C | qwen3.8-max | off/low/medium/xhigh | 61.2% | 1.2% | 0% |
| D | minimax-m3.1-flash | low/medium/high/xhigh/max | 65.9% | 32.9% | 31.8% |
| E | glm-5.3 | low/high/max | 32.9% | 31.8% | 31.8% |
v0.2.0 基线(复核报告实测):
| 集 | 精确命中(v0.2.0) | 超支率(v0.2.0) |
|---|---|---|
| A | 21.2% | 52.9% |
| B | 21.2% | 52.9% |
| C | 32.9% | 55.3% |
| D | 34.1% | 70.6% |
| E | 17.6% | 75.3% |
补充说明:
- 「结构性超支下限」 = 档位集本身没有该档时必然超支的比例(例如 D / E 没有
off,落在off的指令只能被抬到low)。这部分不是引擎能修的,引擎只对超出下限的部分负责。 - A / B 超支降到 0% 的代价是「平局取低档」,因此欠配变多;所有有损映射都标了「近似」,并在高档任务上要求人工确认。
- 免责声明:这 85 条是维护者标注的开发用种子集,不是独立双标注的独立测试集,不能当作泛化精度承诺。E1 仍然是规则引擎,它的输出只是建议,最终由人确认。
11. 已知限制与未做项
- 没有本地 token:
/effort-lens/*对本机任意进程开放,调用会消耗用户配置的 JEV 额度(见 §8.3)。 - 本版未真调网关做额度消耗验证:全部验证都在本地桩(stub
fetch)上完成,没有消耗真实网关额度;Retry-After与协议字段以审计实测为准。 - 面板未提供
context输入入口:宿主已支持该字段(≤2000 字),徒手或其它客户端可传。 - 真机 UI 未复核:v0.3.0 的行内「应用」按钮、结果区提示条、
panel.apply.note曾在test/render.test.mjs里用最小 hooks 运行时做过渲染级断言(按钮存在、按钮文案、禁用态),但该测试文件未随 0.3.1 发布,且真实浏览器里的视觉与交互仍未经复核,需要在 DSH 里实际打开一次面板确认。 - 确认应用会顺带写全局默认:DSH 的
session.selectModel在改当前会话的同时把同一选择写成全局默认,官方 API 无法分离。子代理会话不支持切换档位(UI 会提示)。 - E1 是规则引擎:85 条种子集只覆盖常见说法,未覆盖的写法会落到兜底分档,置信度只有 0.42–0.5。
- 插件描述的刷新:locale 元数据更新后,插件管理器可能需要刷新 / 重载才会显示新描述。
12. 文件清单
| 文件 | 作用 |
|---|---|
lib/client.js |
大脑按钮、手动判定面板(E1 + JEV 合并)、行内「应用」、设置页卡片;E1 引擎在 >>> E1-BEGIN / <<< E1-END 区块内 |
lib/index.js |
宿主 Config(15 字段)、/effort-lens/health、/effort-lens/judge、凭据解析、限流与缓存、volatile 热更新 |
lib/core.js |
档位校验、criteria 构建、JEV / openai 答案归一化、缓存键 |
test/core.test.mjs |
33 项纯函数单测(零依赖,无需 npm install) |
test/package.test.mjs |
10 项打包契约测试:files 白名单、exports 可解析、README/package.json 版本一致、LICENSE 正文 |
test/run-tests.mjs |
测试入口:按当前 Node 是否支持 --test-isolation=none 决定是否追加该参数 |
LICENSE |
MIT 正文 |
CHANGELOG.md |
版本变更记录 |
.github/workflows/ci.yml |
GitHub Actions:Node 20 / 22 / 24 上跑 npm test |
locale/zh.json / locale/en.json |
插件标题与描述 |
cordis.patch.yml |
插件声明与可选的 config 覆盖示例 |
icon.svg |
Lucide brain(ISC),署名见文件内注释 |
图标仍为 Lucide
brain(ISC),署名见icon.svg注释。
No comments yet. Be the first to write one.