AB
AiBoss
チュートリアル

Jev

チュートリアル

Jev 简历初筛打分系统搭建教程:用判别式模型替代 LLM 生成式评估

用 Next.js、Supabase 与 Jev 判别式模型搭建一套内部简历初筛打分系统:由招聘方用自然语言写下评分标准,模型返回带校准概率的分数与置信度,结果以可排序表格呈现,低置信度自动标记待人工复核。本文覆盖前置条件、数据模型、分步搭建流程、完整示例与已知限制。

招聘一个工程岗位,一周内收到三四百份简历并不罕见。逐份细读大约每份两分钟,光是把这些简历看完就要十一个小时,而这还发生在任何面试开始之前。现实中没有人真的逐份读完,HR 做的是快速分诊:扫一眼职位名称、工作年限、框架名,十五秒左右把简历分进「再看看」和「大概不行」两堆。问题是,读到第四十份时,注意力和第四份时已经不是一个量级,而真正合适的候选人,可能恰恰因为经历描述方式不在快速扫读的关键词里而被漏掉。

这套系统要替换的正是分诊这一步,而不是阅读本身。它由招聘负责人用自然语言写下评分标准,由判别式模型 Jev 对每份简历按标准逐项打分,返回带校准概率的数值与置信度,结果落进一张可排序、可筛选的表格。系统不会自动淘汰任何简历,它只负责把这一堆排好序,最终判断始终由人来做。本文面向已经会写 Next.js 应用、但不需要事先了解 Jev 或机器学习的开发者。

准备工作

在动手之前,先确认自己具备以下基础,并准备好对应的环境与账号。

知识前提

  • 熟悉 Next.js App Router:知道什么是服务端组件,大致清楚服务端动作在什么时机执行。整套搭建会同时用到这两者。
  • 能读懂 SQL 迁移文件即可,不需要手写。所有迁移都随仓库提供。
  • 不需要任何 Jev 或机器学习背景,后文会补齐所需概念。

环境与工具

  • Node 与 npm:使用 Node v24.21.0 及配套 npm。
  • Docker:需要处于运行状态。Supabase CLI 依赖它在本机跑起 Postgres、认证与存储。
  • Supabase CLI:用于本地拉起整套后端服务。全部组件都在本地运行,直到最后部署那一步才需要云端项目。
  • Claude Code:整套搭建由九个提示词驱动,每个提示词在一个全新的 Claude Code 会话里执行。换用其他编码代理也能跑通,只需小幅调整,但安装 TypeSafe 技能这一步是 Claude Code 专属的。
  • TypeSafe API 密钥:从 TypeSafe 控制台获取。Jev 按输入 token 计费,搭建与测试这套系统的花费在「几分钱」这个量级。具体价格与配额请以官网当前信息为准。
  • Vercel AI Gateway 密钥:可选。它只在一处用到——从简历文本里抽出候选人姓名和邮箱。跳过它,手工填写这两项即可。
  • Vercel 账号:仅在最后需要部署时才用得上。

为什么不直接用现成的筛选工具

市面上的筛选工具大致分三类,在动手之前值得先想清楚它们各自卡在哪里。

关键词与布尔过滤是多数申请人跟踪系统的默认能力:你挑词,它计数。候选人知道这一点,所以一份投 React 岗位的简历里 React 会出现六次。这种过滤衡量的是「为过滤器写作的熟练度」,几乎说明不了这个人能不能把东西做出来,而且会悄悄丢掉那些用别的措辞描述同样工作的强候选人。

匹配分是多数 ATS 厂商现在主推的升级项:模型把简历和职位描述比对,返回一个百分比。这个数字是真实的,也能排序,但标准不是你写的,你看不见它,也改不了它。当用人经理问「为什么这个人是 81 分、那个是 64 分」,得到的回答是「模型算的」——对一个每次开岗定义都在变的工程岗位来说,这不是一个能据以行动的答案。这类产品通常也是卖给几十人的招聘团队,而不是只有两个人的 HR 部门。

LLM 评估是最新的一类,也是最先尝试的一类:把简历和职位描述发给模型,拿回一段评语。评语写得不错,但一列段落是没法排序的。

真正想要的东西比这三类都更窄:由用人经理按岗位用大白话写下的标准;一个 HR 能拆开成这些标准、并据此争论的数字;以及足够便宜的评分,便宜到当经理改了主意、重新定义什么才算重要时,所有候选人能在几秒内重算一遍,而不是重读一遍。

操作步骤

第一步:理解要构建的东西

目标是一个内部招聘门户。HR 创建职位并设定重要标准,例如什么算「深度技术经验」、带人经历是否重要、需要什么级别。简历一份一份上传,每份都按这些标准打分。结果以可排序、可筛选的表格行呈现,每行显示总分、按各条标准的拆分、模型对自己答案的置信度,以及一个标记——当置信度低到需要人工复核时亮起。

技术栈如下:

  • Next.js(App Router)
  • Supabase:负责认证、Postgres 与文件存储
  • Tailwind CSS 与 shadcn/ui
  • TanStack Table:渲染申请列表
  • unpdf:从 PDF 中抽取文本
  • Vercel AI SDK 配合 AI Gateway:只用于一个很小的抽取任务
  • TypeSafe Jev:负责打分
  • Zod:所有输入处都做校验

第二步:理解打分为什么是判别问题

先想清楚招聘人员拿到一份已初筛的简历后会做什么:排序、筛选、和其余简历比较,然后仔细读排在前面的几份。这些动作每一步都需要一个数字或一个标签,而不是一段话。

第一版工具的做法是把每份简历和职位描述发给 LLM,要一段简短的书面评估。返回的内容有想法、很具体,往往比人写得还好,但没用——因为一列段落没法排序。HR 读了几份,点点头,然后回去继续打开 PDF。

要理解为什么修法不是「那就让它只给个数字」,需要知道语言模型到底怎么产出答案。

自回归生成。大语言模型是自回归模型,输出是一块一块产生的,每一块都通过回看此前所有内容来选定。这些块叫 token,一个 token 大致是一个词或词的一部分。提问时,模型并不是先算好整个答案再打印出来,而是先算出下一个 token 的概率分布,挑一个,追加到文本后面,再跑一遍整个流程挑下一个。一个 200 token 的答案,就是 200 次串行地穿过一个非常大的神经网络。这就是 LLM 用起来感觉慢的原因:延迟不是额外开销,而是工作方式本身的一部分;每个 token 都要完整过一遍模型,而这些过程无法并行,因为每一步都依赖上一步。这也是输出 token 比输入 token 贵的原因——输入是一次性处理的,输出是逐步生成的。

约束解码解决的是另一个问题。现代 LLM 提供结构化输出模式:给它一个 JSON schema,它保证返回一个能通过校验的对象。底层机制是约束解码——每一步生成时,会把会导致非法输出的 token 先屏蔽掉,再让模型选择。如果 schema 规定下一个必须是数字,模型就只能挑数字。这个做法确实有效,不必再用正则去解析模型输出,也不必在 JSON 坏掉时重试。

但要想清楚约束解码到底改变了什么。模型仍然在逐 token 生成字符串,延迟和成本一模一样。当你看到 "score": 7 时,模型并没有真的算出一个分数,它只是预测了 7 是此处最可能出现的 token——依据是简历、提示词,以及它在训练中学到的关于「评估」的一切。这个数字只是看起来像数字的文本。

生成出来的数字不是概率。这是关键区别。假设模型告诉你某候选人是十分制的 7 分,或者有 70% 的概率是合适人选。一个校准过的模型说这句话时有明确含义:在它评为 70% 的所有候选人里,大约 70% 最终确实是合适人选。这个数字是一次测量,可以据此行动——把阈值设在 60%,你大致知道自己接受了什么、拒绝了什么。

语言模型的 70% 不是这个意思。它的训练里没有任何东西把字符串「70%」和真实的 70% 概率联系起来。模型输出「70%」,是因为在训练数据里,类似的评估常常用这样的数字。它只是在模仿一种自信判断的写法。

实践中还有两点让情况更糟。采样:多数 LLM 的温度设置大于零,模型不总是挑最可能的 token,而是采样。同一份简历跑两次,可能一次 7 分、一次 8 分,尽管什么都没变。把温度设为零有帮助,但解决不了问题,因为这个数字从来就不是真实测量。没有共同尺度:给候选人 A 打分之后再给 B 打分时,模型看 B 的时候并不记得 A。每个 7 都是独立生成的,依据是模型当时正在比较的东西。表格里两个 7 看起来一样,其实不一样。一列看起来可比、实际不可比的数字,比完全没有数字更糟,因为人倾向于相信列里的东西。

真正搞垮第一版的就是这一点,而不是解析或延迟。分数在不同行之间含义不同,按它排序等于按随机噪声排序。

生成与判别。机器学习里有一个更早的区分正好描述了这个情况。生成式模型学习产出看起来像训练集的数据;判别式模型学习把输入归入一组固定类别,并为每个类别输出一个概率。LLM 是生成式模型,它的输出空间是所有可能的字符串。这既是它灵活的原因,也是它会幻觉的原因:没有任何东西约束它只能输出真实的字符串,或对应某个真实选项的字符串。它可以编造一条引用、一个函数、一项候选人资历,因为每个字符串都是合法输出。判别式模型的输出空间是固定的,它只能在给定选项之间分配概率,因此它给出的数字天然带有可解释的含义。

第三步:理解 Jev 的请求形状

打分由 TypeSafe AI 发布的 Jev 模型承担。与 GPT 或 Claude 不同,Jev 不生成任何文本。你把一份简历和一组带类型的问题发给它,它返回带校准概率的数值。不需要写提示词、不需要解析 JSON、不需要读段落,直接拿到代码可以使用的答案。

请求由三部分组成:

  • 输入文本:这里是简历的纯文本内容。
  • 问题集合:一组带类型定义的问题,每个问题对应一条评分标准。
  • 问题类型:Jev 支持三种问题类型,分别对应不同的答案形态。

三种问题类型覆盖了招聘场景里绝大多数判断需求:需要「是/否」的判断、需要在若干选项里选一个的分类判断、以及需要落在某个区间内的程度判断。每条标准用哪种类型,取决于这条标准本身想问什么——「是否具备带团队经验」是布尔判断,「资深程度属于哪一档」是分类判断,「技术深度如何」是程度判断。

Jev 的三个特性决定了它适合这个场景:置信度,每个答案都带模型自己的置信水平,低置信度可以触发人工复核;速度,判别式推理比逐 token 生成快得多;成本,按输入 token 计费,具体价格以官网当前信息为准。

同样重要的是 Jev 不是什么:它不是聊天模型,不能跟它对话;它不生成解释性文字,只给数值和标签;它不会替你做决定,只提供可排序、可拆解的信号。

第四步:设计应用结构与数据模型

应用围绕一条主线组织:一份简历从上传到出现在表格里的完整旅程。

数据模型需要承载以下实体:

  • 职位:包含职位名称、描述,以及该岗位的评分标准集合。标准由用人经理用自然语言写下,并逐条指定问题类型。
  • 候选人/申请:每份上传的简历对应一条申请记录,关联到某个职位。
  • 评分结果:每条申请针对每条标准产生一个答案、一个数值或标签、一个置信度。总分由各条标准的结果汇总而来。
  • 文件:原始 PDF 存在 Supabase 存储里,抽取出的纯文本存在数据库里供打分使用。

有几个设计决定值得单独说明。标准与结果分开存储,是为了让「经理改了标准」这件事变成一次重新打分,而不是一次重新上传。置信度必须落库,因为它是表格里那一列标记的依据,也是事后复盘「哪些行需要人看」的唯一凭据。总分不存成单一不可拆解的数字,而是保留各条标准的明细,这样 HR 才能把总分拆开、逐条争论。

安全模型上,这是一个内部工具:访问受认证保护,简历文件与候选人数据不对公网开放,所有读写都经过服务端。这些约束在本地开发时同样成立,不要因为图省事而绕过。

第五步:明确刻意不做的东西

范围控制是这套系统能跑通的前提。以下能力是刻意不做的:

  • 不自动拒绝任何简历。系统只排序,不做淘汰决定。
  • 不做候选人沟通、面试安排、offer 流程。
  • 不做多租户、不做对外注册。
  • 不做简历解析的复杂兜底——PDF 抽不出文本的情况按失败处理,而不是猜。

第六步:按提示词驱动的方式搭建

搭建过程由九个提示词组成,每个都在一个全新的 Claude Code 会话里执行。之所以用提示词而不是直接给代码,是因为这套流程的重点是让每一步的产出可验证:每个提示词对应一个明确的里程碑,跑完就能看到结果,而不是一口气生成一大堆需要自己排查的代码。

执行顺序上,先立起项目骨架与 Supabase 本地环境,再建数据模型与迁移,然后接入文件上传与 PDF 文本抽取,接着定义评分标准的数据结构与界面,之后接入 Jev 打分,最后做结果表格与置信度标记。每一步跑完都应当能独立验证,不要跳过验证直接进入下一步。

安装 TypeSafe 技能这一步是 Claude Code 专属的。换用其他编码代理时,需要手动完成对应的配置,其余提示词只需小幅调整。

第七步:接入打分与结果展示

打分环节的核心是把职位标准翻译成 Jev 的问题集合。每条标准对应一个问题,问题类型按标准性质选择。简历文本作为输入一并发送。返回的每个答案都带置信度,落库后用于两件事:计算总分,以及决定是否在表格里标记「需人工复核」。

结果表格用 TanStack Table 实现,支持按总分排序、按各条标准筛选。每一行展开后可以看到该候选人在每条标准上的答案与置信度。低置信度的行会被标记出来,提示 HR 优先人工查看。

一个完整示例

下面走一遍最小可用的流程,从创建职位到看到一行打分结果。

1. 启动本地环境。确认 Docker 正在运行,然后用 Supabase CLI 拉起本地服务:

supabase start
supabase db reset

第二条命令会应用仓库里的全部迁移,把数据模型建好。

2. 配置环境变量。在项目根目录的 .env.local 里填入 TypeSafe API 密钥,以及可选的 Vercel AI Gateway 密钥:

TYPESAFE_API_KEY=你的密钥
AI_GATEWAY_API_KEY=可选的密钥

如果跳过 AI Gateway,姓名和邮箱在后续步骤里手工填写即可。

3. 启动应用。

npm install
npm run dev

打开本地地址,用 Supabase 本地认证登录。

4. 创建一个职位并写下标准。例如一个后端工程岗位,可以写下三条标准:

  • 是否具备带团队或指导他人的经验(布尔判断)
  • 资深程度属于哪一档(分类判断)
  • 后端技术深度如何(程度判断)

每条标准都用大白话写,不要写成关键词列表——这正是这套系统相对关键词过滤的价值所在。

5. 上传一份简历。系统把 PDF 存进 Supabase 存储,用 unpdf 抽出纯文本,写入数据库。如果 AI Gateway 已配置,此时会自动抽出姓名和邮箱;否则手工填写。

6. 触发打分。服务端把简历文本和该职位的标准集合组装成 Jev 请求,拿到每个问题的答案与置信度,落库并汇总总分。

7. 查看结果。回到申请列表,新的一行出现,显示总分、各条标准的拆分、置信度,以及是否被标记为需人工复核。按总分排序,展开任意一行查看明细。

8. 改标准并重算。回到职位设置,修改某条标准的措辞或增删一条标准,然后对该职位的所有申请重新打分。这一步应当在几秒内完成,而不是重新读一遍简历——这正是判别式打分相对生成式评估的核心优势。

注意事项

以下限制来自实际运行这套系统时的观察,值得在采用前想清楚。

  • Jev 的能力边界在真实简历上会显现。判别式模型只能在给定选项之间分配概率,它不会告诉你标准本身写得不好,也不会替你发现标准之外的重要信号。标准的质量直接决定结果的质量。
  • 低置信度不等于低质量。置信度低只说明模型对自己的答案不确定,这通常意味着该简历在对应标准上表述模糊,或者标准本身有歧义。这类行需要人看,但不该被自动降权。
  • 不要把这套系统用在它不该用的地方。它替代的是分诊,不是阅读,也不是决策。任何形式的自动淘汰都超出了它的设计范围。
  • PDF 文本抽取有失败的可能。扫描件、排版异常的 PDF 可能抽不出可用文本。这类情况应当按失败处理并提示人工介入,而不是让模型在残缺文本上打分。
  • 成本与配额随用量变化。Jev 按输入 token 计费,搭建与测试的花费很小,但生产环境的实际开销取决于简历数量与标准条数。具体价格、配额与可用性请以官网当前信息为准。
  • 本地开发与部署是两回事。整套流程在本地跑通不需要云端项目,但部署到 Vercel 时需要相应的账号与配置,这一步的环境差异要提前考虑。

把标准交给用人经理、把排序交给判别式模型、把决定留给人,这套分工是整件事的关键。系统能做的只是让那一堆简历有一个可拆解、可争论、可重算的顺序。