尧图精选

Resume-Matcher 评测体系(Eval Harness)实战指南:用结构化 Scorer 与 LLM-as-Judge 度量简历 Tailoring 质量

🕒 发布时间:2026/9/11 9:29:21 📁 来源:尧图网络
Resume-Matcher 评测体系Eval Harness实战指南用结构化 Scorer 与 LLM-as-Judge 度量简历 Tailoring 质量【免费下载链接】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导读在 Resume-Matcher 后端中pytest确定性测试只能回答管线是否正确接通plumbing correct却无法回答这次 prompt 修改到底让定制的简历变得更好还是更差。这正是apps/backend/tests/evals/评测体系的用武之地它以两层刻意分离的架构——纯函数、零成本的结构化 Scorer 层与按需运行、调用真实模型的 LLM-as-Judge 层——为简历定制tailoring质量提供可量化、可回归的证据。读完本文你将掌握该评测体系的设计动机、五个结构化 Scorer 的判定逻辑、LLM 裁判的评分 Rubric 与 Key 门控机制、Golden Fixture 的编写规范以及如何在本仓库中实际运行和扩展它。一、为什么需要 Eval确定性测试的盲区docs/agent/testing-strategy.md第 3.1 节给出了全仓库测试策略的核心二分确定性测试Deterministic testLLM 被 mock断言代码在给定已知响应下的行为。速度快每次改动都跑回答管线是否正确。Eval真实或录制的LLM 调用按 Rubric 打分。非确定性、花费时间和金钱按需或定时运行绝不进入 PR 门禁回答这次 prompt 改动让输出变好了吗。该文档明确指出你无法用确定性测试回答prompt 改动是否有帮助反之也不该用非确定性 Eval 阻塞 PR。评测体系因此被设计成两层各自独立、职责分明其完整立项缘由记录在 测试策略文档Phase 5§3.2 的 prompt 链示意中也描述了每阶段的确定性层与 Eval 层的分工。二、第一层结构化 Scorer——确定性、免费、随处可跑结构化 Scorer 是 scorers.py 中的纯函数给定原始简历与定制后简历检查无论 LLM 如何措辞都必须成立的不变量。不调用 LLM、不访问网络、不读磁盘因此在常规测试套件中零成本运行是抵御prompt 改动破坏了某处回归的第一道廉价防线。Scorer检查内容sections_preserved(original, tailored) - bool定制过程中不得丢失任何原本有内容的顶层 section工作经历、教育等no_fabricated_employers(original, tailored) - list[str]定制后工作经历中出现在原始简历里没有的公司名——即被编造出来的雇主。空列表 真实jd_keywords_present(tailored, keywords) - floatJD 关键词实际出现在定制简历中的比例0–1大小写不敏感is_valid_resume(data) - bool结果仍然能通过ResumeData校验personal_info_unchanged(original, tailored) - bool候选人的身份块personalInfo逐字节不变2.1 Scorer 的底层判定逻辑从源码看五个 Scorer 的实现各有讲究sections_preserved依赖模块级常量_TRACKED_SECTIONS (summary, workExperience, education, personalProjects, additional)其中workExperience与education是定制绝不能丢弃的关键 section。它借助递归的_is_nonempty判断内容是否真实存在空字符串、空列表/字典、None乃至所有值都为空的字典例如全是空列表的additional块都视为空只有原本非空的 section 才被要求保留——原本为空的 section 保持为空是允许的scorers.py。no_fabricated_employers从workExperience逐条提取company字段strip 后非空才计入对原始与定制双方做大小写不敏感、去空白的比较返回出现在定制侧但不在原始侧的公司名列表以定制侧的原始大小写呈现并对重复项去重scorers.py。jd_keywords_present通过flatten_resume_text把整个简历字典递归拍平成一段小写文本summary、bullet、skills、自定义 section 全覆盖再做大小写不敏感的子串匹配keywords为空列表时直接返回 1.0scorers.py。is_valid_resume直接调用 Pydantic 的ResumeData.model_validate(data)捕获ValidationError后返回Falsescorers.py。ResumeData定义于 schemas/models.py其字段全部带默认值——test_scorers.py中专门有一个canary测试固化这一假设一旦未来某字段改为必填is_valid_resume({})会由True翻转成False并响亮地失败提醒 Scorer 的空即合法前提不再成立test_scorers.py。personal_info_unchanged对personalInfo块做字典整体相等比较缺省视为空 dict候选人的姓名、联系方式等身份信息绝不允许被定制改写scorers.py。2.2 反剧场验证Anti-Theater Prooftest_scorers.py的价值不在于测试通过而在于证明每个 Scorer 在已知坏输入上确实触发删掉一个 section →False编造一家公司 → 被返回改掉姓名 →False……这正是 测试策略文档 第 4 节所强调的反剧场检查——避免落入测试因错误原因通过passes for the wrong reason的陷阱。测试组织为多个 Test 类覆盖边界情形例如TestSectionsPreserved空 section 原本为空则不要求保留只改写 bullet 措辞仍视为保留成功。TestNoFabricatedEmployers大小写与空白差异 acme corp vsAcme Corp不算编造同一编造公司只列一次。TestJdKeywordsPresent全命中 1.0、零命中 0.0、部分命中按比例2/4 0.5、嵌套字段可被搜索到。TestPersonalInfoUnchanged改 email 会被标记改其他 section 不影响该 Scorer。三、第二层LLM-as-Judge——真实模型按 Rubric 打分确定性 Scorer 无法回答这份定制简历针对 JD 到底好不好。为此test_tailoring_eval.py 把一条 golden 定制简历连同其 JD 发送给真实 LLM请它按 Rubric 打分返回{score: 1-5, reasons: …}并断言score 3。3.1 裁判 Rubric 与 Prompt 构造内置裁判系统提示词_JUDGE_RUBRIC把 LLM 定位为严格但公正的技术招聘官从三个维度评分test_tailoring_eval.pyRELEVANCE相关性——简历是否突出了 JD 要求的技能/经验TRUTHFULNESS真实性——是否避免编造候选人历史未暗示的雇主、头衔或事实FORMATTING格式——是否连贯、结构良好、无明显瑕疵。并要求只返回 JSON 形式的{score: 整数 1-5, reasons: 一两句话}。_build_judge_prompt随后把 Rubric、JD 文本与定制简历的 JSONensure_asciiFalse, indent2拼装成一个完整 prompttest_tailoring_eval.py。3.2 Key 门控_needs_key()必须是第一行该测试有两条关键约束标记为pytest.mark.evalevalmarker 在 pyproject.toml 中声明且默认运行通过addopts [-m, not eval]将其排除——CI/快速运行永不触碰它。使用开发者自己配置的 key/provider经app.llm并且在无 key 环境下必须跳过而非报错。_needs_key()作为测试函数体的第一条语句执行保证未配置 key 时永不发起真实调用test_tailoring_eval.py。门控判定逻辑值得细读只有当既没有 api_key、且 provider 不属于本地/自托管ollama、openai_compatible时才视为无 key并跳过——这与整个后端的 key 解析策略一致。另外若config.json损坏不可读也会pytest.skip而非硬失败。这里的get_llm_config()定义于 llm.py它按top-level api_key api_keys[provider] env/settings的优先级解析 key并保留ollama/openai_compatible不向环境变量LLM_API_KEY回退的安全规则。3.3 一次真实的打分调用test_llm_judge_scores_good_tailoring_highly使用GOLDEN_CASES[0]调用complete_json(prompt, system_promptYou are an impartial resume-tailoring evaluator., max_tokens512, schema_typeenrichment)随后依次断言返回结果是 dict、包含score、1 score 5、且score 3test_tailoring_eval.py。其中schema_typeenrichment的选择有讲究——complete_json内部会用_appears_truncated做截断检测对enrichment这类小型自由 JSON 采用宽松的截断启发式llm.py避免把裁判这个小 JSON 误判为截断而触发不必要的重试。整个调用链路JSON mode、重试、超时计算、reasoning_effort 透传均由 complete_json 承担。四、如何运行在apps/backend目录下执行# 只跑结构化 Scorer —— 处处可跑无需 key免费且快 uv run pytest tests/evals # 加上 LLM-as-judge Eval —— 仅在配置了 key 时才有意义 # 无 key 时跳过不报错 uv run pytest tests/evals -m eval干净的 keyless 运行结果是Scorer 测试全部通过唯一的 judge 测试被skipped。要真正行使裁判需要像运行应用一样配置 provider/key环境变量或 Settings UI →data/config.json然后加-m eval重跑。注意默认uv run pytest不带-m因为addopts -m not eval自动排除 judge 测试全量确定性套件与评测体系的运行差异详见 测试策略文档 第 6 节。五、添加 Golden FixtureEval 的数据基石Golden Fixtures 以GOLDEN_CASES列表的形式存放在 golden/cases.py每条是一个普通 dict{ name: short_id, original: { ... }, # master resume (ResumeData-compatible) job_description: …, # 目标 JD 文本 jd_keywords: […, …], # 定制应呈现的关键词 tailored_good: { ... }, # 忠实的定制——通过所有 Scorer tailored_bad: { ... }, # 有缺陷的定制——必须触发 Scorer }5.1 编写规范让original与tailored_good对ResumeData合法is_valid_resume才有意义并确保每个jd_keywords条目都真实出现在tailored_good中结构化测试断言完美的 1.0。让tailored_bad故意违反至少一条不变量——删掉一个 section、编造一个雇主或改写姓名——使 Scorer 测试持续证明检测能力。追加而非改写既有用例。test_scorers.py中的参数化测试pytest.mark.parametrize(case, GOLDEN_CASES, idslambda c: c[name])会自动拾取新用例。5.2 仓库中的两个真实用例当前仓库已内置两个黄金场景golden/cases.pybackend_engineer_platform_role后端工程师 Jane DoePython/FastAPI/Docker/AWS/PostgreSQL/Redis 技能栈应聘 TechCorp 高级后端岗。tailored_good只把 JD 关键词Python、FastAPI、Docker、Kubernetes、AWS、microservices、CI/CD织入已经成立的事实——如把 Acme 的 monolith→microservices 迁移明确为deployed on Kubernetestailored_bad则改姓名、清空并替换工作经历为编造雇主 Globex Industries、清空教育。data_analyst_to_data_engineer数据分析师 Carlos ReyesSQL/Python/Tableau/pandas转向 DataFlow 数据工程岗。tailored_good在不发明工具的前提下把既有 SQL/Python 与数据质量工作重构为 ETL/pipeline 语境tailored_bad用编造的 DataFlow Systems 替换真实雇主并改写姓名。两个用例的关键设计细节tailored_bad中的personalInfo用{**_CASE1_ORIGINAL[personalInfo], name: John Smith}这种 dict 展开方式保留其余字段、仅篡改姓名确保personal_info_unchanged精确命中jd_keywords中的data quality、data modeling属于多词关键词验证了flatten_resume_text的子串匹配能力。test_scorers.py的TestGoldenCasesStructural对两个用例做端到端断言good 用例必须全数通过所有 Scorerbad 用例必须被抓住至少违反一条规则且具体断言每个 bad fixture 都编造了雇主并改了姓名test_scorers.py。六、在整条 Tailoring 管线中的位置从 测试策略文档 第 3.2 节的 prompt 链可以看出该 Eval 覆盖的环节upload → parse_resume_to_json → extract_job_keywords → generate_skill_target_plan → verify_skill_target_plan (verify 纯函数确定性门禁) → generate_resume_diffs [strategy: keywords | nudge | full] → apply_diffs (纯函数) → render_resume_pdf对每一阶段确定性层检查与措辞无关的结构性不变量合法 JSON/schema、不编造雇主与日期、保留每个 section、JD 关键词实际出现在输出中、diff 路径可解析大多数prompt 改动破坏了什么的回归在此被免费捕获Eval 层则用黄金(resume, job_description)fixtures 以真实模型运行该阶段按 Rubric 打分跨时间、跨 prompt 改动追踪质量。Phase 5 完成后统计为 31 个 Scorer 测试 1 个带门控的 judge 测试测试策略文档 第 5 节。七、小结两层 Eval 的分工哲学结构化 Scorer是免费的护城河纯函数、无依赖、处处可跑捕捉大多数确定性回归测试用例以坏输入必须触发的方式证明其真实性。LLM-as-judge是质量的最后裁决调用真实模型按三轴 Rubric 打分通过-m eval按需运行用_needs_key()保证无 key 环境下绝不发生未受控的真实调用永远不进入 keyless CI 门禁。Golden Fixtures是两层共用的数据基石追加式维护good/bad 成对设计让检测能力始终有据可依。这一体系与 测试策略文档 的确定性 vs Eval二分一脉相承是 Resume-Matcher 衡量 prompt 工程质量的核心工具。扩展它只需三步在 golden/cases.py 追加一条 fixture必要时在 scorers.py 增加新的纯函数不变量然后在 test_scorers.py 中补齐好输入通过、坏输入触发的反剧场测试。【免费下载链接】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),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →