大模型输出格式化实战:如何用 Prompt + 后处理让 LLM 稳定输出规范数据

3 阅读4分钟

📌 痛点:模型输出太自由,后端解析直接崩溃

在用 LLM 做文本分类或信息抽取时,最让人头疼的不是准确率,而是输出格式不可控

你可能会遇到这些情况:

  • 让模型输出“基础层”,它偏偏输出“我认为这属于基础层。”
  • 让模型输出逗号分隔的关键词,它给你一行一个,或者用中文逗号、英文逗号混着来。
  • 让模型输出不超过5个字,它给你整出一句话。

如果你的后端代码用正则或 split 来解析,程序就直接崩了。

我在用 DeepSeek 和 OpenAI 处理 14,000 条地铁反馈时,就彻底解决了这个问题。这篇文章分享一套“Prompt 设计 + 参数调优 + 后处理兜底”的组合拳,让你的大模型输出像函数返回值一样稳定。

🎯 目标:让模型只输出“列车空调”,而不是“空调太冷了希望改进”

以情感因素提取为例,我的需求是:输入乘客反馈,输出 1-3 个 3-7 汉字的核心服务要素,用中文逗号分隔

期望输出示例:

列车空调,车厢拥挤

现实中的“自由发挥”:

好的,为您提取的服务要素是:列车空调、车厢拥挤。

或者:

1. 列车空调
2. 车厢拥挤

🛠️ 第一道防线:Prompt 中的“格式强约束”

我把约束直接写进了 System Prompt,并且用“核心原则”的方式反复强调:

system_content = """你作为南京地铁服务分析专家,请严格从乘客反馈中提取南京地铁的具体服务特征要素。请遵守:

核心原则:
1. 所有输出关键词必须直接指向南京地铁的具体属性/服务/特征
2. 表面现象→本质问题:将乘客的具体遭遇映射到南京地铁的可改进点
3. 采用最精简的名词短语(3-5个汉字)
4. 禁止任何修饰词和情感词

输出要求:
- 每个要素3-7个汉字
- 用中文逗号分隔,不要解释"""

关键技巧

  • 用“禁止”比用“请勿”更强硬:模型更倾向于遵守否定式指令。
  • 明确分隔符:“用中文逗号分隔”比“用逗号分隔”更精确,避免中英文标点混用。
  • 禁止解释:这句话能省下大量输出 token,并且防止模型在前面加废话。

🎯 第二道防线:Few-Shot 示例比规则更有效

LLM 是“模仿大师”,给它看标准答案比给它念规则管用得多。我在 User Prompt 中加入了“转化示例”:

user_content = f"""
【转化示例】
 输入:"坐南京地铁十几年了真有感情"
 → 输出:地铁历史

 输入:"毕业季的车厢装饰太浪漫了"
 → 输出:毕业季装饰

 输入:"列车空调冷得像冰窖"
 → 输出:列车空调

 输入:"安检员帮我找回了钱包"
 → 输出:安检服务

【当前任务】
请提取1-3个南京地铁具体服务要素:{text}
"""

为什么有效?

  • 示例展示了输入和输出的直接映射,没有中间分析过程(分析过程在 System Prompt 里)。
  • 示例覆盖了不同场景:隐晦表达、正面评价、负面抱怨、服务互动。

⚙️ 第三道防线:参数调优——用低温度锁死随机性

即使 Prompt 完美,模型仍可能“抽风”。参数设置是最后一道软件层面的防线。

response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=messages,
    temperature=0.1,  # 极低温度,确保输出稳定
    max_tokens=50      # 限制输出长度,防止模型“发挥”
)
  • temperature=0.1:分类和抽取任务不需要创造性,越低越稳定。我测试过,从 0.3 降到 0.1 后,格式违规率下降了约 40%。
  • max_tokens=50:一个要素 5 个字,3 个要素加逗号最多 20 字。给 50 token 的冗余足够,但又不至于让模型长篇大论。

🧹 第四道防线:后处理——代码层面的最终兜底

即使前三道防线做到极致,偶尔仍会有“漏网之鱼”。这时就需要在代码里做防御性解析

result = response.choices[0].message.content.strip()

# 1. 统一分隔符:中文逗号 → 英文逗号
result = result.replace(",", ",")

# 2. 按逗号分割
factors = [f.strip() for f in result.split(",")]

# 3. 过滤掉长度不符合要求的(2-7个汉字)
factors = [f for f in factors if 2 <= len(f) <= 7]

# 4. 如果过滤后为空,返回默认值
return factors if factors else ["无法提取"]

为什么这样做?

  • 统一分隔符后,无论模型输出的是“A,B”还是“A,B”,都能正确分割。
  • 长度过滤能去掉“好的”这类残留词。
  • 返回默认值保证程序不会因为空列表而报错。

💡 总结:格式化输出的四层防御体系

防线方法作用
第一层System Prompt 强约束告诉模型“必须怎样”和“禁止怎样”
第二层Few-Shot 示例用标准答案引导模型模仿
第三层低 temperature + max_tokens从参数层面锁定输出稳定性
第四层代码后处理最终兜底,确保程序不崩溃

这套组合拳下来,我在 14,000 条数据上的格式合规率达到了 99% 以上。

🔗 完整代码

完整实现已开源在 GitHub:
👉 nanjing-metro-analysis/scripts/03_topic_extraction/extract_factors.py