Metadata-Version: 2.5
Name: resume-flow
Version: 0.1.6
Summary: LangGraph 简历筛选应用 — 基于 LLM + 规则引擎的自动化候选人评估工具
Author-email: Chandler <275737875@qq.com>
License: MIT
Keywords: langgraph,llm,recruiting,resume,screening
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: langchain-core>=0.2.0
Requires-Dist: langgraph-checkpoint-sqlite>=2.0.0
Requires-Dist: langgraph>=0.2.0
Requires-Dist: openai>=1.0.0
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0.0
Requires-Dist: typer>=0.9.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# 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 声明）

---

## 目录

- [一、项目概述](#一项目概述)
- [二、架构说明](#二架构说明)
  - [2.1 模块结构](#21-模块结构)
  - [2.2 LangGraph 工作流](#22-langgraph-工作流)
  - [2.3 注册表驱动机制](#23-注册表驱动机制)
  - [2.4 双重验证机制](#24-双重验证机制)
- [三、开发指南](#三开发指南)
  - [3.1 环境搭建](#31-环境搭建)
  - [3.2 配置文件说明](#32-配置文件说明)
  - [3.3 CLI 参数说明](#33-cli-参数说明)
- [四、扩展教程 — 费曼学习法](#四扩展教程--费曼学习法)
  - [4.1 什么是节点](#41-什么是节点)
  - [4.2 新增节点（3 步）](#42-新增节点3-步)
  - [4.3 run_check 和 run_verify 在做什么](#43-run_check-和-run_verify-在做什么)
  - [4.4 删除节点（2 步）](#44-删除节点2-步)
  - [4.5 修改节点](#45-修改节点)
  - [4.6 Stage 参数说明](#46-stage-参数说明)
  - [4.7 常见坑](#47-常见坑)
  - [4.8 速查表](#48-速查表)
- [五、LLM 调用场景](#五llm-调用场景)
- [六、设计哲学](#六设计哲学)
- [七、run_check / run_verify 速查](#七run_check--run_verify-速查)
- [八、init 命令详解](#八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` 列表是整个系统的核心声明：

```python
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 的样板代码：

```python
# 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 环境搭建

```bash
# 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 配置）

```ini
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` 生成，只需保留想修改的配置项：

```yaml
# 覆盖目标公司
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 参数说明

```bash
# 初始化工作环境
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 会犯错，规则是兜底裁判。

代码里，一个节点就是**两个普通函数**：

```python
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`，填入逻辑：

```python
"""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`，加两处：

```python
# 顶部加 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 自动适配。

#### 验证

```bash
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 步）

1. 编辑 `registry.py`，删除 `Stage(name="title", ...)` 那一行和对应 import
2. 删除 `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 参数说明

```python
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` 支持两种写法：

```python
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`，无需改代码：

```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

```python
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

```python
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 命令详解

```bash
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` |
