大模型结构化输出方案——JSON Mode 与 Function Call 的工程取舍

发布时间:2026/7/9 3:07:47

大模型结构化输出方案——JSON Mode 与 Function Call 的工程取舍 大模型结构化输出方案——JSON Mode 与 Function Call 的工程取舍一、结构化输出的应用场景与技术路线概览大语言模型的核心产出是自然语言文本但在实际应用中大多数下游系统需要的是结构化数据。例如从一段对话摘要中提取主体、金额、日期字段或让模型根据用户指令生成符合特定 Schema 的 JSON 对象。这种非结构化输入到结构化输出的转换是工程集成的关键环节。目前行业中主要存在三种技术路线。第一种是 Prompt 约束法通过 System Prompt 明确要求模型输出 JSON 格式并给出 Schema 示例依赖模型自身理解和遵守指令的能力。第二种是 JSON Mode通过 API 参数要求模型强制输出合法的 JSON 字符串。第三种是 Function Calling / Tool Use将期望的输出定义为函数参数模型以调用函数的方式输出结构化数据。flowchart LR subgraph 技术路线对比 A[Prompt 约束法] -- A1[自由文本 正则提取] B[JSON Mode] -- B1[模型强制输出 JSON] C[Function Calling] -- C1[结构化 Schema 驱动] end subgraph 适用场景 A1 -- S1[简单信息提取] B1 -- S2[纯数据填充] C1 -- S3[需要推理的复杂抽取] end subgraph 可靠性 S1 -- R1[低格式不稳定] S2 -- R2[中JSON 合法但不一定符合 Schema] S3 -- R3[高Schema 约束 参数校验] end三条路线的取舍不取决于技术优劣而取决于具体业务对结构化输出的可靠性要求和推理复杂度要求。二、JSON Mode 的实现原理与工程实践JSON Mode 是 OpenAI 在 2023 年底推出的功能通过设置response_format: { type: json_object }来约束模型必须输出合法的 JSON。它的实现原理是在模型的解码阶段添加约束确保生成的 Token 序列能形成合法的 JSON 结构。/** * JSON Mode 调用封装 * * 为什么 JSON Mode 不能完全替代 Function Calling * JSON Mode 只保证输出是合法 JSON不保证 JSON 的 Schema 结构。 * 模型可能遗漏字段、添加未定义的字段、或使用错误的类型。 * 因此需要后校验层来兜底 */ Service public class JsonModeExtractor { private final OpenAiChatModel chatModel; private final ObjectMapper objectMapper; public JsonModeExtractor(OpenAiChatModel chatModel, ObjectMapper objectMapper) { this.chatModel chatModel; this.objectMapper objectMapper; } /** * 使用 JSON Mode 提取结构化信息 */ public OrderExtractionResult extractOrder(String userMessage) { // 构建严格的 Prompt要求模型按照指定 Schema 输出 String prompt 请从以下用户消息中提取订单相关信息严格按照指定的 JSON Schema 输出 Schema 要求 { orderId: 订单编号字符串, amount: 金额数字不含单位, customerName: 客户姓名字符串, items: [商品列表字符串数组] } 注意 1. 必须包含所有字段如果某个字段无法提取填写 null 2. 不要添加任何额外字段 3. 所有金额字段为纯数字不要包含元等单位 用户消息 %s .formatted(userMessage); ChatResponse response chatModel.call( new Prompt(prompt, OpenAiChatOptions.builder() .withResponseFormat(new ResponseFormat( ResponseFormatType.JSON_OBJECT )) .withTemperature(0.1) // 低温度提高输出稳定性 .build() ) ); String jsonOutput response.getResult().getOutput().getContent(); // 后置校验确保输出符合预期 Schema return validateAndParse(jsonOutput); } /** * Schema 校验与容错处理 * * 为什么需要后校验 * JSON Mode 只能保证 JSON 语法合法 * 但模型可能输出 amount: 100元 而非 amount: 100 * 或者遗漏某个字段。这里做容错解析而非直接拒绝 */ private OrderExtractionResult validateAndParse(String json) { try { JsonNode root objectMapper.readTree(json); // 逐字段解析并做类型修正 String orderId root.has(orderId) !root.get(orderId).isNull() ? root.get(orderId).asText() : null; // 金额字段容错处理 100元 - 100 的情况 Double amount null; if (root.has(amount) !root.get(amount).isNull()) { JsonNode amountNode root.get(amount); if (amountNode.isNumber()) { amount amountNode.asDouble(); } else if (amountNode.isTextual()) { // 文本型金额去除非数字字符后解析 String cleaned amountNode.asText().replaceAll([^\\d.], ); try { amount Double.parseDouble(cleaned); } catch (NumberFormatException e) { amount null; // 无法解析则设为 null } } } String customerName root.has(customerName) !root.get(customerName).isNull() ? root.get(customerName).asText() : null; ListString items new ArrayList(); if (root.has(items) root.get(items).isArray()) { root.get(items).forEach(item - { if (!item.isNull()) { items.add(item.asText()); } }); } return new OrderExtractionResult(orderId, amount, customerName, items); } catch (JsonProcessingException e) { // JSON Mode 一般不会到这一步 // 但如果模型仍然输出非 JSON如前面带文字说明这里做兜底 log.warn(模型输出非标准 JSON尝试清洗: {}, json.substring(0, Math.min(200, json.length()))); return new OrderExtractionResult(null, null, null, Collections.emptyList()); } } public record OrderExtractionResult( String orderId, Double amount, String customerName, ListString items) {} }JSON Mode 的最佳实践是在 Prompt 中明确描述期望的 JSON 字段结构在代码中做 Schema 校验和容错解析将低 temperature 设置作为稳定性辅助。这种方式适合信息提取任务但不适合需要模型做复杂推理决策的场景。三、Function Calling 的结构化输出方案Function Calling 的本质是将期望的输出结构定义为函数的参数列表模型以意图调用函数的方式输出参数值。这种方式比 JSON Mode 更适合需要推理的参数填充场景。/** * Function Calling 方式的结构化提取 * * 为什么 Function Calling 更可靠 * 模型在输出 Function Call 参数时会使用与训练时 Function Call 格式 * 一致的内部处理逻辑参数类型的匹配度更高。 * 比如金额字段模型更倾向于输出数字类型而非字符串类型 */ Component Description(从用户消息中提取订单信息并返回结构化数据) public class OrderExtractionFunction implements FunctionOrderExtractionFunction.Request, OrderExtractionFunction.Response { /** * 参数结构定义 * * 为什么使用详细的字段描述 * JsonPropertyDescription 会作为函数声明发送给模型 * 描述越准确模型输出的参数越符合预期。 * 这是 Function Calling 方案的核心优势——声明即文档 */ public record Request( JsonProperty(required true) JsonPropertyDescription(订单编号通常为数字或字母数字组合) String orderId, JsonProperty(required true) JsonPropertyDescription(订单金额纯数字类型不包含单位符号) Double amount, JsonProperty(required true) JsonPropertyDescription(客户姓名中文或英文均可) String customerName, JsonPropertyDescription(订单中的商品名称列表数组类型) String[] items, JsonPropertyDescription(订单创建日期格式为 yyyy-MM-dd) String createDate ) {} public record Response( JsonPropertyDescription(是否成功提取所有必要字段) boolean success, JsonPropertyDescription(提取的订单信息) Request extracted, JsonPropertyDescription(缺失或不确定的字段说明) String note) {} Override public Response apply(Request request) { // Function Calling 不需要做额外处理 // 只是作为提取结果的接收器 // // 但这里可以做业务校验如 // - 订单号格式校验 // - 金额合理性校验非负数 // - 日期格式校验 if (request.amount ! null request.amount 0) { return new Response(false, request, 金额不能为负数); } return new Response(true, request, 提取成功); } }四、方案对比与选择决策在实际工程中两种方案的选择可以遵循以下决策路径信息提取任务NER/实体识别优先使用 Function Calling因为其参数类型约束更严格模型不易输出类型错误的字段值。纯格式转换文本 → JSONJSON Mode 更直接省去了函数注册的环节适合批处理和 Pipeline 场景。对延迟敏感的场景JSON Mode 省去了 Function Call 的往返调用平均延迟低 15%-20%。内容生成 结构化混搭如生成一份包含标题、摘要、关键词的文档结构JSON Mode 比 Function Calling 更自然因为 Function Calling 的设计初衷是调用工具而非生成内容。/** * 混合方案动态选择提取策略 * * 为什么需要动态选择 * 不同任务的特性不同一刀切的方案无法兼顾稳定性和延迟 */ Service public class AdaptiveExtractor { private final JsonModeExtractor jsonModeExtractor; private final ChatClient functionCallClient; public AdaptiveExtractor(JsonModeExtractor jsonModeExtractor, ChatClient functionCallClient) { this.jsonModeExtractor jsonModeExtractor; this.functionCallClient functionCallClient; } public Object extract(String userMessage, ExtractionType type) { return switch (type) { case SIMPLE_ENTITY - // 简单实体提取不需要推理用 Function Calling 保证类型正确 functionCallClient.prompt() .user(userMessage) .function(order_extraction) .call() .entity(OrderExtractionFunction.Response.class); case COMPLEX_DOCUMENT - // 复杂文档生成内容生成为主用 JSON Mode jsonModeExtractor.extractComplexDocument(userMessage); case HIGH_THROUGHPUT - // 高吞吐场景减少函数调用开销用 JSON Mode jsonModeExtractor.extractBatch(userMessage); }; } public enum ExtractionType { SIMPLE_ENTITY, COMPLEX_DOCUMENT, HIGH_THROUGHPUT } }五、总结JSON Mode 和 Function Calling 不是非此即彼的关系而是解决不同层面问题的两个工具。JSON Mode 解决的是输出格式是否合法的问题适合轻量级提取和生成任务。Function Calling 解决的是输出结构是否符合预期 Schema的问题适合需要推理的字段填充和类型约束严格的场景。生产实践中两种方案往往组合使用用 Function Calling 做核心字段的精准提取用 JSON Mode 做辅助字段的批量产出。最终的目标不是追求绝对的提取准确率而是在可接受的错误率下找到延迟和成本的最佳平衡点。结构化输出还需要考虑模型差异问题。不同模型对 JSON Mode 和 Function Calling 的支持程度不同GPT-4 系列对两种方式的支持都很成熟Claude 系列对 Function Calling 的支持较好但 JSON Mode 的稳定性略逊开源模型如 Llama、Qwen可能需要额外的 LoRA 微调或 Grammar Constraint 才能稳定输出 JSON。在实际工程中如果使用了多模型切换架构需要为不同模型准备不同的输出解析策略或者统一在应用层做 Schema 校验和容错而不是假设所有模型都能稳定输出合规的结构化数据。成本也是一个重要的考量维度。Function Calling 通常比纯文本生成更贵因为模型需要消耗更多 Token 来处理函数签名和参数约束。如果业务流程中需要多次调用模型进行结构化提取成本差异会快速累积。一种优化思路是对于简单的信息提取任务使用小模型 JSON Mode对于需要复杂推理的任务才使用大模型 Function Calling。在 Pipeline 中根据任务复杂度动态选择模型和输出方式可以在保证质量的前提下显著降低成本。

相关新闻