news 2026/9/3 7:49:38

结构化输出实战:用JSON Schema约束LLM解析简历文本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
结构化输出实战:用JSON Schema约束LLM解析简历文本

如果你正在做一个简历助手项目,大概率经历过这种场景:用户把写好的简历文本粘进来,模型读完之后,吐出一大段排版混乱、字段随意、中英混用的自然语言。前端拿到这段内容没法直接渲染成表单,数据库也没法按字段入库,后端的同学只能被迫写一堆正则去猜“这段到底是项目经历还是工作经历”。此时你最先怀疑的往往是模型能力不够,但真正的问题,很可能出在“输出”这件事本身没有约束。

这正是结构化输出(Structured Output)要解决的痛点:不是让模型“说得更聪明”,而是让模型“按合同交货”。把交货格式定义为一份 JSON Schema,让模型在生成阶段就遵守这份约束,再配合 Pydantic 之类的校验工具做二次兜底,整条数据链路就从“文本清洗地狱”变成了“接口对接”。这个思路在简历助手这类强结构化场景里,价值会被放大得极其明显。

这篇文章会围绕“简历助手 + LLM 大模型”这个项目实战,讲清楚为什么需要结构化输出、JSON 结构化输出如何落地、实体类怎么设计、接入 LLM 时有哪些真正的坑,以及生产环境里应该怎么把这套能力工程化。无论你是后端开发、算法工程师,还是正在做 AI 应用的前端同学,读完都应该能动手跑通一个最小可用版本。

1. 这篇文章真正要解决的问题

先看一个真实到不能再真实的场景。你在做简历解析产品,用户上传的简历格式千奇百怪:有的是一段纯文本,有的是从 PDF 里复制出来的乱序段落,有的甚至带着特殊符号和换行。你调用大模型,想让模型“理解”简历里的信息,结果返回的内容长这样:

我叫张三,5年Java开发经验,求职意向是后端工程师。 工作经历:2019-2021在某某公司做Java开发,2021-2023在某某公司做高级开发。 项目经历:负责过某某电商系统,订单模块,用Spring Cloud实现,亮点是接口性能优化。

这段内容人类看着没问题,但对程序来说就是灾难。前端要怎么渲染?数据库的表结构怎么对齐?你说“工作经历”是哪个字段?项目名和项目描述之间连个稳定的分隔符都没有。哪怕你让模型“用 JSON 输出”,模型也可能输出experiencework_experience工作经历三种不同的字段名。

我们要解决的问题,就是把“模型自由发挥的输出”变成“符合业务契约的输出”。这个契约就是 JSON 和 JSON Schema。模型只负责抽取和整理内容,字段结构由开发者在实体类里定义,输出由校验层兜底。结构化输出改变的不仅是返回格式,而是整个下游系统的开发方式:数据可以直接入库、直接进表单、直接对接人才库系统。

什么样的读者最该读这篇文章?如果你是下面三类人之一,这篇文章会非常对胃口:

  • 正在做简历解析、简历助手、人才库、HR 系统相关项目的开发者,被大模型“自由文本”输出折磨过。
  • 已经接触过 OpenAI、DeepSeek 等大模型 API,但只在用prompt控制格式,没有用过response_format、JSON Schema、Pydantic 校验这类正经方案。
  • 后端技术栈是 Java,关心 Spring AI 里“结构化输出如何定义实体类”这个具体问题,想先建立完整认知再落地。

2. 结构化输出的核心概念与适用场景

结构化输出,简单说就是让大模型按照预先定义的格式生成结果。最常见的形式是 JSON:模型只输出一个合法 JSON 对象,字段名、字段类型、嵌套关系都按开发者的定义来。

它的实现通常由三层配合完成:

第一层,Prompt 约束。在系统提示词里写明“只输出 JSON,不要解释,不要 Markdown 代码块标记”,这是最粗糙也是最重要的一层。第二层,模型能力约束。很多模型 API 提供response_format={"type": "json_object"},或者更严格的 JSON Schema 约束,让模型在解码阶段就按指定格式生成。第三层,程序校验约束。用 Pydantic、Jackson、类似工具把模型输出解析成实体类,校验不通过就重试或报错。

为什么要三层都做?因为任何一层都可能失效。模型不见得完全遵守 prompt,JSON 模式可能不支持所有字段约束,校验层才是最后的安全网。

没有结构化输出时,开发流程是这样的:模型返回自由文本 -> 正则表达式硬抠 -> 发现规则千奇百怪 -> 反复修改正则 -> 某个用户案例又来打脸。引入结构化输出之后,流程变成:定义实体类 -> 生成 JSON Schema -> 放在请求里约束模型 -> 返回合法 JSON -> 解析校验 -> 入库。你可以把这件事理解成“给模型填一张带格式的表格”,而不是“让模型自由写一篇小作文”。

那么结构化输出适合什么场景?这里用一个对比表格说明会更直观:

场景是否适合结构化输出原因
简历信息抽取非常适合字段固定,下游需要入库和渲染
表单自动填充非常适合每个字段对应一个表单项
商品信息结构化非常适合属性、价格、规格高度规范化
客服聊天回复不太适合需要自然语言表达,JSON 反而怪异
创意文案写作不适合约束格式会限制模型发挥
代码生成视情况而定结构代码可以,但夹杂解释时要注意

这里尤其想说一个误区:很多人以为“结构化输出就是让模型返回 JSON”,于是只在 prompt 里加一句“请返回 JSON 格式”。这种做法的成功率可能只有 60%,因为模型可能返回 ````json` 围栏、注释、多余解释,字段名还会有各种变体。真正的结构化输出,是把 Schema 变成模型生成时的约束条件,再在程序里做强校验。这一层认知差异,决定了你的项目是“偶尔能跑通”还是“稳定可上线”。

3. 简历助手项目:为什么结构化输出是刚需

简历助手的业务本质,是“把非结构化的简历文本,变成结构化的简历数据”。这一步是整个产品的核心资产。简历数据一旦结构化,就可以被人才库检索、被表单回填、被 HR 系统消费、被算法做岗位匹配。而这一切的前提,就是数据格式必须是机器可读的。

从需求上看,简历里通常包含这些信息:基本信息(姓名、电话、邮箱、城市)、求职意向、教育背景、工作经历、项目经历、技能标签、荣誉证书。这些字段在业务系统里对应的就是一张张数据库表。如果模型输出的是自由文本,数据库设计和接口设计会非常痛苦;如果模型输出的是固定 JSON,数据库表结构可以直接对着 JSON 设计,接口文档也可以直接给出实体类。

整个简历助手项目的核心链路是这样的:

  1. 用户上传简历文本或 PDF。
  2. PDF 场景先做文本抽取(常见工具包括 PDF 解析库、OCR 服务),把内容转成文本。
  3. 后端把文本交给大模型,要求模型按预设的 JSON Schema 抽取结构化字段。
  4. 后端用 Pydantic 或 Java 实体类校验模型输出,校验通过后得到“干净的简历对象”。
  5. 简历对象写入数据库,同时返回给前端,前端按字段渲染简历卡片,或回填到编辑表单。

这里最关键的设计决策,是“先定义 Schema,再写业务代码”。很多项目的错误做法是反过来:先调通模型,看看模型返回什么,再回头建表。这么做的结果就是数据库字段跟着模型心情走,今天叫experience,明天叫workExperience,后天又多出一个projects_detail。正确做法是业务要什么字段,Schema 就定义什么字段,模型必须适配业务,而不是业务适配模型。

另外,在简历助手这个场景里,结构化输出还会直接影响产品体验。用户从“上传简历”到“看到编辑好的简历表单”,如果中间只需要一次稳定的 JSON 抽取,体验是顺滑的;如果模型输出不稳定,前端一会儿渲染成功、一会儿解析失败,用户就会觉得这个 AI 功能很“智障”。

4. 环境准备与依赖安装

在开始写代码之前,先把环境准备好。下面的示例采用 Python 技术栈,依赖不多,逻辑清晰,适合作为项目实战的第一步。如果你要在生产环境用 Java/Spring AI,思路相同,我会在第 9 节补充路径。

建议环境如下:

  • Python 3.10 及以上版本。
  • 一个支持 JSON 模式的大模型 API。示例代码使用 OpenAI 兼容协议,常见支持response_format参数的大模型服务都可以用,具体模型是否支持json_object模式,请以服务提供方的文档为准。
  • 安装以下依赖:openai(OpenAI Python SDK,用于调用大模型)、pydantic(数据校验,v2 版本)、fastapiuvicorn(用于提供 HTTP 接口,方便前端调用)。版本号以你实际环境为准,本文重点演示通用思路。

依赖安装命令如下:

pip install openai pydantic fastapi uvicorn

然后配置 API 密钥。推荐使用环境变量,不要把密钥写死在代码里。在命令行或 IDE 的启动配置中可以这样设置:

export LLM_API_KEY="你的API密钥" export LLM_BASE_URL="https://你的模型服务地址"

这里有一点需要提醒:如果用的是国内大模型服务,通常也会提供 OpenAI 兼容的 base_url,配好之后,代码里的调用方式几乎不变,这会让项目在不同模型服务之间迁移很容易。

基础环境确认无误后,下面进入核心环节:定义简历实体类。

5. 设计简历实体类与 JSON Schema

结构化输出的第一步,永远是设计实体类。“实体类”这个名字在 Java 世界里叫 POJO、Entity、DTO,在 Python 世界里通常指 Pydantic Model,在 Spring AI 里则是你定义的那个普通 Java 类。名字不同,本质相同:把业务字段和类型固定下来。

简历业务需要抽取的字段,我的建议是拆成四个分组,避免一个大扁平的 JSON 把所有字段堆在一起,那样既难校验也难扩展。

分组代表字段类型是否必填说明
基本信息namestring姓名
基本信息job_targetstring求职意向
基本信息contactobject电话、邮箱、城市
教育经历educationarray学校、学历、专业、起止时间
项目经历projectsarray项目名、角色、描述、亮点
技能标签skillsarray技能关键词列表
工作年限years_of_experiencenumber从简历推算的年数

在 Python 里,用 Pydantic 定义实体类非常顺手,因为它既能定义字段,又能生成 JSON Schema,还能在校验失败时给出清晰错误。下面是完整示例。

# 文件路径:models/resume_schema.py from typing import List, Optional from pydantic import BaseModel, Field class ContactInfo(BaseModel): phone: str = Field(default="", description="手机号") email: str = Field(default="", description="邮箱") location: str = Field(default="", description="所在城市") class EducationItem(BaseModel): school: str = Field(description="学校名称") degree: str = Field(description="学历,本科/硕士/博士等") major: str = Field(description="专业") start_date: str = Field(description="开始时间,格式YYYY-MM") end_date: str = Field(description="结束时间,格式YYYY-MM,仍在读写'至今'") class ProjectItem(BaseModel): name: str = Field(description="项目名称") role: str = Field(description="担任角色") description: str = Field(description="项目描述") highlights: List[str] = Field(default_factory=list, description="项目亮点,3到5条") class Resume(BaseModel): name: str = Field(description="姓名") job_target: str = Field(description="求职意向岗位") years_of_experience: float = Field(default=0, description="工作年限") contact: ContactInfo = Field(default_factory=ContactInfo, description="联系方式") education: List[EducationItem] = Field(default_factory=list, description="教育经历") work_experience: List[str] = Field(default_factory=list, description="工作经历要点") projects: List[ProjectItem] = Field(default_factory=list, description="项目经历") skills: List[str] = Field(default_factory=list, description="技能标签")

设计这个实体类时,有几个关键决策值得展开:

第一,字段描述不能省。Pydantic 里的description会被转成 JSON Schema 里的description,而大模型在生成时会参考这些描述。描述写得越具体,模型越不容易把“教育经历”和“培训经历”混在一起。

第二,数组字段用default_factory=list,而不是直接给[]。这是 Python 里避免可变默认参数坑的标准写法,也能让缺失字段自动变成空列表,而不是校验报错。

第三,日期格式要提前约定。简历里的时间写法人人不同,“2023.6”“2023年6月”“June 2023”都有,Schema 里直接写明YYYY-MM,模型会按约定去规范化。虽然不能保证 100% 标准,但比放任自由强得多。

定义好 Pydantic Model 之后,可以直接生成 JSON Schema,这段 Schema 后面会作为模型输出的约束条件:

from models.resume_schema import Resume schema = Resume.model_json_schema() print(schema)

生成的 Schema 大致长这样。这个结构你不需要手写,但建议看一眼,因为它就是给模型看的那份“表单说明书”。

{ "type": "object", "properties": { "name": { "type": "string", "description": "姓名" }, "job_target": { "type": "string", "description": "求职意向岗位" }, "years_of_experience": { "type": "number", "default": 0, "description": "工作年限" }, "contact": { "$ref": "#/$defs/ContactInfo" }, "education": { "type": "array", "items": { "$ref": "#/$defs/EducationItem" } }, "skills": { "type": "array", "items": { "type": "string" } } }, "required": ["name", "job_target"], "$defs": { "ContactInfo": { "type": "object", "properties": { "phone": { "type": "string", "default": "", "description": "手机号" } } } } }

到这里,实体类和 JSON Schema 已经就绪。下一步就是真正接入大模型,让模型按照这个 Schema 输出。

6. 接入 LLM:完整示例与代码实现

现在进入项目实战的核心部分:调用大模型 API,让它把一段简历文本变成结构化的 Resume 对象。

首先是抽取函数。这里使用 OpenAI 兼容协议,重点看两个参数:response_formattemperatureresponse_format={"type": "json_object"}要求模型输出合法 JSON,temperature=0降低随机性,让抽取结果更稳定。

# 文件路径:resume_agent/resume_extractor.py import json import os from openai import OpenAI from models.resume_schema import Resume client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"), ) SYSTEM_PROMPT = """你是简历解析助手。 请从用户提供的简历文本中提取信息,严格按照 JSON Schema 输出 JSON 对象。 规则: 1. 只输出合法 JSON,不要输出解释,不要输出 Markdown 代码块标记。 2. 字段缺失时,字符串填空字符串,数组填空数组,不要编造内容。 3. 日期统一为 YYYY-MM 格式,无法确定时填空字符串。 4. 数组字段最多保留 5 条,按时间倒序排列。 5. 个人信息只做抽取,不做任何评价。""" def extract_resume(resume_text: str) -> Resume: chat_completion = client.chat.completions.create( model="gpt-4o-mini", # 请替换为你实际可用的模型名 temperature=0, response_format={"type": "json_object"}, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"简历文本:\n{resume_text}"}, ], ) raw = chat_completion.choices[0].message.content # 调试阶段建议先打印原始输出,方便排查 print("raw output:", raw) data = json.loads(raw) resume = Resume.model_validate(data) return resume if __name__ == "__main__": sample_text = """张三,5年Java开发经验,求职意向:后端工程师。 2019-2021 在某某网络科技公司担任Java开发工程师,负责订单模块。 2021-2023 在某某电商公司担任高级开发工程师,负责交易系统。 项目经历:某某电商平台,负责订单中心重构,主要技术栈Spring Cloud,将下单接口性能提升40%。 教育经历:2015-2019 某某大学,软件工程,本科。 技能:Java、Spring Boot、Spring Cloud、MySQL、Redis。""" resume = extract_resume(sample_text) print(resume.model_dump_json(indent=2, ensure_ascii=False))

代码里的关键逻辑分成三步:

第一步,组装消息。System prompt 负责定规则,User 消息里放待解析的简历文本。这里有个小技巧:把简历文本用“简历文本:”这样的前缀包起来,可以避免简历内容和指令混在一起产生歧义。

第二步,调用模型并拿到原始字符串。这一步返回的一定是 JSON 字符串,因为response_format已经约束了json_object。但要记住,合法 JSON 字符串不等于业务字段正确,所以还要继续校验。

第三步,解析并校验。json.loads(raw)负责把字符串变字典,Resume.model_validate(data)负责按 Schema 校验。如果模型漏了必填字段,或者把数字写成了字符串,这一步会抛异常,你就能在日志里定位问题。

对于一个完整的简历助手项目,光有函数还不够,最好封装成 HTTP 接口,方便前端调用。下面用 FastAPI 写一个最小可用的接口。

# 文件路径:app/main.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from models.resume_schema import Resume from resume_agent.resume_extractor import extract_resume app = FastAPI() class ExtractRequest(BaseModel): text: str class ExtractResponse(BaseModel): resume: Resume raw: str @app.post("/api/resume/extract", response_model=ExtractResponse) def extract(req: ExtractRequest): if not req.text.strip(): raise HTTPException(status_code=400, detail="简历文本不能为空") try: resume = extract_resume(req.text) except Exception as e: # 生产环境不要把异常细节直接返回给前端,这里简化演示 raise HTTPException(status_code=422, detail=f"解析失败: {e}") return ExtractResponse(resume=resume, raw=resume.model_dump_json(ensure_ascii=False))

这个接口做的事情很清楚:前端把简历文本 POST 过来,后端调用大模型,校验通过后把结构化结果返回。raw字段可以用于前端预览原始抽取结果,也可以后续做日志分析。

运行接口:

uvicorn app.main:app --reload --port 8000

然后在另一个终端用 curl 测试:

curl -X POST http://127.0.0.1:8000/api/resume/extract \ -H "Content-Type: application/json" \ -d '{"text": "李四,3年产品经理经验,求职意向:AI产品经理。..."}'

到这里,一个最小可用的“简历助手后端抽取服务”就算跑通了。

7. 运行结果与效果验证

拿第 6 节的示例简历文本跑一次,预期输出是这样的:

{ "name": "张三", "job_target": "后端工程师", "years_of_experience": 5.0, "contact": { "phone": "", "email": "", "location": "" }, "education": [ { "school": "某某大学", "degree": "本科", "major": "软件工程", "start_date": "2015-09", "end_date": "2019-06" } ], "work_experience": [ "2019-2021 在某某网络科技公司担任Java开发工程师,负责订单模块", "2021-2023 在某某电商公司担任高级开发工程师,负责交易系统" ], "projects": [ { "name": "某某电商平台", "role": "高级开发工程师", "description": "负责订单中心重构,主要技术栈Spring Cloud", "highlights": [ "将下单接口性能提升40%" ] } ], "skills": [ "Java", "Spring Boot", "Spring Cloud", "MySQL", "Redis" ] }

怎么判断抽取是否成功?我的建议是看三个层面:

第一,JSON 是否合法。只要你能执行json.loads(raw)不出错,第一层就过了。这一层通常由response_format保证,但有时仍会因为模型输出被截断而失败,所以代码里依然要 try。

第二,是否通过 Schema 校验。Resume.model_validate(data)会检查必填字段、类型、嵌套结构。这里最常见的失败是name为空、skills不是数组、或者模型多输出了一些 Schema 里没定义的字段。Pydantic v2 默认会忽略多余字段,但如果你希望严格报错,可以配置model_config = ConfigDict(extra="forbid")

第三,抽取结果是否符合业务真实性。这一点机器校验不了,需要人来看。建议找几份真实脱敏简历做样本集,人工对比模型抽取结果,统计字段准确率。只有字段准确率达标,这个功能才敢推向用户。

如果运行失败,第一步应该看哪里?先看raw output打印出来的原始字符串。如果原始输出就是纯文本而不是 JSON,说明模型服务可能不支持response_format,或者 system prompt 里的规则被忽略了。如果原始输出是 JSON 但校验失败,把异常信息贴到日志里,通常能一眼定位是缺字段还是类型错误。

8. 常见问题与排查思路

这个章节的价值,是把项目实战里你大概率会踩的坑提前列出来。下面的排查表每一条都是真实项目里会遇到的问题,建议收藏备用。

问题现象可能原因排查方式解决方案
模型返回了 JSON 但被 Markdown 围栏包裹,json.loads报错模型没有严格执行“不要输出代码块标记”打印原始输出,观察是否包含```json在解析前做清洗,去掉围栏字符;如果频繁出现,加强 prompt 并升级到强约束模式
输出 JSON 被截断,最后缺失括号长简历超出上下文窗口或输出 token 上限检查保存的最大 token 和响应长度增加max_tokens;先对简历做分段摘要,再抽取关键字段
必填字段缺失,如name为空简历文本本身缺信息,或模型遗漏查看原始输出中的字段值在 prompt 中说明“缺失填空字符串,不要编造”;业务侧对必填字段单独校验并提示用户补全
项目经历疑似编造了简历里没有的内容模型幻觉,temperature过高对比原文和输出设置temperature=0;prompt 明确“只能抽取原文信息,不能补充"
Java 实体类里叫url的字段,序列化后变成URL或反之Jackson 对 Java Bean 属性的命名映射规则导致打印序列化后的 JSON 对比在字段上加@JsonProperty("url")显式指定 JSON 字段名
前端JSON.parse报错后端返回时 JSON 被转义或混入非 JSON 内容用浏览器 Network 面板查看完整响应体后端先校验再返回;接口层统一用response_model输出标准 JSON
中文内容在数据库里乱码数据库连接或表字符集不是 utf8mb4查看建表语句和连接参数统一使用 utf8mb4 字符集,连接串带上参数
同一份简历每次抽取结果不一致temperature不为 0,或模型版本变动多次测试对比输出生产环境固定模型版本,temperature设为 0,必要时做结果缓存

表格里的每条问题,在面试或团队评审时都可能被问到。这里特别多说一句 Java 场景的坑:如果你用 Spring AI 做结构化输出,通常需要定义一个普通 Java 类,并把输出的泛型类型传进去,框架会帮你完成 JSON 到对象的转换。但 Java Bean 的属性命名一旦涉及缩写,比如URLID,序列化时非常容易踩雷。遇到这类问题,不要和 Jackson 的默认行为硬杠,直接在字段上用@JsonProperty把名字钉死,是最省心的方案。

9. 工程化最佳实践与生产建议

一个简历助手项目从 demo 到上线,中间还差着不少工程细节。下面这些实践建议,是我认为结构化输出项目里最值得投入的部分。

第一,不要让前端直接面对模型原始输出。模型输出必须经过后端的 Schema 校验和格式统一后再返回。这样即使换模型服务商,前端接口也不会变。简历字段一旦被前端直接消费,任何字段名变更都会引发线上事故。

第二,给 Schema 加上版本。简历结构化字段会演进,比如未来要加“期望薪资”或“意向城市”。建议在 JSON 里加一个schema_version字段,或者用接口版本号管理。否则老数据和新数据混在一起,下游消费方很难兼容。

第三,保留原始文本和结构化结果两份数据。不要因为拿到了结构化 JSON 就丢掉原始简历文本。后续如果 Schema 升级、模型换新,你还可以用旧数据重新跑一遍抽取,做批量迁移和效果对比。同时,结构化结果只能算“模型标注”,原始文本才是事实依据。

第四,把校验失败当作正常流程,而不是异常崩溃。模型永远不会 100% 准确。最稳妥的做法是:解析失败时,把错误信息拼进下一轮 prompt,让模型“看着错误改一遍”,做一次自动重试。如果重试还失败,再转人工或降级为用户手动填写。

第五,简历是强个人隐私数据,安全边界必须提前设计。开发阶段使用脱敏的假数据;生产环境必须获得用户明确授权,传输走 HTTPS,数据库中只保存业务必需的字段,日志里不要打印姓名、手机号、邮箱等敏感信息。数据保留周期和删除机制也要在需求阶段定清楚。

第六,关于 Java/Spring AI 落地的路径。如果你所在团队是 Java 技术栈,结构化输出的思路完全一致:定义普通实体类(对应 Python 侧的 Pydantic Model),调用 Spring AI 的 chat model 时指定目标类型,框架内部会完成 JSON Schema 生成和结果解析。它的特点是和 Spring Boot 融合好、工程化方便,但不同版本 API 变化较大,接入时务必以你使用的 Spring AI 版本官方文档为准,先写最小示例跑通,再扩展字段。

第七,性能和成本要一起考虑。简历解析这种场景,用户不会频繁调用,但仍建议做结果缓存:同一份文本在短时间内的重复请求直接返回缓存,避免重复消耗 token。异步化也很重要,特别是上传 PDF 的场景,文本抽取和模型调用都可能耗时较长,接口设计成异步任务、前端轮询结果,体验会比同步等待好很多。

第八,定义一份“抽取质量评测集”。准备二三十份不同风格、不同岗位的脱敏简历,标注好期望输出的 JSON。每次更换模型或调整 prompt 后,用这份数据集跑一遍准确率和字段完整度,避免“改了一个 prompt 修复一个 case,又弄坏另外三个 case”的恶性循环。

10. 总结与后续学习方向

这篇文章从“简历助手项目输出混乱”这个痛点切入,把结构化输出的三层体系讲了一遍:Prompt 约束是地基,模型能力约束是主干,实体类校验是兜底。JSON 结构化输出真正厉害的地方,不是让模型输出更“整齐”,而是让 AI 能力和现有工程体系无缝衔接——数据可以直接入库、直接渲染、直接对接下游系统。

你可以先按第 4 到第 7 节的步骤,用 Python + FastAPI + Pydantic 跑通一个最小简历解析服务,把示例里的实体类改成你自己业务的字段。然后做三件事:准备一份真实脱敏简历文本,反复测试抽取稳定性;把第 8 节的排查表挨个验证一遍;再基于第 9 节的最佳实践,给项目加上校验失败重试和日志记录。

后续值得继续深入的方向还有:简历文本中的 PDF 解析和 OCR 处理、岗位匹配算法、流式输出的结构化约束、多轮对话中保持结构化状态的 Agent 设计,以及基于抽取结果做简历质量评分。如果你对 Java/Spring AI 更感兴趣,下一步就是动手写一个Resume实体类,接入 Spring AI 的结构化输出功能,把第 5 节的思路迁移到 Java 工程里。

最后留一个实战提醒:别一上来就设计一个二十个字段的大 Schema。先用五个必填字段跑通全链路,再逐步加嵌套结构。结构化输出的复杂度是随字段数量指数增长的,小步快跑,你会少踩很多坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 7:49:22

农村自建房三层装修全流程实战:从规划到验收的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 7:49:22

STM32宠物智能项圈:高精度定位+抗干扰计步+SOC精准估算

简介:本资源是一套完整的基于STM32的宠物智能项圈嵌入式开发源码工程,面向嵌入式初学者、物联网项目开发者及智能硬件爱好者,解决宠物定位追踪、日常活动监测(计步)、低功耗充电状态可视化等核心功能的软硬件协同实现问…

作者头像 李华
网站建设 2026/9/3 7:48:46

华为IPD流程之华为-PDT经理角色认知培训教材【文末附下载关键字】

本文介绍了PDT经理的角色认知,包括其在IPD体系中的位置、基本角色定位、关键管理活动、能力模型和评估方法以及培养路径。文章指出PDT经理是重量级产品开发团队的管理者,负责产品的商业成功和跨功能部门合作,通过绩效管理加强团队凝聚力,对商业结果负责。 绑定资源目录: …

作者头像 李华
网站建设 2026/9/3 7:48:04

树莓派部署NanoDLP:打造智能光固化3D打印控制中心

简介:本资源是专为树莓派(Raspberry Pi)平台优化的64位NanoDLP 3D打印控制软件发行包,面向嵌入式3D打印开发者、DIY爱好者及教育场景下的创客实践者,解决在资源受限硬件上部署轻量级光固化(DLP)…

作者头像 李华
网站建设 2026/9/3 7:47:51

Kopia:自带加密和去重的开源备份工具,数据安全自己掌控

Kopia:自带加密和去重的开源备份工具,数据安全自己掌控跨平台开源备份工具,把快照加密后保存到本地、NAS 或云存储,支持增量备份、压缩去重和灵活恢复。📖 背景说明 Kopia 是一款跨平台的开源备份工具,同时…

作者头像 李华
网站建设 2026/9/3 7:47:33

AI 推理优化实践:vLLM Continuous Batching:5800 t/s 吞吐量的核心引擎

一、为什么需要 Continuous Batching 1.1 三种 Batching 策略对比 ① 静态批处理 (Static Batching) — 传统方式 ┌──────────────────────────────────────────────┐ │ Req A: [Prefill][D][D][D][D][D][D][D][D][D][D] │…

作者头像 李华