Resume Matcher 如何运行提示词质量评估(pytest -m eval)?
【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters & more, locally with 100+ LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher
修改 tailoring 提示词之后,确定性的单元测试只能回答"管线逻辑是否正确",无法回答"这次提示词改动让定制后的简历变好还是变差了"。Resume Matcher 在apps/backend/tests/evals/下为此提供了一套两层评估(eval)harness:一层是无需 LLM 的结构化打分器,一层是调用真实 LLM 的 judge。本文说明如何运行这套评估、如何配置 LLM key,以及如何解读运行结果。
适用前提:你已在本地检出 Resume Matcher 仓库,并能从apps/backend目录使用uv run执行 Python 命令。
评估由哪两层组成
两层刻意分开,职责不同(见 evals README 与 测试策略文档 §3.1):
- 结构化打分器(scorers.py)——纯函数,不调用 LLM、不碰网络、不读磁盘,检查"无论 LLM 怎么措辞都必须成立"的不变量:
| Scorer | 检查内容 |
|---|---|
sections_preserved(original, tailored) | 原本有内容的顶层 section(工作经历、教育等)没有在校准中消失 |
no_fabricated_employers(original, tailored) | 定制后的工作经历中不存在原简历没有的公司名(返回空列表即无捏造) |
jd_keywords_present(tailored, keywords) | JD 关键词实际出现在定制后简历中的比例(0–1) |
is_valid_resume(data) | 结果仍能通过ResumeDataschema 校验 |
personal_info_unchanged(original, tailored) | 候选人身份信息块(personalInfo)未被改写 |
它们的测试在 test_scorers.py 中,且每个打分器都用"已知坏输入"验证过确实会触发(删掉 section 返回False、编造公司会被列出……),而不是永远返回"OK"。
- LLM-as-judge(test_tailoring_eval.py)——把一条 golden 定制简历加对应 JD 发给真实 LLM,按 relevance / truthfulness / formatting 三维度打分,返回
{"score": 1-5, "reasons": "…"},然后断言score >= 3。该测试标记为@pytest.mark.eval,只有按需运行,且使用开发者自己配置的 key/provider。
golden 数据(简历、JD、预期关键词、好/坏两个定制版本)是纯 Python 常量,位于 golden/cases.py 的GOLDEN_CASES列表中,无 I/O、无 LLM 调用。
前置条件
- 在
apps/backend目录下工作,用uv run执行 pytest。 - pytest、pytest-asyncio、httpx、respx 属于
pyproject.toml中的devoptional-dependencies。如果当前环境里uv run pytest找不到 pytest,先执行uv sync --extra dev装好 dev 依赖(pyproject.toml 的[project.optional-dependencies]一节)。 - 只跑结构化打分器不需要任何 LLM key。要真正触发 LLM judge,需要按跑应用本身同样的方式配置一个 provider/key:通过环境变量,或 Settings UI 写入
apps/backend/data/config.json。
key 的判定逻辑在测试的_needs_key()中:当"没有 api_key 且 provider 不是ollama/openai_compatible"时才视为无 key。也就是说本地自托管的ollama、openai_compatibleprovider 不配 key 也能跑 judge。
一个值得知道的机制:pyproject.toml的[tool.pytest.ini_options]里默认addopts带-m "not eval",所以平时跑全量测试时 judge 测试是被自动排除的。eval标记本身也在markers中声明(strict markers 模式下未声明的 marker 会直接报错)。
运行评估
从apps/backend目录执行:
cd apps/backend # 只跑结构化打分器 —— 无需 key,免费且快 uv run pytest tests/evals # 加上 LLM-as-judge —— 仅在配置了 key 时才有意义;无 key 时跳过(skip)而不是报错 uv run pytest tests/evals -m eval测试策略文档 §6 中给出的按需入口是等价的uv run pytest -m eval(作用于整个tests目录的 marker 选择)。两条命令的区别只在作用范围:前者限定在tests/evals,后者是全局 marker 选择。
结果如何判断
无 key 的干净运行:结构化打分器测试全部通过,唯一一条 judge 测试显示skipped(README 明确以此为正常现象,skip 原因是 "no LLM key configured; set one to run LLM-judge evals")。
配置无法读取时:如果
config.json损坏或不可读,judge 测试同样跳过而不是硬失败,skip 原因形如could not read LLM config (...)。配置了 key 时:judge 测试真正发出一次 LLM 调用(
max_tokens=512),断言链路为:返回是 dict → 含score字段 →1 <= score <= 5→score >= 3。若 LLM 给出的分低于 3,失败信息形如(断言消息为文档源码中的固定格式,分数与理由因模型而异):LLM judge scored the good tailoring below threshold: score=2, reasons='...'看到低于 3 分并不意味着代码坏了——judge 是非确定性的,这正是它"按需运行、不进默认门禁"的原因。测试策略文档明确:不要用非确定性的 eval 阻塞 PR。
结构化这一层的规模可供参考:Phase 5 记录为 31 条 scorer 测试 + 1 条受门禁的 judge 测试(测试策略文档 §5)。
扩展:新增一条 golden case
评估的覆盖面由GOLDEN_CASES决定。追加一条 case 时,按 cases.py 头部注释和 README 的格式,向列表追加一个普通 dict:
{ "name": "short_id", "original": { ... }, # master resume (ResumeData-compatible) "job_description": "…", # the target JD text "jd_keywords": ["…", "…"], # keywords the tailoring should surface "tailored_good": { ... }, # faithful tailoring — passes every scorer "tailored_bad": { ... }, # broken tailoring — must trip the scorers }README 给出的约束:
original与tailored_good必须对ResumeData有效(否则is_valid_resume失去意义),且jd_keywords中的每个词必须真的出现在tailored_good里——结构化测试断言关键词覆盖率为完美的 1.0;tailored_bad要故意违反至少一条不变量(删 section、编造雇主、改写候选人姓名),这样 scorer 测试才能持续证明"检测真的在工作";- 只追加,不重写既有 case。test_scorers.py 中的参数化测试(
@pytest.mark.parametrize("case", GOLDEN_CASES, ...))会自动拾取新 case,无需改测试代码。
追加后直接重跑uv run pytest tests/evals即可验证新 case 被纳入且通过。
限制
- judge 层消耗一次真实模型调用,结果非确定,且"无 key 就跳过"是设计行为——它永远不会在没有 key 的环境里发起未受控的真实调用(
_needs_key()是测试体的第一行)。 - 结构化打分器只覆盖 tailoring 阶段的不变量(section 保留、不捏造雇主、JD 关键词命中、schema 有效、身份块不变),不评估文案质量;质量维度完全依赖 judge 层。
- eval 与确定性测试是两层不同的东西,仓库的本地
pre-push门禁只跑确定性套件,eval 按需运行,不进 CI 门禁。
【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters & more, locally with 100+ LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考