我用 SKILL.md 做了一个简历生成器
同一份简历数据,第一次生成像后端,第二次生成像产品,第三次连“负责核心模块”这种废话都冒出来了。
我第一眼就不太信模型。
简历生成器这东西,最怕的不是生成慢,也不是样式丑,而是它太会“补”。候选人只写了“维护订单接口”,它能给你扩成“主导订单中台架构升级”。看起来顺眼,投出去就是坑。
所以我后来没把重点放在 prompt 上,而是单独写了一个 SKILL.md。
这个文件不是简历模板,更像一份现场规矩。哪些词不能用,哪些经历必须保留原事实,项目描述按什么顺序写,技术栈不能乱扩,成果没数字就别硬编数字,都写进去。
大概长这样:
# Resume Skill## 生成原则
- 不编造项目、公司、学校、时间
- 没有量化指标时,不主动补百分比、QPS、DAU
- 项目经历优先按:背景 -> 动作 -> 结果 写
- 技术栈只允许来自 user_profile.skills
- 禁止使用:精通、主导、赋能、闭环、抓手、深度参与
## Python 岗位偏好
- 强调脚本自动化、数据清洗、接口开发、日志分析
- 少写空泛协作,多写处理过什么脏数据、什么异常、什么边界
一开始我是把这些规则直接塞进 prompt。
后来发现不行。
prompt 一长,业务数据一多,模型就会挑着执行。尤其是禁用词、事实约束这种东西,很容易被后面的简历内容冲掉。所以我把 SKILL.md 当成一个固定输入,每次生成前先解析,再拼成更硬的任务描述。
代码没搞复杂,就一个读取器:
from pathlib import PathclassSkillBook:
def__init__(self, skill_file: str):
self.raw = Path(skill_file).read_text(encoding="utf-8")
self.rules = self._pick_rules()
def_pick_rules(self):
blocks = {}
current = None
for line in self.raw.splitlines():
line = line.strip()
ifnot line:
continue
if line.startswith("## "):
current = line.replace("## ", "").strip()
blocks[current] = []
continue
if current:
blocks[current].append(line.lstrip("- ").strip())
return blocks
defget(self, name: str):
return self.rules.get(name, [])
这里我没用什么 Markdown 解析库。不是不能用,是没必要。
SKILL.md 这类文件最好别写得太花,越像配置越好。标题一层,规则一行一条,后面排查问题也方便。
简历原始数据我用 JSON 放着,结构也尽量土一点:
profile = {
"name": "陈某",
"target": "Python 后端开发",
"skills": ["Python", "FastAPI", "MySQL", "Redis", "Pandas", "Linux"],
"projects": [
{
"name": "销售数据清洗平台",
"role": "后端开发",
"facts": [
"接入 Excel、CSV 两类文件",
"处理字段缺失、日期格式混乱、金额带中文单位的问题",
"提供导入任务查询接口",
"失败记录可下载"
]
}
]
}
这地方我踩过一次坑。
不要把原始经历写成大段自然语言。大段文字一进去,模型就会顺着原文润色,润着润着就开始“显得更厉害”。最好拆成事实点,生成时只允许围绕 facts 改写。
拼 prompt 的时候,我会把规则放在前面,用户事实放在后面,再单独加一段硬约束。
defbuild_resume_prompt(profile: dict, skill: SkillBook) -> str:
base_rules = "\n".join(f"- {x}"for x in skill.get("生成原则"))
job_rules = "\n".join(f"- {x}"for x in skill.get("Python 岗位偏好"))returnf"""
你是一个简历生成器,只能基于给定事实写简历。
通用规则:
{base_rules}
岗位规则:
{job_rules}
候选人数据:
{profile}
输出要求:
1. 使用中文
2. 不新增公司、项目、时间、指标
3. 不使用禁用词
4. 项目经历每条控制在 2 行以内
5. 输出 Markdown 简历
"""
.strip()我不太喜欢一上来就让它生成 Word。
先生成 Markdown,肉眼能看,程序也好检查。Word 是最后一步。否则样式、内容、结构混在一起,出了问题你都不知道该怪谁。
生成完还不能直接保存,得过一遍校验。
尤其是禁用词和技术栈。
FORBIDDEN = {"精通", "主导", "赋能", "闭环", "抓手", "深度参与"}defcheck_resume(text: str, profile: dict):
errors = []
for word in FORBIDDEN:
if word in text:
errors.append(f"出现禁用词:{word}")
allowed_skills = set(profile["skills"])
suspicious = ["Go", "Kubernetes", "Spark", "Vue", "Docker"]
for item in suspicious:
if item in text and item notin allowed_skills:
errors.append(f"疑似新增技术栈:{item}")
if"提升"in text and"%"in text:
errors.append("出现疑似编造量化结果")
return errors
这个校验很粗,但够用。
简历生成不是论文纠错,不需要一开始就搞一套复杂 NLP。先把最容易翻车的地方拦住:乱加技术栈、乱写指标、乱用大词。
有一次原始数据里只写了“用 Pandas 处理 Excel”,生成结果里冒出来一句:
基于 Spark 完成百万级数据清洗任务,显著提升处理效率。
这句看起来挺漂亮,但候选人压根没写 Spark,也没写百万级。
这种简历投出去,面试官追问两句就露馅。
所以后面我又加了一个重试逻辑。校验不过,就把错误塞回去,让它按错误修。
defgenerate_with_retry(client, profile, skill, max_round=2):
prompt = build_resume_prompt(profile, skill) last_text = ""
for i in range(max_round + 1):
text = client.generate(prompt)
errors = check_resume(text, profile)
ifnot errors:
return text
last_text = text
prompt = f"""
下面这份简历违反了规则,请只修正违规内容,不要新增事实。
违规点:
{errors}
原简历:
{text}
"""
.strip()raise ValueError(f"简历仍未通过校验:{check_resume(last_text, profile)}")
这里有个小判断。
重试次数别太多。超过两轮还过不了,通常不是模型问题,是你的输入数据太乱,或者 SKILL.md 规则互相打架。
比如一边要求“突出结果”,一边又不给任何结果事实。那模型肯定会忍不住补。
最后一步才是渲染。
我这里没上复杂模板,只把 Markdown 分段写进 docx。真正投递用的简历,样式越克制越好。花里胡哨的图标、进度条、技能评分,我一般都删。
from docx import Documentdefexport_docx(markdown_text: str, output: str):
doc = Document()
for line in markdown_text.splitlines():
line = line.strip()
ifnot line:
continue
if line.startswith("# "):
doc.add_heading(line[2:], level=1)
elif line.startswith("## "):
doc.add_heading(line[3:], level=2)
elif line.startswith("- "):
doc.add_paragraph(line[2:], style="List Bullet")
else:
doc.add_paragraph(line)
doc.save(output)
做完之后,整个流程就清楚了:
resume.json
↓
SKILL.md 规则读取
↓
生成 Markdown
↓
规则校验
↓
失败重试
↓
导出 docx
这个生成器最值钱的地方,不是“自动生成简历”。
自动生成很容易。
真正有用的是它不会乱写,或者至少乱写了能被拦下来。
SKILL.md 在这里像一个老审核员,脾气不太好,看到“精通”“主导”“提升 80%”这种没证据的词就直接打回。简历可以写得好看,但不能写得像另一个人。
后面我准备再加两个检查。
一个是时间线检查,比如工作经历不能重叠得太离谱。
一个是岗位匹配检查,比如投 Python 后端,就少写“熟练使用 Office”,多写接口、脚本、SQL、日志处理。
简历生成器做到最后,拼的不是模型多聪明,而是规则够不够硬。
模型负责把话写顺。
SKILL.md 负责把它按住。