Skip to content

ethanyandev/resume-parser-poc

Repository files navigation

resume-parser-poc

把简历 PDF 解析成结构化数据,并自动生成一份人可读的 HTML。电子版直取文字层、扫描件自动 OCR;用多模态 LLM「看图」保真,规则校验防幻觉。

Parse résumé PDFs into structured data + a readable HTML report. Born-digital via the text layer, scanned via an OCR fallback; multimodal-LLM reading with rule-based grounding to fight hallucination.

一个端到端的简历解析小工具:输入 PDF → 输出结构化字段(JSON)+ 可读 HTML。强调三件事:尽量 1:1 不丢内容、防幻觉/张冠李戴、每步可拆可重放

特点

  • 📄 电子版 / 扫描件都能处理:有文字层直接读;没有就自动走 OCR 兜底。
  • 🖼️ 图像为真源:版面再怪(双栏 / 侧边栏 / 伪粗体重影 / CJK 乱码)也按「人眼看到的」来,靠模型理解而非写死正则——不挑格式。
  • 🧷 grounded 抽取:每条经历带 evidence,逐字命中原文才算数,低置信标红走人工。
  • 🧱 结构化到细节:公司/部门分离、项目带公司、每段经历的「职责 / 成果 / 技术栈 / 亮点」分组保留、schema 外章节(自我评价 / 荣誉…)兜底。
  • 🔌 可插拔 OCR:默认用多模态 LLM,可切到自建 PaddleOCR-VL(零业务改动)。
  • 🧾 全程留痕 + 单步重放:每步产物落盘,换模型/prompt 只改一处。

快速开始

git clone https://github.com/ethanyandev/resume-parser-poc
cd resume-parser-poc
python -m venv .venv && .venv/bin/pip install -r requirements.txt

cp .env.example .env          # 填 OPENAI_BASE_URL / OPENAI_API_KEY / OPENAI_MODEL

.venv/bin/python samples/make_sample.py                    # 生成一个示例简历(虚构数据)
.venv/bin/python run.py samples/sample_resume.pdf --open   # 跑全流程,浏览器打开结果

需要一个 OpenAI 兼容、且支持图片输入(多模态) 的模型;默认 gpt-5.4-mini,改 OPENAI_MODEL 即可换。 跑完后 artifacts/<doc_id>/resume.html 就是给人看的可读结果。

用法

# 全流程(扫描件会自动插入 OCR 步)
.venv/bin/python run.py 你的简历.pdf [--open]

# 单步 / 重放(不重复跑 PDF 解析、不重复调 LLM)
.venv/bin/python run.py 你的简历.pdf --step 1     # 取文本 + 渲染页图(本地,零成本)
.venv/bin/python run.py --doc-id <id> --step ocr  # 扫描件:转写页面图
.venv/bin/python run.py --doc-id <id> --step 2    # 重放结构化抽取
.venv/bin/python run.py --doc-id <id> --step 3    # 重放 source-span 校验(无 LLM)
.venv/bin/python run.py --doc-id <id> --step html # 重渲 HTML(无 LLM)

# 批量:多份简历 -> 各自 HTML + 一张汇总对比页
.venv/bin/python batch.py a.pdf b.pdf c.pdf

同步 HTTP 接口

启动服务:

.venv/bin/uvicorn resume_poc.api:app --host 0.0.0.0 --port 8000

健康检查:

curl http://127.0.0.1:8000/healthz

解析简历:

curl -X POST http://127.0.0.1:8000/internal/resumes/parse \
  -H "Authorization: Bearer $RESUME_API_TOKEN" \
  -F "file=@你的简历.pdf" \
  -F "use_vision=true" \
  -F "include_source_text=true" \
  -F "compat=talentos_agent" \
  -F "include_html=false" \
  -F "trace_id=caller-trace-id"

如果未设置 RESUME_API_TOKEN,接口不会校验 Authorization,仅适合本地调试。响应里返回 request_id / doc_id / resume / verify / source / mode / usage / cost_usd;include_source_text=true 时返回用于抽取/校验的原文文本;compat=talentos_agent 时额外返回 compat.talentos_agent.markdowncompat.talentos_agent.parsed_json, 用于 talentos-agent 保持现有 parse_resume tool 输出契约。include_html=true 时额外返回 渲染后的 HTML 字符串。

服务默认限制上传 PDF 不超过 RESUME_MAX_UPLOAD_MB=10、页数不超过 RESUME_MAX_PAGES=20;超限会返回稳定错误码 file_too_largetoo_many_pages

Docker:

docker build -t resume-parser-poc .
docker run --rm -p 8000:8000 --env-file .env resume-parser-poc

MCP 接口

除 HTTP API 外,也可以用 MCP stdio 暴露给 agent 工具平台:

.venv/bin/python -m resume_poc.mcp_server

MCP 运行依赖 mcp Python 包,需要 Python 3.10+;默认 Dockerfile 使用 Python 3.12 运行环境。

MCP tool 名称为 parse_pdf_resume,支持传本地 pdf_path,也支持传 pdf_base64 + filename。返回结构与 HTTP /internal/resumes/parse 一致,默认包含 compat.talentos_agent.markdowncompat.talentos_agent.parsed_json

管线(每步可独立重放)

模块 做什么 用 LLM?
1 extract_text.py PDF→阅读顺序 markdown(pymupdf4llm)+ 渲染每页 PNG;无文字层→标记 needs_ocr
ocr ocr.py 仅扫描件:页面图→阅读顺序文本(可插拔 provider)
2 extract_fields.py 一次结构化抽取 + 严格 schema;默认把页面图 + 文字层一起喂多模态模型
3 verify.py 每条 evidence 必须逐字命中来源文本(电子版=文字层 / 扫描件=OCR 文本),否则标红
html render_html.py 渲染成可读 HTML(resume.html),每条带「已核 / 待核」徽章

产物落在 artifacts/<doc_id>/(页面图在 pages/、OCR 文本 step_ocr.json、可读输出 resume.html),LLM 调用全量留痕在 artifacts/<doc_id>/ai_calls.jsonl

图像为真源(为什么不挑格式)

对电子版简历,渲染出来的页面图才是「内容真相」:无论简历用什么工具生成,人眼看到的永远是干净的、每样东西只出现一次。文字层是 PDF「怎么写」的副产物,可能被排版方式搞坏(有的 PDF 用重复描字做粗体,导致文字层整块重复;CJK 还常出现 Kangxi 部首等兼容码点)。所以:

页面图 = 内容真相(按图中出现次数,每项只输出一次);文字层只当拼写参考;凡文字层里有、图里没有的重复/乱码一律忽略。

这是个交给模型判断的通用原则,不针对任何具体格式——遇到新写法的 PDF 通常无需改代码。

扫描件 OCR 兜底(可插拔)

无文字层时 step1 标 needs_ocr,自动插入独立 ocr 步把页面图转成文本,再走正常的 grounded 抽取。

  • vlm(默认):用多模态模型逐页转写,走统一 capability 层 + 全量留痕。
  • paddleocr_vl(槽位):ocr.py 里的 adapter,POST 页面图到自建 PaddleOCR-VL 服务,设 OCR_HTTP_URL 后启用(服务端映射待接)。OCR_PROVIDER=paddleocr_vl 切换。
.venv/bin/python samples/make_sample_scanned.py            # 造一个图片版(无文字层)PDF
.venv/bin/python run.py samples/sample_resume_scanned.pdf  # 自动走 ocr→抽取→校验

配置(环境变量)

变量 说明 默认
OPENAI_BASE_URL OpenAI 兼容端点 https://api.openai.com/v1
OPENAI_API_KEY API key
OPENAI_MODEL 多模态模型名 gpt-5.4-mini
RESUME_USE_VISION 0=纯文本臂(更省,但不抗文字层污染) 1
OCR_PROVIDER vlm / paddleocr_vl vlm
OCR_HTTP_URL paddleocr_vl 服务端点
AI_TIMEOUT / AI_MAX_RETRIES 单次调用超时(秒)/ 重试次数 300 / 3
AI_PRICE_IN / AI_PRICE_OUT 成本估算(每 1M token,留痕用) 0.15 / 0.60
RESUME_ARTIFACT_CLEANUP_ENABLED 是否启用本地产物自动清理 1
RESUME_ARTIFACT_RETENTION_DAYS 请求产物保留天数 7
RESUME_CLEANUP_INTERVAL_SECONDS 清理扫描间隔(秒) 3600

工程化设计

  1. 统一接入:gateway.call(capability=...),业务不碰模型名;能力(文本/视觉/OCR)都在 config.py 注册,换模型/网关只改这一处。
  2. 全量留痕:artifacts/<doc_id>/ai_calls.jsonl(request_id / trace_id / model / prompt_version / 输入快照 / 原始输出 / 延迟 / token / 成本 / n_images)。
  3. 可拆可重放:每步 fixed in/out,产物落盘,单步可独立重跑(--step 1/ocr/2/3/html)。
  4. 结果有兜底:source-span 规则校验 + confidence,低置信走人工;HTML 每条带「已核/待核」徽章。
  5. 改动可回归:每能力独立 prompt 版本;tests/ 是回归集(pytest -q,含 NFKC / 多模态 payload / OCR provider / HTML 渲染转义)。
  6. 降级:无文字层→可插拔 OCR 兜底;LLM 失败→重试后由上层降级。

生产清理与链路日志

服务默认在启动时及之后每小时清理一次超过 7 天的请求产物。原始 PDF、页面图、 解析 JSON、HTML 和包含 PII 的原始 AI 调用记录作为同一个请求组一起删除:

RESUME_ARTIFACT_CLEANUP_ENABLED=1
RESUME_ARTIFACT_RETENTION_DAYS=7
RESUME_CLEANUP_INTERVAL_SECONDS=3600

生产链路日志以单行 JSON 输出到标准输出,由日志平台采集。使用响应中的 request_id 可以串起请求接收、上传、PDF 提取、OCR、模型调用、校验、HTML 渲染、 响应和清理事件。标准输出只记录耗时、状态、稳定错误码、页数、模型、Token 用量和 成本等元数据,不记录文件名、简历正文、Prompt、模型原始输出或密钥。

需要核查原始输入输出时,可在 7 天内查看 artifacts/<request_id>/ai_calls.jsonl;该文件包含 PII,访问权限应与简历原件一致。

测试

.venv/bin/python -m pytest -q

隐私与密钥

  • .env(密钥)与 artifacts/(简历 PII、留痕)已在 .gitignore,不会被提交
  • 务必用 git 推送、不要打包整个目录,否则 .env 会被带走。
  • 简历内容会发送给你配置的 LLM 服务;生产环境请改用自有网关并做 PII 脱敏。

Roadmap

  • 扫描件 OCR 兜底(可插拔 provider,vlm 默认已跑通)
  • paddleocr_vl HTTP adapter 的服务端映射
  • 完整性校验:用图当真相、让模型当 critic 列出「图里有但没抽到」的内容,量化 1:1
  • OCR 文本 vs 原图的二次保真校验
  • 字段 schema 按需细化、work/education 加 location
  • PII 脱敏、批量并发

License

MIT


这是一个 POC / 学习项目,欢迎 issue 与 PR。注意:LLM 抽取并非 100% 可靠,关键场景请配合 evidence/confidence 做人工复核。

About

简历 PDF → 结构化数据 + 可读 HTML;电子版直取文字层、扫描件自动 OCR,多模态 LLM 读图 + source-span 规则校验防幻觉

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors