一个面向可解释性和可交付性的 AI 简历命令行工具:从本地 PDF 提取文本,调用 Moonshot Kimi K3 输出结构化信息,并依据岗位描述生成有原文证据、可复核的匹配评分。
CLI 是 Command-Line Interface 的缩写,中文通常称为“命令行界面”。安装后可以直接在 PowerShell、Terminal 或其他终端中执行 resume-cli,它不是只能在 Windows 上运行的软件。
resume-cli parse examples/sample-resume.pdf
resume-cli extract examples/sample-resume.pdf --mock
resume-cli score examples/sample-resume.pdf --jd examples/sample-jd.txt --mock仓库中的中文简历、JD 和所有示例身份均为虚构数据,不包含真实个人信息;公开文档也不会记录原始需求方的真实名称。
这个项目不把大模型输出的一个数字直接当作评分。模型只负责识别四个固定维度的匹配状态、理由以及简历/JD 原文证据;程序验证引用确实存在,再按照公开权重确定性计算分数。因此相同的已验证分析一定得到相同分数,无法核实的模型证据不会获得分数。
flowchart LR
A[本地 PDF] --> B[文本层解析]
J[本地 JD] --> C[输入校验]
B --> D{Provider}
C --> D
D -->|Kimi| E[严格 JSON Schema 分析]
D -->|Mock| F[确定性离线 Fixture]
E --> G[原文证据校验]
F --> G
G --> H[Python 四维评分]
H --> I[加权 overall_score]
I --> O[统一 JSON 输出]
核心设计:
parse完全本地运行,不需要 AI,也不会上传文件。extract/score的真实模式只发送提取出的必要文本,不上传 PDF 文件本身。- Kimi 使用 OpenAI Python SDK 兼容接口、
json_schema + strict和 Pydantic 二次校验。 --mock只替换 AI Provider,仍执行真实的文件解析、输入校验、评分和序列化。- stdout 只输出正文或 JSON;日志、警告和保存确认写到 stderr,便于管道处理。
- 不内置 OCR,不假装支持无法可靠完成的扫描件识别。
先安装 uv,再从固定 Git tag 安装:
uv tool install --python 3.12 git+https://github.com/Mars13333/resume-cli.git@v1.0.1
resume-cli --help
resume-cli --versionuv tool install 会创建隔离环境并把 resume-cli 放入用户命令路径。安装完成后,不需要每次进入源码目录。
通常安装完成后即可直接使用。如果终端提示找不到 resume-cli,再执行 uv tool update-shell,重新打开终端后重试。
只临时运行、不保留安装:
uvx --from git+https://github.com/Mars13333/resume-cli.git@v1.0.1 resume-cli --helppipx install "git+https://github.com/Mars13333/resume-cli.git@v1.0.1"
resume-cli --helpgit clone https://github.com/Mars13333/resume-cli.git
cd resume-cli
uv sync --all-groups --frozen
uv run resume-cli --helpDocker 是可选的隔离运行方式。选择 Docker 后,宿主机不需要安装 Python 或 uv,但需要 Docker;镜像构建内部仍使用 uv 锁定依赖。
docker build --tag resume-cli:local .macOS/Linux Mock 示例:
docker run --rm --network none \
--volume "$(pwd)/examples:/data:ro" \
resume-cli:local \
score /data/sample-resume.pdf --jd /data/sample-jd.txt --mockWindows PowerShell Mock 示例:
$sampleDir = (Resolve-Path .\examples).Path
docker run --rm --network none `
--volume "${sampleDir}:/data:ro" `
resume-cli:local `
score /data/sample-resume.pdf --jd /data/sample-jd.txt --mock真实 Kimi 调用去掉 --network none 和 --mock,并在运行时传入环境配置:
docker run --rm --env-file .env \
--volume "$(pwd)/examples:/data:ro" \
resume-cli:local \
extract /data/sample-resume.pdf本地目录以只读方式挂载,镜像使用 UID 10001 的非 root 用户,.env 不会被复制进镜像。
真实 extract 和 score 默认使用 kimi-k3。在 Moonshot 开放平台 创建 Key,然后选择一种配置方式。
当前 shell 环境变量:
export MOONSHOT_API_KEY="your-key"Windows PowerShell:
$env:MOONSHOT_API_KEY = "your-key"或者在执行命令的当前目录创建 .env:
MOONSHOT_API_KEY=your-key配置优先级是“操作系统/当前进程环境变量 > 当前目录 .env”。仓库只包含 .env.example,.env 已忽略。Base URL 固定为 https://api.moonshot.cn/v1,避免误配;默认模型只在需要实验时通过 --model 覆盖:
resume-cli extract resume.pdf --model kimi-k3不提供 --api-key,避免密钥进入 shell 历史。Mock 模式不会读取 API Key、模型或网络。
resume-cli parse PDF [--full] [--output PATH] [--verbose]默认仅显示路径、页数、字符数和预览(最多前 40 行或 3000 字符),避免把整份简历铺满终端:
File: .../sample-resume.pdf
Pages: 1
Characters: 541
Preview:
陈默
...
查看完整文本:
resume-cli parse examples/sample-resume.pdf --full保存完整 UTF-8 文本:
resume-cli parse examples/sample-resume.pdf --output parsed.txtparse 没有 --mock,因为它本来就不调用 AI。--output 使用同目录临时文件和原子替换,避免只写出半个结果。
resume-cli extract PDF [--mock] [--model MODEL] [--output PATH] [--verbose]无需 Key 的完整演示:
resume-cli extract examples/sample-resume.pdf --mock输出:
{
"name": "陈默",
"phone": "+86 138 0000 0000",
"email": "chen.mo@example.com",
"city": "上海",
"education": [
{
"school": "示例大学",
"major": "计算机科学与技术",
"degree": "本科",
"graduation_time": "2020"
}
],
"skills": ["Python", "TypeScript", "FastAPI", "Vue.js"]
}真实模式:
resume-cli extract your-resume.pdf --output profile.json模型找不到的信息输出 null 或空数组,不根据常识猜测个人信息。
resume-cli score PDF --jd JD [--mock] [--model MODEL] [--output PATH] [--verbose]无需 Key 的完整演示:
resume-cli score examples/sample-resume.pdf --jd examples/sample-jd.txt --mock输出:
{
"overall_score": 94,
"skill_score": 100,
"experience_score": 90,
"education_score": 100,
"growth_score": 88,
"comment": "简历与岗位技术栈匹配度较高,并体现了生产交付、量化成果和持续技术输出……",
"interview_questions": [
"项目上线后,你具体负责过哪些运行维护工作?",
"处理耗时降低 40% 的数据是如何测量和对比的?",
"你如何安排开源项目中的版本发布、Issue 和长期维护工作?"
]
}score 直接使用完整简历文本和完整 JD,不先调用 extract,因为姓名/技能摘要会丢失项目深度、所有权、上线运维和量化结果等关键证据。
这里不是让 LLM 直接生成四个维度分数。更准确的职责划分是:
- LLM 提供语义判断:逐项判断四个维度下的内部标准,为每项返回
matched、partial或missing,并准备好判断理由以及简历/JD 原文短引用; - 代码固定评分规则:内部标准、内部权重和四个维度的总权重都写在代码中,模型不能修改;
- 代码校验并计分:确认引用能在输入原文中找到,再把状态映射为 100%、50%、0%,计算维度分和
overall_score。
也就是说,LLM 判定的是“必备技能覆盖”“项目应用深度”“上线运维迭代”等 14 个内部标准是否有充分证据,不直接决定最终数字。
overall_score = round_half_up(
skill_score × 30% +
experience_score × 40% +
education_score × 10% +
growth_score × 20%
)
overall_score 是主要综合指标,不是四项算术平均值;只有在同一份 JD 下才适合横向比较。使用时仍应同时阅读分项、结论和待核实问题。
| 维度 | 总权重 | 代码固定的内部标准及维度内权重 |
|---|---|---|
| 技能 | 30% | 必备技能覆盖 50%、项目应用深度 30%、加分/相邻技能 20% |
| 项目与交付经验 | 40% | 相关性 25%、所有权 20%、复杂度/设计 20%、上线运维迭代 20%、量化结果 15% |
| 教育与基础 | 10% | 学历/专业 70%、基础/证书/等价学习 30% |
| 持续成长与技术影响力 | 20% | 能力演进 25%、持续输出 30%、维护质量 25%、外部影响 20% |
公开字段沿用 experience_score,实际含义是“项目与交付经验”。每个内部标准只接受三种状态:
matched:100% 权重;partial:50% 权重;missing:0%;matched/partial必须有能在简历原文中找到的引用;“必备技能覆盖”“加分/相邻技能”“相关性”还必须有 JD 原文引用,否则同样按 0% 处理。
flowchart TD
A[Kimi 返回状态 + 原文短引用] --> B{引用存在于规范化输入?}
B -->|否| C[降为 missing / 0%]
B -->|是| D[matched 100% / partial 50%]
C --> E[计算四个维度]
D --> E
E --> F[30 / 40 / 10 / 20 加权]
F --> G[round half up]
G --> H[overall_score]
下面使用仓库内置的中文合成简历和 JD。表中的状态和原文依据由 Mock Provider 稳定模拟;真实模式下,这两部分由 LLM 返回。两种模式都由同一段代码校验证据并计算分数。
技能
| 内部标准 | 状态 | 原文依据(简历 / JD) | 维度内计算 |
|---|---|---|---|
| 必备技能覆盖 | matched |
Python、TypeScript、FastAPI / 熟练使用 Python 和 TypeScript |
50 × 100% = 50 |
| 项目应用深度 | matched |
基于 Kimi 兼容接口开发简历分析服务 / 大模型 API 集成和结构化输出 |
30 × 100% = 30 |
| 加分/相邻技能 | matched |
Docker、GitHub Actions / Docker 和 CI/CD |
20 × 100% = 20 |
skill_score = 50 + 30 + 20 = 100
项目与交付经验
| 内部标准 | 状态 | 原文依据(简历 / JD) | 维度内计算 |
|---|---|---|---|
| 相关性 | matched |
生产级人才服务 API / 生产级 API 交付 |
25 × 100% = 25 |
| 所有权 | matched |
架构设计与上线交付 / 从实现、上线到运行维护的完整责任 |
20 × 100% = 20 |
| 复杂度/设计 | matched |
设计原文证据校验和确定性评分机制 / 系统设计经验 |
20 × 100% = 20 |
| 上线运维迭代 | partial |
生产级人才服务 API 的架构设计与上线交付 / 从实现、上线到运行维护 |
20 × 50% = 10 |
| 量化结果 | matched |
将处理耗时降低 40% / 可量化的产品效果或性能优化成果 |
15 × 100% = 15 |
experience_score = 25 + 20 + 20 + 10 + 15 = 90
“上线运维迭代”只有 partial,因为简历证明了上线交付,却没有充分说明上线后的持续运维责任。
教育与基础
| 内部标准 | 状态 | 原文依据(简历 / JD) | 维度内计算 |
|---|---|---|---|
| 学历/专业 | matched |
计算机科学与技术,本科 / 计算机相关专业本科 |
70 × 100% = 70 |
| 基础/证书/等价学习 | matched |
计算机科学与技术 / 具备同等基础 |
30 × 100% = 30 |
education_score = 70 + 30 = 100
持续成长与技术影响力
| 内部标准 | 状态 | 原文依据(简历 / JD) | 维度内计算 |
|---|---|---|---|
| 能力演进 | partial |
高级全栈工程师,示例科技,2021 年至今;缺少更早阶段经历 |
25 × 50% = 12.5 |
| 持续输出 | matched |
每月更新技术博客 / 技术写作经验 |
30 × 100% = 30 |
| 维护质量 | matched |
自 2023 年持续维护 Resume Toolkit / 开源项目长期维护 |
25 × 100% = 25 |
| 外部影响 | matched |
1200 个 GitHub Star / 开源项目长期维护 |
20 × 100% = 20 |
growth_score = round_half_up(12.5 + 30 + 25 + 20) = 88
最后使用四个维度的总权重计算:
overall_score = round_half_up(
100 × 30% +
90 × 40% +
100 × 10% +
88 × 20%
)
= round_half_up(93.6)
= 94
因此,94 不是 LLM 直接给出的主观分数,而是 LLM/Mock 给出逐项语义状态和依据后,由代码验证并按公开公式计算出的结果。
GitHub Star 只影响“外部影响”这个小项,不能让整个成长维度直接满分。工具不联网核验开源项目或 Blog,只评价简历明确写出的事实;没有证据时表述为“简历未体现”,而不是“候选人不具备”。
面试问题不参与分数,用于核实能力缺口、证据不足、项目所有权、生产使用、量化结果和持续维护。问题限定 2–5 个,并排除与岗位无关的隐私或歧视性问题。
Mock 只替换 AI 调用,不绕过任何输入和业务校验:
extract --mock仍需要真实可解析的 PDF;score --mock仍需要 PDF 和非空 UTF-8 JD;- Mock/真实模式输出同一个 Pydantic Schema,不增加
mock或provider字段; - 内置合成样例使用稳定 fixture,可用于录屏和 CI;
- 其他文件使用保守回退,并在 stderr 明确提示它不是真实语义分析。
“网上下载”还是“本地生成”不能判断 PDF 是否需要 OCR,关键是文件有没有文本层:
- Word、WPS、浏览器或在线简历系统正常导出的 PDF,通常包含文本层,可以直接解析;
- 扫描仪生成、手机拍照、截图拼接或把图片另存为 PDF,通常只有像素,没有可提取文字;
- 最简单的判断方法:用 PDF 阅读器尝试选择并复制一段正文。能得到正常文字,通常不需要 OCR;只能框选整张图片,则需要 OCR;
- 加密 PDF 也可能含文本层,但 v1 不接受加密文件。
遇到扫描版时,CLI 会提示先使用系统或第三方 OCR,重新导出带“可搜索文本层”的 PDF。v1 不内置 OCR,是为了避免额外模型、系统依赖、语言包、版面恢复和准确率承诺把一个可靠 CLI 变成不可控 Demo。
复杂双栏、图表、特殊字体和阅读顺序仍可能导致文本顺序不理想,这是当前已知限制。
| 退出码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 未预期内部错误 |
| 2 | 路径、文件类型、JD、编码或参数错误 |
| 3 | PDF 损坏、加密、无文本或疑似扫描件 |
| 4 | API 配置、认证、限流、超时或 AI 响应错误 |
| 5 | 输出文件写入错误 |
JSON/正文写到 stdout,因此可以安全管道处理:
resume-cli score resume.pdf --jd jd.txt --mock | jq '.overall_score'日志、Mock 警告和保存位置写到 stderr。--verbose 只记录文件名、页数、字符数、模型和错误分类,不记录简历/JD 原文或 API Key。
跨平台原始命令:
uv sync --all-groups --frozen
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytest --cov=resume_cli --cov-report=term-missing --cov-fail-under=90
uv buildmacOS/Linux 或装有 GNU Make 的环境可以使用快捷入口:
make sync
make check
make build
make docker-smokeMakefile 只是上面 uv/Docker 命令的项目内别名,不是新的依赖管理器。Windows 用户不需要安装 Make,直接使用 uv 命令即可。
CI 在以下组合执行同一套门禁:
- Ubuntu + Python 3.11;
- Windows + Python 3.12;
- macOS + Python 3.13;
- Ubuntu 额外构建 wheel/sdist 和非 root Docker 镜像,并断网执行 Mock smoke test。
flowchart LR
C[Commit / Pull Request] --> L[Ruff format + lint]
L --> M[mypy strict]
M --> T[pytest + coverage >= 90%]
T --> P[wheel + sdist]
T --> D[Docker build + offline smoke]
P --> R[v1.0.1 Release]
D --> R
生成合成样例 PDF:
uv run python scripts/generate_sample_pdf.pyresume-cli/
├── src/resume_cli/
│ ├── cli.py # Typer 命令和 stdout/stderr 边界
│ ├── pdf.py # 本地文本层 PDF 解析
│ ├── models.py # AI、内部领域和公开输出模型
│ ├── scoring.py # 证据校验与确定性评分
│ └── providers/ # Kimi 与 Mock Provider
├── tests/ # 单元、CLI、异常和公开样例契约测试
├── examples/ # 完全虚构的 PDF 与 JD
├── scripts/ # 可重复生成样例 PDF
├── docs/adr/ # 关键架构取舍
├── .github/workflows/ # 三平台 CI 与 tag Release
├── Dockerfile
├── Makefile
└── pyproject.toml
- 本地 PDF 文本提取、终端预览、完整输出和原子保存;
- 文件不存在、假 PDF、损坏、加密、空文本/扫描件检测;
- Kimi K3 严格 JSON Schema 结构化提取;
- 常见 Markdown JSON 围栏和前后包装文本修复;
- 证据驱动的四维评分、固定权重与面试问题;
- 无 Key 的确定性 Mock;
- 统一错误码、stderr 日志和敏感信息边界;
- Python 3.11–3.13 三平台 CI、90% 覆盖率门禁;
- wheel/sdist、uv tool、pipx、Docker 和 Makefile 工作流。
- 不支持 OCR、加密 PDF、DOC/DOCX、在线 URL 或图片输入;
- pypdf 对复杂双栏、图表、特殊字体的阅读顺序可能不理想;
- 真实 AI 结果仍可能受模型、输入质量和服务状态影响,结构正确不等于语义绝对正确;
- 不联网核验简历中的学校、公司、开源 Star 或 Blog;
- 评分是辅助理解工具,不应成为自动招聘决策或唯一筛选依据;
- v1 从 GitHub tag 安装,尚未发布到 PyPI,也不提供原生 exe/App。
不要提交真实简历、JD、原始需求方名称、.env、API Key 或包含个人信息的调试日志。公开材料统一使用“需求方”“示例组织”等通用称谓。安全问题请参阅 SECURITY.md,开发约定见 CONTRIBUTING.md。