dsh-interviewer
在 DSH 里跑一场真的模拟技术面试 —— 真实模型当面试官,读你的简历一层层往下追问, 按证据打分并推导薪资区间。面试官是考官,不是助教:不讲解、不纠正、不鼓励, 只负责在有限时间里测出你的真实能力边界 —— 这一点决定了它的所有行为。
它同时是技能 + 面板插件:右下角一个可拖拽、可收起的悬浮窗,有自己的对话框,
不占用主会话;同一份面试 SOP 还会注册成 interviewer 技能,主会话里 skill interviewer 一样能用。
它能做什么
- 真实模型当面试官。 用你 DSH 里已经配好的模型,插件不额外要 API key。
- 简历驱动,一层层往下钻。 从你简历上的事实问起,追到反事实和复盘,不走过场。
- 四种面试官风格 × 四种轮次。 温和引导 / 务实工程 / 冷峻压测 / 学术严谨, 配技术一面 / 技术二面 / 主管面 / HR 面 —— 风格换的是问法和压力,不是标准。
- 不会在一个话题上死磕。 内置面试状态机,同一话题最多追 2 次, 保证考察广度而不是把人逼死在一处(见下文「面试状态机」)。
- 按证据打分。 五个维度加权,每一分都要能在你的原话里找到落点; 薪资区间由确定性脚本算出来,不靠模型心算 —— 同一份输入永远得到同一个结果。
- 过程可见。 面板顶部一条状态条:已用时间、面试计划推进到哪、当前话题问了几次。
- 语音作答。 用浏览器内置的语音识别边说边转文字,识别结果只填进草稿、不自动发送 (Chrome / Edge 有,Firefox / Safari 没有)。
和其他插件的区别
DSH 市场里做「求职」的插件目前分两类,都不是面试本身:
| 插件 | 视角 | 做什么 |
|---|---|---|
recruiting-copilot |
招聘方 | HR / 猎头的寻源、初筛、约面试 |
resume-reviser |
候选方 | 改简历 → 岗位推荐 → 末尾顺带一次模拟面试 |
dsh-resume-expert |
候选方 | 引导式生成简历,直出单页 A4 PDF |
dsh-scout(司察) |
候选方 | 面试前查公司、查岗位的尽调 |
dsh-interviewer |
候选方 | 只做面试本身 |
区别不在功能多少,在产出是什么:
- 那些插件的产出是文档 —— 简历、诊断报告、岗位清单。面试只是它们链路的一小段。
- 这个插件的产出是你的表现数据:哪些问题你答得实、哪些地方在含糊、边界在哪。 它不给你讲题,也不负责让你自我感觉良好。
所以它能做一件「顺带跑一次模拟面试」做不到的事:把同一个话题真正追到底, 然后再换下一个。这也是这个插件里最费劲的一块(见下文「面试状态机」)。
安装
dsh plugin --profile web add github:ufiredong/dsh-interviewer
装完重启 dsh web(重启会断开当前会话,属正常)。重启后右下角出现「模拟面试」悬浮按钮。
不需要另外装技能。 面试 SOP 随插件一起分发,插件启动时把它注册成名为 interviewer
的运行时技能 —— 装了插件就等于装了技能。
卸载:
dsh plugin --profile web remove dsh-interviewer
用的是哪个模型? 插件不自己管密钥、不自己发 HTTP 请求。它注入 DSH 已经配好的
llm服务,从你settings.yaml的agent-default-model读当前选择。 所以效果取决于你当前用的模型。
怎么用
- 点右下角的悬浮按钮 → 弹出设置弹窗
- 选目标方向(大模型/Agent、Java 后端、算法)、职级、面试轮次、面试官风格
- 确认后进入面试面板,把简历粘进对话框(PDF 读不出字,用 Markdown 或纯文本), 或者直接说「开始面试」
- 聊完说一声,面试官会根据全程对话给出评分与薪资结论
面板顶部有一条状态条,显示已用时间、面试计划推进到哪、当前话题问了几次 —— 到上限时会标红并显示接下来要换到哪个话题。这条是给用户看的进度, 也是「面试官有没有在一个话题上死磕」的可视化证据。
界面
面试设置 —— 方向 / 职级 / 轮次 / 风格。前四项决定整场面试的难度和问法:

面试进行中 —— 顶部两条:上面是配置摘要,下面是状态条,实时显示已用时间、 当前话题问到第几问、面试计划推进到哪:

刚打开时 —— 直接发消息就能开始,简历可以粘进来:

面试官到底是什么样
定位
你的任务是在有限时间里测出候选人的真实能力边界。不是让他感觉良好, 不是把知识讲给他听,也不是帮他通过面试。
六条铁律
| # | 规则 |
|---|---|
| 1 | 一次只问一个问题。不列 1/2/3,不给选项菜单 |
| 2 | 不讲解、不纠正、不铺垫。答错时记录,继续往下问 |
| 3 | 含糊必追问。「大概」「差不多」「应该是」「我们」→ 立刻要具体数字或你的角色 |
| 4 | 不放过「不知道」。给一次换角度的机会,仍不会才记录并推进 |
| 5 | 不替候选人组织语言。不说「你是不是想说……」 |
| 6 | 不评价、不安慰、不鼓励 |
追问阶梯
从简历上的事实出发,一层层往下钻:
L0 事实核查 「这个项目你负责哪部分?」
L1 技术选型 「为什么选它,不选另一个?」
L2 实现细节 「具体怎么做的,关键代码在哪?」
L3 反事实 「如果 X 挂了会怎样?」
L4 复盘 「现在回头看,哪一步做错了?」
L5 迁移 「换个场景,你还会这么做吗?」
同一个话题最多追 2 次。追问能力很强但不知道什么时候停,等于没有追问能力 —— 这条是实测吃出来的(见下面的「面试状态机」)。
四种面试官风格
风格改变怎么问和压力多大,不改变铁律 —— 温和风格一样要追到底。
| 风格 | 什么样 |
|---|---|
| 温和引导 | 语气平和,用「我们来看看」把问题递出去,压力低但一样追 |
| 务实工程 | 直接问工程细节和取舍,不在意包装,关心「你到底做了什么」 |
| 冷峻压测 | 专门压边界和失败路径,答得顺不代表过得了 |
| 学术严谨 | 要定义、要前提、要证据链,不接受「业界都这么做」 |
评分
五个维度加权(1–5 分),每一分都要能在你的原话里找到落点:
| 维度 | 权重 | 在测什么 |
|---|---|---|
| 技术深度 | 30% | 是否理解原理,而非记住方案 |
| 系统设计 | 25% | 能否在约束下做取舍 |
| 工程严谨 | 20% | 是否考虑边界、失败、可验证性 |
| 表达与结构 | 15% | 能否把复杂的事讲清楚 |
| 边界认知 | 10% | 知不知道自己不知道什么 |
加权分定档(一线城市总包):
| 加权分 | 职级对标 | 定位 | 年薪总包 |
|---|---|---|---|
| ≥ 4.5 | P8 / 3-1 / T7 | 专家级 | 120 – 180 万 |
| 3.8 – 4.5 | P7 / 2-2 / T6 | 高级 | 70 – 120 万 |
| 3.0 – 3.8 | P6 / 2-1 / T5 | 中级 | 45 – 70 万 |
| 2.2 – 3.0 | P5 / 1-2 / T4 | 初级 | 30 – 45 万 |
| < 2.2 | — | 不建议推进 | — |
一线以外按城市系数折算(新一线 0.82、二线 0.68、其他 0.58)。
薪资数字由 skills/interviewer/scripts/score.mjs 算,不靠模型心算。
脚本是唯一口径来源,这样两次面试的结论才可比 —— 也让「同一份输入永远得到同一个分」
成为一条可验证的性质(见下面的自检)。
这是总包不是 Base,级差不是线性的,而且模拟面试 ≠ 真实定级。 真实结果还受城市、公司、团队缺口和议价能力影响。
面试状态机:为什么需要它
第一版是无状态的:每轮把整份对话丢给模型问「下一句问什么」。
结果它在同一个话题上追了 5 轮(SOP 只允许 2 轮),代价是没余力接别的话题 —— 放掉了一段教科书级的含糊回答(候选人说「JVM 调过一些参数,后来就好了」, 面试官因为还卡在上一个话题,整段放过去了)。
根因不是提示词写得不好,是模型没有工作记忆:它每轮都要从零推导「该问什么」, 而最近上下文里最抢眼的就是「他刚才没回答我」,所以它一直拉那根线。
所以状态被显式化了。分工很明确:
- 模型负责判断这一轮在聊什么话题(它擅长这个)
- 状态机负责数它追了几次、该不该换、还剩多少时间(机械计数,不该交给模型)
一条重要的设计教训
让模型每轮吐一个状态标记,命中率只有 50% 上下。换措辞、把协议挪到提示词最末尾, 都救不回来(挪末尾反而从 4/5 掉到 1/5)。
所以状态机不依赖模型配合才能正确:
- 到上限时,提示词会点名下一个话题(「本轮换到「数据库与扩展」」)—— 「换个话题」是抽象指令,模型大概率继续追问;点名它才动得起来
- 下一轮如果仍然没有标记,状态机就乐观前进,自己把话题指针推到那里
- 计划要不到就装一份按方向预设的骨架
结果是即使模型完全不吐标记,广度推进依然成立。
回归实测
interview-sim/state-run.mjs,11 轮真实模型调用,与无状态版同一套候选人台词:
| 无状态版 | 状态机版 | |
|---|---|---|
| 单话题最多追问 | 4 轮(「多出来的 1.5 秒」反复重问) | 2 轮 |
| 走过的不同话题 | 2 个 | 6 个 |
| 收尾表现 | max-tokens 挂掉 |
「好,我整理一下」 |
数据与隐私
- 简历和全部对话内容会发送给你在 DSH 里配置的模型服务商。 这是它工作的前提, 不要往里粘你不想外发的东西。
- 面试状态(计划 / 话题 / 观察)保存在浏览器内存里,每轮随请求回传给主机侧的
POST /interviewer/api/chat。主机侧是无状态的纯函数 —— 不写数据库、不落盘, DSH 重启也不会留下面试记录(对话本身也随之丢失)。 - 插件不额外收集任何遥测。
开发
自检
node dev-check.mjs # 插件结构与接线(含用桩真跑一遍浏览器侧模块)
node state-test.mjs # 面试状态机(纯函数,可离线断言)
dev-check.mjs 不是"看起来对",是"真的执行了":它用 node:vm + 桩
window.__ModuleLoader__ 把 lib/client.js 真的跑一遍,用桩 ctx 执行 apply(),
抓取它实际发出的每一条槽位注册,再逐条核对槽位名。还包含发布前必查的自包含断言 ——
包内 SOP 是否齐全、files/exports 有没有漏掉 skills。这类 bug 在本机永远复现不了,
只在别人机器上炸,所以只能靠断言钉住。
迭代边界
没有构建步骤。 lib/client.js 手写在 DSH 的浏览器模块格式里,改完直接生效:
| 改了什么 | 需要 |
|---|---|
lib/client.js |
刷新浏览器 |
lib/index.js、lib/skill.js、lib/state.js、lib/prompt.js、package.json |
重启 dsh web |
package.json 的依赖声明 |
重新 dsh plugin add |
只有真的动了依赖才需要重装。改代码不需要。
文件结构
package.json 包清单:dsh.bundle.patch + dsh.client 两个声明
cordis.patch.yml 插件行:装插件时 CLI 读它,把包接进 dsh.profile.bundles
lib/index.js 主机侧:HTTP 路由 /interviewer/api/{health,chat}
lib/prompt.js 系统提示词装配(纯 Node,可离线拼出来做断言)
lib/state.js 面试状态机(纯函数)
lib/skill.js 把 SOP 注册成运行时技能 + 定位包内 SOP 目录
lib/client.js 浏览器侧:悬浮窗、设置弹窗、状态条、语音输入
skills/interviewer/ 随包分发的面试 SOP(SKILL.md + references/ + score.mjs)
dev-check.mjs 插件自检
state-test.mjs 状态机单元测试
技术说明
它是怎么接到界面上的
DSH 的 Web 界面是一棵插槽(slot)树。插件不直接改界面,只能往别人已经声明的槽位里
加条目 —— register 到未声明的槽位会直接抛错。所以这不是「改 DSH」,是「插进 DSH
预留的座位」。
本插件只用一个座位:
| 槽位 | 类型 | 作用 |
|---|---|---|
shell.overlay |
list |
全框架浮层,渲染一个可拖拽的悬浮窗口 |
ctx.slots.inject(槽位名, 回调) 是延迟注册,等槽位被声明之后再注入,所以不用担心加载顺序。
为什么只有这一个入口:
| 槽位 | 为什么不用 |
|---|---|
main |
全屏接管中间区域,用户进得去出不来。体验太霸道,而且挡住主会话 |
sidebar.panellist |
注册成功(回读确认「账面 1 条,生效 1 条」),但侧边栏就是不渲染它。原因未定位,疑似 dsh-better-sidebar 介入那块区域的渲染 |
路由
| 端点 | 用途 |
|---|---|
GET /interviewer/api/health |
探测:路由挂上没有、模型解析到没有、SOP 从哪个目录读的 |
POST /interviewer/api/chat |
发对话,拿回复 |
浏览器 → 主机走一条普通 HTTP 路由,不走 DSH 的 Remote:Remote 要接 typert 协议 (host / client 各一份描述),而这里只需要一次「发过去、拿回来」。路由更直接, 出问题 curl 就能验。
额度这件事
推理(thinking)token 也计入 maxTokens。 所以这个数字不是「回复能有多长」,
而是「想 + 说总共能有多长」。
实测下来模型的思考长度波动极大 —— 同一段对话同一个额度,有时 3 秒答完,
有时思考 14 秒一个字都没吐就直接 max-tokens,面板上出现一个断掉的话轮。
所以有一层保护:一轮如果没有产出任何内容,自动把额度翻倍重试一次, 并追加一句「别想了直接说」。已经写出半句话的情况不重试 —— 那会得到一个前后不连贯的答案。
已知限制
- 只有右下角悬浮窗一个入口(原因见上)
- 评分报告不在面板里。评分通过 SOP 在主会话里跑
scripts/score.mjs得到, 面板目前只负责面试对话 - 简历要手动粘贴,没有拖放区;PDF 读不出字
shell.overlay是rootscope,浮层里拿不到sessionId, 所以面试对话不进入 DSH 的会话历史- 状态条上的话题名依赖模型偶尔吐出的标记,可能有一轮偏差(乐观前进的代价)
装插件之后 DSH 起不来?
见 docs/dev-notes.md。那不是这个插件的问题,是
dsh plugin add 会跑一次 pnpm install,扰动 DSH 的自定义原生插件加载器。
文档里有失败签名和官方退路。
No comments yet. Be the first to write one.