DSH 插件优化集合(dsh-prompt-tuner)
一个个人向的 DSH Web 插件集合:把日常真正用得上的优化做进输入框旁边,而不是只做一件事。目前五件:
- ✨ 提示词改写(单一模式):把输入框里已经写好但还不够明确的内容,用一套内置默认提示词(或你自己写的那一份)改写为指向清楚、更有逻辑、可直接执行的提示词,再交给 agent;可把最近 n 条会话消息按时间正序一起拼进提示词,可选改写结果用中文还是英文,并可指定用哪个模型、思考到什么档位(默认跟随当前会话、不思考)。改写输出期间输入框里的发送被禁用(打字不受影响),结果落进输入框那一刻起才放开(见历史改进记录 · 提示词改写、优化期间锁住发送与改写自己的模型与强度)。
- 💬 旁路提问(
/btw):主任务跑着的时候,顺手问一个小问题——答案只浮在输入框上方,不写进主对话、不打断 agent(见历史改进记录 · 旁路提问)。 - 🔔 任务完成通知:每个对话任务结束时弹一条系统桌面通知,标题取会话标题,正文先由你指定的模型把本轮回答压缩成一句能完整显示的话(这次调用的思考强度可配,默认关闭;也绝不把最后一条消息原样推出去)——压不进设置的长度才以
...结尾;宿主按平台分发——Linux 用notify-send、Windows 用 PowerShell 通知、WSL 走 Windows(见历史改进记录 · 任务完成通知)。 - 🏷 会话标题重总结:初始标题仍是会话的第一句话(DSH 自己写的,插件不动);此后每积累 N 条你的消息,用你指定的模型根据最近 N 条消息重写一次标题(长度上限与 N 都在设置页里调)。计数按历史会话长度,重启不会清零——长会话在启动时就会按历史长度补一次重总结;composer 工具行还有一个「重建标题」按钮,随时可以跳过轮数立刻重写,成功与失败都会写进会话日志留痕(见历史改进记录 · 会话标题)。
- 🛣 供应商熔断与顺序切换(已拆成本地独立服务):某个供应商任何失败都在原路由上重试 N 次,用完即熔断,按顺序表切到下一个可用供应商(不必等原供应商的退避);冷却时长随连续熔断次数递增,成功一次归零;到期后按所选方式恢复——默认 probe:先放行一次探测请求,成功才真正切回,也可选
immediate。这一半不再跑在插件里:插件只负责把它作为独立进程拉起来,顺序表、供应商与密钥、熔断参数都在服务自己的页面上改,插件设置页的「路由」页签变成只读状态 + 跳转链接。服务对外是一个 OpenAI 兼容的本地代理(默认http://127.0.0.1:8790/v1),所以本机任何 agent 都能用,不限于 DSH。它还带一个可注册的协议转换器接口,内置的maas转换器把 ZTE 网关那两个模型(maas-dsv4/deepseek-v4-flash、maas-coclaw/co-claw)的出入参对齐到api.deepseek.com的契约(见路由服务与历史改进记录 · 路由服务与 maas 转换器)。
所有模型调用都在宿主进程里发生:浏览器不接触凭据,也不直接调用任何 provider;桌面通知同样由宿主按平台派发;熔断状态也只活在宿主进程内存里(不落盘、不写会话)。
> 包名、路由前缀(/dsh-prompt-optimizer/*)、设置与旁路历史文件名、dspo- 类名前缀等标识符保持不变:它们是已装实例与用户设置的引用点,改名会丢配置或需要重装,不属于定位更名。
入口
插件占四个座位(五个注册入口),没有自己的面板(集合里的每个功能各占一格):
| 座位 | 内容 |
|---|---|
conversation.input.left |
工具行里的 ✨ 优化提示词 按钮与一行状态(Alt+O 等价);同排的 💬 旁路提问 按钮(Alt+B、斜杠命令 /btw 等价)。改写输出期间它还会把输入框自己的发送按钮握在关闭状态(编辑器照常可打字),见优化期间锁住发送。 |
conversation.input.dock |
改写预览卡片:原文与结果并排、模型标注的待确认、采用/撤销/再改一次/关闭。 |
conversation.input.overlay |
旁路提问浮层(刻意不是模态框,主对话仍在后面继续跑)与一个不可见的完成通知座位。 |
settings.section |
「插件优化集合」设置页(5 个页签)。 |
界面示意(浮层与卡片的实际样子)见历史改进记录 · 界面。
设置
设置页按功能模块分成 5 个页签:优化提示词 / 旁路提问 / 标题 / 通知 / 路由。改写功能只占「优化提示词」一个页签:它没有任何模式开关,页签里只有该功能真正暴露的五项(优化用的模型 + 改写的思考强度 + 自定义提示词 + 改写结果的语言 + 携带最近会话消息);模型默认「跟随当前会话」、强度默认 off,不填就是零配置行为。四个用到模型的功能(改写 / 旁路提问 / 标题 / 通知)都各自可选模型并各自可选思考档位,互不影响;固定了改写模型后,改写也不再走「路由」页签的熔断切换(那条链路只接管会话自己的模型调用)。「路由」页签不再编辑任何东西:配置与熔断状态都在独立服务里,这个页签只显示服务的地址、存活状态、实时的熔断表与最近的切换记录,并提供跳转到服务自身页面的入口——两处都能改同一份配置就会漂移,所以这里只读。
配置文件:$DSH_HOME/prompt-optimizer.json(默认 ~/.dsh/prompt-optimizer.json),设置页底部显示实际路径。
{
"systemPrompt": null,
"recentMessages": 8,
"outputLang": null,
"provider": null,
"model": null,
"reasoningEffort": "off",
"btwContextTurns": "all",
"btwContextCount": 8,
"btwSaveHistory": true,
"btwProvider": null,
"btwModel": null,
"btwReasoningEffort": "off",
"notifyOnComplete": true,
"notifyMaxChars": 120,
"notifyProvider": null,
"notifyModel": null,
"notifyReasoningEffort": "off",
"titleProvider": null,
"titleModel": null,
"titleReasoningEffort": "off",
"titleRerollTurns": 100,
"titleMaxChars": 24
}
每个设置项的逐条说明在历史改进记录 · 设置页,已移除的旧键如何处理见历史改进记录 · 配置文件与已移除的旧键。
思考档位发到网关上是什么:插件自己只声明标准的 reasoningEffort,不发任何网关私有字段(profile 里那两条 MaaS 路由的 reasoningEfforts 仍然有效)。而经过路由服务的调用,转换器会进一步把入参按参照契约翻译:thinking: {type: 'disabled'} 落成 reasoning_effort: "none",thinking: {type:'enabled', budget_tokens} 落成就近的档位,max_completion_tokens 折成 max_tokens,response_format: json_object 按官方的前置条件校验;出参则把 reasoning 改名为 reasoning_content、摘掉网关常驻的空字段、补齐 usage 与 system_fingerprint。判据是实测而不是文档,逐条对照见路由服务。
路由服务(独立进程)
熔断与路由不再属于插件运行时,而是一个本地独立服务:lib/service/。插件只做三件事——在插件激活时把它 fork 起来(没在跑的话)、
把设置页的「路由」页签指向它、把三个按钮(探测 / 清空熔断 / 刷新)转达过去。没有任何熔断状态留在插件进程里,也没有第二份实现。
它对外长什么样
| 面 | 地址 | 说明 |
|---|---|---|
| OpenAI 兼容 | POST http://127.0.0.1:8790/v1/chat/completions(GET /v1/models) |
把任何 OpenAI 客户端/agent 的 baseURL 指过来即可;对外只服务三个模型名 auto / peak / off-peak,入参出参按参照契约(见下) |
| 管理页 | http://127.0.0.1:8790/ |
配置供应商与密钥、顺序表、熔断参数;看实时熔断表、最近切换与统计 |
| 管理 API | POST/GET http://127.0.0.1:8790/admin/api/{state,config,reset,probe,models,keys/restore,converters} |
管理页用的接口,需要 X-Router-Token(页面自带,令牌在配置文件里) |
| 存活 | GET /healthz |
插件用它判断该接入还是该拉起 |
只监听 127.0.0.1,不做鉴权面之外的暴露。/admin/api/* 需要令牌:一个用户随手打开的网页可以对 127.0.0.1 发跨源 POST,
但不该能改路由或读到(掩码的)凭据。/v1/* 不需要,因为它只对本机开放,而它持有的凭据本来就已经在这台机器上。
接入方式
自己把本地模型地址指过去就行,插件不会替你改 profile:
// 以 DSH 的 pi-ai provider 为例(也可以改用任何 OpenAI 客户端)
"local-router": {
"api": "openai-completions",
"baseURL": "http://127.0.0.1:8790/v1",
"models": [{ "id": "auto" }]
}
北向只服务三个模型名,且它们选的是「时段语境」而不是某一行:调用方不需要知道(也无从影响)里面有哪些供应商和模型,选中谁是路由规则的事。GET /v1/models 就广告这三条,管理页「路由」页签与插件只读页签也列出它们。
model |
用哪张顺序表 | 什么时候用 |
|---|---|---|
auto |
日历决定:高峰表或低谷表 | 默认。调用方不想操心时段 |
peak |
强制高峰表 | 调用方自己知道这单就该按高峰试序走 |
off-peak |
强制低谷表 | 批量/不着急的活,明确想走空闲时段那张表 |
- 点名语境有效,点名内部行无效:
peak/off-peak(offpeak、大小写与首尾空白都算同义词)会压过时钟,直接用对应的那张表;除此之外没有任何「按名字匹配到某一行」的入口——写deepseek-v4-flash、写maas-dsv4/deepseek-v4-flash、写错、甚至不写,一律当auto服务(不是被拒绝),并记一条warn日志,方便发现过期的客户端配置。 - 点名的表是空的就退回
auto:不报错、也不静默换到另一张表;只有当两张表都空时才 503(no route is configured)。 - 回显是服务时的那个规范名:非流式响应与每个 SSE 分片的
model只可能是auto/peak/off-peak,绝不泄漏实际作答的供应商/模型(想看实际走了谁,去管理页或/state,那是运维面)。 - 当前时段(高峰/低谷)的那张顺序表就是候选池:它决定先试哪一行,之后按排好的顺序往后走,回绕——顺序表是池子加偏好,不是单向链表。切换对调用方透明:只要上游还没吐出第一个字节,失败的那一跳不会出现在响应里。
- 回显也是
auto:非流式响应与每个 SSE 分片的model一律是auto,不会泄漏实际作答的供应商/模型(想看实际走了谁,去管理页或/state,那是运维面)。 enabled: false(纯代理):规则仍然决定先试哪一行,但只打一次、不重试也不记熔断。
转换器
服务提供一个注册接口,转换器是代码模块(lib/service/converters/),在配置里按 id 列出来即可:
defineConverter({ id, label, match, toUpstream, fromUpstream, fromUpstreamChunk, errorBody, listModels })
只有 id/label 是必填,其余缺省都是原样透传——所以「没注册转换器的路由」行为不变。这么设计是因为转换器要改的是流式 SSE 的增量、
要合成的 usage、要重写的错误体:那是状态机,不是替换表,声明式规则表达不了。内置一个 maas 转换器:
| 轴 | 参照契约(api.deepseek.com) |
ZTE 网关 | 转换器怎么做 |
|---|---|---|---|
| 思考开关 | thinking: {type: 'disabled'\|'enabled', budget_tokens} + effort |
忽略 thinking;认 reasoning_effort(none 真的关) |
翻译成 reasoning_effort,并把 thinking/effort 从上游请求里删掉 |
| 输出上限 | max_tokens(含思考 token) |
两种都认 | max_completion_tokens 折进 max_tokens |
response_format: json_object |
提示词里没有 "json" 就 400 | 无条件接受 | 强制参照契约的前置条件(这是唯一一处比上游更严) |
| 思考正文 | message.reasoning_content |
message.reasoning |
改名 |
| 思考 token 数 | 总是上报 | co-claw 上报,deepseek-v4-flash 不上报 |
没思考就写 0;思考了但上游没报就省略,不估算 |
model 回显 |
请求的名字 | 后端名(DeepSeek-V4-Flash-0731) |
一律写成 auto(内部模型不外泄) |
usage 细节 |
prompt_cache_hit_tokens / miss_tokens |
prompt_tokens_details.created_cache_tokens 等 |
归一到参照契约;网关多出来的计数器丢掉 |
system_fingerprint |
有 | 有(vllm-…) |
原样透传,没有时按 provider+后端名合成 |
| 错误体 | {error: {message, type, param, code}} |
vLLM 自己的形状 | 按状态码重写 |
| 未知字段 | — | — | 一律原样透传,不丢数据 |
转换器会把思考档位缺省成一个明确的默认:调用方什么都没说时,按路由规则登记成 reasoning_effort: "max"(缺省 = 思考,两条路由都按这一档运营),要关思考只能显式传 none(各种拼法都归一成 none,模板开关同步为 false)。它保证的是「显式控制两边同义,没说就默认思考」。逐条实测见历史改进记录 · 路由服务与 maas 转换器。
配置与密钥
配置在 $DSH_HOME/router-service.json(默认 ~/.dsh/router-service.json,0600),由服务的页面读写。首次运行会从插件的旧设置里
把顺序表搬过来,并预置两条 MaaS 路由的地址——但不预置任何密钥,密钥要你自己在页面上填。服务不会去读 DSH 的凭据库。
一个供应商可以配多把 key,每把有自己的稳定 id、标签和密钥。稳定 id 是刻意的:熔断、失败计数、拉黑都按这个 id 记账,
所以轮换密钥不会丢掉它已有的熔断状态,也不会把两把 key 的状态混在一起(按明文或按掩码认领都会——掩码会撞)。旧配置里的
单个 apiKey 会被读成一条 key(id 固定为 k1),所以老文件照常能用。
页面上留空即保留已存密钥:服务从不把明文回传给页面,只回掩码,所以"不改密钥"只能是留空;填入新值即覆盖,从列表里删掉即删除。
请求头仍然是一个字符串值的扁平 JSON 对象,直接合并进上游请求。
每个模型可以单独覆盖上下文窗口:GET /v1/models 给三个语境名各报一个 context_window(取各自候选里最小的那个),取值顺序是
管理页填的窗口 → 上游 GET /models 自己声明的窗口 → 100 万 token 默认。ZTE 那两个网关的 /models 是 404 不报窗口,
所以不填的话它们和直连官方的小窗口模型都按 1M 报——想要真实数字,就在供应商区块的「上下文窗口」里给对应模型填一个:窗口留空 =
用上游发现的值(都没有才落到 1M 默认),把整行删掉再保存 = 清掉覆盖回到默认/发现。窗口存在配置(providers..contextWindows)里,
页面保存时校验 1–100,000,000 的整数。
熔断:颗粒度在 key,不在供应商
- 切换颗粒度是 key:某个 key 失败时,先在同一供应商的其它 key 之间切换;只有该供应商所有 key 都不可用,才轮到顺序表里的下一家。
无密钥的供应商(本地 Ollama 这类)仍然是一个单位,行为与从前一致。 - 只有该记账的失败才记账:失败先被分类(
lib/service/failure.js)。请求本身写错(400/404/422…)、模型不存在、调用方自己取消——
这些是调用方的问题,一次都不计,否则一个坏客户端就能把健康的供应商熔断掉。5xx/超时/传输错误算retryable;
429/503/529 算overloaded,当次即熔断(上游已经在说"慢点",再等一次确认只是多花一个请求)。 - 凭据死了就摘掉,不是冷却:401 / 402 / 403 是回答而不是抽风,它们进拉黑表——该 key 之后被完全跳过(不再向上游发请求),
直到你手动恢复、或上游给了恢复时间到期、或那把密钥被轮换/删除。把它们交给冷却式熔断,只会得到"永远在重试一把废掉的 key"。 - 双阈值:连续失败到
failureThreshold(默认 2,且"连续"限定在窗口内,隔一周的两次失败不算)或窗口内失败率到
failureRateThreshold(默认 0.7,且至少要有minSamples个样本才让比例说话)。前者在低频使用下也能生效,后者抓"时好时坏"。 - half-open 是租约:冷却到期只放行一次探测,
halfOpenSuccesses次连续成功才闭合;探测失败立刻重新熔断且冷却升级
(cooldownMs起、每轮乘cooldownFactor、封顶cooldownMaxMs)。上游给了retry-after就用它的时长,但仍受同一个上限约束。
探测一结束就交还租约(成功、被忽略的失败、转换器拒绝、调用方中途取消都算),否则"两次确认"会变成"两次确认相隔一个冷却"。 - 候选全部用完时,回写最后一个上游答案:上游真的答了 4xx/5xx,就把它自己的状态码与错误体(经转换器对齐契约)交给调用方——
"这个模型不存在"、"余额不足"是可行动的,换成服务自造的 503 恰好抹掉了唯一能用的信息。反过来,超时、连接断开、200 但 body
不是 JSON 这些不是"上游的答案",它们拿到的是服务自己的 502 错误体(带具体原因),绝不把不可解析的字节透传给调用方。
真正没有候选(没有路由、供应商未配置、所有凭据都被拉黑、所有候选都被熔断跳过)才回 503。 - 顺序表里的模型列是可手填的下拉:选项来自该供应商的手工模型列表 ∪ 从
GET {baseURL}/models拉到的列表。页面上的「刷新模型」
按钮会真的去问上游(零 token,顺便验证凭据),结果记在运行态里,重启后仍在。手工列表可以留空(只有一条不拦保存的提醒):
留空的含义是"以上游拉到的为准",这也正是新建供应商唯一合理的第一步——一个还没保存的供应商根本无处可拉。
高峰/低谷:排序就是两张可拖动的顺序表
时间路由没有数字、没有开关、没有时间窗口——排序本身就是优先级。路由页签上是两张一模一样的顺序表,分别叫
「高峰顺序」与「低谷顺序」:用鼠标拖动行(或点行首的 ↑↓ 按钮)调整顺序,谁排在前面谁更优先,同样也是失败切换的顺序;改完保存立即生效。
- 只判定一次「高峰 or 低谷」,然后当场选表:判定条件是「今天算不算工作日」+「现在在不在
09:00–12:00/
14:00–18:00之间」(北京时间,固定 UTC+8,不看进程时区)。两个都满足才是高峰,其余全部是低谷:非工作日全天、工作日的
午休12:00–14:00与夜间18:00–09:00。此刻是哪个语境,就用哪张表当候选链(页面顶部与/state都写着
「当前=高峰/低谷(时刻)→ 实际试序」,并注明这次是谁判的、为什么)。 - 排前面 = 更优先:候选链就是当前表的行序,第一行最先试,失败后按行序往下切;整张表仍是候选池,走完回绕、对调用方透明。
- 不在表里 = 该时段不试:把一家供应商从某张表里删掉,就是它在那个时段被「停用」——没有数字档位、没有单独的开关,删除是唯一的停用方式。
- 两张表拖成一样 = 时间无关:这就是旧版那单一顺序表的行为;想忽略峰谷,把两张表拖成一样即可。
- 迁移:老文件的
router.order读成「低谷顺序」;老router.timeSlots的档位表迁移成「高峰顺序」(档位1…9升序排前、0
剔除、没档位就复制低谷表);老timeSlots/businessRule的写入会被大声拒绝,提示改成peakOrder/offPeakOrder。 enabled: false(纯代理):仍然按当前表的行序选第一行,只打一次、不重试也不记熔断。- 可核对:
/state与/admin/api/state返回timeSlot: { flavor, status, calendar, order: { offPeak, peak } }——flavor是此刻的峰谷
判定、order是两张表,status带北京时间的{ status, weekday, minutes, weekend, holiday, date, workday, makeup, source, info }
(workday= 计费意义上的高峰日、makeup= 调休上班的周末、source= 这次判定由谁给出),calendar是日历层自身的状态
(缓存条目、来源、最近一次失败)。
插件设置页「路由」页签只读展示同一句「当前=高峰/低谷 → 实际试序」加一行「日历判定」。
图片输入:带图的请求只走「图片」行,当前表一行都没有就报错
各家网关能力不均匀——maas 那几行现在都不吃图片,只有直连官方的行是多模态。所以顺序表的每一行多了一个「图片」勾选框
(行级能力,存成 vision: true),带图片的请求只在这些行里挑:
- 不带图 → 按原规则走:正常按当前时段的顺序表试,一行不漏。
- 带图 → 收窄到「图片」行:只在当前表里勾过「图片」的行里按序试(熔断/重试/切换照旧),排在前面的非图片行会被跳过。
- 当前表一行「图片」都没有 → 400:错误信息点名是哪个时段缺能力(
peak/off-peak),不跨到另一张表去找人、也不落到会拒图的
模型上。比如现在高峰表只有maas-dsv4 / maas-coclaw,高峰时段带图请求就会明确报错,直到你把官方那行也排进高峰表。 - 图片的判据:任一消息的
content数组里含有{type: "image_url"}部件即算(参照契约里纯字符串content装不了图)。 - 插件只读页签里支持图片的行带 🖼 标记,并有一行「图片行」列出当前能吃图的行;管理页「路由」页签每行一个勾选框,勾完保存立即生效。
口径以 DeepSeek 的峰谷计费为准:调休上班的周末、法定节假日全天都算空闲时段
这条是参考方的规则,不是我们发明的:DeepSeek API 的峰谷说明里,调休上班的周末与中国法定节假日全天均按空闲时段计费。
所以这里的「工作日」指的是计费意义上的高峰日,不是「政府说这天上班」:
- 周末一律低谷,哪怕它是调休上班的周六——那一天人们确实在上班,但价格表仍按空闲时段算,路由就跟着低谷走。
- 法定节假日全天低谷,落在工作日也一样。
- 只有「普通工作日 + 在
09:00–12:00/14:00–18:00之内」才是高峰。
两个事实、两个来源,都在 lib/service/calendar.js 里:
- 今天是不是周末:纯日历算术,不容商量,任何来源都改不了它。
- 这个工作日是不是法定节假日:这是内置表最弱的一环(手写清单、不知道明年的安排、写错一个日期会静默算错价),所以去问公开的
工作日接口(https://www.iamwawa.cn/workingday/api?date=YYYY-MM-DD,返回is_workingday)作为修正层。
也就是说:接口能把高峰从某个工作日拿掉,但绝不能把高峰给一个周末。接口说某个周末上班时,那个事实被记在 makeup
字段里(makeup: true),页面会写明「补班周末:调休上班的周末按空闲时段计费,故按低谷处理」——不写清楚,一条「周六 = 低谷」的
日志看起来就像 bug。
三条硬规矩:
- 一天一问:每个北京日期最多请求一次,结论缓存在运行态文件
$DSH_HOME/router-service.state.json的calendar
字段里;服务重启直接读盘上的结论,不再打扰接口(那个接口几次调用后就会限流,status: 0并提示 48 秒)。 - 失败就退回内置表,绝不猜测:没网、超时、HTTP 错、返回不是 JSON、被限流、答的是别的日期——一律回退到内置日历(
lib/service/time-slots.js
的STATUTORY_HOLIDAYS+ 周末判断),并在/state的timeSlot.calendar.lastError里如实写出来;失败后还有 30 分钟退避,
不会被自己的重试打爆。想彻底离线(自检、内网机器)就把startService({ calendarFetch: null })传进去。 - 绝不阻塞请求:路由判定读的是同步的
calendar.status(),网络刷新是服务启动时和每 60 秒的一次后台动作;所以最坏情况下
只是「新的一天开头一小段时间还用内置表口径」,永远不会有一个请求在等接口。
内置表是兜底而不是装饰:接口不认的日期(比如表外年份)、或者接口整个不可用时,判定完全由它给出——内置 2025–2026 的常见节假日
区间(元旦 / 春节 / 清明 / 劳动节 / 端午 / 中秋 / 国庆),依据是国务院每年公布的放假安排,跨年时按新公告更新
lib/service/time-slots.js 里的 STATUTORY_HOLIDAYS;表外年份一律按「非节假日」处理,也就是只按周末判定。接口答得上来的日子里
以接口为准(内置表把某天列成假、接口说那天上班,就按上班算)。页面与 /state 会明确标出这次判定是 source: 'api'
(外部工作日接口,带 info 名称,如「国庆节后补班」)还是 source: 'builtin'(内置表兜底)。
运行态与配置是两件事
配置是人的意图(页面保存时不可接受就拒绝,不悄悄改),运行态是运行时的事实(拉黑表、已发现的模型、当天的工作日结论),放在
$DSH_HOME/router-service.state.json(0600,原子写)。分开的理由很实际:运行态由服务自己写,如果和配置同文件,服务就会去改一份
你可能正在手编的文件;而且一份坏掉的运行态绝不应该让服务起不来——配置那边"写时拒绝"的规则在这里不适用。
改供应商,路由表跟着改
顺序表是针对某一个供应商集合写的,所以增删改供应商时服务会对账这张表:provider 已经不在、或它所用的模型已经不在该供应商
可用模型里的行会被移除,并在保存的返回值里报告删了哪些行(页面会提示,不静默)。反过来,供应商只声明了空模型列表时不删任何行——
空是"未知"而不是"没有",在那里猜会删掉用户有理由写的行。
生命周期
服务由插件 fork,随 DSH 退出而停。这是刻意的取舍:换来的是零手工步骤。想让它活过 DSH 重启,本机已有现成的约定——
systemd-run --user --unit= node <插件目录>/lib/service/main.js(dsh-remote-gateway 就是这么做的);
服务本身不关心是谁拉起它的,插件发现端口上已经有服务在跑时会接入而不是再拉起一个。
自检
npm run check:service # 458 项:桩上游 + 真服务 + 真 socket,离线、零 token
npm run check:ui # 115 项:管理页(假 DOM 跑真脚本),离线、零 token
npm run check:live # 28 项:对真实网关的端到端实测,需网络与密钥,非离线自检
覆盖转换器的入参/出参映射、熔断与切换、重试、坏响应、管理面鉴权、配置校验与迁移、转换器注册表、SSE 解析、按时段选路与三个对外模型名、以及插件真的能把它拉起来并转达。
另有 npm run check:live:对真实网关的端到端实测(28 项,需要网络、需要你自己在凭据库里配好那两个 ref,会花真实 token),所以它故意不进 npm run check;结论记在历史改进记录里。
安装
dsh plugin add github:lzyyzznl/dsh-prompt-tuner
仓库根有 cordis.patch.yml(一条 insert),package.json 声明 dsh.bundle.patch 与 dsh.client.platform=web;不含构建步骤,lib/ 即源码。
npm run check # 自检(858 项),不需要本机装 DSH
npm run eval:prompt # 三臂盲评:只打印计划,加 --live / --judge 才发真实请求
npm run eval:attribution # 归因审计:读已有运行记录,量「改写加了什么用户没提的东西」
文档
- 历史改进记录:每个功能的设计取舍与固定行为、实测延迟与失败阶梯、与其他同类插件的差异、架构与文件职责、自检覆盖范围,以及全部已知限制。
- 内置提示词的依据与实测:默认提示词为什么是这六步、三臂盲评的方法与数字、归因审计(改写里有多少东西是用户没提的)的口径与结果,以及中英两条输出路径的对照与偏差。
- 通知路径的实测记录:Windows toast 派发的实测与推断边界。
许可
BSD-3-Clause。