Agent-study项目教程(02):智能简历结构化提取工具
一、理论部分
1. 为什么要“结构化输出”:从文本到可编排数据
在 Agent 开发中,模型输出如果只是自然语言文本,往往难以被程序可靠消费;而一旦把输出约束为结构化数据(如 JSON),就可以直接用于:
- 写入数据库/生成文件(JSON、CSV)
- 作为下一步工具调用的参数(函数调用、工作流编排)
- 做校验、做评估、做自动化测试
因此,“让模型稳定输出符合规范的数据结构”是从聊天走向可落地系统的关键一步。
2. 用 Pydantic 定义“目标数据结构”:让模型做填空题
实现结构化输出的核心不是“让模型自己随便写 JSON”,而是先明确你需要的数据结构,然后让模型按这个结构填写。
Pydantic 的价值在于:
- 用 Python 类型定义字段(必填/可选、列表、嵌套对象)
- 自动生成 JSON Schema(可作为约束条件写入提示词)
- 对模型输出进行校验(不合格就直接报错,便于定位问题)
在本项目中,我们定义了Education、WorkExperience、Resume三个模型,最终希望模型输出一个完整的ResumeJSON。
3. “双重约束”保证稳定:System Prompt + JSON Mode + 低温度
仅靠提示词约束,有时模型仍可能输出多余解释、Markdown 包裹或字段缺失。本项目采用三项组合策略来提高稳定性:
- System Prompt 明确输出要求:只输出纯 JSON,不要 Markdown,不要解释文本
- JSON Mode(response_format):让服务端强制返回 JSON 对象格式(尽量减少跑偏)
- temperature=0.0:信息抽取类任务追求稳定一致,一般将温度设为 0
此外,得到模型输出后还会再用 Pydantic 做一次校验,实现“生成—校验”的闭环。
二、实战部分
1. 项目目标
实现一个“简历结构化提取工具”,输入一份简历文本(例如resume.txt),输出一份结构化 JSON,字段包括:
- 基本信息:姓名、邮箱、电话
- 技能:技能列表
- 教育经历:学校/专业/学位/时间段
- 工作经历:公司/岗位/工作描述摘要
2. 运行准备
请确保本地已安装依赖,并配置好环境变量(不要把真实密钥写进代码或公开仓库):
依赖安装(示例):
pipinstall-rrequirements.txt环境变量(示例):
DEEPSEEK_API_KEYDEEPSEEK_BASE_URL
输入文件:将待解析简历放在项目目录下的resume.txt(项目已提供示例文件)。
3. 项目代码(完整实现)
下面给出项目的完整代码(核心逻辑完整,篇幅可控,便于直接运行):
importosimportjsonfromopenaiimportOpenAIfromdotenvimportload_dotenvfrompydanticimportBaseModel,FieldfromtypingimportList,Optional# 加载环境变量# 假设 .env 文件在项目根目录project_root=os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))load_dotenv(os.path.join(project_root,".env"))client=OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),base_url=os.getenv("DEEPSEEK_BASE_URL"),)# 1. 定义目标数据结构(Pydantic Model)classEducation(BaseModel):school:str=Field(...,description="学校名称")major:str=Field(...,description="专业")degree:str=Field(...,description="学位,如本科、硕士")year:str=Field(...,description="毕业年份或就读时间段")classWorkExperience(BaseModel):company:str=Field(...,description="公司名称")role:str=Field(...,description="职位")description:str=Field(...,description="工作描述摘要")classResume(BaseModel):name:str=Field(...,description="候选人姓名")email:Optional[str]=Field(None,description="电子邮箱")phone:Optional[str]=Field(None,description="联系电话")skills:List[str]=Field(...,description="技能列表")education:List[Education]=Field(...,description="教育背景列表")work_experience:List[WorkExperience]=Field(...,description="工作经历列表")defextract_resume_info(file_path:str):# 2. 读取简历文本try:withopen(file_path,"r",encoding="utf-8")asf:resume_content=f.read()exceptFileNotFoundError:print(f"错误: 找不到文件{file_path}")returnNoneprint(f"正在分析简历:{file_path}...")# 3. 构造 Prompt:输出必须严格遵循 Schemasystem_prompt=f""" 你是一个专业的简历解析助手。请从用户的简历文本中提取关键信息。 请严格按照以下 JSON 格式输出,不要包含任何 Markdown 格式标记(如 ```json ... ```),只输出纯 JSON 字符串。 目标 JSON 结构定义如下:{json.dumps(Resume.model_json_schema(),indent=2,ensure_ascii=False)}""".strip()messages=[{"role":"system","content":system_prompt},{"role":"user","content":resume_content},]# 4. 调用 API:JSON Mode + 低温度保证稳定try:response=client.chat.completions.create(model="deepseek-chat",messages=messages,temperature=0.0,response_format={"type":"json_object"},)result_json=response.choices[0].message.content# 5. 解析并验证:不符合结构就会抛错,便于调试与兜底resume_data=Resume.model_validate_json(result_json)# 6. 输出结构化结果print("\n提取成功。结构化数据如下:\n")print(json.dumps(resume_data.model_dump(),indent=2,ensure_ascii=False))returnresume_dataexceptExceptionase:print(f"\n发生错误:{e}")if"result_json"inlocals():print(f"原始返回内容:{result_json}")returnNoneif__name__=="__main__":current_dir=os.path.dirname(os.path.abspath(__file__))resume_path=os.path.join(current_dir,"resume.txt")extract_resume_info(resume_path)4. 如何运行
在项目根目录执行:
python stage_01_llm_basics/project_02_resume_extractor/extractor.py5. 运行结果示例
如果输入的resume.txt类似“张三 / 电话 / 邮箱 / 教育背景 / 工作经历 / 技能清单”这种结构,运行后你将得到类似如下的结构化输出(示例):
{"name":"张三","email":"zhangsan@example.com","phone":"138-0013-8000","skills":["Java","Python","Golang","C++","MySQL","Redis","MongoDB","Git","Docker","Kubernetes","英语","普通话"],"education":[{"school":"清华大学","major":"计算机科学与技术","degree":"本科","year":"2018.09 - 2022.06"}],"work_experience":[{"company":"字节跳动","role":"后端开发工程师","description":"负责推荐系统后端开发,优化接口响应时间;参与高并发方案设计;使用Golang重构部分服务提升吞吐。"},{"company":"腾讯","role":"后台开发实习生","description":"参与微信支付营销活动后台开发;编写自动化测试脚本提升测试效率。"}]}6. 项目总结
- 结构化输出是 Agent 工程化的前提:让模型输出变成程序可直接消费的数据。
- Pydantic 提供了“先定义结构,再生成约束,再做校验”的完整闭环,显著提升稳定性。
- System Prompt + JSON Mode + temperature=0.0 的组合适用于“信息抽取/格式化”任务,可作为后续工具调用参数生成的通用模板。
