把 temperature 降到 0.05 输出还是野 JSON,真正管用的是 schema
刚接手公司客服机器人的时候,我觉得自己离“上线”就差一个 API 调参--把温度拉到 0.05,模型总该老老实实吐 JSON 了吧?结果第二天,前端同事把日志甩到群里:Expecting ',' delimiter: line 5 column 46,一天崩了 11 次。
就在我准备再加十句“务必返回合法 JSON”的 prompt 时,一位做 AI 落地的老哥私聊我:“你用深度学习入门那门课的第 6 章讲得很清楚,自回归模型不是调温度就能锁死输出的。你不把 schema 约束写进采样逻辑,temperature 调成 0 也挡不住它随机游走出一个野格式。”我顺着链接找到亚马逊云科技的深度学习入门,花了一个周末从头过完模型输出约束那一节,顺便把 few-shot 模板、思维链提示和结构化输出指令揉进了 prompt。三天后重测同一批 200 条对话,合法 JSON 率从 61% 直接拉到 97%,响应时延反而降了 40%。
这就是本文要复盘的事:同一套大模型,为什么我改了 3 版 prompt 就能让结构化输出质量翻倍--以及为什么调 temperature 这个动作,原本就不该是主线。
第一版 prompt 翻车:我以为加“必须”二字就足够
产品给的需求很直白:用户的每一次问答,后端需要拿到一个结构体,记录意图、情绪、摘要和是否转人工。我写的第一版 prompt 大概是这样的:
你是一个客服机器人。对于用户的问题,必须返回 JSON 格式,字段包括 intent, sentiment, summary, transfer。 示例:{"intent": "退换货", "sentiment": "生气", "summary": "客户要求退换货", "transfer": true} 务必只返回 JSON,不要添加多余解释。上线当天跑了不到 300 轮,就有二十几条解析失败。我用脚本从日志里捞出来一看,失败的情形分几类:
- 模型在 JSON 前加了“好的,这是你要的结果:”的前导文字。
- JSON 内的字符串里嵌入了未转义的双引号,直接炸解析器。
- 偶尔在末尾多一个逗号,看起来像 Python 字典写法。
我以为把temperature拉到 0.1 就能消灭这种随机性。在深度学习入门之前,我的认知是:temperature越低,模型越“确定”,就越不会放飞自我。所以我做了第一个 A/B 测:
| 配置 | 合法 JSON 率 | 平均时延 |
|---|---|---|
| temperature=0.8, 基础 prompt | 61% | 1.8s |
| temperature=0.1, 基础 prompt | 63% | 2.9s |
合法率几乎没动,时延反而拉长了。这说明输出格式是否合规,和采样温度根本不是同一层的问题。真正的根因,是我从来没有把输出的“结构”写进模型的生成约束里--温度只能影响 token 选择的随机性,却无法阻止模型在 JSON 里埋一颗语法炸弹。
我当时并不知道这些,直到在深度学习入门的课程里看到“带约束的序列生成”小节,才弄明白模型输出的 token 序列背后,还有一条我没用上的控制路径。
打脸时刻:我以为 prompt 越严厉越好,其实越“死板”越易翻
第二版我走向了另一个极端。既然“必须返回 JSON”说得不够狠,我就加码:
你是一个不能出错的结构化输出机器人。 你的回复只能包含一个 JSON 对象,不能有任何额外字符。 JSON 必须包含字段:intent, sentiment, summary, transfer。 如果用户问题不完整,也必须返回一个 JSON,其中 transfer 为 false。 绝对不要添加任何说明、前缀或后缀。 {"intent": "退换货", "sentiment": "生气", "summary": "客户要求退换货", "transfer": true}这套 prompt 比第一版长了近一倍。结果?前导文字少了,但JSON 内部字符串的非法字符、不闭合的括号、多出来的逗号反而更频发。我陷入了典型的“过度指令陷阱”--模型为了满足“只能是 JSON”的强度约束,开始像背答案一样套一个模板,一旦用户输入稍微偏离训练数据的分布,它就在 JSON 内部犯错。
我后来复盘时才意识到,这就是没有理解模型生成机制的下场。深度学习入门里有一个小节讲到,Transformer 解码器每一步都是基于先前已生成的 token 计算下一个 token 的概率分布;你给的指令越极端,模型的注意力越集中在“看起来像 JSON”的形态上,而不是“语法正确的 JSON”。真正锁死格式的方法,不应该在 prompt 里跟模型比嗓门,而是把格式约束从 prompt 层剥离,交给输出 schema 或约束解码。
于是我开始重写第三版 prompt,并把 few-shot 模板从 1 个扩展到 3 个,加入角色设定和输出格式声明。代码变长了,但逻辑反倒清晰了:
prompt_v3 = f""" [角色] 你是一个严格的客服分析器,只输出合法 JSON。 [任务] 根据用户消息,输出一个 JSON 对象,字段定义如下: - intent: string, 用户意图(退换货/咨询/投诉/其他) - sentiment: string, 用户情绪(生气/着急/平静/高兴) - summary: string, 一句话摘要 - transfer: boolean, 是否需要转人工 [输出规则] 1. 只输出 JSON,不添加任何前缀、后缀或注释。 2. 字符串内如包含双引号,必须用反斜杠转义。 3. 不要输出尾随逗号。 [示例1] 用户:东西坏了,我要退钱! 输出:{{"intent": "退换货", "sentiment": "生气", "summary": "客户要求退款", "transfer": true}} [示例2] 用户:这个怎么用? 输出:{{"intent": "咨询", "sentiment": "平静", "summary": "客户询问使用方法", "transfer": false}} [示例3] 用户:你们客服态度太差了,叫你们经理! 输出:{{"intent": "投诉", "sentiment": "生气", "summary": "客户投诉服务态度", "transfer": true}} [现在] 用户:{user_input} 输出: """但关键不止 prompt。深度学习入门课程里那节“结构化输出与受控生成”让我学会了在调用 API 时传入response_format参数或使用强制语法约束器。这样一来,prompt 只负责意图和内容,schema 负责格式--分工清晰,翻车率直线下降。
突破点:把 schema 写进协议,而不是写进“威胁”
改完第三版 prompt 后,我跑了一个有 500 条真实客服对话的评测集,每条生成 5 次取多数结果。同时我把温度调回 0.7,让模型有一定的表达弹性。结果如下:
| 版本 | 合法 JSON 率 | 意图识别准确率 | 转人工误判率 |
|---|---|---|---|
| 第 1 版 (基础 + temp 0.1) | 63% | 74% | 18% |
| 第 2 版 (严厉 prompt + temp 0.1) | 67% | 71% | 22% |
| 第 3 版 (few-shot + schema) | 97% | 89% | 6% |
提升肉眼可见。而真正让我理解这 97% 为什么能从 63% 跃迁的,是深度学习入门课程里对“注意力机制”和“生成多样性-质量权衡”的讲解。它帮我建立起了一个关键直觉:你想控制模型的输出结构,就要控制它每一步可以选择的 token 范围,而不是在 prompt 里赌它会不会听你的话。
我在公司内部做的分享里画了一张对比图,把“粗暴调 temperature”和“加输出 schema”两种策略画成两条曲线--前者是一条几乎平躺的直线,后者在加入 few-shot 和 schema 后陡峭拉升。当时有同事问我,“那为什么一开始不直接学这些?”。我说白了,我之前根本不知道深度学习入门里有这些东西--我以为这类课程只会教反向传播和卷积网络,没想到连生成式 AI 提示词工程这一层的底层原理都拆得很干净。
A/B 测试框架:怎样系统化地迭代 prompt
踩完这次坑,我把 prompt 迭代流程固化成了一套 A/B 测试小框架,直接嵌进了 CI(持续集成)流程里。每次改 prompt 都会自动跑一次评测集并生成报告。代码骨架大概长这样:
import json import time from my_llm_client import ModelAPI # 封装过的调用客户端 def evaluate_prompt(prompt_template, test_cases, n_samples=3): results = [] for case in test_cases: user_input = case['input'] expected_intent = case['intent'] for _ in range(n_samples): prompt = prompt_template.format(user_input=user_input) raw_output = ModelAPI.generate(prompt, temperature=0.7, max_tokens=200, response_format='json_object') # schema 约束 parsed = parse_json_safe(raw_output) results.append({ 'case_id': case['id'], 'valid_json': parsed is not None, 'intent_match': parsed.get('intent') == expected_intent if parsed else False }) valid_rate = sum(r['valid_json'] for r in results) / len(results) intent_acc = sum(r['intent_match'] for r in results if r['valid_json']) / max(sum(r['valid_json'] for r in results), 1) return valid_rate, intent_acc # 伪评测集 test_cases = [ {'id': 1, 'input': '我要退货,货物损坏了', 'intent': '退换货'}, {'id': 2, 'input': '你们怎么一直不发货', 'intent': '咨询'}, # ... 实际会放几百条 ] prompt_v3 = """[角色]...(上文模板)""" valid, acc = evaluate_prompt(prompt_v3, test_cases, n_samples=5) print(f"JSON合法率: {valid:.2%}, 意图准确率: {acc:.2%}")这套框架的可贵之处是:每次模型厂商更新版本,我可以一键跑全量回归;遇到新的边缘 case,直接把 example 加进测试集,再用生成式 AI 的思路去扩展 few-shot 模板。我后来把这段经验写进了团队的技术文档里,标题就叫《Prompt 工程的尽头不是调参,是构建测试闭环》。
学完后的变化:从调参侠到能讲清“为什么”
在系统学过深度学习入门之前,我对模型的理解停留在“输入文本,输出文本,中间是个黑盒”。遇到问题时,我能做的只有三件事:加 prompt、调温度、换更贵的模型。结果就是老板问我“为什么第三版 prompt 比第二版好”,我只能含糊其辞。
学完课程之后,变化最明显的是两点:
- 排查问题的速度:之前一个 JSON 解析错误我要翻日志、改 prompt、重跑,平均耗时 40 分钟;现在我可以先判断是“格式指令未生效”还是“模型生成了不在 token 允许集中的字符”,10 分钟定位问题,15 分钟修掉。工时缩小了 60%。
- 可复用的技能树:这套 prompt 方法论迁移到邮件摘要、合同关键字段抽取、代码评审输出等场景都很顺滑。因为万变不离其宗--你总要理解模型如何选择 token,才知道在哪里加约束。这种基础能力,恰恰是亚马逊云科技机器学习类课程里反复强化的点,也是很多做生成式 AI 应用的同行最容易跳过的部分。
我甚至开始试着教团队里新来的毕业生:你先别急着写 prompt,先用机器学习基础那门课把特征到模型输出的链路搞明白,再去看生成式 AI 的提示词技巧,这样你写的 prompt 每一次改动都有据可查。
给同样处境的人的建议
如果你也正为「模型输出的 JSON 永远不对」「加了 30 句要求还是乱来」这种事头疼,下面几条是我用实实在在的出包事故换来的建议:
- 永远先检查输出格式约束机制,而不是调 temperature。生成式 AI 的 API 大多都提供了
response_format或类似 schema 参数,这比任何 prompt 里的“必须”都管用。 - few-shot 示例不要只给一个,给 2~3 个能覆盖不同边界情形的例子,且示例 JSON 要严格符合自己的 schema 定义。
- 搭建评测闭环:写个 Python 脚本,每次改 prompt 或模型版本都自动跑一遍评测集,量化 JSON 合法率和业务指标。评测集要持续从线上 badcase 中补充。
- 花一个周末补一下深度学习入门里的序列生成原理,你会发现自己以前对 prompt 的理解可能只停留在表象。亚马逊云科技的深度学习入门课不要求你手写反向传播,但把注意力机制、解码策略、约束采样这些概念讲得非常落地,看完就能用。
- 警惕“过度指令陷阱”:prompt 越长不一定越好,把格式约束和内容约束分开,分别由 schema 和 prompt 各司其职。
- 团队内建立共享 prompt 库,要求每条 prompt 附带评测结果和迭代日志,避免每个人各自拍脑袋。AI 编程助手比如 CodeWhisperer 在写评测脚本时能帮你省掉大量样板代码,值得用起来。
- 如果时间有限但想快速了解生成式 AI 的落地边界,可以先看面向高管的生成式 AI 这门短课,它会帮你建立业务视角;然后再扎进深度学习入门去啃技术细节,这样学习路径更高效。
最后想说:prompt 工程的乐趣,不在于堆砌魔法词汇,而在于你终于能解释清楚为什么这一版比上一版好。这种掌控感,是学完深度学习入门之后才真正出现的。