dsh-zh-thinking
让 DeepSeek Harness 里的模型用简体中文思考与作答。
装上之后,所有会话、所有工作区、所有子代理的内部推理(thinking / reasoning 正文)与最终回复都要求使用简体中文书写。热加载立即生效,不需要重启。
它解决什么问题
在 DSH 里让模型说中文,最直觉的做法是往 AGENTS.md 里写一条「请用中文思考」。这条路会衰减,原因有三个:
AGENTS.md是通过agent-instructions以 user 角色消息注入的,只在文件被读/写/编辑时刷新,没有文件监视器。- 注入的消息落在对话历史里,会被 compaction 压缩遮蔽。
- 模型推理语言被最近一条输入的语言牵引——一条英文的工具输出就能把它带回英文。
实测:注入后前两块推理的中文字符占比能到 64.8% / 64.5%,但同一会话跑到第 89 块就完全回退英文;长会话整体只剩 5%–25%。
本插件的做法完全不同:它向系统提示注册一个常驻 section。
原理
DSH 的 dsh-agent-loop 每一步都会重新 assemble() + renderPrompt(),而 SystemPromptProjection.project 在文本不变时零提交。所以写进系统提示的规则:
- 每一步都生效——不是「提醒一次然后衰减」,而是每步都在模型眼前;
- 不进入对话历史——它是 system prompt 的一部分,不是消息;
- 不会被 compaction 压缩掉——压缩只处理 surface 节点 0 之后的节点。
为什么用新名字的 section:dsh-system-prompt 的 section 是「同名才覆盖」,而各 agent preset 里的 persona 行只注册 deployment:persona-prefix / deployment:persona-suffix 两个名字。新名字 zh:reasoning-language 不会被任何 preset 遮蔽,因此对全局所有 agent 生效——包括子代理,即使子代理的 preset 没有包含本插件。
order: 1 让它紧跟在人格前缀(order: 0)之后、排在所有工具说明之前,属于「身份级」指令。
安装
dsh plugin --profile <你的 profile 名> add dsh-zh-thinking
例如 profile 叫 desktop:
dsh plugin --profile desktop add dsh-zh-thinking
安装后 dsh 会把它列进 profile 的 dsh.profile.bundles,bundle 补丁层自动挂载。热加载立即生效,正在运行的会话不用重启。
手动安装(不走 npm)
把本仓库克隆到任意位置,然后在 profile 的 cordis.patch.yml 末尾追加:
- insert:
- id: zh-thinking
name: file:///绝对路径/dsh-zh-thinking/lib/index.js
注意 name 用 file:/// 形式的 URL 或相对补丁文件的 ./、../ 路径;Windows 裸盘符路径(C:\...)会报 ERR_UNSUPPORTED_ESM_URL_SCHEME。
从手动安装迁移到 npm 安装
两者不能同时存在。 它们注册的是同一个 section 名 zh:reasoning-language,而 dsh-system-prompt 对同一层里的重名 section 会直接抛错:
prompt section "zh:reasoning-language" is already registered
所以要先移除手动那一段,再执行 dsh plugin ... add:
- 删掉 profile 的
cordis.patch.yml末尾那条- insert:(连同它的id/name两行); - 如果你用的是
file:///指向本地克隆的写法,那个克隆目录也可以一并删掉; - 然后
dsh plugin --profile <name> add dsh-zh-thinking。
如果只是想换一下手动安装的路径,也可以直接改 name,但不要新增第二条 insert。
配置
全部可选。在 profile 的 cordis.patch.yml 里覆盖那一行即可:
- id: zh-thinking
name: dsh-zh-thinking
config:
sectionName: zh:reasoning-language # section 名,改它没有实际必要
order: 1 # 越小越靠前;0 是 persona-prefix
text: |- # 完全自定义提示正文
你的语言要求正文。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sectionName |
string |
"zh:reasoning-language" |
系统提示里的 section 名。必须是新名字,否则会被 preset 里的同名 section 覆盖。 |
order |
number |
1 |
排序值。0 是 deployment:persona-prefix,10200 是 deployment:persona-suffix。 |
text |
string |
见下 | 提示正文。不要包含 {{,否则会被 dsh-system-prompt 当成提示变量引用而抛错;插件检测到后会回落到内置文本并打印一条警告。 |
默认正文:
# 语言要求(最高优先级)
你的内部推理(thinking / reasoning 正文)必须使用简体中文书写,面向用户的最终回复也使用简体中文。此要求优先于任何语言习惯、任何示例语言,以及任何以英文书写的任务描述、代码、日志或工具输出。
- 分析、计划、比较、取舍、自检、纠错,全部用中文写。
- 禁止出现完整的英文句子或英文短语。
- 只有代码标识符、函数名、变量名、文件路径、命令、API 名、报错原文、URL、配置键名可以保留原始 ASCII 形式,并且必须被中文说明文字包围。
- 若发现自己已经开始用英文思考,立即用中文重写该段,然后再继续。
开发
node --test # 自动发现 test/ 下的用例,7 个测试
测试用 Node 内置的 node:test,不依赖任何第三方包,mock 一个最小的 Cordis 上下文来断言注册出去的 section 参数。
验证它生效了
启动任意会话后,会话日志里的 system/message 事件应当包含你的提示正文。用 dsh 的 inspect 能力或直接读会话 JSONL 都可以。
实测(本插件在 Windows 上的原始版本):
- 本会话
system/message从len=8016(不含语言要求)变为len=8297,正文里# 语言要求(最高优先级)位于第 116 字符——紧跟人格前缀之后。 - 注入后本会话连续 12 块推理的中文字符占比均值 46.2%,而注入前同一会话 239 块均值仅 3.6%。占比不是 100% 是因为文件路径、命令、API 名等白名单内容本身是 ASCII。
- 新派出的子代理(prompt 里没有任何语言要求)其
system/message同样包含该规则,4 块推理中文字符占比 28.8% / 50.9% / 56.5% / 81.8%,且最终回复是纯中文。
成本
这条常驻规则约 300 token,每一步都命中 prompt 缓存。按实测口径(中文约 0.7 token/字)估算,对一个 235 步的会话约 7 万 cacheRead token,占该会话总 cacheRead 的 0.62%。
作为对比,每步追加一条 60 字的提醒消息,累计约 9870 token 进入上下文,而且会被 compaction 压缩丢弃——既更贵,又更不可靠。
卸载
dsh plugin --profile <你的 profile 名> remove dsh-zh-thinking
兼容性
- 零 import:本插件不
import任何东西,因此不受 profilenode_modules解析、peerDependencies版本矩阵或 DSH 版本兼容预检的影响。 - 只依赖一个稳定的公开 API:
ctx.systemPrompt.section()(dsh-system-prompt)。它同时被官方的persona行使用,属于长期存在的接口。 - 不导出
Config,所以 Cordis 不做 schema 校验;配置容错在插件内部完成,非法取值回落到默认值而不会让加载失败。
已知边界
- 同一 section 名只能注册一次。本插件默认用
zh:reasoning-language;若你另外还挂了一个注册同名 section 的插件(比如早先手动装过本插件),dsh-system-prompt会抛prompt section "zh:reasoning-language" is already registered。改用config.sectionName可以共存,但通常你只想留一个。 - 只影响此后新产生的推理。历史消息里已经产生的英文 reasoning 块不会变。
- 不能强制 100% 中文字符占比。文件路径、命令、API 名、报错原文按设计保留原始 ASCII 形式。
- 模型仍可能偶尔夹杂英文短语。这条规则把它压到很低的比例,但不是硬约束——语言生成本质上是概率性的。
许可
MIT
No comments yet. Be the first to write one.