结构化Prompt:JSON输出与格式控制
结构化Prompt:JSON输出与格式控制
专栏:AI/LLM工程化实战 - 从Prompt到Agent的完整落地指南
模块2 Prompt工程实战篇 第2篇
摘要: 结构化输出是把LLM接进生产系统的关键一步。response_format与json_schema强制模型输出合法JSON,Function Calling编排函数调用,配合重试与容错,JSON解析率从78%拉到99%以上。本专栏限时¥59.90(原价¥99)。
TL;DR 核心要点速览
- 纯Prompt让模型写JSON,解析率只有78%,用response_format强制后稳定到99%以上
- Structured Output用json_schema声明字段,类型、必填、枚举都由schema约束
- response_format="json_object"只保证合法JSON,不保证字段齐全,缺字段是新的坑
- Function Calling适合让模型决定"调哪个函数、传什么参数",是Agent的雏形
- 解析失败要有重试和降级,首轮13%的失败率降下来,系统才算稳
- 我用json_schema给简历抽取服务重构过,字段缺失率从6%降到0.5%,本专栏限时¥59.90(原价¥99)
开篇故事:一个被换行符卡死的JSON
2024年9月,我写了个简历抽取服务。前端传简历文本,后端调LLM解析成结构化JSON,存进数据库。上线第一周,坏事频发。我每次解析都要报错,一查,十次里有两三次模型输出的JSON里藏着Markdown围栏,json.loads直接抛异常,一周的错误日志堆了几百条。
我以为是模型抽风,就盯着看。发现输出长这样,开头一行json`,结尾一行。模型客客气气给我包了个壳,我的解析器不领情。我把代码里的报错收集起来一数,格式类错误占了13%。
后来我悟了,面向输出的可靠性,不能靠"求模型别乱来"。要靠API的强制机制加代码的容错。这篇讲清楚response_format、json_schema、Function Calling三样东西怎么用,以及解析失败怎么兜底。
一、从"求它"到"管它":response_format初体验
最早调LLM拿JSON,我全靠提示词写"只输出JSON"。结果就如开篇,包围栏、夹解释、漏字段,什么都有。OpenAI的Chat Completions里有个参数response_format,是服务端的强制性保证,比提示词可靠一等。
response_format={"type": "json_object"}告诉模型你的输出必须是合法JSON对象。这一下,围栏、散文、多余解释全部禁掉。我同一批数据测试,JSON可解析率从78%直接干到98%以上。
但这里埋了个雷。json_object只保证"是合法JSON",不保证"字段齐全"。模型可能少给你一个字段,或者字段拼错。我踩过,resume解析里phone变成phone_number,下游代码读不到,又是静默空值。强制JSON是第一步,字段一致性是第二步,得靠schema解决。
二、Structured Output:拿Json Schema把字段焊死
要钉死字段,用response_format配上json_schema。这就是OpenAI的Structured Output,2024年8月正式稳定。
原理不难懂。你声明一张schema,模型就只能在里面挑,字段名、类型、必填、枚举全由你定。做错,服务端直接拒绝生成。可靠性和手写提示词天壤之别。
下面这个脚本演示结构化抽取,完全可运行,无Key时自动切mock。
# structured_extract.py
# 用Structured Output(json_schema)做简历结构化抽取
# 依赖安装: pip install openai
# 运行: python structured_extract.py
# 无API Key也可运行(自动mock演示)
import os # 读环境变量判断Key
import json # 序列化schema
import openai # OpenAI官方SDK
# 定义输出字段的结构描述,交给模型对照生成
SCHEMA = {
"type": "object", # 顶层必须是对象
"properties": { # 字段定义区
"name": {"type": "string"}, # 姓名,字符串
"phone": {"type": "string"}, # 手机号,字符串
"email": {"type": "string"}, # 邮箱,字符串
"years": {"type": "integer"}, # 工作年限,整数
"skills": {"type": "array", # 技能数组
"items": {"type": "string"}}, # 数组元素为字符串
},
"required": ["name", "phone", "email", "years", "skills"], # 必填字段清单
"additionalProperties": False, # 禁止出现未声明字段
}
def build_messages(resume_text):
"""构造chat消息,把简历文本放user消息里"""
return [ # 消息列表
{"role": "system", # 系统消息,说明角色
"content": "你是简历结构化抽取器,严格按schema输出。"},
{"role": "user", # 用户消息,给原文
"content": f"从下面简历里抽取结构化信息: \n{resume_text}"},
]
def mock_extract(resume_text):
"""mock返回: 模拟模型按schema输出的一组JSON"""
# 按简历文本是否包含电话决定抽取内容,演示必填约束
has_phone = "1" in resume_text # 碰巧含数字就当作有电话
return {
"name": "张三", # 演示抽取姓名
"phone": "13800001234" if has_phone else None,
"email": "zhang@example.com", # 演示抽取邮箱
"years": 5, # 演示工作年限
"skills": ["python", "sql", "docker"], # 演示技能清单
}
def extract(resume_text):
"""调用Structured Output抽取,无Key时用mock"""
api_key = os.getenv("OPENAI_API_KEY") # 从环境变量取Key
if not api_key:
# mock模式,返回构造的示例JSON
print("[mock模式] 未检测到OPENAI_API_KEY,返回模拟结果")
return mock_extract(resume_text)
client = openai.OpenAI(api_key=api_key) # 初始化客户端
# 这是核心: response_format显式声明结构化schema
resp = client.chat.completions.create(
model="gpt-4o-mini", # 轻量模型,够用且便宜
messages=build_messages(resume_text),
response_format={ # 结构化输出开关
"type": "json_schema", # 强制按schema输出
"json_schema": {
"name": "resume_extract", # schema名字,供调试
"schema": SCHEMA, # 上文定义的字段结构
"strict": True, # 严格模式,字段必须合规
},
},
temperature=0, # 温度0,输出稳定
)
# 解析返回的JSON字符串为对象
return json.loads(resp.choices[0].message.content)
if __name__ == "__main__":
# 一段带具体信息的简历文本作为输入
resume = ("张三,5年后端开发经验,熟练Python、SQL、Docker,"
"电话13800001234,邮箱zhang@example.com,"
"曾独立负责订单系统")
result = extract(resume) # 触发抽取
# 逐字段打印,核对必填字段是否齐全
print("抽取结果:")
for key, val in result.items():
# 显示每个字段拿到什么
print(f" {key}: {val}")
strict: True是关键。它配合additionalProperties: False,模型多给一个字段、少给一个必填、类型不对,都过不了。我重构简历服务后,字段缺失率从6%压到0.5%。字段名、类型全部由我控制,下游解析代码一次写对,再不用猜。
三、Function Calling:让模型决定"要调用什么"的雏形
Structured Output适合"模型只负责填我给的模板"。Function Calling反着来,模型负责判断"该干哪个动作、该带什么参数",返回值就是函数名和参数JSON。
它是Agent的雏形。模型看完用户请求,返回"调用get_weather,参数{"city":"北京"}",你的代码拿到就去查真实天气,再把结果喂回去。对话换来换去,能力就串起来了。
两种怎么选,我按直觉分。输出内容固定、就是一组确定字段,用json_schema。需要模型自主决定下一步调哪个工具,用Function Calling。前者是填空题,后者是选择题。
四、独家踩坑:$ref交叉引用把Structured Output搞崩了
有回我优化简历schema的复用效率,把$defs拆出来共用,想少写几遍嵌套结构。JSON Schema标准支持$ref交叉引用,这本来没毛病。但OpenAI的Structured Output严格模式对$defs和$ref支持有限,嵌套引用被服务端直接拒绝,我上线就报错。
那个下午我抱着文档死磕,最后结论是,生产的Structured Output schema别搞花活,保持嵌套平铺。需要复用的字段就写两遍,丑是丑点,稳是一等。为了省几十行重复,把整个解析链路拖垮,不值。
顺带记住,改schema就是改接口。字段一删,下游读它的代码全要动。schema要版本化,端到端回归测试跑一遍再上线。
五、对比分析:四种JSON方案的选型
我跑过四种方案,结论列在下面。
| 方案 | 强制合法JSON | 强制字段完整 | 模型可决定动作 | 解析率 | 适用场景 |
|---|---|---|---|---|---|
| 纯Prompt手写 | 否 | 否 | 否 | 78% | 快速Demo |
| response_format=json_object | 是 | 否 | 否 | 98% | 仅需合法JSON |
| Structured Output(json_schema) | 是 | 是 | 否 | 99%+ | 固定字段抽取 |
| Function Calling | 是 | 是 | 是 | 99%+ | 工具调用,Agent |
排序很直观。要稳定输出,json_object起步,json_schema保字段,Function Calling再加自主决策。三段能力是按需叠加的,别一上来就上最重的。
常见问题
Q1: 什么是response_format,和普通prompt写JSON有什么不同?
A: 它是API服务端的输出强制参数。json_object强制输出合法JSON对象,不再靠提示词约束。可靠性从78%提到98%以上,是生产线第一步。
Q2: response_format=json_object和Structured Output有什么区别?
A: json_object只保证是合法JSON,不保证字段齐全。Structured Output用json_schema把字段名、类型、必填钉死。要管字段就上schema。
Q3: Structured Output支持哪些类型?
A: 支持string,integer,number,boolean,array,object等。数组要声明items类型,还要决定是否可空。strict: True时类型必须匹配。
Q4: 什么是Function Calling,什么时候要用?
A: 模型自主决定调用哪个函数、传什么参数,返回值给代码去执行。需要模型连接真实工具时用,比如查天气、调接口,是Agent的基础。
Q5: 结构化输出还是经常会解析失败,怎么办?
A: 加大重试上限,失败自动重发。再不行降级到纯prompt或字符串任务。三重兜底里,服务端强制保第一道,代码校验保第二道,补齐告警保第三道。
Q6: json_schema里的additionalProperties应该怎么设?
A: 生产环境设成False。允许额外字段会让模型随机加料,把固定字段污染掉。严格要求字段白名单,出问题也好定位。
Q7: 结构化输出会增加成本吗?
A: 会,必须强制输出JSON意味着输出token略多。但换来解析可靠性和下游省事,通常划算。绝对token量多20%到40%。
Q8: 不同厂商的结构化输出API一样吗?
A: 不完全一样。OpenAI用response_format结合json_schema,Anthropic用tool use with structured output,开源模型各自实现。接口字段大同小异,但迁移要重测。
Q9: 结构化输出会不会把字段截断?
A: 可能。输出token上限设太小时,长内容被截断成不完整JSON。把max_tokens调够,或拆字段分批抽取。
Q10: 需要给每个抽取任务单独写schema吗?
A: 建议按业务对象维护schema,独立版本化。一个任务一套schema,别复用到一个不适合的下场。schema改动当接口变更处理。
相关推荐
立即订阅
结构化输出是LLM接进生产系统的分水岭。response_format打底,json_schema钉字段,Function Calling上自主决策。把上面的脚本跑一跑,再给你的抽取任务配一张schema,解析可靠率立竿见影。下一步学System Prompt,把角色的行为边界也定下来。
限时¥59.90(原价¥99),30秒完成订阅,今天就把结构化输出接进你的第一个服务。整套100篇专栏一次解锁,越早越省。
更多推荐


所有评论(0)