大模型JSON提示词实战:结构化输出、状态管理与批量任务
1. 为什么说大模型的母语是代码1.1 从一次惨痛的解析经历说起前阵子我在做一个文本信息抽取的小工具需求很简单给大模型一段商品描述让它告诉我商品名称、价格、品牌还有三个卖点。我一开始用的是纯文本提示词就是那种请提取以下信息并用自然语言回答的写法。表面上看没什么问题模型也确实给出了一个像模像样的回答。但真正落地的时候我的代码就遭殃了——模型的回答里夹杂着好的我来帮您提取末尾还有一句希望对您有帮助中间偶尔还会冒出一段补充说明根据描述该商品的价格可能是2499元不过有些平台做活动可能会更便宜。我拿着正则表达式左改右改从简单的字符串匹配改到用关键词定位再改到加权打分愣是折腾了两三天。后来我换了个思路不用自然语言提问直接让模型输出JSON。变化是立竿见影的——模型仿佛突然被框住了老老实实按我给的字段结构输出连多余的寒暄都基本绝迹。解析代码从五十多行缩减到十行以内准确率反而提高了不少。这次经验让我深刻意识到一个事实大模型的母语可能真的是代码而JSON就是跟模型交流最顺手的普通话。1.2 结构化表达天然契合模型的工作方式为什么会这样我个人的理解是大模型在预训练阶段看过海量的代码文件代码本身就是一种极其结构化的信息载体。变量声明、函数定义、数据结构——这些东西的训练语料占比相当可观。模型学到的不只是语法更是一种按规则组织信息的底层能力。当你给出一个明确的JSON模板时模型不需要猜测你想要什么格式它只需要把内容填充到对应字段里。这就像跟一个熟练的实习生交代工作你说把结论写在一张表格里列好标题他就能高效执行你要是什么都不说他就自由发挥写出一段流畅但很难直接用的散文。这话不是我瞎编的。很多模型在量化评估中结构化输出任务的成功率都明显高于自由文本任务。原因也不难理解JSON Schema本身就携带了类型约束字段约束嵌套关系等信息这些信息极大缩小了模型的输出空间。输出空间小了自由度低了出错率自然就降下来了。这篇文章我会围绕JSON提示词分享三种我自己用下来相当硬核的玩法第一种是用JSON Schema把输出格式彻底锁死解决解析噩梦第二种是把JSON当作多轮对话的记忆仓库管理上下文状态第三种是设计JSON配方模板把重复性任务变成批量流水线。每一种玩法背后都有真实的踩坑经历和优化逻辑我会把能直接复用的代码片段、参数配置和排查思路都摊开来讲。2. 玩法一用JSON把输出框死——结构化输出的正确姿势2.1 核心逻辑为什么输出总是带废话先说一个基础但很重要的概念大模型的生成过程本质上是按概率逐token预测下一个token。它没有一个严格遵循格式的天然开关所谓格式约束靠的是prompt引导和模型对指令的理解。如果你只是轻描淡写地说一句以JSON格式输出模型很可能给你一个伪JSON——看起来像JSON实际上有注释、有省略号、有模型自己加上的说明文字解析器一跑就报错。我见过最离谱的情况是模型在JSON里输出了一段以JSON格式输出如下的说明文字然后紧跟一个花括号。解析器当然直接炸了。用正则硬清可以但总有下一个意外格式等着你。所以核心思路是在system prompt里把JSON结构定义得像一份合同每个字段都给出明确的类型说明和取值范围。模型看到的信息越精确它自由发挥的空间就越小。2.2 实操构建一份有效的JSON Schema模板我实际使用的是一个三段式结构成本低、效果好。三段分别是任务定义、输出格式定义、示例。以下是一个信息抽取场景的提示词模板你是一名信息抽取助手。你的任务是阅读用户提供的商品描述文本抽取关键信息并返回结构化结果。你必须严格遵守以下输出格式不得输出任何额外文字、注释或解释。 规定输出格式如下 { 商品名称: string, 品牌: string, 价格: number, 价格单位: string, 在售状态: 在售 | 已下架 | 未知, 卖点: string[], 置信度: number } 字段说明 - 价格提取数字部分不包含货币符号和单位。 - 在售状态根据文本线索判断若文本未提及则返回未知。 - 卖点最多返回3条每条不超过20字。 - 置信度0到1之间的小数表示你对本次抽取的整体把握。数值越低表示文本信息越模糊。这段提示词里我特别做了几件事用string[]明确数组类型用在售 | 已下架 | 未知限定枚举值用number约束价格字段必须是一个数值而不是约2499元这样的自然语言片段。约束越明确模型的输出越老实。这不算什么新知识但很多人写提示词的时候就是懒得把这层约束写清楚结果解析阶段疯狂加班。2.3 代码侧如何接住JSON输出提示词写得再好代码侧也得配合。我推荐的流程是先调API拿到模型回复再用JSON解析器做严格解析解析失败或者校验不通过时再触发修复机制。import json import jsonschema from typing import Any output_schema { type: object, properties: { 商品名称: {type: string}, 品牌: {type: string}, 价格: {type: number}, 价格单位: {type: string}, 在售状态: {enum: [在售, 已下架, 未知]}, 卖点: {type: array, items: {type: string}, maxItems: 3}, 置信度: {type: number, minimum: 0, maximum: 1}, }, required: [商品名称, 品牌, 价格, 在售状态, 置信度], } def parse_model_output(raw_output: str) - dict[str, Any]: try: data json.loads(raw_output) except json.JSONDecodeError as exc: raise ValueError(f模型输出不是合法JSON: {exc}) from exc try: jsonschema.validate(instancedata, schemaoutput_schema) except jsonschema.ValidationError as exc: raise ValueError(fJSON不符合Schema定义: {exc}) from exc return data这里有两层校验第一层是语法校验用json.loads保证输出是合法JSON第二层是语义校验用jsonschema.validate保证字段存在、类型正确、枚举值合法。我在实际项目中见过太多只做第一层校验的例子——JSON能解析成功但价格字段是个字符串置信度变成0.999这种让人哭笑不得的值。加了Schema校验之后所有这类问题都在解析阶段被拦截而不是流到下游模块里变成难查的bug。2.4 玩法一的易错点与优化方向这套玩法最大的坑在于模型不保证100%输出合法JSON。就算你提示词写得滴水不漏偶尔还是会出现输出被截断、花括号缺失、字符串转义出错等情况。我的应对办法是在解析失败时不直接给用户报错而是把报错信息反馈给模型让它自己修正上次生成的JSON解析失败错误信息如下 【JSON解析错误期待属性名但第47行发现字符}】 请根据错误信息修正输出只返回修正后的JSON内容。实测下来这种纠错重试策略能把成功率从大概92%拉到99%以上。代价是每次纠错会多一次API调用但在业务上完全值得。另一个优化方向是开启模型厂商提供的JSON模式如果有的话它会从底层约束模型的输出格式比单靠提示词约束可靠得多。我在前面提到的经验是提示词约束打底API的JSON模式兜底解析校验最后把关三层防御基本能解决99%的结构化输出问题。3. 玩法二把记忆装进JSON——多轮对话的上下文管理3.1 对话一长就失忆的真相做过客服机器人或者AI助手的朋友应该都有体会对话轮数一多模型就开始失忆。用户在第3轮说过的事情到了第10轮模型就忘得一干二净。背后的原因其实很朴实——上下文窗口有限你没把关键信息喂进去模型当然不知道。早期我做过一个方案每轮对话都带上完整的历史记录也就是所谓的[用户消息、助手回复、用户消息、助手回复......]这样无脑拼接。结果窗口很快被撑爆费用也水涨船高。后来我意识到一个关键问题模型不需要完整的对话流水账它只需要知道当前任务相关的最小状态集。而这个状态集天然适合用JSON来承载。3.2 实操用JSON管理对话状态举个具体的场景一个项目需求梳理助手用户跟它连续对话逐步确认项目名称、目标人群、核心功能、上线时间等信息。如果用传统方式每轮对话把前几轮内容全塞进去既费token又会引入噪音。我的做法是把这些信息统一维护成一个JSON状态对象{ project: { name: 智能排班系统, goal: 根据员工可用时间自动生成最优排班表, target_audience: 餐饮门店店长, core_features: [排班生成, 冲突检测, 员工偏好管理], deadline: 2025-06-30, confirmed_fields: [project_name, goal, target_audience], pending_fields: [core_features, deadline] } }这个JSON对象维护在哪我建议放在代码侧每次API调用的system prompt里动态注入当前项目状态如下 {json.dumps(state, ensure_asciiFalse, indent2)} 请阅读用户最新消息并根据对话内容更新项目状态。如果用户提供了新的信息请给出更新后的JSON如果用户没有提供新信息请沿用当前JSON。只输出JSON本身。注意一个细节我要求模型每次都输出更新后的完整JSON而不是只输出变更的字段。这样代码侧的处理就变得极其简单——解析JSON替换旧状态存入变量下轮继续用。如果你只输出变更字段代码侧就得做字段合并反而多了一步容易出错的操作。3.3 状态管理背后的token节省逻辑我算过一笔账。假设一轮对话的平均历史长度是1000个token10轮对话就是1万个token。如果用完整历史拼接第10轮的实际输入约为1万token加上当前用户消息。而如果使用JSON状态压缩无论对话进行到第几轮注入的token数量基本固定——一个几百token的JSON状态对象加上最新的一轮消息即可。指令越多历史越长压缩收益越明显。在我实测的项目中token消耗降低了60%以上而且模型的响应稳定性反而提升了。原因也好理解完整历史里不可避免会混入很多无关闲聊这些内容会把模型的注意力拽向无关方向而精简后的JSON状态只包含跟任务强相关的信息注意力集中了准确率自然就上去了。3.4 玩法二的两个关键设计原则第一个原则是**确认后写入**。不能用户说一句话就直接写进状态要判断这句话是否真正提供了新信息。我在某个项目里踩过坑用户随口说了句这个系统如果能做成网页版应该更好模型就把 web_required 字段自动加上并标记为已确认。结果后续对话里用户又改口说还是做App吧状态就来回横跳。后来我在提示词里加了一句只有用户明确表达需求时才更新对应字段不确定的表述放入pending字段不要写入confirmed字段。这样状态维护的稳定性好了很多。第二个原则是**状态可回滚**。维护状态时不能只有一个当前版本要保留最近两三个版本。一旦模型误更新你可以快速回退到上一个版本避免错误状态污染后续所有对话。我在代码里用一个简单的栈来维护版本state_stack [state] def update_state_with_rollback(new_state: dict) - None: state_stack.append(new_state) if len(state_stack) 3: state_stack.pop(0) def rollback_state() - dict: if len(state_stack) 1: state_stack.pop() return state_stack[-1]状态回滚机制在真实业务里非常实用。有一次模型错把用户的一句反问这个功能一定要吗理解为用户已经确认了这个功能直接把字段从pending改成了confirmed。我主流程里压根没意识到这个问题直到第二轮对话时发现模型的推荐策略明显偏了回退上一个状态版本后一切恢复正常。从那以后我的多轮对话项目里一定会加状态版本管理。4. 玩法三JSON配方批量跑任务——自动化工作流4.1 场景价值从一条条处理到一键批量跑第三种玩法是我在工作流自动化上用得最多的把任务描述、输出要求、处理规则打包成一个JSON配方批量喂给大模型处理。这特别适合内容分类、商品标签生成、评论情感分析这类高重复性任务。举个例子。我有一个内容运营项目每天要从后台导出几十篇新文章给每篇文章打标签。传统做法是用人肉或者规则引擎——规则引擎写多了就变成一团乱麻关键词匹配、正则判断、优先级调整改一个规则影响一片。用JSON配方之后整个流程变成了配方定义加批量执行两步。4.2 实操设计一个可复用的JSON配方先看配方文件长什么样{ task_type: content_tagger, model: some_default_model_id, temperature: 0.3, output_format: { fields: [ {name: title, type: string, description: 文章标题}, {name: primary_tag, type: string, description: 主要标签}, {name: secondary_tags, type: array, items: string, max_items: 5}, {name: summary, type: string, description: 用于摘要展示的一句话} ] }, rules: [ 文章涉及编程、技术开发、项目实践时primary_tag必须为「技术」, 文章涉及团队管理、项目管理流程时primary_tag必须为「管理」, 文章同时涉及技术与管理的以技术为主, secondary_tags不能为空至少给出2个, summary不得超过50字 ], batch_config: { concurrency: 3, retry_times: 2, max_input_text_length: 2000 } }配方的核心优势在于内容与实现的解耦。运营人员不需要看懂代码只需要按这个模板填写字段和规则就能控制批量任务的行为。我把这个文件放在一个独立目录里每次要调整规则就改JSON不碰代码。对于技术团队来说这等于把模型调优的一部分工作下沉给了业务角色沟通成本低了不少。4.3 批量执行的关键环节批量执行阶段代码侧的逻辑大致是读取配方、遍历任务列表、逐条构造prompt、调用模型接口、解析输出、校验JSON格式、落库。这个流程本身不难但有几个环节要做扎实。第一个环节是任务数据的分批策略。如果一次任务有50篇文章要处理理论上可以分5批每批10篇。为什么要分批因为单次输入过长会引发模型注意力的稀释——文章一长模型后面生成的标签质量就会下降。我在某个项目里做过对比实验单篇文章1300字10篇一起输入时标签的准确率比单篇输入低了大约6个百分点。所以我后来都采用单篇处理或者最多两篇一组牺牲一点并发能力换取准确率。第二个环节是失败重试与隔离。我对批量任务的处理策略是失败不阻塞。某篇文章处理失败了先记录错误稍后统一重试。不要把整批任务停下来更不要把失败的任务混入下一批。隔离失败任务很重要因为重试时我可以针对失败原因做调整——如果是超时就降低并发如果是输出格式非法就带解析错误信息重试一次。第三个环节是结果校验。模型输出的标签可能存在内部矛盾比如primary_tag是管理但secondary_tags全是技术标签。我在批量流程里加了一个简易的一致性检查用配方里的rules逐条验证输出。验证不通过的任务会被标记为待人工复核。这种机器跑人复核的模式在工程上非常稳定。def validate_output(item: dict, recipe: dict) - list[str]: errors [] if item.get(primary_tag) 管理: if not any(tag.startswith((技术, 开发, 项目)) for tag in item.get(secondary_tags, [])): errors.append(主标签为「管理」但次要标签不匹配) if len(item.get(secondary_tags, [])) 2: errors.append(次要标签数量不足2个) if len(item.get(summary, )) 50: errors.append(摘要字数超限) return errors4.4 玩法三的适配边界JSON配方这套思路也不是万能的。我踩过最大的一个坑是配方规则写得过于刚性导致模型在处理模糊内容时频繁校验失败。比如某个规则写涉及人工智能的内容必须标记为AI但很多文章只是提到用了某种算法并不真的是人工智能主题模型就陷入了选择困难输出标签忽左忽右。后来我把规则改成了概率语言如果文章的核心主题涉及人工智能技术或应用请标记为AI如果仅仅是泛泛提及不要标记。规则从必须改成判断依据模型的执行稳定度反而更高了。这也让我意识到一个反直觉的事实大模型能更好处理带判断的指令而不是无脑的硬性规定。另外批量任务需要合理设计并发数。某次我做并发压测把并发数从3调到10跑起来之后模型接口调用开始飘出超时错误重试机制反而拖慢了整体进度。后来我把并发控制在3到5之间单任务耗时略有增加但整体吞吐量反而更稳定了。并发不是越高越好尤其是当任务依赖外部接口响应时间时过高并发只会放大抖动。5. 常见问题排查与避坑指南5.1 常见问题速查表问题现象根因分析解决方法模型输出JSON解析失败输出有Markdown代码块包裹、有注释、被截断提示词明确禁止额外文字解析前先剥离代码块标记解析失败时带错误信息重试字段值类型不对字段值被模型写成自然语言片段在提示词里明确字段类型如价格为number类型并给出负例示例必填字段缺失模型没理解字段重要性在提示词中把必填字段单独列出并说明缺少字段会导致系统错误模型输出的JSON内出现无关说明模型自由度太高系统级提示词里明确只输出JSON本身可配合API的JSON输出模式多轮对话状态被错误更新模型混淆了用户表述与当前事实增加确认后写入机制维护状态版本栈支持回滚批量任务耗时过长并发配置不合理或输入文本过长降低并发数分批处理单篇输入限制在模型的有效长度以内批量任务规则校验失败率高规则过于刚性把必须改为判断依据给模型提供具体判断例子5.2 我总结的六条独家经验第一条提示词里的示例比规则更有效。我在定义输出格式时永远附带一个正确示例和一个错误示例。模型看到负例之后不要做什么的约束力远强于不允许做什么的文字说明。比如正确示例 {商品名称: 无线蓝牙耳机, 价格: 299, 在售状态: 在售} 错误示例 {商品名称: 无线蓝牙耳机, 价格: 约299元左右, 在售状态: 在售每个渠道价格不同}这个简单的负例把价格字段里的单位、修饰词、模糊表达全一次性堵住了比在字段说明里写三行字都管用。第二条解析JSON失败时别急着改提示词。很多人的第一反应是回炉重写提示词但在我实际操作中最有性价比的修复方式是直接把解析错误报给模型让它自己修正。模型的纠错能力很强只需要给它一次机会。真正把这些修正逻辑固化成代码流程比每次人工调提示词要省事得多。第三条JSON和自然语言混用有奇效。不是所有信息都适合塞进JSON里。我在做内容分类时会把JSONSchema和一段自然语言规则同时放在提示词里JSON管结构化自然语言管判断逻辑。二者配合比单独用任何一种效果都好。第四条警惕模型在JSON里说谎。这是必须提的一点。我在某个对话系统里用JSON状态保存了用户偏好模型生成更新后的JSON时可能脑补出用户从未说过的内容填入对应的偏好字段。这种幻觉在结构化输出中比自由文本输出更隐蔽因为JSON看起来非常确定。解决方案无他——校验环节必须做字段来源标记至少要在业务逻辑上判断这个字段值有没有被用户在对话里明确确认过。第五条关注JSON的嵌套深度。嵌套层级越多模型生成的稳定性越差。我在早期设计过一个五层嵌套的配方字段包括data.items[].details.metadata.author这种结构。实际效果很崩溃——模型经常漏掉中间层字段要么多层嵌套出错要么把metadata和details结构搞混。后来我把嵌套压到两层以内并给每个嵌套字段都加了完整路径说明稳定性才上来。第六条把JSON提示词版本化。每次改动提示词不只是改文字最好把版本号写进注释或者文件名里。我习惯在配方的meta字段里维护prompt_version和schema_version这样出了问题能快速定位到是哪一版提示词导致的也方便A/B测试不同版本的输出效果。5.3 一个实战排错案例最近一次批量任务里我发现某一条数据模型总是输出非法JSON——大概每三次就会有一次返回下面这种内容{ title: 关于智能排班系统的五大优势, primary_tag: 技术, summary: 本文介绍了如何通过智能排班系统提高门店效率 减少人工成本并且提升员工满意度, ... }一眼看出来问题summary字段内容换行了。JSON字符串内直接带换行符但没有做转义。我原先以为是个别案例排查后发现该输入文本本身就包含大量换行和引号模型在生成时把原始文本的换行符号直接带进了JSON字符串字段破坏了JSON的合法性。解决方法有两步第一步在提示词里明确要求所有字符串值中的换行必须替换为空格第二步在解析前对原始字段做预处理——把换行符统一替换为转义形式。还有一个更保险的做法在schema校验之外额外用正则检查JSON字符串字段里是否有未转义的控制字符。有了这层保障这个任务的成功率从94%提升到了99.5%。我个人在实际项目里绕了挺多弯路才把这三套玩法跑通。从一开始对着模型输出发愁到后来慢慢理解结构化约束的力量再到把JSON提示词变成我日常工作流里的标配工具这中间的进步不是靠某一个天才技巧而是靠一次次的实验和复盘。如果说有什么最值得分享的体会那就是跟大模型沟通别让它猜也别给它太多自由。JSON提示词不是银弹它解决不了所有生成质量问题但它能把不可控的输出变成可控的输入把你和大模型之间的协作从玄学变成工程。如果你也想在自己的项目里尝试JSON提示词我建议从最简单的玩法一开始挑一个你目前解析最痛苦的任务给它定义一个字段清晰的JSON结构加上一个负例跑一轮对比实验。你会很直观地感受到模型在结构化约束下的表现往往比自由发挥要可靠得多。等你跑熟了再逐步引入多轮状态管理和批量配方会发现整个系统的稳定性和可维护性都上了一个档次。