Resume Flow
基于 LangGraph 的简历筛选应用,使用 LLM + 规则引擎双重验证机制自动化评估候选人。
核心特性
- 五维评估体系:姓名有效性、目标公司经历、学历毕业年份、软件技术岗位、技术方向
- 双重验证机制:每个 LLM 判断后紧跟规则引擎交叉验证,冲突时以规则为准
- 技术方向重试循环:第二个 LLM 独立审核,不一致时自动重试(最多 3 次)
- 断点续传:基于 SqliteSaver 的 Checkpoint 持久化,中断后可从断点恢复
- 多格式输出:JSON 单文件结果 +
_summary.json汇总 + Rich 终端美化 + Excel 报表 - YAML 配置:
config_default.yaml内置默认值,config.yaml用户覆盖,Prompt 文件独立管理 - 注册表驱动:
STAGES列表声明评估维度,graph/finalize/output/cli 自动适配 - 极简扩展:新增评估维度只需 3 步(写 prompt + 写节点 + 加 Stage 声明)
目录
- 一、项目概述
- 二、架构说明
- 三、开发指南
- 四、扩展教程 — 费曼学习法
- 五、LLM 调用场景
- 六、设计哲学
- 七、run_check / run_verify 速查
- 八、init 命令详解
- 九、常见修改场景
一、项目概述
Resume Flow 是一个自动化简历筛选工具,面向技术招聘场景。它读取 JSON 格式的简历文件,通过 LangGraph 构建的状态图工作流,依次对候选人进行五个维度的评估,最终判定是否符合条件并为合格候选人生成招聘邮件。
设计理念:LLM 擅长理解语义但可能产生幻觉,规则引擎确定性强但无法处理模糊表述。两者结合,取各自所长,实现高准确率的自动化筛选。
输入:resume_json/ 目录下的 JSON 简历文件
输出:
resume_json_eval/<filename>.json— 每份简历的详细评估结果resume_json_eval/_summary.json— 汇总统计resume_json_eval/eval_resume.xlsx— Excel 报表
二、架构说明
2.1 模块结构
resume_flow/
├── __init__.py # 包初始化
├── __main__.py # python -m resume_flow 入口
├── cli.py # Typer CLI (init / run 子命令)
├── config.py # 日志初始化、.env 加载、常量定义
├── config_default.yaml # 内置默认配置(随包分发)
├── config_loader.py # YAML 配置加载、深度合并、Prompt 渲染
├── state.py # LangGraph State 定义 (ResumeState)
├── graph.py # LangGraph StateGraph 构建(从 STAGES 自动组装)
├── llm.py # LLM 客户端封装 (OpenAI 兼容)
├── output.py # JSON / Excel / Rich 终端输出
├── prompts/ # Prompt 模板文件(自动发现)
│ ├── check_name.txt
│ ├── check_company.txt
│ ├── check_grad_year.txt
│ ├── check_title.txt
│ ├── check_tech.txt
│ ├── verify_tech.txt
│ └── generate_email.txt
└── nodes/ # 工作流节点
├── __init__.py # 节点函数导出
├── _template.py # 新增节点模板(copy 即用)
├── llm_helpers.py # LLM 工具函数 + run_check + run_verify
├── registry.py # Stage 注册表 + STAGES + make_initial_state
├── eval_logic.py # 评估逻辑 (is_all_pass / get_fail_reasons)
├── load_resume.py # 加载简历 JSON
├── check_name.py # 姓名判断 + 验证
├── check_company.py # 公司判断 + 验证
├── check_grad.py # 毕业年份判断 + 验证
├── check_title.py # 职位判断 + 验证
├── check_tech.py # 技术方向判断 + 验证 (含重试循环)
├── finalize.py # 汇总结果
└── generate_email.py # 生成招聘邮件
各模块职责:
| 模块 | 职责 |
|---|---|
config_loader.py |
YAML 加载、深度合并、Prompt 自动发现与渲染、Settings 单例管理 |
config_default.yaml |
内置默认配置:目标公司、学历年份、关键词、LLM 参数、路径等 |
nodes/llm_helpers.py |
call_llm_json / call_llm_text LLM 调用封装;run_check / run_verify 节点模板函数 |
nodes/registry.py |
Stage dataclass + STAGES 列表 + make_initial_state + build_check_outputs |
nodes/eval_logic.py |
is_all_pass / get_fail_reasons / has_llm_error 等评估逻辑 |
graph.py |
从 STAGES 自动组装 LangGraph 工作流 |
2.2 LangGraph 工作流
graph.py 从 STAGES 列表自动构建工作流:
load_resume
│
▼
check_name ──→ verify_name
│
▼
check_company ──→ verify_company
│
▼
check_grad_year ──→ verify_grad_year
│
▼
check_title ──→ verify_title
│
▼
check_tech ──→ verify_tech ──┬── confirmed ──────────→ finalize
│── conflict_need_retry ─→ check_tech (重试)
│── failed_after_retry ──→ finalize
│
▼
finalize ──────┬── 全 pass ──→ generate_email ──→ END
│── 任一 fail ─→ END
节点和边从 STAGES 列表自动生成,无需手动注册。唯一需要特殊处理的是 tech 维度的重试循环。
2.3 注册表驱动机制
nodes/registry.py 中的 STAGES 列表是整个系统的核心声明:
STAGES = [
Stage(name="name", check=check_name, verify=verify_name,
fail_reason="姓名无效", extra_fields={"detected_name": ""}),
Stage(name="company", check=check_company, verify=verify_company,
fail_reason="不在目标公司", extra_fields={"company_matched": ""}),
Stage(name="grad", state_prefix="grad_year", check=check_grad_year, ...),
Stage(name="title", check=check_title, verify=verify_title, ...),
Stage(name="tech", check=check_tech, verify=verify_tech, ...),
]
STAGES 驱动以下自动化:
- graph.py:自动添加 check/verify 节点和连边
- make_initial_state():自动生成初始 state 字段
- build_check_outputs():自动生成输出 dict
- eval_logic.py:自动计算 all_pass / fail_reasons
- finalize.py:自动汇总所有维度结果
2.4 双重验证机制
每个评估维度由一对节点完成:check(LLM 判断)→ verify(规则验证)。
- LLM 擅长理解模糊表述,但可能产生幻觉
- 规则引擎基于确定性逻辑,不会幻觉但无法处理复杂语义
- 两者交叉验证,冲突时以规则为准
run_check 和 run_verify 辅助函数消除了 check/verify 的样板代码:
# check 函数只需写"发什么消息给 LLM"和"看哪个字段判断通过"
def check_name(state: dict) -> dict:
return run_check(
stage_name="name",
state=state,
user_message=f"候选人姓名: {state['name']}\n...",
pass_field="is_valid_name",
extra={"detected_name": "detected_name"},
)
# verify 函数只需写"规则是什么",比较逻辑由 run_verify 处理
def verify_name(state: dict) -> dict:
rule_pass = ... # 规则判断
return run_verify(prefix="name", llm_result=..., rule_pass=rule_pass, ...)
三、开发指南
3.1 环境搭建
# 1. 创建虚拟环境
conda create -n resume_flow python=3.10 -y
conda activate resume_flow
# 2. 安装项目
pip install -e .
# 3. 初始化工作环境(生成 .env + config.yaml + 创建输入/输出目录)
resume-flow init
# 4. 编辑 .env,填入真实 API Key
# 5. 将简历 JSON 文件放入 resume_json/ 目录
# 6. 运行
resume-flow run
resume-flow init --with-prompts可同时复制 prompt 模板到本地prompts/目录,方便自定义。
3.2 配置文件说明
.env(LLM API 配置)
LLM_API_KEY: sk-your-real-api-key
LLM_API_URL: https://api.openai.com/v1/chat/completions
MODEL: gpt-4o-mini
config_default.yaml(内置默认配置)
随包分发,包含:目标公司、学历年份、学位关键词、职位关键词、邮件解析规则、LLM 参数、路径配置等。请勿直接修改。
config.yaml(用户覆盖配置)
由 resume-flow init 生成,只需保留想修改的配置项:
# 覆盖目标公司
target_companies:
- Apple
- Google
# 覆盖学历年份
bachelor_years: [2015, 2016, 2017, 2018]
配置加载顺序:config_default.yaml ← config.yaml(深度合并)← .env(LLM 连接参数)
Prompt 文件
resume_flow/prompts/*.txt 中的文件被自动发现,无需在配置中注册路径。用户可通过 config.yaml 的 prompts: 段覆盖指向自定义 prompt 文件。
3.3 CLI 参数说明
# 初始化工作环境
resume-flow init
resume-flow init --with-prompts # 同时复制 prompt 到本地
# 运行
resume-flow run
resume-flow run -i /data/resumes -o /data/results
resume-flow run --config /path/to/config.yaml # 指定配置文件
resume-flow run --fresh # 清除旧结果重新运行
resume-flow run --no-checkpoint # 内存模式,不持久化
| 选项 | 说明 |
|---|---|
--input / -i |
输入目录(默认 <项目根>/resume_json) |
--output / -o |
输出目录(默认 <项目根>/resume_json_eval) |
--excel / -e |
Excel 文件路径 |
--config |
指定用户配置文件路径 |
--fresh |
清除输出目录和 checkpoint,从头运行 |
--no-checkpoint |
内存模式,不使用 SqliteSaver |
四、扩展教程 — 费曼学习法
4.1 什么是节点
用工厂流水线理解:
简历进厂 → 质检员A检查 → 质检员B检查 → ... → 全合格则发邮件 → 出厂
节点 = 质检员。每个质检员做两件事:LLM 判断 + 规则验证。LLM 会犯错,规则是兜底裁判。
代码里,一个节点就是两个普通函数:
def check_xxx(state): # 质检员A:调 LLM 做判断
return run_check(...)
def verify_xxx(state): # 质检员B:用规则验证 A 的判断
return run_verify(...)
run_check 和 run_verify 是辅助函数,帮你处理调 LLM、处理错误、比较结果等重复劳动。你只写"判断什么"和"规则是什么"。
节点涉及 3 个文件:
prompts/check_xxx.txt ← 工作手册(告诉 LLM 怎么判断,自动发现,不用注册路径)
nodes/check_xxx.py ← 工作能力(函数实现,用 run_check/run_verify 简化)
nodes/registry.py ← 花名册(一行注册,自动排工位、发工资、做考核)
关键:
registry.py是"HR"——你把员工登记到花名册,HR 自动安排流水线位置、初始化工作表、汇总考核结果。
4.2 新增节点(3 步)
以新增"地理位置检查"为例。
第 1 步:写工作手册
创建 resume_flow/prompts/check_location.txt:
你是简历分析专家。判断候选人是否在目标城市工作。
返回JSON:
{
"is_target_city": true/false,
"matched_city": "匹配到的城市",
"reason": "判断理由"
}
放到 prompts/ 目录即可,系统自动发现,不需要在任何配置文件中注册路径。
第 2 步:写工作能力
复制模板 resume_flow/nodes/_template.py 为 resume_flow/nodes/check_location.py,把 xxx 替换成 location,填入逻辑:
"""Node: 地理位置判断 + 验证"""
from __future__ import annotations
from resume_flow.config_loader import get_settings
from resume_flow.nodes.llm_helpers import run_check, run_verify
def check_location(state: dict) -> dict:
"""LLM 判断是否在目标城市"""
resume = state["resume_data"]
return run_check(
stage_name="location",
state=state,
user_message=f"所在地: {resume.get('location', '')}\n请判断是否在目标城市。",
pass_field="is_target_city",
extra={"location_matched_city": "matched_city"},
)
def verify_location(state: dict) -> dict:
"""规则验证:直接搜城市名"""
cfg = get_settings()
target_cities = cfg["location"]["target_cities"]
resume = state["resume_data"]
all_locs = [resume.get("location", "")] + [
exp.get("location", "") for exp in resume.get("experience", [])
]
rule_matched = [c for c in target_cities if c in " ".join(all_locs)]
rule_pass = len(rule_matched) > 0
return run_verify(
prefix="location",
llm_result=state["location_check_result"],
rule_pass=rule_pass,
rule_reason=f"匹配={rule_matched}" if rule_matched else "无匹配",
extra={"location_matched_city": ", ".join(rule_matched) if rule_matched
else state.get("location_matched_city", "")},
)
就这么多。 run_check 帮你调 LLM、处理错误、解析结果。run_verify 帮你比较规则和 LLM 的结论。你只写"发什么消息"和"规则是什么"。
第 3 步:登记到花名册
编辑 resume_flow/nodes/registry.py,加两处:
# 顶部加 import
from resume_flow.nodes.check_location import check_location, verify_location
# STAGES 列表末尾加一行
STAGES = [
# ... 现有的 5 个维度 ...
Stage(
name="location",
check=check_location,
verify=verify_location,
fail_reason="不在目标城市",
extra_fields={"location_matched_city": ""},
),
]
state_prefix省略了——默认等于name,即"location"。只有grad维度需要显式指定state_prefix="grad_year"。
完成。 graph/finalize/output/cli 自动适配。
验证
python -m py_compile resume_flow/nodes/check_location.py
ruff check resume_flow/nodes/check_location.py resume_flow/nodes/registry.py
4.3 run_check 和 run_verify 在做什么
run_check:LLM 判断的标准流程
你调用 run_check(...) 时,它帮你做 5 件事:
1. 查注册表,找到 stage_name 对应的 state_prefix
2. 从 prompts/ 加载 check_{stage_name}.txt 作为 system 消息
3. 调用 LLM,传入 system + user_message
4. 如果 LLM 报错,返回 fail + 错误原因
5. 如果正常,从 LLM 结果提取 pass_field 判断通过,提取 extra 中的额外字段
你只需要告诉它:检查什么维度、发什么消息、看哪个字段判断通过、提取什么额外字段。
run_verify:规则验证的标准流程
你调用 run_verify(...) 时,它帮你做 1 件事:
比较 rule_pass 和 llm_result:
一致 → confirmed(规则和 LLM 达成共识)
不一致 → conflict(以规则为准,覆盖 LLM 的结论)
你只需要告诉它:前缀是什么、LLM 说了什么、规则说了什么、额外返回什么字段。
有特殊需求的维度不用这两个辅助函数。
check_company和check_grad的 check 有自定义逻辑,verify_grad和verify_tech有特殊处理(unknown / 重试循环),保留原写法。辅助函数是帮忙的,不是强制的。
4.4 删除节点(2 步)
- 编辑
registry.py,删除Stage(name="title", ...)那一行和对应 import - 删除
nodes/check_title.py
完成。 流水线自动重新连接。
4.5 修改节点
| 需求 | 改哪个文件 | 改什么 |
|---|---|---|
| 改 LLM 指令 | prompts/check_xxx.txt |
直接编辑文本,无需改代码 |
| 改规则参数 | config.yaml |
覆盖默认配置,无需改代码 |
| 改判断逻辑 | nodes/check_xxx.py |
编辑函数体,所见即所得 |
| 改流水线顺序 | nodes/registry.py |
调整 STAGES 列表中 Stage 的顺序 |
注意:
tech维度有重试循环,目前假设它是最后一个维度。如果要把其他维度放到 tech 后面,需检查graph.py中 tech 的条件边处理。
4.6 Stage 参数说明
Stage(
name="location", # 维度名(日志、prompt 文件名、失败原因标识)
check=check_location, # LLM 判断函数
verify=verify_location, # 规则验证函数
fail_reason="不在目标城市", # 失败原因(字符串或函数)
extra_fields={ # 额外 state 字段及默认值
"location_matched_city": "",
},
# state_prefix="location", # 省略则默认等于 name,只有 grad 需要显式指定
)
state_prefix 自动生成三个 state 字段:{prefix}_check_result、{prefix}_check_reason、{prefix}_check_verified,加上 extra_fields 中的额外字段。初始值由 make_initial_state() 自动设置。
fail_reason 支持两种写法:
fail_reason="不在目标公司" # 字符串:固定原因(大多数维度)
fail_reason=_grad_fail_reason # 函数:动态原因(接收 state,返回 str 或 None)
4.7 常见坑
| 坑 | 症状 | 修复 |
|---|---|---|
| 忘记注册到 STAGES | 节点函数写了但运行时没执行 | 在 STAGES 列表加一行 Stage(...) |
| prompt 文件名不匹配 | run_check 报错找不到 prompt |
文件必须叫 check_{stage_name}.txt |
| config 段名与代码不匹配 | KeyError: 'location' |
YAML 段名与 cfg["xxx"] 键名保持一致 |
| pass_field 写错 | 所有结果都是 fail | pass_field 必须与 prompt 中要求的 JSON 字段名一致 |
4.8 速查表
| 操作 | 步数 | 改哪些文件 |
|---|---|---|
| 新增节点 | 3 | prompts/check_xxx.txt + nodes/check_xxx.py + nodes/registry.py |
| 删除节点 | 2 | nodes/registry.py(删一行)+ nodes/check_xxx.py(删文件) |
| 改 Prompt | 1 | prompts/check_xxx.txt |
| 改规则参数 | 1 | config.yaml |
| 改判断逻辑 | 1 | nodes/check_xxx.py |
| 改流水线顺序 | 1 | nodes/registry.py(调整 STAGES 顺序) |
五、LLM 调用场景
大模型在 7 个节点中被调用,分为两类:
| # | 节点 | 调用方式 | 温度 | 返回 JSON 键 | 用途 |
|---|---|---|---|---|---|
| 1 | check_name |
chat_json |
0.1 | is_valid_name |
判断姓名是否有效 |
| 2 | check_company |
chat_json |
0.1 | is_target_company |
判断是否在目标公司工作过 |
| 3 | check_grad_year |
chat_json |
0.1 | is_target_year |
判断毕业年份是否在目标范围 |
| 4 | check_title |
chat_json |
0.1 | is_software_tech |
判断当前职位是否为软件技术岗 |
| 5 | check_tech |
chat_json |
0.1 | is_tech |
综合判断是否技术方向 |
| 6 | verify_tech |
chat_json |
0.1 | is_consistent |
LLM 自我审核技术判断 |
| 7 | generate_email |
chat |
0.3 | — | 生成招聘邮件(唯一文本生成) |
规律:判断类调用使用
chat_json+ 低温度(0.1),保证确定性;文本生成使用chat+ 中温度(0.3),允许创造性。
两层错误处理
| 层 | 机制 | 行为 |
|---|---|---|
| LLMClient 内部 | 指数退避重试 | 对 RateLimitError/APITimeoutError 自动重试(最多 3 次,等待 2^n 秒) |
call_llm_json/text 包装 |
异常降级 | 捕获所有异常,返回 {"error": ...} 或 "[LLM调用失败]" 标记,不中断流程 |
多模型支持
切换模型只需修改 .env,无需改代码:
LLM_API_KEY: sk-xxx
LLM_API_URL: https://api.deepseek.com/v1/chat/completions
MODEL: deepseek-chat
支持任何 OpenAI 兼容服务(OpenAI、DeepSeek、本地 Ollama 等)。
六、设计哲学
LLM + 规则引擎双验证
LLM 判断(语义理解) ←→ 规则引擎验证(确定性判定)
↓ ↓
覆盖模糊边界 覆盖明确规则
↓ ↓
└──── 冲突时以规则为准 ────┘
↓
最终判定结果
- 为什么不用纯 LLM? LLM 会幻觉、不稳定、有成本
- 为什么不用纯规则? 规则无法覆盖语义边界("技术 Lead" 是技术岗还是管理岗?)
- 冲突处理:规则引擎是"底线",LLM 是"增强",冲突时信任确定性更高的规则
全量评估,不短路
五个维度全部判断,不因前面失败而跳过。便于完整诊断、数据积累和断点续传。
错误降级,不中断
单次 LLM 失败不中断批处理。失败简历标记为 error,可后续重试。
结构化输出优先
所有判断类调用要求 LLM 返回 JSON({"is_xxx": true, "reason": "..."}),布尔字段直接用于逻辑判断,reason 提供可解释性。
七、run_check / run_verify 速查
run_check
run_check(
stage_name="xxx", # 维度名,决定用哪个 prompt 和 state 前缀
state=state, # LangGraph state
user_message="...", # 发给 LLM 的 user 消息
pass_field="is_xxx", # LLM JSON 中判断通过的字段名
extra={"field": "json_key"}, # 额外提取字段 {state字段: json字段}
)
run_check 做的事:查注册表找 state_prefix → 加载 prompt → 调 LLM → 处理错误 → 解析结果 → 返回 state 更新。
run_verify
run_verify(
prefix="xxx", # state 前缀
llm_result="pass", # LLM 判断结果
rule_pass=True, # 规则引擎判断是否通过
rule_reason="...", # 规则引擎的原因
extra={"field": "value"}, # 额外返回字段
)
run_verify 做的事:比较 rule_pass 和 llm_result → 一致返回 confirmed → 不一致返回 conflict(以规则为准)。
有特殊需求的维度不用这两个辅助函数。
check_company和check_grad的 check 有自定义逻辑,verify_grad和verify_tech有特殊处理(unknown / 重试循环),保留原写法。
八、init 命令详解
resume-flow init # 生成 .env + config.yaml + 创建目录
resume-flow init --with-prompts # 同时复制 prompt 模板到本地 prompts/
init 执行 4 步:
| 步骤 | 做什么 |
|---|---|
1. 生成 .env |
交互式输入 API Key / URL / Model |
2. 生成 config.yaml |
带注释的用户配置模板(目标公司、学历年份等高频项) |
| 3. 创建目录 | resume_json/(输入)+ resume_json_eval/(输出) |
| 4. 复制 prompts(可选) | --with-prompts 时复制 7 个 prompt 到本地 prompts/ |
九、常见修改场景
| 需求 | 修改文件 | 说明 |
|---|---|---|
| 修改目标公司列表 | config.yaml → target_companies |
无需改代码 |
| 修改学历年份范围 | config.yaml → bachelor_years 等 |
无需改代码 |
| 修改 LLM prompt | prompts/check_xxx.txt |
直接编辑文本文件 |
| 修改规则引擎逻辑 | nodes/check_xxx.py 的 verify_xxx |
所见即所得 |
| 修改技术重试次数 | config.yaml → tech_max_retry |
无需改代码 |
| 修改 LLM 模型/温度 | .env 或 config.yaml → llm |
无需改代码 |
| 新增评估维度 | 3 个文件 | 见第四章 |
| 删除评估维度 | 2 个文件 | registry.py 删一行 + 删 check_xxx.py |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file resume_flow-0.1.6.tar.gz.
File metadata
- Download URL: resume_flow-0.1.6.tar.gz
- Upload date:
- Size: 124.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/4.0.2 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad1beeae2bbab2433e6ebffc4c39aac30d19fd27df29922b9c7cd207c6e3d3d8
|
|
| MD5 |
38d093cf613ee96e5961430350ab21f3
|
|
| BLAKE2b-256 |
97fa019cdcf04877c21b084be2223306065c37a755221c31e30a490f07a860f2
|
File details
Details for the file resume_flow-0.1.6-py3-none-any.whl.
File metadata
- Download URL: resume_flow-0.1.6-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/4.0.2 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a80c7eae9bd539f5334b4ba073b62f82561924b1d4d3185b1be1a51439ccd2ca
|
|
| MD5 |
7d5e3f3715dd01b849ef911233eb9045
|
|
| BLAKE2b-256 |
f4368f1054802aa9544b0df42b035ffbbc7c1994d42f31d7ee86fc6e158ff4da
|