dsh-doctor:给编程题助教加一道「先跑代码、再回答」的门

前言

用 DSH 给编程题做助教时,常见的两个失败不是模型不够聪明,而是流程上没有约束:一是凭印象下结论,比如「你这里下标写错了」,其实没跑过学生代码;二是忍不住给答案,学生说一句「就给我看一眼完整代码」,模型就把正解贴出来了。这两件事靠自觉挡不住,靠一条系统提示也挡不住。

buguoshixc/dsh-doctor 的思路是把回答链路拆成工具:归因必须来自一次真实执行的裁决结果,面向学生的回复发出前必须先过一道门控,门控判定「能跑出结果」或「与正解高度相似」就拦下来。它不是又一个提示词模板,而是插进 DSH 工作流里的插件。

这是什么

插件维护者是 buguoshixc,分类为工作流,采用 MIT 许可证(package.json 里声明 "license": "MIT")。一句话定位:给 DSH 桌面端加一个「诊断模式」agent 预设,外加一个零第三方依赖的 Python 判定核,让模型在回答之前先跑代码拿证据,用苏格拉底式提问带学生自己修。

作者给出的自我界定很明确,是这几样;不是另外几样:

是 不是
DSH 插件(bundle 加 agent preset) LLM 应用;不调用任何模型 API
离线判定核(Python 3.11+ 标准库,零第三方依赖) 判题云服务;不接 judge0、不联网
两个原生工具加一份教学技能加一个预设 Web 平台;没有前端、没有数据库、没有 HTTP 服务
一个答案门控 安全沙箱;本地执行只有超时、内存上限、尽力禁网

架构叫「架构 C」:会话里的模型负责出用例和正解,工具只负责裁决。判定核不生成任何东西,也不需要 API key,只做三件确定性的事——跑代码、比输出、挡答案。

安装与启用

官方安装命令来自 README 和 docs/INSTALL.md,装到桌面 profile:

dsh plugin --profile desktop add github:buguoshixc/dsh-doctor

装完必须重启 DSH,然后在新建会话的预设选择器里选「诊断模式」。有两种等价做法:把 docs/INSTALL-PROMPT.md 里的 prompt 粘给 agent,让它自己体检加安装加验收;dsh plugin 会把参数转发给 profile 目录里的 pnpm,GitHub 短名解析、缓存、锁文件都是 pnpm 的行为。

前置条件按 docs/INSTALL.md 的检查项:

依赖 要求 检查命令
DSH 桌面版 0.2.1-rc.3 及以上(0.3.0 以下) dsh --version
Python 3.11+,且能被 python 或 python3 找到 python --version
Git 从 GitHub 安装时需要 git --version

package.json 里写明的版本约束是:

"engines": { "node": ">=22.0.0", "dsh": ">=0.2.1-rc.3 <0.3.0" }

本包没有构建步骤,纯 ESM 加纯 Python,不需要 prepare 脚本。Python 找不到时典型报错是 E_NO_PYTHON,README 给的做法是用 DSH_DOCTOR_PYTHON 写死解释器绝对路径,或确认它装在 PATH 里,Windows 下 py -3 是最后一个候选。

两个工具

插件注册两个原生工具,JSON Schema 与执行体在 lib/tools.mjs。

doctor_judge:唯一裁决器

只要学生给了代码加题目,就先调它。不跑就下结论等于编造证据。

参数(来自 README):

problem        题面全文(Markdown,含示例)
code           学生代码全文
cases          用例集 JSON,形如 {"func_name":"…","cases":[{"case_id","input","expected","kind"}]}
refs           1~4 份正解代码,会交叉校验
func_name / timeout_ms / memory_mb   可选

它先跑正解做自校验:任何一份正解与 expected 不符,立刻返回 verdict=REF_REJECTED,不执行学生代码、不进入归因。这样「模型自己写错正解」不会变成「学生被冤枉」。

成功路径返回 verdict、compile、ref_status、failure_distribution、counterexample、category / category_label、confidence 和 reason。README 给了一个 lc-053-maximum-subarray 边界错误样本的节选:

{
  "verdict": "WA",
  "phenotype": "WA",
  "compile": { "passed": true, "errors": "" },
  "ref_status": "trusted",
  "failure_distribution": { "total": 5, "failed": 1, "pattern": "boundary_only" },
  "counterexample": {
    "case_id": "b1",
    "input": [[-3, -2, -5]],
    "expected": -2,
    "actual": 0,
    "kind": "boundary"
  },
  "category": 3,
  "category_label": "边界处理错",
  "confidence": "high",
  "reason": "1/5 条用例失败,失败模式 boundary_only,归类为第 3 类(边界处理错)"
}

counterexample 里写死的 case_id、input、expected、actual,是模型唯一可以写进回复的事实。字段全表见 docs/SCHEMA.md。

doctor_gate:回答门控

任何面向学生的回复,发出之前必须调它。三层判定:

1、layer1 能跑即拦:抽出回复里的代码片段真的执行,能跑出结果就判 block。只看语法不够——while 左指针 < 右指针: 语法合法,但它跑不起来,是合格伪代码。
2、layer2 相似度防漏:与正解做 AST 结构相似度(0.55 权重)加 token Jaccard(0.45 权重)加权;>=0.75 判 downgrade-to-level2,>=0.60 打 suspect=true 放行但标记。
3、fail-closed:内容像代码却分析不了,一律拦或降级,绝不当成「安全」。

返回里的 action 只有 pass 和 block,downgrade-to-level2 落在 recommended_level 和 reasons 里。README 给的样本:

{
  "action": "block",
  "reason": "layer1 判定为可运行代码(定义了 twoSum)",
  "layer1": { "runnable": true, "detail": "定义了 twoSum" },
  "layer2": { "similarity": 1.0, "ast_sim": 1.0, "token_sim": 1.0, "suspect": true },
  "leaked_fragments": ["def twoSum(nums, target):\n    # 正解 B:哈希表一次遍历,O(n)\n    …"],
  "recommended_level": 1,
  "reasons": ["extract:found_fragment", "layer1:runnable"]
}

「诊断模式」预设与技能手册

预设 id 是 diagnose,名称是「诊断模式」。README 给了它的声明方式与组合说明,另附一份旧格式的等价描述(presets/diagnose/,说明里写 DSH 0.2.1 起不读该目录,留作对照):

项 内容
persona 中文助教人格;铁律:过 gate、不给完整代码、只用工具证据、REF_REJECTED 时先修不归因
声明的工具面 bash(POSIX)/ pwsh(Windows)、read/write/edit、glob/grep、skill
插件自带工具 doctor_judge、doctor_gate
技能 doctor,只在预设里可见:includeDefaultRoots: false 加随包目录
继承的 host 工具 present、ask_user_question 等,由 profile 决定

真正的「度」在 presets/skills/doctor/SKILL.md 这份教学手册里。技能不是装饰,它是提示工程的一部分,作用是让模型每次会话都读到「先跑代码、再组织回复、再过门控」这个顺序。另外预设不到 {{cwd}} 这类提示变量——DSH 0.2.1 起在 preset persona 里会整轮失败,只用 {{model}}。

怎么验证装对了

docs/INSTALL.md 给的验收流程很具体:新建一个「诊断模式」会话,贴一段有明显错误的代码,例如:

def maxSubArray(nums):
    best = 0
    for x in nums:
        best = max(best, best + x)
    return best

期望的模型行为按顺序是:先写一份自己的用例集和正解,调用 doctor_judge;回复里引用具体的反例,case_id、输入、期望值、实际值都要有;组织回复后调用 doctor_gate,action 不是 pass 就改写重试;最终回复里没有完整可运行代码,而是一到两个引导性问题。

只要「工具调用」面板里出现了 doctor_judge 和 doctor_gate,说明插件装对了。如果模型压根没调工具,说明技能没被加载或它没读技能。

想核对包本身的质量,README 给的命令是:

bash scripts/verify.sh

它跑环境自检、零第三方依赖扫描、Python 单元测试(README 写的是 93 个)、Node 单元测试(README 写的是 22 个,含用本机真实 @deepseek-ai/dsh-tools 校验 schema 的契约测试)、三题 fixture 演示(AC/WA/CE/TLE/REF_REJECTED 加门控三种动作)、包元数据契约、stdout 纯净性,全绿退出码 0。

包的暴露面和依赖关系也值得看一眼。files 白名单包含 lib/、doctor_core/、presets/、locale/、icon.svg、cordis.patch.yml、scripts/verify.sh 和部分 docs/;三个 peer dependency 都标了 optional: true:

"peerDependencies": {
  "@deepseek-ai/dsh-tools": ">=0.2.1-rc.3 <0.3.0",
  "@deepseek-ai/dsh-agent-preset": ">=0.2.1-rc.3 <0.3.0",
  "@deepseek-ai/cordis": ">=4.0.0 <5.0.0"
}

诚实的边界

README 自己写了几条不保证,这些不是免责声明式的套话,而是设计上的固有代价。

正解由模型生成,这是架构 C 的固有代价。 只能做到「正解与题面示例不符就终止」,做不到「保证正解正确」。所以 doctor_gate 是最后一道防线,不是唯一一道。

本地执行不是安全沙箱。 只有 5 秒墙钟超时、256MB RLIMIT_AS,以及尽力而为的网络屏蔽(替换 socket.socket)。Windows 上 resource 模块不存在,内存上限不生效,会带 memory_limit_not_enforced 标记。真正的隔离要靠 OS 级沙箱。

只支持 Python。 C++ 与 Java 的 wrapper、原平台的 tree-sitter 相似度都没搬过来。

「必须先过 gate」是提示工程,不是机制。 工具能拦住被送进来的回复,拦不住模型干脆不调用它。降低风险的办法是技能里写死步骤,加上 persona 里的第一条铁律。

适用场景与注意

适合的情况写清楚:

  • 编程题助教、在线判题辅导,需要「先跑再答」的流程约束;
  • 想用确定性执行证据替代凭印象下的归因;
  • DSH 桌面版版本在 0.2.1-rc.3 到 0.3.0 之间,且本机有 Python 3.11+。

限于当前的资料,下面这些需要读者在装之前自行确认,资料里没有给出结论:

  • 多用户并发场景下的行为,资料没有提;
  • 非 Python 语言、以及需要更严格隔离的执行环境,README 明确说不支持;
  • 正解正确性对应的模型能力上限,插件本身只能做到 REF_REJECTED 终止,不能保证;
  • 这个插件以当前 DSH 进程的权限运行,判定核会真的执行回复里抽出来的代码。安装前应打开 GitHub 仓库和 docs/SCHEMA.md、docs/PLUGIN_API.md、docs/EXTRACTION.md 核对一遍源码与许可证。社区目录页是独立站点,与 DeepSeek / 幻方无官方从属关系。

结尾

dsh-doctor 的价值不在多一个工具,而在把「先跑代码再回答」「答案由门控挡不由自律挡」写进了插件。对做编程题助教的人来说,它比单纯写一条系统提示更硬的地方是:归因必须带 case_id 和实际输出,完整可运行的代码一定被 doctor_gate 拦下。代价是正解仍由模型给出,本地执行也不是安全边界。

目录页:https://www.skillhub.cn/plugins/buguoshixc/dsh-doctor
GitHub:https://github.com/buguoshixc/dsh-doctor

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜