ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSON结构化输出实战:用Spring AI打造可靠的简历解析助手

JSON结构化输出实战:用Spring AI打造可靠的简历解析助手 很多同学在接触大模型应用开发时都会遇到一个绕不开的话题结构化输出。尤其是最近 AI 简历助手、AI 文档解析、Agent 工具调用这类应用越来越火大家会发现同样是让大模型干活有的人做出来的功能稳定可靠有的人做出来的功能只能“看运气”。这个差距很大程度上就来自于是否做好了结构化输出。我在做简历解析助手这个功能时就踩过不少坑。最开始是直接把一段候选人的工作经历文本丢给大模型然后在提示词里写“请帮我提取姓名、电话、工作经历”模型确实能返回内容但返回格式非常不稳定——有时候是 Markdown 表格有时候是一段带星号的文本还有时候在 JSON 外面额外补一句“以下是提取结果”。为了兼容这些情况解析层被迫写了一大堆补丁逻辑代码又乱又难维护。后来我把方案改成了 JSON 结构化输出再配合实体类和校验逻辑整个流程一下子变得清晰可控。这篇文章我会从“为什么需要结构化输出”讲起解释 JSON 结构化输出的核心概念然后带你完整实现一个“简历助手”项目实战帮你理解实体类如何定义、提示词如何设计、返回结果如何验证最后分享常见报错的排查思路和工程化建议。本文适合两类读者一类是刚接触大模型开发想搞清楚结构化输出到底是怎么回事的新手另一类是有一定开发经验准备在项目中落地 LLM 信息抽取、文档解析、报表生成等功能的开发者。读完你不仅能理解结构化输出的原理还能直接复制代码跑通一个简历解析助手。1. 为什么需要结构化输出1.1 大模型原生输出的痛点大模型本质上是一个文本生成模型它接收文本也输出文本。对于闲聊、问答、写文章这类场景来说自然语言输出是最合适的。但当你需要把模型输出交给程序继续处理时自然语言反而是最大的障碍。举个很简单的例子你想让模型从一段简历文本中提取出姓名和电话。模型可能输出候选人叫张三联系电话是 13800138000邮箱是 zhangsanexample.com。这段文字人类一眼就能看懂但程序想要把“张三”和“13800138000”从这句话里准确切出来就需要写正则、做关键词匹配而且一旦模型换了一种表达方式比如“这位候选人是张三可以通过 13800138000 联系到他”正则就很有可能匹配失败。这就是大模型原生输出的三个典型痛点格式不统一同一份输入每次返回的表达方式都可能不同经常夹带解释性文字把数据混在自然语言里程序解析成本高容易出错且难以穷尽所有情况。在项目开发中我们希望模型“输出的内容能直接被程序消费”也就是说程序拿到结果后可以一行代码解析成对象直接落库、渲染或者传给下游系统。结构化输出就是为了解决这个问题。1.2 结构化输出是什么结构化输出简单来说就是让大模型按照我们预先定义好的结构和格式来返回内容。最常见的形式是 JSON也可以是 XML、YAML 或者一段符合特定模板的文本。在 LLM 应用开发语境下结构化输出通常包含两层含义第一层是“格式约束”也就是要求模型只输出合法 JSON不要输出解释性文字不要使用 Markdown 代码块包裹。第二层是“结构约束”也就是我们提前定义好 JSON 里有哪些字段、每个字段的类型是什么、哪些是必填项模型必须按照这个 Schema 来输出。只做到第一层模型偶尔还是会漏字段、填错类型做到第二层输出质量才会真正稳定。你可以把结构化输出理解为给大模型发了一张“填空题答题卡”而不是让它自由发挥写作文。1.3 典型应用场景结构化输出在真实项目中的应用非常广这里列出几个最常见的场景信息抽取从简历、合同、发票、物流单等文本中提取关键字段这是本文实战项目的核心场景数据转换把一段非结构化的描述转换成标准格式再写入数据库或发送给下游系统Agent 工具调用让模型决定调用哪个函数、传入什么参数必须严格遵循工具定义的 JSON SchemaRAG 查询条件抽取在知识库问答中从用户问题里提取查询条件转成结构化查询参数报表生成让模型把统计数据转换成前端可以直接渲染的 JSON 结构。你会发现这些场景都有一个共同点模型只是处理链路中的一环处理结果要被后续程序继续使用。只要程序需要处理模型输出结构化输出就不是“可选项”而是“必选项”。2. JSON 结构化输出的核心概念2.1 JSON 格式基础JSONJavaScript Object Notation是一种轻量级的数据交换格式结构简单几乎所有编程语言都内置了解析支持所以它成了大模型结构化输出的事实标准。一个 JSON 对象由键值对组成键是字符串值可以是字符串、数字、布尔值、数组、嵌套对象或者 null。比如{ name: 张三, age: 28, skills: [Java, Spring Boot], address: { city: 北京, street: 朝阳区某街道 } }在 Java 中可以用 Jackson、Gson 解析在 Python 中可以用json模块解析前端更是原生支持。这也是为什么大家都选择 JSON 作为大模型输出的载体——解析成本几乎为零且与业务对象可以非常方便地相互转换。2.2 结构化输出的几种实现方式在具体实现上让大模型输出结构化内容主要有三种方式它们的约束强度依次递增。第一种是纯提示词约束。你在 system prompt 里写“请只输出 JSON不要输出其他内容”然后自己用JSON.parse或ObjectMapper解析。这种方式实现成本最低任何模型都支持但稳定性最差模型偶尔还是会“不听话”输出一些额外文字。适合原型验证和临时脚本。第二种是开启 JSON Mode。OpenAI 的 API 支持response_format{type: json_object}参数强制模型输出合法 JSONOpenAI 兼容接口的厂商大多也支持这个参数。这种方式能保证输出是合法 JSON但不能保证 JSON 里面真的有你要的所有字段。第三种是 Function Calling 或 Structured Output。模型根据你定义的函数参数 Schema 来输出结构化内容平台会尽可能严格地按 Schema 校验结果。这是目前生产环境最推荐的方式。在 Spring AI 这类框架中我们直接传一个实体类进去框架会自动把实体类转换成 Schema 描述并解析模型返回的内容。下表对比了三种方式的差异方式输出稳定性字段约束实现成本推荐场景纯提示词较低无低临时脚本、原型验证JSON Mode较高无低信息抽取、数据转换Function Calling / Structured Output高有中生产环境、Agent 工具调用2.3 选型建议在实际项目中我的建议是如果模型平台支持 JSON Mode 或 Structured Output优先使用不要把稳定性赌在提示词上。提示词仍然要写但它是“兜底约束”不是“唯一约束”。另外要注意“结构化输出”不等于“输出格式是 JSON 就行”。模型返回了合法 JSON但字段名和你定义的不一致或者数组元素类型不对程序解析时一样会出错。所以在设计阶段就要把实体类、字段名、字段类型想清楚这也是后面实战部分要重点讲的内容。3. 项目实战简历助手环境准备3.1 项目需求与功能拆分本文要实现的“简历助手”功能很简单用户输入一段纯文本简历系统调用大模型自动提取出候选人姓名、联系方式、技能、工作经历、教育经历等信息并输出结构化的 JSON 数据。功能拆分如下接收原始简历文本调用大模型进行信息抽取按照预定义的实体类解析模型输出把解析结果返回给调用方。整个项目不涉及数据库和前端聚焦在“结构化输出”这条主线上方便你只看核心逻辑。3.2 技术栈与版本说明本文示例使用 Java Spring Boot Spring AI 实现这套组合在目前的企业级项目中很常见而且 Spring AI 提供了比较完善的结构化输出支持。版本说明如下Java 17Spring Boot 3.xSpring AI 1.x以你实际引入的版本为准Maven 3.8支持 OpenAI 兼容协议的大模型接口。如果你的项目使用的是 Python 技术栈也不用担心我在后面会单独给出一段 Python OpenAI SDK 的参考实现原理完全一致。实际开发时版本需要根据你的项目情况调整本文重点演示的是实现思路。3.3 创建项目并引入依赖先创建一个 Spring Boot 项目并在pom.xml中引入 Web 和 Spring AI 的 OpenAI Starter 依赖?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent groupIdcom.example/groupId artifactIdresume-assistant/artifactId version1.0.0/version nameresume-assistant/name properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency /dependencies /projectSpring AI 的版本迭代比较快不同版本间的 API 命名可能有差异引入依赖时建议以官方 Maven 仓库中的最新稳定版本为准。接着在application.yml中配置模型接口信息server: port: 8080 spring: ai: openai: base-url: https://api.openai.com api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.2这里需要说明几点api-key通过环境变量OPENAI_API_KEY注入不要把密钥硬编码在配置文件里如果你接入的是阿里云百炼、DeepSeek、智谱、Kimi 等平台只要它们提供 OpenAI 兼容接口就把base-url换成对应地址即可temperature设置为 0.2是为了让模型输出更稳定减少随机性信息抽取类任务建议保持较低温度。4. 定义输出契约实体类设计4.1 为什么先定义实体而不是先写提示词很多初学者做结构化输出时习惯先写提示词告诉模型“你要输出姓名、电话、邮箱……”然后再写解析逻辑。这个顺序其实是反的。更推荐的做法是先把实体类定义好让实体类成为“输出契约”。理由有三个第一实体类本身就是 Schema字段名、字段类型一目了然模型和程序共用同一份契约第二使用 Spring AI 的entity()方法时框架会读取实体类结构自动生成格式说明注入到提示词中你不需要在提示词里手写一遍 JSON 示例第三实体类可以直接交给 Jackson 解析模型输出和 Java 对象之间的映射关系完全由注解控制出错时也容易排查。4.2 简历实体类完整代码在src/main/java/com/example/resumeassistant/model目录下创建三个实体类。首先是简历主类Resume.javapackage com.example.resumeassistant.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; JsonIgnoreProperties(ignoreUnknown true) public record Resume( JsonProperty(name) String name, JsonProperty(phone) String phone, JsonProperty(email) String email, JsonProperty(years_of_experience) Integer yearsOfExperience, JsonProperty(skills) ListString skills, JsonProperty(work_experience) ListWorkExperience workExperience, JsonProperty(education) ListEducation education ) { }然后是工作经历类WorkExperience.javapackage com.example.resumeassistant.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; JsonIgnoreProperties(ignoreUnknown true) public record WorkExperience( JsonProperty(company) String company, JsonProperty(title) String title, JsonProperty(start_date) String startDate, JsonProperty(end_date) String endDate, JsonProperty(description) String description ) { }最后是教育经历类Education.javapackage com.example.resumeassistant.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; JsonIgnoreProperties(ignoreUnknown true) public record Education( JsonProperty(school) String school, JsonProperty(degree) String degree, JsonProperty(major) String major, JsonProperty(start_date) String startDate, JsonProperty(end_date) String endDate ) { }这里使用 Java record 来定义实体类代码非常简洁。如果你的项目还在使用传统 JavaBean效果也是一样的重点是每个字段上的JsonProperty注解它决定了模型输出的 JSON 字段名。4.3 字段命名与 JSON 映射注意事项在字段命名上有一个很常见的坑你的 Java 实体类字段名通常使用 camelCase但模型输出的 JSON 字段名如果也使用 camelCase在中文大模型场景下问题不大。不过如果你希望 JSON 字段名使用 snake_case下划线风格就必须显式地用JsonProperty声明。举个例子如果实体类里写public record WorkExperience( String company, String startDate ) { }Jackson 默认会把startDate序列化成startDate但很多模型在训练数据里更习惯start_date这种表达。如果不对齐模型输出的start_date就无法正确映射到 Java 字段上解析结果会是 null。另外一个和 Jackson 命名有关的经典问题是如果 JavaBean 的字段以大写字母开头比如URL、QRCodeJackson 默认命名策略会把首字母小写导致 JSON 里变成url、qrCode。遇到这种情况同样是用JsonProperty显式指定 JSON 字段名来解决。所以我建议字段命名策略一旦确定就在实体类上用JsonProperty写清楚不要让框架猜测。还有一点要注意JsonIgnoreProperties(ignoreUnknown true)可以忽略模型输出中多出来的字段。模型有时候会自作主张多输出一个字段如果不加这个注解Jackson 解析时可能直接报错加了之后就能安全忽略多余字段增强兼容性。5. 核心实现提示词设计与调用 LLM5.1 提示词设计虽然有了实体类作为 Schema提示词仍然需要精心设计。Spring AI 的entity()方法会自动把实体类格式说明注入到提示词里但提取规则、缺失值处理、日期格式这些“业务语义”还是需要我们自己写清楚。看一下本项目使用的 system promptprivate static final String SYSTEM_PROMPT 你是一名专业的简历解析助手。 用户会提供一段候选人简历文本你需要从中提取以下字段 姓名、手机号、邮箱、工作年限、技能列表、工作经历列表、教育经历列表。 要求 1. 如果字段在原文本中不存在统一填空字符串或空数组。 2. 只输出 JSON 本身不要输出 Markdown 代码块不要输出任何解释文字。 3. 日期格式统一为 yyyy-MM例如 2020-07如果只有年份则补充为 yyyy-01。 ;这段提示词有几个关键点明确告诉模型“缺失字段填空字符串或空数组”避免模型编造数据强调“只输出 JSON 本身”这是和“纯提示词方案”之间的最后一道防线对日期格式做了归一化要求方便下游存储和排序。注意System prompt 里不需要重复实体类的每个字段因为 Spring AI 的entity()会帮我们把字段结构加入提示词。你只需要补充提取规则即可。5.2 编写 ResumeAnalysisService在src/main/java/com/example/resumeassistant/service目录下创建ResumeAnalysisService.javapackage com.example.resumeassistant.service; import com.example.resumeassistant.model.Resume; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class ResumeAnalysisService { private static final String SYSTEM_PROMPT 你是一名专业的简历解析助手。 用户会提供一段候选人简历文本你需要从中提取以下字段 姓名、手机号、邮箱、工作年限、技能列表、工作经历列表、教育经历列表。 要求 1. 如果字段在原文本中不存在统一填空字符串或空数组。 2. 只输出 JSON 本身不要输出 Markdown 代码块不要输出任何解释文字。 3. 日期格式统一为 yyyy-MM例如 2020-07如果只有年份则补充为 yyyy-01。 ; private final ChatClient chatClient; private final ObjectMapper objectMapper; public ResumeAnalysisService(ChatClient.Builder chatClientBuilder, ObjectMapper objectMapper) { this.chatClient chatClientBuilder.build(); this.objectMapper objectMapper; } public Resume analyze(String rawResumeText) { return chatClient.prompt() .system(SYSTEM_PROMPT) .user(rawResumeText) .call() .entity(Resume.class); } public String analyzeToJson(String rawResumeText) { Resume resume analyze(rawResumeText); try { return objectMapper.writeValueAsString(resume); } catch (Exception e) { throw new IllegalStateException(序列化解析结果失败, e); } } }核心代码只有analyze方法这一小段。entity(Resume.class)是 Spring AI 结构化输出的关键方法它会自动完成两件事把Resume类的结构描述注入到提示词中然后把模型返回的 JSON 反序列化成Resume对象。这里有一个重要的设计细节analyze返回的是类型安全的Resume对象而不是一个字符串。这意味着业务代码里不需要再写任何 JSON 解析逻辑直接调用对象上的 getter对 record 来说是 accessor 方法就能拿到数据。5.3 接入控制器与运行入口为了让接口可以被外部调用我们再加一个简单的 REST 控制器。先定义一个请求体 DTOpackage com.example.resumeassistant.model; public record AnalyzeRequest(String text) { }然后创建ResumeAssistantController.javapackage com.example.resumeassistant.controller; import com.example.resumeassistant.model.AnalyzeRequest; import com.example.resumeassistant.model.Resume; import com.example.resumeassistant.service.ResumeAnalysisService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/api/resume) public class ResumeAssistantController { private final ResumeAnalysisService resumeAnalysisService; public ResumeAssistantController(ResumeAnalysisService resumeAnalysisService) { this.resumeAnalysisService resumeAnalysisService; } PostMapping(/analyze) public Resume analyze(RequestBody AnalyzeRequest request) { return resumeAnalysisService.analyze(request.text()); } }启动 Spring Boot 应用后调用POST /api/resume/analyze请求体是一个 JSON 对象里面包含待解析的简历文本。5.4 Python 参考实现如果你的项目是 Python 技术栈可以参考下面这段代码。它使用 OpenAI SDK开启 JSON Mode并手动解析结果import json from openai import OpenAI client OpenAI() # 自动读取环境变量 OPENAI_API_KEY SYSTEM_PROMPT 你是一名专业的简历解析助手。 用户会提供一段候选人简历文本你需要从中提取指定字段。 如果字段在原文本中不存在统一填空字符串或空数组。 只输出 JSON 本身不要输出 Markdown 代码块不要输出任何解释文字。 def analyze_resume(raw_text: str) - dict: resp client.chat.completions.create( modelgpt-4o-mini, temperature0.2, response_format{type: json_object}, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: raw_text}, ], ) content resp.choices[0].message.content return json.loads(content)两者思路完全一致先定义输出契约再设计提示词最后解析成结构化数据。差别只在于 Java 用实体类定义 SchemaPython 用response_format参数约束输出为 JSON。6. 运行与验证6.1 准备测试输入下面给出一段模拟的简历文本方便你直接测试姓名张三 手机13800138000 邮箱zhangsanexample.com 工作年限5年 技能Java、Spring Boot、MySQL、Redis、Kubernetes、Docker 工作经历 2020.07 - 至今 某互联网科技有限公司 高级后端开发工程师 负责核心交易系统的设计与开发主导订单服务重构将接口性能提升 40%。 2017.06 - 2020.06 某某软件有限公司 后端开发工程师 参与企业内部 ERP 系统的开发和维护负责库存模块与报表模块。 教育经历 2013.09 - 2017.06 某某大学 本科 计算机科学与技术调用接口的方式如下curl -X POST http://localhost:8080/api/resume/analyze \ -H Content-Type: application/json \ -d {text:姓名张三\n手机13800138000\n邮箱zhangsanexample.com\n工作年限5年\n技能Java、Spring Boot、MySQL、Redis、Kubernetes、Docker\n\n工作经历\n2020.07 - 至今 某互联网科技有限公司 高级后端开发工程师\n负责核心交易系统的设计与开发主导订单服务重构将接口性能提升 40%。\n2017.06 - 2020.06 某某软件有限公司 后端开发工程师\n参与企业内部 ERP 系统的开发和维护负责库存模块与报表模块。\n\n教育经历\n2013.09 - 2017.06 某某大学 本科 计算机科学与技术}6.2 预期 JSON 输出正常情况下接口会返回类似下面的 JSON{ name: 张三, phone: 13800138000, email: zhangsanexample.com, years_of_experience: 5, skills: [Java, Spring Boot, MySQL, Redis, Kubernetes, Docker], work_experience: [ { company: 某互联网科技有限公司, title: 高级后端开发工程师, start_date: 2020-07, end_date: 至今, description: 负责核心交易系统的设计与开发主导订单服务重构将接口性能提升 40%。 }, { company: 某某软件有限公司, title: 后端开发工程师, start_date: 2017-06, end_date: 2020-06, description: 参与企业内部 ERP 系统的开发和维护负责库存模块与报表模块。 } ], education: [ { school: 某某大学, degree: 本科, major: 计算机科学与技术, start_date: 2013-09, end_date: 2017-06 } ] }这就是结构化输出的价值程序收到这串 JSON 后不需要任何额外解析直接就能映射到实体对象存数据库、渲染到前端都可以。6.3 怎么判断解析质量判断一次解析是否成功不能只看“有没有返回 JSON”还要看字段级质量。建议你做三件事第一检查字段完整性。name、phone是简历的核心字段缺失率应该接近 0如果经常缺失说明提示词或 Schema 设计有问题。第二检查字段类型。years_of_experience应该是整数而不是字符串“5年”这也是为什么实体类里用Integer而不是String。第三构建一个包含 20 份不同格式简历的测试集跑一遍统计每个字段的缺失率和错误率。数据比感觉可靠如果你准备把简历助手做到生产环境这一步不能省。7. 常见问题与排查思路7.1 高频错误一览结合我自己的开发经验结构化输出最常见的几类问题如下问题现象常见原因解决思路解析 JSON 时报错提示 Content 不是合法 JSON模型输出了 Markdown 代码块或夹带了说明文字在提示词中强调“只输出 JSON 本身”开启 JSON Mode忽略外层 json 标记某个字段经常为 null原文本没有该信息或字段语义不明确在提示词中规定缺失值处理方式检查实体类字段名与提示词描述是否一致返回字段名和实体类不一致字段名大小写问题、命名策略不一致统一使用JsonProperty显式声明 JSON 字段名数值字段被识别成字符串Schema 中未说明字段类型或模型对文本理解有误实体类中使用Integer、Long等强类型在提示词中补充格式说明中文乱码请求响应编码不一致确保消息请求和响应统一使用 UTF-8 编码输出不稳定相同输入返回不同结果temperature 设置过高信息抽取类任务建议temperature设为 0 到 0.2JavaBean 大写字母开头的字段在 JSON 中变成小写Jackson 默认命名策略导致通过JsonProperty显式指定 JSON 字段名7.2 排查清单如果你遇到解析失败可以按下面顺序排查先看模型原始返回内容。记住一定不要只记录解析失败后的异常信息要把模型最原始的返回值打到日志里。很多时候问题一眼就能看出来——返回了 Markdown、字段名不对、多了注释文字。确认 JSON 字段名和实体类注解是否完全一致特别注意大小写和下划线。确认实体类字段类型是否合理。模型把“5年”识别成字符串而实体类期望数字解析就会失败。确认是否开启了 JSON Mode 或 Structured Output。如果只是依赖提示词稳定性会明显差很多。检查 temperature。太高会让模型“自由发挥”建议降到 0.2 以下。8. 最佳实践与工程建议8.1 输出 Schema 设计实体类就是输出 Schema设计时要注意几点字段要尽量扁平不要过度嵌套嵌套层级越多模型出错的概率越高。一个招聘系统需要候选人技能列表直接用ListString就够不要设计成ListSkill加一堆子属性。字段名要见名知义避免用a、b、tmp这类无法理解的命名。因为模型会读取字段名来理解“要提取什么”字段名越清晰提取越准确。给字段添加合理的类型约束。固定枚举值尽量用字符串并可以在提示词里限定取值范围比如“degree 只允许填写高中、大专、本科、硕士、博士”。8.2 提示词工程不要因为用了 Structured Output 就忽略提示词。框架能保证格式但不能保证业务规则。以下三条建议非常重要第一把 system prompt 和 user 输入分开。不要让用户输入直接拼接进 system prompt否则容易引入提示词注入风险。第二对缺失值做明确约定。你希望缺失时填空字符串还是 null希望日期归一化成什么格式这些都要写清楚否则模型会自行发挥。第三在提示词里加一个“只输出 JSON 本身”的强调。即使有 JSON Mode 兜底这句话也能减少模型输出说明文字的概率。8.3 异常处理与重试结构化输出不是 100% 可靠的生产环境必须处理解析失败的情况。解析失败时建议先捕获异常记录模型原始输出日志然后重试一次。重试时可以稍微调整提示词比如强调“不要输出代码块”。如果连续两次失败就走人工兜底或返回友好错误提示不要让调用方直接看到堆栈。同时对解析成功的结果也要做业务校验。比如电话字段要符合手机号格式邮箱要包含符号必填字段不能为空。校验失败时同样要记录日志并处理。8.4 安全与隐私简历数据属于个人敏感信息涉及隐私合规生产环境中必须注意不要记录完整的原始简历文本日志必要时对姓名、电话、邮箱做脱敏处理最小化存储只保留业务真正需要的字段调用大模型时如果使用的是第三方 API要遵循最小化原则避免传输与解析任务无关的敏感字段密钥通过环境变量或配置中心管理严禁提交到代码仓库。8.5 性能与成本信息抽取类任务建议把temperature调低这样得到稳定结果的概率更高也减少了重试带来的额外成本。除此之外还可以考虑以下优化如果模型支持更小的型号先用小模型跑通流程再评估是否需要升级对相同或相似的输入做缓存避免重复调用精简 system prompt减少输入 token在业务低峰期做批量解析而不是实时逐个调用。9. 下一步可以继续做的事本文的简历助手只是一个最小可运行版本跑通之后你可以沿着下面几个方向继续扩展。首先是接入数据库。把解析出来的Resume对象落库然后提供查询候选人列表、搜索技能匹配的接口这就从一个解析工具变成了真正的简历管理系统。其次是支持文件上传。把接口入参从纯文本改成 PDF、Word 简历文件上传先用文件解析工具提取文本再交给大模型做结构化抽取这个能力在招聘系统里非常实用。然后是增加多轮追问。如果解析结果缺失了手机号可以基于已有的结构化结果向用户追问“请补充手机号”再做一次补全而不是直接返回空值。最后你还可以把这段经验迁移到其他信息抽取场景比如合同字段抽取、发票识别、工单解析。原理
返回列表