Skip to content

Repository files navigation

Resume CLI

CI Python License

一个面向可解释性和可交付性的 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 输出]
Loading

核心设计:

  • parse 完全本地运行,不需要 AI,也不会上传文件。
  • extract/score 的真实模式只发送提取出的必要文本,不上传 PDF 文件本身。
  • Kimi 使用 OpenAI Python SDK 兼容接口、json_schema + strict 和 Pydantic 二次校验。
  • --mock 只替换 AI Provider,仍执行真实的文件解析、输入校验、评分和序列化。
  • stdout 只输出正文或 JSON;日志、警告和保存确认写到 stderr,便于管道处理。
  • 不内置 OCR,不假装支持无法可靠完成的扫描件识别。

更完整的取舍见 架构决策记录,实施过程见 从零到一计划

安装

方式一:uv tool(推荐)

安装 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 --version

uv 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 --help

方式二:pipx

pipx install "git+https://github.com/Mars13333/resume-cli.git@v1.0.1"
resume-cli --help

方式三:源码开发

git clone https://github.com/Mars13333/resume-cli.git
cd resume-cli
uv sync --all-groups --frozen
uv run resume-cli --help

方式四:Docker

Docker 是可选的隔离运行方式。选择 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 --mock

Windows 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 不会被复制进镜像。

配置 Kimi

真实 extractscore 默认使用 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、模型或网络。

命令说明

parse:本地 PDF 文本解析

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.txt

parse 没有 --mock,因为它本来就不调用 AI。--output 使用同目录临时文件和原子替换,避免只写出半个结果。

extract:AI 结构化提取

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 或空数组,不根据常识猜测个人信息。

score:JD 证据评分

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 提供语义判断:逐项判断四个维度下的内部标准,为每项返回 matchedpartialmissing,并准备好判断理由以及简历/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]
Loading

完整示例:94 分是怎样算出来的

下面使用仓库内置的中文合成简历和 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 的准确含义

Mock 只替换 AI 调用,不绕过任何输入和业务校验:

  • extract --mock 仍需要真实可解析的 PDF;
  • score --mock 仍需要 PDF 和非空 UTF-8 JD;
  • Mock/真实模式输出同一个 Pydantic Schema,不增加 mockprovider 字段;
  • 内置合成样例使用稳定 fixture,可用于录屏和 CI;
  • 其他文件使用保守回退,并在 stderr 明确提示它不是真实语义分析。

PDF 与 OCR 边界

“网上下载”还是“本地生成”不能判断 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 build

macOS/Linux 或装有 GNU Make 的环境可以使用快捷入口:

make sync
make check
make build
make docker-smoke

Makefile 只是上面 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
Loading

生成合成样例 PDF:

uv run python scripts/generate_sample_pdf.py

项目结构

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

License

MIT

About

可解释的 AI 简历 CLI:本地 PDF 解析、Kimi 结构化提取与证据驱动的 JD 匹配评分。

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages