doc: usage-declaration plugin: "@local/dsh-localmodels-tokensavior" version: 1.13.0 audience: AI agent(人类也可直接阅读) purpose: 让任何一台刚装上本插件的机器上的 AI,无需历史对话即可正确启用、使用并验收本插件 host-tools: [ollama_local_models, subagent_local] settings-section: 设置 → 本地模型 config-namespace: local-ollama-models hard-requirements:
- 本机 Ollama 可访问(默认 http://127.0.0.1:11434)
- llm-pi-ai 中存在一条 provider 路由(默认名 ollama-local)—— 见 §2.2
- 要委派的模型 id 必须写在该路由的 models: 列表里 —— 见 §2.3 integrity: 本文件与 package.json 的 version 应当一致;不一致说明插件被改过,优先信任代码
这个插件能干啥:让你的 DSH 调用本地模型,帮你省些大白饭(token)
DSH-LocalModels-TokenSavior(本地模型子代理)· 使用声明 | DSH 插件 | 设置页在「设置 → 本地模型」
让它调用本机 Ollama 模型当"子代理"用,干一些它力所能及的活(读大文件、扫日志、归类、抽取、问答、看图) —— 原文不进你的上下文、只有结论回来,帮你省下一些大白饭(token)。
它不万能,先记住一句:分类与行号可信,计数必须自己数(§6);哪些活该派、哪些别派,看 §5.1。
DSH (DeepSeek Harness) plugin — delegate read-only collection tasks (scan logs, extract fields, count, dedupe, read a large file and return only the conclusion) to a local Ollama model: zero cloud token cost, zero API keys. Ships a Settings panel, a model-selection ruler (
bench.mjs), and this AI-readable usage declaration. Requires DSH + Ollama ≥ 0.3 + one declaredllm-pi-aiprovider route. Full details below (Chinese; the plugin's UI text is Chinese as well).
这份文件是插件的自带说明书。它假设读者(AI 或人)此前对这台机器、这个插件一无所知, 因此把"怎么开、怎么用、怎么验收、怎么排错"全部写死在这里,不依赖任何历史对话。
装它(第一次来的人看这里)
这是一个 DSH profile bundle(不是独立程序)。两种装法,任选其一:
A. 从 GitHub 直接装(推荐,跟着仓库更新)
在 DSH 里让 AI 执行:plugin_manager → action: install_bundle → target: "github:Movingelated/DSH-LocalModels-TokenSavior"。
B. 克隆后按路径装
git clone https://github.com/Movingelated/DSH-LocalModels-TokenSavior.git
然后 install_bundle 的 target 填克隆到的绝对路径。
装完重启 DSH —— 宿主插件是模块代码,DSH 不做热替换(这条是实测结论)。重启后打开「设置 → 本地模型」。
⚠ 光装插件不够:委派还需要一条 provider 路由。没有 §2.2 那条路由,任何委派都会失败。 建议顺序:装插件 → 重启 → 按 §2.1 量本机模型 → 按 §2.2 声明路由与凭据占位 → 再重启 → 用面板开关。
0. 给 AI 的 60 秒速览
- 我拿到什么:两个工具 ——
ollama_local_models(只读查状态)、subagent_local(把只读采集任务派给本机 Ollama 模型,零云端 token)。 - 第一步永远先调
ollama_local_models:它同时告诉你 Ollama 是否活着、有哪些模型可用、以及本 README 的绝对路径。 - 模型名与参数一律以本机实测为准:本文档里出现的任何模型名、吞吐、上下文、显存数字都只是样本,不是推荐值。
换一台机器,就照 §2.1 用
bench.mjs重新量一遍,再决定往路由里写什么。 - 派活公式:
subagent_local({ prompt }),prompt 必须自包含(绝对路径 + 要提取什么 + 输出格式),因为子代理看不到对话。 - 三条铁律:
- 只派读多写少的活(采日志、计数去重、字段抽取、模式匹配、图转文);代码、架构、措辞交付别派。
- 它的数字不可信:分类与定位基本可信,计数必须自己机械复核(一条 grep 的事)。
- 大语料先切块:路由上下文默认 32K token,超了直接报错。
- 省钱的原理:不是本地模型算得快,而是主上下文不必吞下原文。原文只进本地,回来的是结论。
- 改配置不用重启:
enabled/model/baseURL是 volatile 字段,在「设置 → 本地模型」点一下即生效。 - 改代码要重启:宿主插件是 ESM 模块,DSH 不会热替换模块代码。
1. 它是什么,不是什么
是:一台"零成本的只读采集工人"。它把一段自包含的采集任务交给本机 Ollama 模型,在独立上下文里执行,
只把结论拿回主对话。子代理自己会调 read / grep 等只读工具去读文件、翻日志。
不是:
- 不是省钱魔法 —— 本地模型烧的 token 比云端更多(Ollama 没有提示缓存),它省的是主上下文,也就是钱。
- 不是万能工人 —— 它判断力弱:归类可以,计数不准,架构与质量类结论不可用。
- 不是写手 —— 不要让它产出最终交付文本或改代码。
2. 前置条件(换新机器只需照做这四步)
2.1 Ollama、模型与选型(本文件不指定推荐模型)
ollama --version # 需要 0.3 以上(/v1 兼容层 + tools 能力)
curl -s http://127.0.0.1:11434/api/version
ollama pull <模型> # 装哪个由下面的判据决定,别照抄本文档
本机一个模型都没有时,先随便拉一个支持工具调用的当起点(例如 qwen3:30b-a3b,约 17 GB),
再用下面的尺子实测决定要不要换 —— 起点不等于推荐。
选型判据(六条,逐条量,别抄参数)
| # | 判据 | 怎么得到 |
|---|---|---|
| 1 | 必须支持工具调用(tools) |
node bench.mjs 直接把不支持的排除掉 |
| 2 | 别挑带内置人设的 | 同上,输出里带 ⚠ 的排除(人设会污染任务) |
| 3 | 装得进显存(要 100% GPU) | node bench.mjs <id> 的「显存驻留」;不是 100% 就换更小或更低量化 |
| 4 | 吞吐够用 | 同上「吞吐tok/s」;长任务低于 ~50 会很难受 |
| 5 | 上下文 ≥ 你要喂的素材 | 同上「原生上下文」;路由里的 contextWindow 填 min(原生, 所需),保守可先填 32768 |
| 6 | 实测真的会调工具 | 同上「实测会调用工具」必须是 true(有的模型声明支持却调不出来) |
node bench.mjs # 第一步:列出本机可用模型 + 各自的事实(秒回,不测速)
node bench.mjs <模型id> # 第二步:实测它(工具调用/吞吐/显存),并打印可直接粘贴的路由 YAML
node bench.mjs <模型id> --json # 给程序或 AI 解析用
三条命令只读:只调 Ollama 的
/api/*,不拉取、不删除、不改配置。 它输出的 YAML 已经把本机实测值填好了 —— 抄它,别抄本文档。
2.2 在 profile 里声明 provider 路由
本插件不自带路由:它委派时必须有一个已注册的 provider 路由。没有这一步,任何委派都会失败。
把下面这段加到 profile 的 cordis.patch.yml(顶层数组里),路由名保持 ollama-local:
- id: llm-pi-ai
name: "@deepseek-ai/dsh-llm-pi-ai"
config:
providers:
ollama-local:
displayName: Ollama 本地(文字)
api: openai-completions
baseURL: http://127.0.0.1:11434/v1
apiKeyEnv: OLLAMA_API_KEY
reasoning: off # 开思考会吃光 token 预算且 content 恒空
timeoutMs: 300000
defaultContextWindow: 32768
defaultMaxTokens: 8192
defaultInput: [text] # 显式声明,防止把图片误发给纯文本模型
models:
- id: "<本机实测选定的模型 id>" # ← 用 bench.mjs 生成这一段,别照抄本文档
name: <显示名,随便取>
contextWindow: 32768 # 建议值;bench.mjs 会按该模型的原生上下文给出
maxTokens: 4096
input: [text]
凭据占位符:Ollama 的 /v1 不校验 key,但 pi-ai 必须有非空凭据。在 <DSH_HOME>/.credentials.yaml
的 refs 下加一条占位值即可(按请求解析,免重启):
OLLAMA_API_KEY: ollama-local-no-key-required
2.3 模型必须写进路由(最容易踩的坑)
- 设置面板列出的是 Ollama 里的全部模型;能被委派的只有路由
models:列表里声明过的那些。 - 选到未声明的模型 → 调用以
UNKNOWN_MODEL失败(pi-ai provider "X" has no configured model "Y")。 - 本插件会在委派前自查并给出可执行的报错(列出该路由已声明的模型 id),但补声明要人工做:
在 §2.2 的
models:下加一条,然后重启 DSH。
2.4 自检清单
ollama ps # 看模型是否 100% GPU 驻留
cordis_inspect_query host / Config / listConfigs {name: "@local/dsh-localmodels-tokensavior"}
→ status 应为 "schema"(为 "absent" 说明 Config 没加载,设置页将不可写)
cordis_inspect_query host / Tool / listTools
→ 应能看到 ollama_local_models 与 subagent_local
cordis_inspect_query client / Slots / listSubTree {root: "settings.section"}
→ occupants 里应有 id "local-ollama"
2.5 首次在这台机器上使用前:跑一遍四步容量校准
自检通过后,先做一次「四步容量校准」(容量 / 吞吐 / 一口多少行 / 可用哪几种 kind,见 §5.7,约 5–8 分钟),
再把结果写成插件目录下的 calibration.json(一台机器一本档案册,按模型分条)。跑过一次的模型不用重复跑 ——
插件读到该模型的有效档案就把里面的参数当默认值;同机换模型只需给新模型校准,换回来直接用旧档案;
读到过期或损坏的档案则退回保守默认,并在每次结果的账本里写明原因。
3. 开启与配置
方式一(推荐,人人可用):设置 → 本地模型 →
- 「启用本地模型子代理」按钮:开 / 关
- 模型列表:点任意条目切换默认模型
- 端点框:改完失焦即保存(留空 = 先读
OLLAMA_HOST,再退回127.0.0.1:11434)
方式二(无界面时):直接改 profile 的 cordis.patch.yml:
- id: local-ollama-models
config:
enabled: true # 开关
model: <本机实测选定的模型 id> # 默认模型(必须已写进路由 models:,见 §2.3)
baseURL: "" # 端点覆盖
toolName: subagent_local # 工具名(非 volatile,界面改不到)
provider: ollama-local # 路由名(非 volatile,需与 §2.2 一致)
disabled: false
生效语义:enabled / model / baseURL 是 volatile 字段 —— 写进 profile patch,
即时生效、无需重启、不重挂插件。其余字段改了要重启。
关闭:把开关关掉,或在「插件」页停用本 bundle(停用会让两个工具一起从工具表消失)。
历史包袱:1.2.0 之前用过 <DSH_HOME>/local-ollama-models.json 存状态,现已废弃、不再读取,可以删。
4. 工具契约
ollama_local_models()
- 入参:无。只读,无副作用,不启动也不下载任何模型。
- 返回:端点 / 连通性 / 版本 / 模型总数 / 可作子代理数 / 可用模型清单(带体积、量化、上下文、人设警告)+ 本 README 路径。
- 用于:委派前的环境确认;以及"这台机器上到底有什么模型"。
subagent_local({ task?, kind?, prompt?, mode?, model?, label?, collect? })
task(推荐):只写任务本身(一句话:要什么 + 对象 + 字段/口径)。插件按kind+mode套上公式化提示词 (角色 / 铁律 / 素材策略 / 输出契约 / 验收提示),调用方不必再手写格式。见 §5.5、§5.6。kind(可选,v1.8.0):任务类型classify(默认)/qa/extract/summary/code,也收中文别名(分类/问答/抽取/摘要/代码)。见 §5.6。prompt(可选,高级用法):完整提示词原文;给了它就不再套模板。mode(可选):a/max-save、b/balanced(默认)、c/fast;省略则用「设置 → 本地模型」里的默认。见 §5.5。model(可选):覆盖默认模型,须在 §2.3 的已声明列表内。label(可选):会话里显示的短标签。collect(可选,v1.5.0 起强烈建议;v1.6.0 起支持自动分批):让宿主侧代取素材,直接把命中行拼进子代理的 prompt。{ path, include?, pattern?, maxChars?, chunkLines? }——path是文件或目录,include是目录下的文件名 glob(如launcher*.log),pattern是 JS 正则会只保留匹配行(建议最简形式WARN|ERROR),maxChars默认 60000 超则截断,chunkLines(默认 0 = 不分批)超过该行数就自动切片 → 逐片归类 → 合并(每片 ≤N 行且 ≤8000 字符,最多 8 片)。 用了它:素材 100% 正确、不占主上下文、子代理的grep/glob会被自动禁用(它就是靠这两个工具静默改坏过素材)。 结果末尾会回[宿主预取素材] N 个文件 / M 行 / K 字符与[分批委派] X 片 × ≤N 行 → … 共 T 秒,供调用方核对。 ⚠ 为什么值得分批:实测同一模型 33 行 → 100% 覆盖,115 行 → 59%(详见 §9.1)。- 返回:本地模型产出的文本结论(不是原始文件内容)。
- 错误语义(都会抛错,不会静默失败):
报错关键字 含义 处置 关闭状态开关是关的 打开「设置 → 本地模型」的开关 尚未指定本地模型没配 model 面板选一个,或传 modelprovider 路由 ... 未注册缺 §2.2 的路由 补路由后重启 没有写进 provider ... 的 models 列表缺 §2.3 的声明 补声明后重启 subagents 服务不可用宿主组合异常 检查 dsh-base 是否完整 - 子代理视角:看不到本对话,独立上下文,跑在配置的 provider 路由上,零云端 token。
- 只读是机制保证的:委派时用
toolFilter.deny摘掉write/edit/pwsh/job_kill/plugin_manager以及再派活类(subagent/subagent_fork/subagent_local/workflow)。 ⚠ 但tools.restrict()只认"继承来的可 restrict 全局工具名"(dsh-tools:view(scope).restrictableNames), 名单里只要有一个不在该集合里,整条调用就抛错。所以插件是"尽力而为":把报错点名的名字逐个剔除后重试, 保住其余防护;整份名单都被拒时才退化为不过滤。 并且它一定会告诉你结果 —— 过滤没能完全生效时,委派结果末尾会附一句⚠ 只读过滤未完全生效:<被剔除的名字>或⚠ 只读过滤未生效:…。 想自行核对权威证据:跑一次委派,再看那个子代理会话的request/header.tools里有没有write。 - 本机实测(一次真实委派,权威证据取自子代理会话的
request/header.tools): 工具数 32 → 24;被摘掉的是write/edit/pwsh/job_kill/plugin_manager/subagent_fork/subagent_local/workflow;唯一摘不掉的是subagent—— 它落在子代理 scope 自己的那一层,而view()里"自己层"的工具只算 known、不算 restrictable,属于tools.restrict()的固有限制。 想彻底封死"子代理再派活",可考虑给agentOptions配maxDepth(本插件未启用,未验证;注意maxDepth: 0会让子代理卡在启动前)。在这一天到来之前,这句⚠就是它诚实的自我声明。
5. 最优使用法
5.1 该派 / 不该派(v1.9.0 判据重写:看"要不要用脑子",不看"文件多大")
| ✅ 该派(需要语义理解) | ❌ 不该派(确定性手段更准更快) |
|---|---|
| 语义归类 / 归因("这些报错属于同一根因吗"、"这条日志说明了什么") | 路径 / ID / 时间 / 版本号等正则能确定性拿到的抽取 —— Select-String / -match / Group-Object 100% 准、快、零幻觉 |
| 大文档问答(读 200 页只回一句 + 行号) | 最终代码、架构判断、质量类交付 |
| 摘要 / 提炼、翻译 | 安全、权限、删除类操作 |
| 图片 / 截图 → 文字 | 计数(本地模型的计数不可信,见 §6) |
两条轴:① 要不要用脑子(语义 → 派;纯机械 → 自己抽)② 值不值得(> 30 KB 或 ≥ 3 个文件 → 派得划算)。 分工原则:机械的部分交给 shell,语义的部分交给本地模型 —— 抽出来的东西"怎么归类"仍然值得派。
⚠️ 旧判据是"文件 > 30 KB 就派",已被实测证伪(见 §9.3 的复盘):对"抽取文件目录"这类正则可确定的任务, shell 更快更准;而且实测里那个 AI 用 shell 也没把 146 KB 读进上下文 —— 它没派是对的。
5.2 prompt 模板(v1.5.0 起:素材交给宿主取)
首选做法:用 collect 参数,不要让本地模型自己去 grep。
// subagent_local({ prompt, collect })
{
"prompt": "把素材里的「告警与报错」按根本原因归类去重。\n输出格式:分类 | 次数 | 代表原文(≤80字) | 出处\n最后单独一行输出 TOTAL=<覆盖条数>。不要开场白、解释、建议。",
"collect": { "path": "D:\\logs", "include": "app*.log", "pattern": "WARN|ERROR", "maxChars": 60000 }
}
prompt 里不用再写路径(素材已附在 prompt 末尾),只写"要什么 / 怎么归类 / 输出格式"。
万不得已要它自己取素材时,两条死规矩:
- 检索式必须是最简形式(如
WARN|ERROR)——给它带括号或转义的正则,它一定会"优化"它。 - 强制自报口径:要求它先输出
HITS=<工具返回的条数>,最后输出TOTAL=<覆盖条数>,两个数必须相等。 - 一口别超过约 40 行:同一批素材行数越多,它越容易只抓大类别、丢掉只有 1~3 条的小类别。
实测同一模型同一任务:33 行 → 100% 覆盖(6/6 根因);115 行 → 59%(4/6 根因,丢的全是 ≤3 条的小类)。
对策(v1.6.0 起首选自动):给
collect加chunkLines: 40—— 宿主自动切片、逐片归类、再合并, 每一口都是"小而完整"的料;先去重再喂同样有效(本例 6 份日志是同一份的累积快照,只取最新那份即从 115 行降到 33 行)。 对照数据见 §9.1。
经验:
- 一次只派一个任务;任务边界越窄,本地模型越不容易跑偏。
- 让它只输出表格/清单,别要散文 —— 散文既贵又难验收。
5.3 模型常驻
切换模型要重新加载(5~30 秒,显存大的要更久)。锁定一个主力模型长期用,别一次任务换一个。
ollama ps 可以看到驻留情况(默认闲置 5 分钟后卸载)。
5.4 让它"在该用的时候被用上"(主动性机制)
问题:工具描述只会说"我能做什么",不会在具体情境里主动冒出来。上线首日实测:某会话 250 次工具调用中, 本插件仅 6 次,全部是用户点名要求演示的,自发调用 0 次。工具没问题 —— 是触发条件没写进模型看得见的地方。
两个机制(v1.4.0 起):
| 机制 | 位置 | 作用 |
|---|---|---|
系统提示词段落 local-ollama-delegation(order 2850,紧跟 TOOL_SUBAGENT) |
每一次请求的系统提示词 | 写死新版判据:先问"要不要用脑子"(纯机械抽取 → 自己抽;语义归因/归类/摘要/问答/翻译/看图 → 优先派),再用规模决定"值不值得"(> 30 KB 或 ≥ 3 文件);> 32K token 的素材必须先收窄(先用 shell 去重归一化成小文件,或用 collect.pattern) |
大文件当场提示(tools/post-execute) |
读进主上下文的 read 结果末尾 |
在花掉钱的那一刻提醒:"这份内容 ≈N k token 已进主上下文,下次这类活可交给 subagent_local"。每个会话只提醒一次 |
怎么验证生效:重启 DSH 后给一个"需要读懂内容"的任务(比如把一批日志按根因归类、或就一份 30 KB 文档提问),
看它是否先调 subagent_local;或让它读一份 > 30 KB 的文件,结果末尾应出现 ⚠ 采集提示。
⚠ 反例也要认:纯抽取任务(抽路径/ID/时间)它就该自己用 grep 抽 —— 那不叫"没生效",那叫判据正确(见 §9.3)。
✅ 成功长什么样:见 §9.4 —— 一个全新窗口在 1.7 MB 日志上自主完成了"shell 收敛 → 委派语义 → 复核 → 交付"。想复现这个效果,
最值得抄的是它那两步:先用 pwsh 把大素材压成小文件,再把小文件交给 collect。
怎么调:阈值与文案都在 index.js —— BIG_READ_CHARS(默认 30000 字符)、DELEGATION_POLICY。
⚠ 政策文本必须保持静态(见 §10 第 7 条)。
5.5 三种调用模式(v1.7.0:公式化提示词)
调用方不必再手写格式:给 task + mode,插件自动套上角色、铁律、输出格式与对账要求。
| 模式 | 别名 | 素材上限 | 分批阈值 | 输出 | 云端 token | 本地时间 | 召回 |
|---|---|---|---|---|---|---|---|
| a 极致省 token | max-save |
200 000 字符 | >40 行强制分批 | ≤15 行 + 逐类穷尽 | 最少* | 最长(N+1 次推理) | 最高 |
| b 均衡(默认) | balanced |
60 000 | >120 行才分批 | ≤12 行 | 少 | 中 | 中 |
| c 快跑 | fast |
15 000 | 从不分批 | ≤8 行,小类并入「其他」 | 省得有限* | 最短 | 低(需补核) |
* 云端节省主要由"素材不进主上下文"决定,三种模式其实一样;差别在召回率: a 召回高 → 调用方几乎不用补核;c 召回低 → 多半要回头补核,实际省得更少。这就是"a 省最多、c 省得有限"的机制。
subagent_local({
"task": "把素材里的告警按根因归类", // ← 只写任务,格式由插件套
"mode": "a", // ← a/b/c 或 max-save/balanced/fast
"collect": { "path": "D:\\logs", "include": "app*.log", "pattern": "WARN|ERROR" }
})
- 省略
mode→ 用「设置 → 本地模型」里的默认(面板可直接切换)。 - 省略
collect.maxChars/collect.chunkLines→ 按模式取默认;显式给值可覆盖(chunkLines: 0= 强制不分批)。 - 模板原文在
index.js的MODES与buildPromptFor()—— 要改文案只改那一处。
5.6 五种任务类型(v1.8.0:素材策略 + 输出契约 + 验收动作)
任务类型决定"怎么用素材、输出什么形状、怎么验";模式只决定"喂多少、分几口"。
| kind | 任务 | 素材策略 | 输出契约 | 验收动作(结果里会自动附上) |
|---|---|---|---|---|
classify(默认) |
分类 / 计数 | 按 mode 分批 | 分类 | 次数 | 代表原文 | 出处 + TOTAL |
grep 复核计数 + 抽查行号 |
qa |
大文档问答 | 禁止分片;素材 > 60k 字符直接报错 | Q<编号> | 答案 | 出处 + ANSWERED=n/N |
抽查行号;ANSWERED 的分母必须等于题数 |
extract |
结构化抽取 | 按 mode 分批,合并成数组 | 严格 JSON 数组,每对象带 _src + COUNT |
JSON.parse + 抽查 _src + COUNT==数组长度 |
summary |
摘要 / 提炼 | 按 mode 分批 | 要点 + 出处 + KEY=实体 + POINTS=n |
抽查行号;KEY 里的数字要能 grep 到 |
code |
代码只读勘查 | 允许它自己 read/grep(不摘这两个工具) |
项目 | 说明 | 文件:行号 + ITEMS=n |
逐条 grep 回查;ITEMS 必须等于行数 |
三条纪律(写在代码里,不是倡议):
- 没有验收动作的类型不许加 —— 上表最后一列就是它存在的准入条件。
- 禁止分片的类型不许悄悄降级:
qa遇到超预算素材会抛错并给出两条出路(缩小素材范围 / 换classify|summary), 而不是偷偷截断或分片 —— 分片问答 = 让它在残缺材料里找答案(§9.2 实测过)。 - 只读不放松:
code只放开read/grep/glob,write/edit/pwsh等依然被摘。
已知短板与对策(v1.8.1 真机实测出来的):
| 现象 | 证据(改前 → 改后) | 对策 |
|---|---|---|
qa 会因为素材是英文而用英文作答 |
5 题里 4 题英文(v1.8.0)→ 5 题全部中文(v1.8.1 回归,同一任务同一文档,单变量对比) | 语言要求嵌进输出格式那一行(模型只服从格式行;单列一条规则实测无效) |
extract 过度保守,能推断的字段也写 null |
33 行里 20 行 root_cause=null(v1.8.0)→ 33/33 全填、零 null(v1.8.1 回归) |
规则改为"能推断就写简短概括,只有完全无法判断才写 null" |
| 改后有没有"为了填满而编造"? | 抽查 6 条"原来空、现在填了"的行(110/154/186/219/264/229)→ 6/6 与原文对得上(含此前四轮实验全漏的"实例 20 s 无响应") | COUNT 与 _src 是防编造的硬约束,改规则时不许动它们(回归里两者都保持:33/33) |
extract 字段冗余 |
任务里自定义 file 字段与模板强制的 _src 重复 |
任务里不要再要 file 字段 —— _src 已经带了出处 |
| 完整性与计数依然不可信 | v1.8.1 回归:Q1 的计数这轮对了(30 = 表格行数),但列表仍是"每行一个代表"而非全部导出名;Q2 的 props 仍只给 2/5 | 只能靠调用方复核(§6)。qa 的 ANSWERED=n/N 只保证"不跳题",不保证"答得全";想声称"稳定改善"需同一任务跑多次取分布(本轮每次各 n=1) |
5.7 换机器 / 换模型:四步容量校准(v1.10.1:一台机器一本模型档案册)
为什么需要它:本插件里所有"机器相关"的数字(素材上限、一口多少行、输出几行)都取决于那台机器的模型。 其中"素材上限"能从模型声明的上下文窗口推出来,但"一口多少行"取决于模型的质量 —— 参数推不出来,只能实测。
没校准 = 按保守默认跑(素材上限 60000 字符 / 一口 40 行),并且插件会在每次结果的账本里写明"未校准"。
校准结果写成一个文件:插件目录下的 calibration.json(已进 .gitignore,不会入库/发布)。
它是一台机器一本"模型档案册":profiles 里每个模型各一条,键是 provider::模型id:
{
"schema": 2,
"profiles": {
"ollama-local::qwen3:30b-a3b": { "calibratedAt": "…", "capacity": { "contextWindow": 32768, "maxChars": 60000, "chunkLines": 40, "maxRows": 12 }, "evidence": { … } },
"ollama-local::llama3:8b": { "calibratedAt": "…", "capacity": { "contextWindow": 8192, "maxChars": 15000, "chunkLines": 15, "maxRows": 8 }, "evidence": { … } }
}
}
两条规矩(插件不强制,但靠它才不出错):
- 写入时先读再合并 —— 只新增/更新本模型那一条,不要覆盖别的模型(
ollama_local_models的输出里会列出本机已有哪几份档案) - 换模型要重校准、换回来不用 —— 切到 B 校一次,切回 A 直接用 A 的档案 ✓(这正是"一本档案册"的意义)
插件会读它、校验它(JSON / schema / 该模型的 contextWindow 对不上就判过期,只废掉那一条),
但它只是自述,不是证明。
档案会不会"造假"或"从别的机器拷来"?(v1.11.0)
会 —— 把别人的 .dsh 整个拷过来、或手写一份 JSON,都可能让插件误判。插件能自动识别的:
| 情况 | 判定 | 依据 |
|---|---|---|
| 拷来的档案,本机没有该模型 | missing → 重新校准 |
档案册里查不到该 provider::模型id |
| 拷来的档案,本机有同名模型但内容不同(换过量化 / 重新 pull) | stale → 重新校准 |
env.modelDigest 不一致 —— 那是模型文件的 SHA256,比"模型名字照应"可靠得多 |
| 拷来的档案,本机有同名同 digest 的模型 | calibrated + 提示"档案来自另一台机器" |
env.machineHash 不一致。但参数仍然适用、不重跑 —— 参数是模型的属性,不是机器的属性(同"换模型要校准、换回来直接用"一个道理) |
| 档案里是手写/伪造的离谱数字 | invalid → 重新校准 |
值域校验:chunkLines 0500 / maxChars 5000400000 / maxRows 1~30 |
| 伪造的看起来合理的数字 | ⚠️ 识别不了 | 只能靠抽样复核:拿档案里的参数跑一个小任务(30~60 行、答案已知),看召回是否达标 |
所以云端 AI 的职责不是"校验模型名字照应"(名字可以相同而内容不同),而是两条:
① 看 ollama_local_models 输出的校准状态;② 当它提示"档案来自另一台机器"或"没记 digest"时,做一次抽样复核。
四步(合计约 5–8 分钟,云端 token 0)
| 步 | 量什么 | 怎么量 | 落进 capacity 的哪个字段 |
|---|---|---|---|
| ① 容量 | 模型声明的上下文窗口 | 插件自动读(ollama_local_models 会显示);读不到就问那台机器的人 |
contextWindow;再算 maxChars ≈ (contextWindow − 8192) × 2.4 |
| ② 吞吐 | tok/s | node bench.mjs <模型id> |
evidence.throughputTokPerSec(决定"愿不愿意为分批等") |
| ③ 胃口(最关键) | "一口多少行时召回还够" | 两个探针一起跑(只用合成素材会得到过于乐观的参数): ① 规则合成素材(同类行重复,如 4 类 / 30·60·120 行)→ 量"容量型"上限; ② 真实杂乱素材(多种措辞 + 长尾小类 + 重复行,答案要能机械核对)→ 量"真实"上限。 chunkLines 取 ② 的结果:取"仍 ≥90% 的最大行数";都 <90% 就压到 20,并接受"这台机器必须复核" |
chunkLines + evidence.appetiteProbe(每点标注 material: synthetic-regular / real-messy) |
| ④ 能力矩阵 | 这台模型能做哪几种 kind | 跑一次最小 kind=extract(输出能否 JSON.parse —— 注意有的模型会给 ```json 围栏,那等于不合格)+ 一次 kind=code:任务要选"答案能被机械核对"的(例如"列出某目录下所有 export function 的定义位置",答案可用 Select-String 逐条比对)。同时看两个环节:工具调没调(账本里的"自调工具"次数)与最后写出来的东西对不对 |
evidence.kinds.* |
⚠️ 两个必须知道的实测反例(本机
qwen3:30b-a3b校准过程中踩到的):
- 合成素材 ≠ 真实难度:合成 120 行(4 类规则重复)100% 全对;真实杂乱 115 行只有 59%(长尾小类被丢)。 所以 ① 只用来量"容量上限",定
chunkLines必须用 ②。本机最终取保守的 40 行。- 工具调用"成功"不等于任务成功:
kind=code那次,子代理确实调了glob+grep×2 且参数没坏, 但最终汇总崩了(ITEMS=0、输出退化成一行、还编造出处plugins/local-ollama-models:0)→ 该 kind 判 unusable。 账本里的"自调工具次数"只说明它动了手,不说明它做对了事。
判据:什么叫"召回够"
用合成素材时先把答案写死(例如"7 类、共 N 条"),跑完把它的输出与答案对齐 —— 分类与行号可信, 计数必须自己数(§6)。三点里选"仍 ≥90% 的那个最大行数"。
⚠️ 校准是 n=1 的带噪声估计:本项目实测同一模型、同一任务出现过 39% / 35% / 59% / 100% 的召回波动。 所以校出来的是保守起点,不是"最佳参数";想更准就在同一尺寸点多跑 2 次。
校准完要做的三件事
- 把结果写成
calibration.json里本模型那一条档案(模板:调一次ollama_local_models,输出里直接带一份可抄的 JSON; 先读再合并,别覆盖其它模型) - 插件此后自动用
capacity.maxChars / chunkLines / maxRows当默认值(调用时显式传参仍可覆盖) - 换模型或改上下文后必须重校准 —— 插件检测到不一致会把它标成"过期"并退回保守默认 (避免"大模型配小参数"这种最坏组合)
6. 验收纪律(本节最重要)
本地模型的"分类"和"定位"基本可信,"计数"和"统计值"不可信。 样本数据(某 30B 级模型的一次真实委派;换模型后偏差幅度会变,但"数字必须复核"这条纪律不变):
| 维度 | 结果 |
|---|---|
| 分类是否真实存在 | 7/7 命中 ✅ |
| 行号是否指向真实内容 | 7/7 命中 ✅ |
| 文件名 | 5/7 正确,2 条张冠李戴(行号却对)⚠ |
| 计数 | 4/7 偏差 2~3 倍(报"34 次"实际 17 次;报"6 次"实际 18 次)⚠⚠ |
因此验收动作固定为三条:
- 先对账再信数:让它回报
HITS(取到多少行)/TOTAL(覆盖多少行),两者不等就有缺口; 再用你自己的 grep 核一遍总量。 - 数字自己数一遍:
Select-String -Path <files> -Pattern <关键词> | Measure-Object(或 grep -c)。成本几秒。 - 抽查 2~3 条引用:打开它给的行号,确认内容对得上;行号对不上就整份打回。
⚠ 口径也要对齐:大小写、编码、是否把续行算作命中,都会改变计数。实测我自己的 PowerShell 计数
(Select-String 默认大小写不敏感)把一条 JSON 续行 [Error: ... 也算成命中,比真实标记多 1 条 ——
差点把一次"33/33 全召回"判成 97%。数错的不只是本地模型,验收工具链也会。
7. 已知限制
| # | 限制 | 说明与对策 |
|---|---|---|
| 1 | 上下文 32K | 路由声明 32768,超了直接报错。大文件必须分块/先 grep 收敛 |
| 2 | Ollama 无提示缓存 | 本地 token 只会更多不会更少;省钱来自零边际成本 |
| 3 | 模型 id 必须已声明 | 见 §2.3,UNKNOWN_MODEL |
| 4 | 凭据不能省 | 见 §2.2,删了会报 No API key for provider |
| 5 | 思考模式默认关 | 路由里 reasoning: off;开了会吃光预算且 content 恒空 |
| 6 | 去审查底模 | 名字带 heretic 的模型是被去审查版本。缓解:只给只读工具、任务本身无争议、输出过 schema 校验 |
| 7 | 写权限靠 deny 名单 | 默认 deny 掉写/执行/再派活类工具;不可 restrict 的名字会被逐个剔除、并在结果里如实告知(见 §4),全部被拒时只读只靠任务约束 |
| 8 | 改宿主代码要重启 | 模块代码不热替换(loader 不做 import 缓存击穿) |
| 9 | 弱模型会篡改工具参数 | 实测:给它 \[(WARN|ERROR),它自作主张补成 \[(WARN|ERROR)\],22/34 行被静默滤掉,而输出行号真、原文真、格式规整,唯一破绽是最大的一整类凭空消失。对策:用 collect 让宿主取素材(§5.2),或给最简正则 + 强制自报 HITS/TOTAL |
8. 故障排查
| 症状 | 根因 | 处置 |
|---|---|---|
| 设置页开关点不动 / 红框说"写不了配置" | 宿主行没有 Config schema,或命名空间没暴露 | 确认 §2.4 里 status 为 schema;仍不行则重启 DSH |
| 面板说"当前 settings 暴露的命名空间:(无)" | 客户端误读 remote.settings 的返回信封(应为 res.ok ? res.value : res.error) |
属插件 bug,按此修 client.js |
| 面板"无法连接 / 0 个模型" | Ollama 没起、端口不对、或浏览器跨域 | 起 Ollama;改端点;确认 OLLAMA_ORIGINS 允许页面来源 |
委派报 UNKNOWN_MODEL |
模型没写进路由 | 见 §2.3 |
委派报 No API key |
缺凭据占位符 | 见 §2.2 |
| 委派很慢(>30s) | 模型冷加载 / 语料太大 | 预热一次;切块;锁定常驻模型 |
| 会话"卡死"几分钟 | 工具表变化导致 prompt 前缀缓存失效,几十万 token 重新 prefill | 别急着按停止(provider 默认 5 分钟空闲超时会自动重试);长期对策:别在会话中途增删工具 |
9. 实测样本(一台具体机器的数字,只示范方法,切勿照抄)
下表来自一台 24 GB 显存的机器,不是推荐清单。换机器后"装不装得下、快不快"都会变。 量你自己的:
node bench.mjs(清单)→node bench.mjs <id>(实测 + 打印路由 YAML)。判据见 §2.1。
9.1 对照实验:素材"怎么喂"决定召回率(同一模型、同一任务)
任务:把 6 份启动器日志里的告警按根因归类。裁判:宿主侧机械分组(大小写敏感、按消息去重,见 §6 口径提醒)。
| 组 | 素材 | 取材方式 | 子代理工具调用 | 覆盖 / 总量 | 召回 |
|---|---|---|---|---|---|
| A 基线 | 115 行(6 份快照) | 它自己 grep(检索式被它改坏) | 1 次,只拿到 12 行 | 46 / 115 | 40% |
| B | 33 行(单文件) | 它自己 grep(同样被改坏) | 1 次,只拿到 12 行 | 12 / 33 | 35% |
| C | 33 行 | 它自己 grep(改坏)+ 强制先清点 | 1 次,只拿到 12 行 | 12 / 33 | 35% |
| D | 33 行 | 它自己 grep(检索式换成最简形式) | 1 次,拿到 33 行 | 33 / 33 | 100% |
| E | 115 行(6 文件) | 宿主 collect 代取(100% 正确) |
0 次(素材内联) | 68 / 115 | 59% |
| E2 | 115 行(6 文件) | 宿主 collect + 自动分批(3 片 + 1 次合并) |
0 次 | 根因 6 / 7,浏览器/进程崩溃/环境标志 全部找回 |
86%(根因口径) |
四条结论:
- 取材错 = 灾难:A/B/C 三组全都漏掉最大的一类(
node.exe,26 条),因为它的 grep 把[WARN ](WARN 后带空格)整类滤掉了 —— 而输出行号真、原文真、格式规整,看起来毫无破绽。 - 取材对了,还有规模门槛:E 组素材 100% 正确、
node.exe找回来了(25/26),但 115 行一口吃下只剩 59%, 丢的全是 ≤3 条的小类。同一模型:33 行 100% vs 115 行 59% —— 小模型的"胃口"是真有上限的。 - 分批把"丢小类"基本治好了:E2(E +
chunkLines: 40)把浏览器 2/进程崩溃 2/环境标志 3这些小类全找回来了,根因覆盖 4/7 → 6/7(只差20 秒无响应那 3 行)。 代价:本地时间 56.9s → 137.6s(3 片 + 1 次合并;本地 token 35,967,零成本); 4 次推理全部零工具调用、工具表 22 个(grep/glob被摘)。分批买的是召回率,付的是本地等待时间。 - ⚠ 合并这一步会让计数膨胀:E2 报
node.exe 54 次,逐行数只有 26;报TOTAL=75,素材实际 115 行。 所以"数字自己数一遍"这条纪律,分批之后更不能省。
9.2 对照实验二:大文档问答(另一种任务形状)
素材:dsh-client-ui-primitives 的 README(34.5 KB / 207 行)。A 组宿主代取(单批、不分片);B 组把全文读进主上下文。
5 道题(2 定位 + 2 多跳 + 1 个幻觉陷阱:问一个文档里根本没写的 Vue 支持)。
| 指标 | A 组(插件) | B 组(纯云端) |
|---|---|---|
| 云端 token(进主上下文) | 1,253 字符 ≈ 392 token | 32,746 字符 ≈ 10,233 token |
| 任务时间 | 62.8 s | 17.1 s |
| 正确率(5 题) | 5 / 5 ✅(引用行号全部真实) | 5 / 5 ✅ |
| 完整性 | 2 / 5 完整(3 题只答一半:漏计数、漏 4/5 个 props、漏主题约束)⚠ | 5 / 5 |
| 幻觉陷阱 | 答「没有」✅ 零编造 | 答「没有」✅ |
两条结论:
- 它不编造,它"答薄" —— 每条都真、只是漏了你问的那一半,而且看起来完全正确。
所以
qa类型必须有"逐题作答不得跳题 +ANSWERED=n/N对账"(v1.8.0 已内置)。 - 问答不能分片:三个模式的分批阈值对问答全是负作用(答案可能在另一片里),
所以
qa强制关掉分片、超预算就报错(v1.8.0 已内置)。 - 顺带:这次 A 组没被自己改坏检索式(零工具调用),说明
collect那条防线在问答场景同样有效。
| 模型类型 | 体积 | 吞吐 | 工具调用 | 结论 |
|---|---|---|---|---|
| MoE 30B 级 / Q4(每 token 只激活 ~3B) | ~17 GB | ~230 tok/s | ✅ | 本样本里最快;思考关不掉,但不污染 content |
| MoE 35B 级 / Q4_K_S | ~18.5 GB | ~200 tok/s | ✅ | 可关思考 |
| 稠密 27B / Q4(带视觉) | ~16 GB | 快 | ❌ | 视觉专用,走 ollama-vision 路由 |
| 推理型(reasoning)14B / Q4 | ~8 GB | ~88 tok/s | ✅ | 能当备胎,质量一般 |
| 推理型 32B / Q4 | ~18.5 GB | ~15 tok/s | ❌ | 不可用:关思考必空输出,且稠密大模型慢一个数量级 |
| 推理型 70B、以及同尺寸的 Q8 量化 | 35–40 GB | — | — | ❌ 装不进本样本那台 24 GB 显存的机器 |
一次真实委派的账本(一批 82.1 KB 的日志,997 行 / 118 条告警):
| 指标 | 数值 |
|---|---|
| 子代理动作 | 1 次 grep + 2 步 LLM,64 秒,100% GPU |
| 本地 token | 输入 22,262 + 输出 8,062(零成本) |
| 云端 token | 0 |
| 回到主上下文 | 692 字符(vs 原文 82.1 KB,压缩 ≈147×) |
9.3 复盘:一次"该派却没派"的真实案例(v1.9.0 判据重写的由来)
任务(另一个对话窗口,用户实测):「把 C:\Windows\DirectX.log 里出现的文件目录归类一下」。
素材:146 KB / 2,101 行 ≈ 46,058 token —— 旧政策第①条(> 30 KB)明确该触发,但它没有派。
| 证据(全部来自该会话日志) | 结论 |
|---|---|
该会话日志里含 本地模型子代理 政策文本 = true |
段落注册没坏,政策确实进了它的系统提示词 |
它的第 2 个工具调用就是 ollama_local_models() |
它想到了这个插件,还查了本地模型状态 |
之后:pwsh 查文件大小 → read 前 30 行 → 4 次 pwsh 用 PowerShell 抽路径 → 自己归类 |
它选了 shell 路线,并且没把 146 KB 读进上下文 |
判定:它是对的,错的是旧政策。
- 「抽取文件目录」是正则可确定性完成的任务 →
Select-String/-match/Group-Object比本地模型更准更快 (本地模型的计数与完整性本来就不可信,见 §6 与 §9.1)。 - 它用"只读 30 行 + shell 抽取"同样达成了我们宣称的核心价值(原文不进上下文)—— 插件在这一局是与 shell 竞争,而 shell 该赢。
- 旧政策的错在于拿规模当扳机:文件大小只决定"值不值得",决定"该不该"的是这件事要不要用脑子。
顺带发现的真实陷阱:该文件 ≈ 46K token > 本地 32K 上下文 —— 就算派了,整份塞进去也会被 collect 的
60k 字符上限截成 41% 的残料。正确姿势是 collect.pattern 预筛(例如只取含路径的行)—— 这条已写进政策。
9.4 成功案例:一次完全自主的委派(v1.9.0 判据生效)
任务(另一个对话窗口,用户实测):「把 ArmouryCrate.UserSessionHelper_2026-09-29.log 里出现的报错归类一下」。
素材:1,707.6 KB / 65,805 行。该窗口不知道本插件的任何历史对话,只看到系统提示词里那 600 字政策。
它做了这些(共 22 次工具调用,序列全部来自会话日志):
| 步 | 动作 | 说明 |
|---|---|---|
| 1–14 | pwsh × 14:签名归一化(0xHEX / GUID 占位)、去重计数、按小时分布 |
机械的活交给 shell —— 政策原话 |
| — | 把 1.7 MB 收敛成 84 行 / 9,946 字符 的签名文件 | 压缩 99.4%,本地模型才吃得起 |
| 15 | subagent_local({kind:"classify", mode:"b", collect:{…}}) |
✅ 语义的活主动派出去 |
| 16–19 | pwsh × 4:验证"ServiceManager CRITICAL 是否全是插件加载清单"、"报错涉及的目录是否真实存在" |
✅ 自己执行了 §6 的复核纪律 |
| 20–22 | 写报告 + 明细 + present |
交付 |
插件返回的账本(原文):
TOTAL=91
[任务类型] classify 分类 / 计数(默认)
[宿主预取素材] 1 个文件 / 84 行 / 9946 字符
[验收建议] 先 grep 复核总数与各类计数,再抽查 2~3 条出处行号 —— 计数一向不可信
[耗时] 56.0 秒(1 次本地推理)
子代理账本:22 个工具、自调工具 0 次、本地 token 17,389(零成本)。
它写的 task 值得抄(比我 README 的示例更好):
…按「根因」归类成若干类别(不是按插件分)…并甄别哪些签名实际上不是错误(例如插件加载成功清单); 最后给出:真正需要处理的报错次数合计,以及被误标的次数合计。
最佳实践(本次验证出来的):大素材不必硬塞给 collect.pattern ——
先用 shell 把它收敛成小文件(去重 / 归一化 / 筛选),再把这个小文件交给 collect。
理由:shell 能做正则做不到的签名归一化去重,收敛比远高于 pattern 过滤(本例 99.4%);
而本地模型只需要那份"小而完整"的料(§9.1 的结论)。
9.5 三个本地模型的实测画像(v1.12.0:这就是"档案册"存在的理由)
同一台机器(RTX 4090 / 20 GB 可用)、同一套协议、同一批探针素材(30/60/120 行、答案已知)、同一套提示词:
qwen3:30b-a3b(MoE 30B) |
qwen3.8:27b(稠密 27B) |
deepseek-r1:14b |
|
|---|---|---|---|
| 吞吐 / 显存 | 233.3 tok/s / 20.2 GB | 60.3 tok/s / 16.2 GB | 85.4 tok/s / 14.2 GB |
| classify 30 行 | ✅ 4/4 计数全对 | ✅ 4/4 | ⚠️「连接被拒」报 11(真值 8),各行合计 33 ≠ 自己写的 TOTAL=30 |
| classify 60 行 | ✅ 4/4 | ✅ 4/4 | ❌ 四类全错(40/18/13/4),合计 75 ≠ TOTAL=60 |
| classify 120 行 | ✅ 4/4 | ✅ 4/4(40.6 s) | 未测 |
| extract | ✅ 裸 JSON | ✅ 裸 JSON(最紧凑,14.7 s) | ⚠️ 字段全对,但被 ```json 围栏包住 → 直接 JSON.parse 失败 |
| code(工具 + 汇总) | ⚠️ glob+grep 调成功、汇总崩了(ITEMS=0 + 编造一处出处) |
✅✅ 7/7 行号机械核对全对,另 2 行诚实标注"未找到",ITEMS=9 自洽 |
❌❌ 0 次工具调用,8.7 秒内编造 4 行假数据(file1.js:10 / file2.mjs:5 / file3.js:20) |
| 档案里的结论 | 一口 40 行 | 一口 120 行(不必分批) | 一口 20 行 |
| 一句话定位 | 最快,能力够用 | 质量最高,最慢 | 只能贴标签 / 抽取;不能计数、不能用工具 |
三条结论:
- "本地模型"不是一类东西:同样十几秒的活,27B 会老实调工具、照着真实结果写;14B 会连看都不看直接编。
- 校准的产出不是"参数",是"可信度" —— 档案里最值钱的字段其实是
evidence.kinds.*,它告诉后面的 AI "这个模型只配干哪几种活"。 - "看起来更快"可能是"干得更少":14B 单次 7.431.6 秒(比 30B 的 21.456 秒快),但它吞吐只有 85 tok/s(30B 是 233) —— 快的原因是它生成的 token 少,也就是它跳过了该干的活。
10. 维护铁律(要改这个插件之前必读)
工具集合恒定:只在
apply()里注册一次,不因配置变化增删。工具表一变,整条 prompt 前缀缓存作废, 几十万 token 的会话要重新 prefill,用户会看到"发一句话卡住几分钟"(实测 166 秒零 token)。配置走 volatile:只有
Schema.volatile()字段才会出现在设置表单并被写入接口接受; 改完即时生效、不重挂插件。非 volatile 字段只在 patch 里改。零裸 import:插件以
link:安装,模块真实路径在工作区,Node 从真实路径向上找不到 profile 的依赖树 (实测ERR_MODULE_NOT_FOUND)。需要 schemastery 时用createRequire以 dsh 安装目录 / profile 目录为基准解析。前端护栏:React Hook 只能在组件函数体内(在
factory里调用组件会让整棵前端树崩掉); 面板要包 ErrorBoundary;remote的命名空间必须逐个显式inject。异步流程不许在模块加载期跑:加载期零副作用,全部进
apply()。toolFilter 名单要跟着部署走:
tools.restrict()只认"可 restrict 的继承全局工具名",名单里有一个不在就整条抛错 (allow 与 deny 一样)。startChild()已把这一步包成"按报错点名逐个剔除 → 重试 → 结果里如实告知", 改名单时务必保留这条自适应路径,否则换台机器就可能静默失去防护(真机踩过一次静默降级)。政策段落文本保持静态:
DELEGATION_POLICY里不要塞 enabled/model 这类会变的状态 —— 它位于系统提示词最前端, 文本一变整条 prompt 前缀缓存作废,长会话要重新 prefill(实测踩过 166 秒零 token)。要降级就用文案里的"报已关闭就自己读"。主动性靠"看得见的触发条件",不靠工具描述:光有工具不给触发规则,实测自发调用率是 0(见 §5.4)。 想让它被用上,就把"什么时候用"写进系统提示词或工具结果,而不是写进 README 等模型来读。
取材不交给弱模型:凡是"它自己选参数"的环节(正则式、路径、扫描范围)都是静默失败的来源 —— 错误不会写在输出里,只会让某一整类凭空消失,而报告看起来毫无破绽。要么给死参数,要么由宿主代劳(
collect)。验证自己的工具链:判分用的 grep/PowerShell 也要声明口径(大小写、编码、续行),否则你会拿被污染的 分母去评判别人(实测差点把 100% 判成 97%)。
弱模型的"胃口"有上限,而且只能靠分批解决:同一模型同一任务 33 行 100% / 115 行 59%(§9.1)—— 提高 prompt 质量、强制先清点,都没能改善(B/C 组实测无效)。
collect.chunkLines就是为这条存在的: 切片 → 逐片归类 → 合并,全程在宿主侧,主上下文不受影响(E 组:零工具调用、只回结论)。要加"新策略"时:先问它该走哪条通道 —— 静态文本最贵,工具输出免费。
通道 改它的代价 适合放什么 系统提示词段落 DELEGATION_POLICY最贵:一变整条 prompt 前缀缓存作废,长会话要重新 prefill(实测 166 秒零 token) 只在判据本身变化时改(该不该派、什么算语义任务) 工具描述 / 参数说明 同样在请求前缀里,同样砸缓存 契约类信息(有哪些 kind、参数语义) 工具返回的账本( [验收建议]、[耗时]、[分批委派])免费(在对话尾部,不进前缀) 每类任务的验收方法、不断进化的经验 tools/post-execute当场提示免费 "在花掉钱那一刻"给出的建议(已这么用) README 免费(按需阅读) 全部细节、样本、复盘 ⇒ 新策略默认先写进免费通道;只有当它改变判断逻辑时才动静态文本,而且要攒着一起改(一次重启付一次缓存代价)。
改代码要重启,改配置不用(2026-09-29 两种都真机钉死过):
改什么 生效方式 实测证据 patch 里的配置(volatile 字段、别的插件的 config,例如 llm 路由的 models:)存盘即生效,不用重启 给路由补上 qwen3.8:27b后没重启就直接派成功index.js的代码必须重启(loader 不重新 import 模块) 新加的代码指纹只在重启后才出现 判定方法:调
ollama_local_models,看输出首行[版本] vX | 代码指纹 xxxxxxxx(v1.11.3 起), 指纹 = 该文件 sha256 前 8 位 —— 跟磁盘上的对一下,就知道宿主跑的到底是不是最新代码。
11. 文件清单
| 文件 | 作用 |
|---|---|
index.js |
宿主半身:两个工具 + Config schema + 前置自检 |
client.js |
客户端半身:「设置 → 本地模型」面板(开关 / 模型选择 / 端点 / 连接状态) |
cordis.patch.yml |
bundle 行声明(插入 local-ollama-models 这一行) |
README.md |
本文件(使用声明;模型与参数一律"自己量",见 §2.1) |
bench.mjs |
选型尺:列本机模型 / 实测吞吐与工具调用 / 打印可直接粘贴的路由 YAML(只读) |
package.json |
包信息;dsh.bundle.patch 与 dsh.client 声明 |
No comments yet. Be the first to write one.