输出格式控制
3693 字约 12 分钟
domain/aiai/promptai/prompt-engineering
2026-07-24
输出格式控制是让 LLM 从"聊天工具"变成"生产工具"的关键一跳——当输出可解析、可断言、可组合时,AI 才能嵌入真正的工程系统。
1. 核心问题
输出格式控制试图回答:
- 如何让模型稳定输出指定格式(JSON / Markdown / XML / CSV),即使面对复杂输入?
- 为什么同样的格式指令,不同模型的表现差异巨大?
- 如何在"格式严格"和"内容质量"之间找到平衡?
- 当格式控制失败时,如何检测、降级和修复?
2. 基本概念
格式控制(Format Control):通过 Prompt 指令或 API 参数,约束 LLM 输出遵循预定义的结构和格式。
格式化输出(Structured Output):确保模型输出具有可预测的结构,使其可以被程序直接解析和处理。
约束解码(Constrained Decoding):在模型生成阶段强制约束 token 输出空间,确保输出在语法层面即符合格式要求。相比 Prompt 层面的"软约束",约束解码是"硬约束"——模型不可能生成不符合格式的内容。
分隔符(Delimiter):用于在文本中标记不同内容区域的特殊符号序列。让模型和下游程序都能准确识别内容的边界。
格式漂移(Format Drift):在长对话或多次生成中,模型输出的格式逐渐偏离初始要求。尤其是在处理复杂嵌套结构时容易发生。
3. 输出格式控制方法总览
| 方法 | 控制强度 | 可靠性 | 实现难度 | 适用场景 |
|---|---|---|---|---|
| Prompt 示例引导 | 低 | 低 | 极低 | 简单格式、对话场景 |
| 格式模板 | 中 | 中 | 低 | Markdown 报告、标准化输出 |
| 分隔符 + 分块 | 中 | 中高 | 低 | 多段复杂输出、后处理解析 |
| JSON Schema 定义 | 高 | 高 | 中 | API 集成、自动化 Pipeline |
| Structured Output API | 极高 | 极高 | 低 | 生产级 JSON 输出(OpenAI) |
| 约束解码(Outlines/Instructor) | 极高 | 极高 | 中 | 企业级、复杂 Schema 场景 |
4. JSON Schema 定义与约束生成
4.1 JSON Schema 基础
JSON Schema 是描述 JSON 数据结构的标准规范,用于约束模型输出的 JSON 字段名称、类型、取值范围和嵌套关系。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"summary": { "type": "string", "description": "分析摘要" },
"risk_level": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "风险评级"
},
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"description": { "type": "string" },
"affected_component": { "type": "string" }
},
"required": ["id", "description"]
}
}
},
"required": ["summary", "risk_level", "findings"]
}4.2 Schema 设计原则
字段名用 snake_case 或 camelCase,保持一致。建议使用小写 + 下划线(snake_case),模型幻觉率最低。
使用 enum 限制取值空间。凡是取值有限的字段(如状态、级别、类别),都用 enum 而非自由字符串,大幅降低无效输出概率。
必填字段标注 required。只标记真正必需的字段,避免过度约束导致模型"为了填而填"。
description 提供语义提示。每个字段的 description 不仅帮助人理解,也帮助模型理解该填什么:
{ "type": "string", "description": "用一句话总结分析结果,不超过 50 字" }避免深度嵌套(超过 3 层)。模型对深层嵌套 JSON 的遵循率显著下降。如果业务需要复杂结构,考虑拆分多个 Schema 分段生成。
4.3 在 Prompt 中使用 JSON Schema
请以 JSON 格式输出分析结果,严格遵循以下 Schema:
```json
{
"type": "object",
"properties": {
"analysis": { "type": "string" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"tags": { "type": "array", "items": { "type": "string" } }
},
"required": ["analysis", "score", "tags"]
}在输出 JSON 两端使用 json 和 标记,不要包含其他内容。
### 4.4 OpenAI Structured Output 模式
OpenAI 的 `response_format` 参数(`response_format: { "type": "json_object" }` 或 `response_format: { "type": "json_schema", ... }`)是当前最可靠的 JSON 输出方案之一。
```python
response = client.chat.completions.create(
model="gpt-4o",
response_format={
"type": "json_schema",
"json_schema": {
"name": "analysis_result",
"schema": { /* JSON Schema */ }
}
},
messages=[...]
)优势:模型在 token 层面被约束只能生成合法 JSON,格式遵循率接近 100%。
局限性:
- 不适用于非 JSON 格式需求(Markdown/XML)
- Schema 变更后需要重新配置
- 增加少量推理延迟(约束解码的开销)
- 仍然需要 Schema 设计合理——"合法 JSON"不意味着"业务正确的 JSON"
4.5 约束解码方案(Outlines / Instructor)
Outlines(Python 库):通过正则表达式、JSON Schema 或 Pydantic 模型约束模型的 token 生成空间。
from outlines import models, generate
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")
generator = generate.json(model, MyPydanticModel)
result = generator(prompt)Instructor(Python 库):在 API 调用层封装,通过 Pydantic 模型自动生成 Schema、解析输出并重试。
import instructor
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
class Analysis(BaseModel):
summary: str
score: int
resp = client.chat.completions.create(
model="gpt-4o",
response_model=Analysis,
messages=[...]
)对比:
| 方案 | 控制位置 | 可靠性 | 灵活性 | 适用模型 |
|---|---|---|---|---|
| Prompt 式 | 输入 | 低 | 高 | 所有模型 |
| OpenAI Structured Output | API 层 | 极高 | 中 | GPT-4o 及以上 |
| Outlines | 解码层 | 极高 | 高 | 本地模型 |
| Instructor | API 层 | 高 | 高 | 所有 API 模型 |
5. Markdown / Table / List 格式控制
5.1 Markdown 结构控制
标题层次:明确说明用几级标题、什么内容放什么层级。
输出结构要求:
# 核心结论(H1)
## 背景(H2)
## 分析(H2)
### 定量分析(H3)
### 定性分析(H3)
## 建议(H2)代码块:指定代码块的包裹方式和语言标注。
所有代码示例使用 ```language 包裹。
语言标注必须是小写:```python ``` ```typescript ``` ```bash强调与列表:明确使用场景。
- 关键数据使用 **粗体** 标注
- 不确定的信息使用 *斜体* 标注
- 对比信息使用 Markdown 表格
- 步骤使用有序列表 1. 2. 3.
- 并列信息使用无序列表 - -5.2 表格格式控制
表格是 LLM 输出中最容易"跑偏"的格式之一——列不对齐、分隔符缺少、内容超出单元格是常见问题。
最佳实践:
先定义表头,再给示例行。
请以表格形式输出对比结果,格式如下: | 特性 | 方案A | 方案B | 方案C | |------|-------|-------|-------| | 性能 | 高 | 中 | 低 |限制列数。超过 5 列的表格模型容易错位,建议拆分为多表或用其他格式。
调整行用列表代替表格。如果数据行数超过 10 行,用列表比表格更稳定。
## 方案对比 - **方案A**(性能高/成本中/风险低):适合核心业务 - **方案B**(性能中/成本低/风险中):适合辅助业务 - **方案C**(性能低/成本高/风险高):暂不推荐表格中避免嵌套格式。Markdown 表格单元格中应避免再使用列表或代码块,模型的遵循率会急剧下降。
5.3 列表格式控制
有序 vs 无序:明确使用场景。
- 需要按优先级排序 → 有序列表
- 不需要排序 → 无序列表
- 需要包含子项 → 嵌套无序列表列表一致性:指定每个列表项的格式模板。
每个列表项格式:[动词] + [宾语] + ([预期效果])
示例:
- 重构支付模块(提升吞吐量 30%)
- 优化数据库索引(查询延迟降低 50%)6. 分隔符策略
分隔符是格式控制中最简单但最有效的技巧之一。
6.1 常用分隔符类型
| 类型 | 符号 | 适用场景 |
|---|---|---|
| Markdown 分隔线 | --- / *** | 模块划分 |
| XML 标签 | <analysis>...</analysis> | 多段复合输出 |
| 代码块 | | 代码/JSON/YAML 输出 |
| 自定义标记 | [START]/[END] | 程序解析 |
| JSON Lines | 每行一个 JSON | 流式/批量输出 |
6.2 XML 标签分隔法
XML 标签是最通用的分隔方案——模型对标签结构的理解优于纯文本分隔。
请按以下格式输出:
<analysis>
在此输出分析过程
</analysis>
<conclusion>
在此输出最终结论
</conclusion>
<next_steps>
在此输出后续步骤
</next_steps>优点:
- 模型对 XML 标签的遵循率高
- 嵌套标签模型也能理解(
<section><subsection>) - 下游解析容易(正则 / XML Parser)
注意事项:
- 标签名用简单英语单词(analysis / summary / code 等),避免缩写或编号(s1 / s2)
- 标签闭合必须匹配,模型有时会漏掉闭合标签——考虑在后处理中做容错
- 内容中不应包含跟标签相同的文本,否则解析会混淆
6.3 结构标签与验证
import re
def extract_section(text: str, tag: str) -> str | None:
pattern = f"<{tag}>(.*?)</{tag}>"
match = re.search(pattern, text, re.DOTALL)
return match.group(1).strip() if match else None6.4 强制终止符
模型有时在输出完成后还会继续"补充说明"。使用强制终止符可以控制输出边界。
请在回答末尾输出「---END---」,不要输出任何在此标记之后的内容。7. Structured Output vs 约束解码
| 对比维度 | Structured Output(OpenAI) | 约束解码(Outlines/Instructor) |
|---|---|---|
| 控制位置 | API 层(API 内部实现约束解码) | 解码层或 API 层 |
| 格式支持 | 仅 JSON | JSON / Markdown / Regex / Pydantic |
| 可靠性 | 极高(官方保证) | 高(取决于实现) |
| 成本 | 按 token 计费 | 零额外费用(本地模型)或 API 调用费 |
| 适用模型 | OpenAI 系列 | 任意模型(本地/LM Studio/OpenAI) |
| 自定义格式 | 受限于 Schema 定义 | 高度灵活(支持任意语法约束) |
| 错误处理 | API 返回错误格式则抛异常 | 支持重试逻辑 |
| 流式输出 | 支持 | 部分支持 |
选择建议:
- 使用 OpenAI 且只需要 JSON → Structured Output
- 使用本地/自部署模型 → Outlines
- 需要复杂重试逻辑 + Pydantic 模型 → Instructor
- 需要非 JSON 格式(Markdown/Text)→ 约束解码(其实超出了这两个范畴,用 Prompt 控制)
8. 格式失败模式与应对
常见失败模式
| 失败模式 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
| JSON 解析失败 | 缺少逗号/花括号不闭合 | Schema 过于复杂或嵌套过深 | 简化 Schema,不要超过 3 层嵌套 |
| 字段遗漏 | 缺少 required 字段 | Schema 中字段太多模型忘记 | 减少字段数,required 控制在 5 个以内 |
| 枚举值错误 | 输出 enum 外的值 | 枚举列表过长 | 限制 enum 不超过 10 个选项 |
| 表格错位 | 列数/对齐异常 | 内容中有特殊字符 | 在表格中少用特殊符号和长文本 |
| 标签不闭合 | <tag>内容</tag> | 标签嵌套过多 | 避免嵌套标签,使用平行标签 |
| 转义错误 | 字符串中引号未转义 | 内容包含 JSON 特殊字符 | 在 Prompt 中要求正确转义 |
| 格式漂移 | 前几轮格式正确,后面逐渐偏离 | 长对话注意力衰减 | 每轮重申格式要求,或在每轮 User Prompt 中重复格式指令 |
8.1 检测策略
| 检测方法 | 实现方式 | 适用格式 |
|---|---|---|
| JSON.parse | 尝试解析,失败则标记 | JSON |
| Schema 校验 | 使用 jsonschema / ajv 等库 | JSON |
| 正则检查 | 检查标签完整性 | XML / 自定义分隔符 |
| LLM-as-Judge | 用另一模型判断格式正确性 | 任意格式 |
8.2 降级策略
当格式控制失败时,按以下优先级降级:
L1 重试 ← 重新调用模型(带格式修正指令)
L2 纠正 ← 用后处理脚本修复格式问题(如补全缺失的 JSON 括号)
L3 降级 ← 退回到非格式化输出并记录日志
L4 告警 ← 触发人工干预8.3 容错后处理示例
import json, re
def safe_json_parse(text: str):
"""容错的 JSON 解析:尝试提取可能 JSON 片段"""
# 尝试直接解析
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# 提取 ```json ... ``` 中的内容
match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', text, re.DOTALL)
if match:
try:
return json.loads(match.group(1))
except json.JSONDecodeError:
pass
# 提取最外层 {} 中的内容
match = re.search(r'\{.*\}', text, re.DOTALL)
if match:
try:
return json.loads(match.group(0))
except json.JSONDecodeError:
pass
return None9. 模型对格式的理解差异
不同模型对格式控制指令的理解和遵循能力差异显著。了解这些差异有助于针对性地设计 Prompt。
| 模型 | JSON 遵循率 | 嵌套处理 | 表格控制 | Schema 理解 | 备注 |
|---|---|---|---|---|---|
| GPT-4o | 极高 | 强 | 强 | 强 | 支持 Structured Output |
| Claude 3.5 Sonnet | 高 | 强 | 强 | 强 | XML 标签最佳 |
| Gemini 2.0 Pro | 高 | 中 | 中 | 中 | 大上下文是优势 |
| DeepSeek-V3 | 中高 | 中 | 中 | 中 | 较长 Schema 偶尔遗漏字段 |
| Qwen 2.5 (72B) | 中高 | 中 | 中 | 中 | 中文格式控制好 |
| Llama 3.1 (70B) | 中 | 弱 | 中弱 | 弱 | 需要更细致的 Prompt 设计 |
小型模型 (<7B) | 低 | 极弱 | 极弱 | 极弱 | 建议用约束解码而非 Prompt |
实用建议:
- 高能力模型:使用简洁的格式指令,模型能理解意图
- 中能力模型:使用示例模板,少用抽象描述(不用"使用 JSON 输出",用"请输出 { field: value } 格式")
- 低能力模型:使用约束解码(Outlines),放弃 Prompt 层面的格式控制
- 所有模型:格式复杂度与模型能力成正比——简单格式(List/Table)适合所有模型,复杂格式(4+ 层嵌套 JSON)只有顶级模型能稳定输出
10. 实战模板
模板一:JSON 输出
请分析以下内容,以 JSON 格式输出。
```json
{
"type": "object",
"properties": {
"summary": { "type": "string", "description": "一句话总结,不超过 60 字" },
"key_points": { "type": "array", "items": { "type": "string" }, "description": "3-5 个关键点" },
"sentiment": { "type": "string", "enum": ["positive", "negative", "neutral"] }
},
"required": ["summary", "key_points", "sentiment"]
}只输出 JSON,不输出其他内容。
### 模板二:XML 分段输出请按以下结构输出:
```模板三:Markdown 报告
请以 Markdown 格式输出分析报告:
# 报告标题
## 背景
(说明分析背景和范围,100-150 字)
## 核心发现
(3-5 个发现,每个用 **粗体** 标注关键数据)
## 详细分析
### 类别一
(文字分析 + 数据表格)
### 类别二
(文字分析 + 数据表格)
## 建议
(有序列表,按优先级排序)
## 附录
(数据来源、方法论说明等)模板四:分页输出(处理长内容)
请分页输出,每页不超过 500 字。
页面格式:
<page>
<page_number>1</page_number>
<content>
(第 1 页内容)
</content>
<continue>yes</continue>
</page>
输出完当前页后,如果还有内容,输出下一个 <page>;如果已经完成,最后一页的 <continue> 设为 no。11. 关联笔记
- System Prompt设计 — System Prompt 中的格式控制体系
- Structured Output结构化输出 — 结构化解码方案的深入介绍
- 角色设定与任务设定 — 任务设定中的格式约束设计
- Prompt模板库 — 包含完整角色 + 任务 + 格式控制的 Prompt 模板
- Prompt反模式 — 格式控制中常见的错误
- AI产品Prompt — 产品级 Prompt 的格式控制方案
- Prompt Engineering方法论 — 格式控制在整体 Prompt 工程中的位置