Prompt Engineering进阶:用结构化指令约束大模型输出的5种策略
一、引言:从“聊天”到“工程”的范式转换
许多初入大模型应用开发的工程师,对Prompt Engineering的理解停留在“写一段自然语言描述,让模型照着做”的层面。当业务场景从简单的问答演进到结构化数据提取、API参数生成、多步推理等复杂任务时,这种“软约束”方式的脆弱性就暴露无遗——同样的Prompt,换一个模型版本输出格式就变了;加一句约束,模型在其他维度上的表现就开始漂移。
问题的根源在于:自然语言指令本质上是一种软约束,模型可以“理解”,也可以“遗忘”。 当我们需要模型输出严格符合Schema的数据、执行确定性的业务逻辑、或者遵循多层次的规则时,就必须引入结构化的约束手段,把“模型自由发挥的空间”压缩到最小。
本文将系统讲解5种经过生产验证的结构化约束策略,每一种都配有可运行的代码和真实的踩坑记录。
二、策略一:Markdown结构模板——让模型“填空”,而非“作文”
2.1 核心思想
大多数模型在预训练阶段已经深度学习了Markdown语法。利用这一特性,我们可以给模型提供一个带占位符的Markdown模板,要求模型按照模板结构“填空”,而不是自由生成Markdown。
本质区别:自由生成的Markdown,模型可能新增章节、调整顺序、或者遗漏字段;而填空模式下,模型的任务被简化为“在每个固定的标题下填入内容”,结构稳定性大幅提升。
2.2 实战模板设计
以智能客服的问题分析报告为例:
# 用户问题分析报告
## 问题分类
[请填写:售后/物流/产品使用/活动咨询/投诉/其他]
## 紧急程度
[请填写:高/中/低]
## 关键实体提取
- 订单号:[请填写订单号,若无则填"无"]
- 产品名称:[请填写产品名称,若无则填"无"]
- 用户诉求:[请用一句话概括用户的核心诉求]
## 历史工单关联
[请填写:是否有相似历史工单,若有则填写工单编号]
## 推荐解决方案
[请填写:1-3条具体建议,每条以"- "开头]
## 转人工建议
[请填写:是/否,若填"是"需说明原因]
关键设计原则:
- 每个占位符都有默认值(如“无”),防止模型留空
- 枚举字段列出所有可选值,消除歧义
- 输出长度有隐性约束(如“1-3条”)
2.3 代码实现
from openai import OpenAI
import re
client = OpenAI(api_key="your-api-key")
def analyze_with_template(user_query: str) -> dict:
"""使用Markdown模板进行结构化输出"""
template = """
# 用户问题分析报告
## 问题分类
[请填写:售后/物流/产品使用/活动咨询/投诉/其他]
## 紧急程度
[请填写:高/中/低]
## 关键实体提取
- 订单号:[请填写订单号,若无则填"无"]
- 产品名称:[请填写产品名称,若无则填"无"]
- 用户诉求:[请用一句话概括用户的核心诉求]
## 历史工单关联
[请填写:是否有相似历史工单,若有则填写工单编号]
## 推荐解决方案
[请填写:1-3条具体建议,每条以"- "开头]
## 转人工建议
[请填写:是/否,若填"是"需说明原因]
"""
system_prompt = f"""你是一个用户问题分析专家。
请根据用户问题,严格按照以下Markdown模板填写分析报告。
【重要】:
1. 不要修改模板结构,不要新增章节,不要删除章节
2. 只在每个章节的[请填写...]位置填入内容
3. 保持Markdown格式不变
4. 如果信息不足,填写"信息不足"而非猜测
模板内容如下:
{template}
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"用户问题:{user_query}"}
],
temperature=0.1
)
output = response.choices[0].message.content
# 解析Markdown为结构化数据
return parse_markdown_to_dict(output)
def parse_markdown_to_dict(markdown_text: str) -> dict:
"""从Markdown文本中提取结构化字段"""
result = {}
# 提取问题分类
category_match = re.search(r'## 问题分类\s*\n\s*\[?([^\]]+)\]?', markdown_text)
if category_match:
result['category'] = category_match.group(1).strip()
# 提取紧急程度
urgency_match = re.search(r'## 紧急程度\s*\n\s*\[?([^\]]+)\]?', markdown_text)
if urgency_match:
result['urgency'] = urgency_match.group(1).strip()
# 提取订单号
order_match = re.search(r'-\s*订单号:\[?([^\]]+)\]?', markdown_text)
if order_match:
result['order_id'] = order_match.group(1).strip()
# 提取用户诉求
demand_match = re.search(r'-\s*用户诉求:\[?([^\]]+)\]?', markdown_text)
if demand_match:
result['demand'] = demand_match.group(1).strip()
# 提取转人工建议
escalate_match = re.search(r'## 转人工建议\s*\n\s*\[?([^\]]+)\]?', markdown_text)
if escalate_match:
result['escalate'] = escalate_match.group(1).strip()
return result
# 使用示例
if __name__ == "__main__":
query = "我前天买的那个电饭煲,今天收到发现盖子合不上,能换吗?"
result = analyze_with_template(query)
print(result)
# 输出示例:
# {
# "category": "售后",
# "urgency": "高",
# "order_id": "信息不足",
# "demand": "用户希望更换有质量问题的电饭煲",
# "escalate": "是,涉及退换货需人工处理"
# }
2.4 进阶技巧:XML标签模板
如果Markdown模板在某些模型上表现不稳定,可以改用XML标签作为结构边界。XML的层级结构更明确,模型在预训练中对XML的格式敏感性也足够强。
XML_TEMPLATE = """
<analysis>
<category></category>
<urgency></urgency>
<entities>
<order_id></order_id>
<product_name></product_name>
<user_demand></user_demand>
</entities>
<solution></solution>
<escalate></escalate>
</analysis>
"""
经验值:GPT-4系列对Markdown格式的遵从度在95%以上,而一些开源模型(如Qwen-7B)对XML格式更敏感。建议根据具体模型做A/B测试。
三、策略二:JSON Schema强制约束——函数调用范式的“平替”
3.1 核心思想
当我们需要模型输出严格的JSON格式时,最理想的方式是使用模型的**函数调用(Function Calling)**能力。但并非所有模型都支持函数调用,且函数调用的参数Schema在某些场景下不够灵活。
一种通用的替代方案是:在系统提示词中嵌入完整的JSON Schema,并配合低温度参数,强制模型输出符合Schema的JSON。
3.2 实战:电商订单信息提取
from openai import OpenAI
import json
from typing import Optional
client = OpenAI(api_key="your-api-key")
class OrderInfoExtractor:
"""订单信息提取器 - 基于JSON Schema强制约束"""
JSON_SCHEMA = {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,格式为ORD-YYYYMMDD-XXXX,若未提及则填null"
},
"customer_name": {
"type": "string",
"description": "客户姓名"
},
"phone": {
"type": "string",
"description": "手机号码,11位数字"
},
"order_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"quantity": {"type": "integer"},
"price": {"type": "number"}
},
"required": ["product_name", "quantity"]
}
},
"issue_type": {
"type": "string",
"enum": ["质量问题", "物流问题", "退换货", "取消订单", "其他"],
"description": "问题类型"
}
},
"required": ["order_id", "issue_type"]
}
def extract(self, user_query: str) -> dict:
system_prompt = f"""
你是一个订单信息提取助手。
请从用户问题中提取订单相关信息,并严格按照以下JSON Schema输出。
【硬性约束】:
1. 输出必须是合法的JSON,不要包含任何其他文字或Markdown标记
2. 所有字段必须符合Schema定义
3. 如果字段缺失,填null而非空字符串
4. 如果用户未提及任何订单信息,issue_type仍必须填写,可选"其他"
Schema定义:
```json
{json.dumps(self.JSON_SCHEMA, ensure_ascii=False, indent=2)}
再次强调:只输出JSON,不要加"```json"或任何解释文字。
“”"
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_query}
],
temperature=0.0 # 零温度,最大确定性
)
raw_output = response.choices[0].message.content
# 清理可能的Markdown标记
raw_output = re.sub(r'^```json\s*', '', raw_output)
raw_output = re.sub(r'\s*```$', '', raw_output)
try:
return json.loads(raw_output)
except json.JSONDecodeError as e:
# 降级处理:尝试用正则提取
return self._fallback_parse(raw_output)
def _fallback_parse(self, raw: str) -> dict:
"""当JSON解析失败时的降级策略"""
# 尝试用正则提取关键字段
result = {"order_id": None, "issue_type": "其他"}
order_match = re.search(r'ORD-\d{8}-\w{4}', raw)
if order_match:
result["order_id"] = order_match.group(0)
issue_match = re.search(r'(质量问题|物流问题|退换货|取消订单)', raw)
if issue_match:
result["issue_type"] = issue_match.group(0)
return result
使用示例
extractor = OrderInfoExtractor()
result = extractor.extract(“我ORD-20260809-A3B2的订单怎么还没发货?”)
print(json.dumps(result, ensure_ascii=False, indent=2))
### 3.3 关键技巧:Schema描述中的“负向约束”
在Schema描述中,不仅要说明“应该填什么”,还要明确“不应该填什么”:
```python
"order_items": {
"type": "array",
"description": "仅提取用户明确提及的商品,不要推理或补充未提及的商品",
"items": {...}
}
为什么有效:负向约束能有效抑制模型的“过度补全”倾向,尤其是在用户提供了部分信息时,模型容易“好心”地推测缺失字段。
四、策略三:Few-shot + 格式示例——用“示范”代替“说教”
4.1 核心思想
对于复杂的多步推理任务,“告诉模型怎么思考”往往不如“给模型看一个完整的思考过程”有效。Few-shot的核心逻辑是:用具体的输入-输出示例,让模型理解期望的思考路径和输出格式,而不是靠抽象规则描述。
4.2 实战:多步推理 + 自检机制
以售后判责场景为例——系统需要综合“购买时间、问题描述、历史记录”三个维度做出判责建议:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
class CustomerServiceJudge:
"""售后判责助手 - 使用Few-shot实现结构化推理"""
FEW_SHOT_EXAMPLES = [
{
"input": {
"purchase_date": "2026-07-15",
"issue": "手机屏幕出现一条亮线,从未摔过",
"history": "用户上个月因为充电问题咨询过一次"
},
"output": """
=== 推理过程 ===
1. 购买日期2026-07-15,今天2026-08-09,已使用25天,在15天换货期内
2. 问题描述为"亮线"且"从未摔过",属于非人为质量问题
3. 历史记录显示用户有1次咨询,非投诉性质
=== 判责结论 ===
- 责任归属:厂商质量问题
- 建议处理:换新(符合15天换货政策)
- 附加动作:无需转人工,自动生成换货工单
- 置信度:高
"""
},
{
"input": {
"purchase_date": "2024-03-10",
"issue": "充电口接触不良,插上充电线后需要按住才能充电",
"history": "无"
},
"output": """
=== 推理过程 ===
1. 购买日期2024-03-10,已使用超过1年,超出质保期
2. 充电口接触不良可能由长期插拔导致,属于正常使用磨损
3. 历史记录为空,用户首次反馈
=== 判责结论 ===
- 责任归属:用户正常使用损耗
- 建议处理:告知超出质保,提供付费维修选项
- 附加动作:推送维修服务链接
- 置信度:高
"""
},
{
"input": {
"purchase_date": "2026-06-01",
"issue": "耳机断连,之前已经换过一次,现在又出现同样问题",
"history": "用户于2026-06-15因断连问题换新过一次"
},
"output": """
=== 推理过程 ===
1. 购买日期2026-06-01,仍在质保期
2. 问题是重复出现的(换新后再次出现),可能存在批次性质量缺陷
3. 历史记录显示用户已换新一次,再次出现问题需要升级处理
=== 判责结论 ===
- 责任归属:厂商质量问题(重复故障)
- 建议处理:退款或升级为更高规格替代品
- 附加动作:标记为"敏感用户",转高级客服处理
- 置信度:中
"""
}
]
def judge(self, purchase_date: str, issue: str, history: str) -> dict:
"""执行判责推理"""
# 构建Few-shot系统提示
few_shot_text = ""
for example in self.FEW_SHOT_EXAMPLES:
few_shot_text += f"""
【输入示例】
购买日期:{example['input']['purchase_date']}
问题描述:{example['input']['issue']}
历史记录:{example['input']['history']}
【正确输出】
{example['output']}
"""
system_prompt = f"""
你是一个售后判责专家。请按照以下推理流程处理每个案例:
【推理流程】
1. 检查购买时间,判断是否在质保/换货期内
2. 分析问题性质,判断是否属于质量问题
3. 查阅历史记录,识别是否为重复故障或敏感用户
4. 给出判责结论和建议处理方式
【Few-shot示例】
{few_shot_text}
【当前案例输入】
购买日期:{purchase_date}
问题描述:{issue}
历史记录:{history}
请输出完整的推理过程和判责结论,格式与示例保持一致。
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": "请进行判责推理"}
],
temperature=0.1
)
output = response.choices[0].message.content
# 解析结构化输出
return self._parse_judge_output(output)
def _parse_judge_output(self, output: str) -> dict:
"""解析判责结果"""
result = {
"responsibility": "",
"suggestion": "",
"extra_action": "",
"confidence": ""
}
# 提取关键字段
resp_match = re.search(r'-\s*责任归属:(.+)', output)
if resp_match:
result["responsibility"] = resp_match.group(1).strip()
sugg_match = re.search(r'-\s*建议处理:(.+)', output)
if sugg_match:
result["suggestion"] = sugg_match.group(1).strip()
extra_match = re.search(r'-\s*附加动作:(.+)', output)
if extra_match:
result["extra_action"] = extra_match.group(1).strip()
conf_match = re.search(r'-\s*置信度:(.+)', output)
if conf_match:
result["confidence"] = conf_match.group(1).strip()
return result
4.3 关键技巧:示例的“边界覆盖”原则
Few-shot示例的选择不是越多越好,而是要覆盖边界情况。以上述判责场景为例,三个示例分别覆盖了:
- 正常换货边界(15天内质量问题)
- 质保过期边界(超1年正常损耗)
- 重复故障边界(换新后复发,需要升级处理)
覆盖边界比覆盖常规情况更重要——模型在边界处的表现直接决定了系统的健壮性。
五、策略四:输出格式验证器——用代码“审查”模型
5.1 核心思想
永远不要相信模型会100%遵守格式要求。 无论Prompt写得多么完美,总有概率(通常2-5%)输出格式偏离。因此,在模型输出后增加一个格式验证器(Validator),自动检测并修正格式问题,是生产环境的必备防线。
5.2 实战:多层级格式验证器
import re
import json
from typing import Any, List, Tuple
from pydantic import BaseModel, ValidationError
class OutputValidator:
"""多层级输出格式验证器"""
def __init__(self, strict_mode: bool = False):
self.strict_mode = strict_mode
def validate_json(self, text: str, schema: dict) -> Tuple[bool, Any]:
"""
第一层:JSON格式验证
第二层:Schema结构验证
"""
# 第一层:JSON格式
try:
data = json.loads(text)
except json.JSONDecodeError:
# 尝试提取嵌入的JSON
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
try:
data = json.loads(json_match.group(0))
except:
return False, None
else:
return False, None
# 第二层:Schema验证
try:
# 检查必填字段
required = schema.get("required", [])
for field in required:
if field not in data or data[field] is None:
return False, None
# 检查枚举值
for field, field_schema in schema.get("properties", {}).items():
if "enum" in field_schema:
if field in data and data[field] not in field_schema["enum"]:
data[field] = field_schema["enum"][0] # 自动修正
except Exception:
return False, None
return True, data
def validate_format(self, text: str, format_type: str = "markdown") -> Tuple[bool, str]:
"""
第三层:格式一致性验证(针对Markdown/文本结构)
"""
if format_type == "markdown":
# 检查是否包含所有必需章节
required_sections = [
"## 问题分类",
"## 紧急程度",
"## 关键实体提取",
"## 推荐解决方案"
]
for section in required_sections:
if section not in text:
if self.strict_mode:
return False, f"缺少章节: {section}"
else:
# 尝试在末尾追加缺失章节
text += f"\n\n{section}\n[信息不足]"
return True, text
return True, text
5.3 实战:LangChain输出解析器
LangChain提供了更成熟的输出解析方案,可以无缝集成到生产链路中:
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from pydantic import BaseModel, Field
class CustomerIssue(BaseModel):
category: str = Field(description="问题分类,可选值:售后/物流/产品使用/活动咨询/投诉/其他")
urgency: str = Field(description="紧急程度,可选值:高/中/低")
summary: str = Field(description="问题摘要,30字以内")
needs_escalation: bool = Field(description="是否需要转人工")
# 自动生成格式说明
parser = PydanticOutputParser(pydantic_object=CustomerIssue)
prompt = PromptTemplate(
template="""分析以下用户问题,按指定格式输出。
{format_instructions}
用户问题:{query}
""",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 使用示例
model = OpenAI(temperature=0.0)
query = "我买的鞋子尺寸不对,想换小一码"
_input = prompt.format_prompt(query=query)
output = model(_input.to_string())
result = parser.parse(output)
print(result)
# CustomerIssue(category='售后', urgency='中', summary='用户购买鞋子尺寸不合适需要换货', needs_escalation=True)
六、策略五:思维链(CoT)约束——强制展示推理过程
6.1 核心思想
复杂推理任务中,模型直接给出最终答案的准确率远低于“先展示推理过程,再给出答案”的方式。思维链的核心价值在于:强制模型暴露推理步骤,让错误更容易被识别和修正。
更进阶的做法是自一致性(Self-Consistency):让模型生成多条推理路径,选取出现最频繁的答案。
6.2 实战:复杂逻辑推理(物流时效预测)
class LogisticsEstimator:
"""物流时效预测 - 强制思维链推理"""
def estimate(self, origin: str, dest: str, product_type: str, holiday: bool) -> dict:
system_prompt = """
你是一个物流时效分析专家。
请按照以下步骤进行推理,每一步都要明确输出。
【推理步骤】
步骤1:检查"发货地与收货地"是否在同一省份
步骤2:判断是否为偏远地区(新疆、西藏、内蒙古、青海、甘肃)
步骤3:检查是否为节假日,节假日需增加2-3天
步骤4:根据产品类型调整时效(生鲜类 +1天,大件商品 +1天)
步骤5:综合计算最终预估时效
【输出格式】
=== 推理过程 ===
步骤1:…
步骤2:…
步骤3:…
步骤4:…
步骤5:…
=== 最终结论 ===
- 基础时效:X天
- 额外加成:Y天
- 总预估时效:Z天
- 预估送达日期:YYYY-MM-DD
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"发货地:{origin},收货地:{dest},产品类型:{product_type},是否为节假日:{holiday}"}
],
temperature=0.1
)
output = response.choices[0].message.content
# 提取关键指标
result = self._extract_estimates(output)
return result
def _extract_estimates(self, text: str) -> dict:
"""提取物流时效预估结果"""
result = {
"base_days": None,
"extra_days": 0,
"total_days": None,
"estimated_date": None
}
base_match = re.search(r'-\s*基础时效:(\d+)天', text)
if base_match:
result["base_days"] = int(base_match.group(1))
extra_match = re.search(r'-\s*额外加成:(\d+)天', text)
if extra_match:
result["extra_days"] = int(extra_match.group(1))
total_match = re.search(r'-\s*总预估时效:(\d+)天', text)
if total_match:
result["total_days"] = int(total_match.group(1))
date_match = re.search(r'-\s*预估送达日期:(\d{4}-\d{2}-\d{2})', text)
if date_match:
result["estimated_date"] = date_match.group(1)
return result
6.3 自一致性增强(Self-Consistency)
对于高价值决策场景,可以让模型生成3-5条独立推理路径,然后投票选出最一致的答案:
def estimate_with_consistency(origin, dest, product_type, holiday, n_paths=3):
"""使用自一致性提升推理准确率"""
results = []
for i in range(n_paths):
# 每次调用使用不同的随机种子或轻微差异化的Prompt
result = estimator.estimate(origin, dest, product_type, holiday)
results.append(result)
# 投票选出出现最多的total_days
total_days_list = [r['total_days'] for r in results if r['total_days'] is not None]
if total_days_list:
# 取众数
from collections import Counter
final_days = Counter(total_days_list).most_common(1)[0][0]
# 返回结果中total_days = final_days的第一条
for r in results:
if r['total_days'] == final_days:
return r
return results[0] # 降级返回第一条
七、策略对比与选型指南
| 策略 | 适用场景 | 优点 | 缺点 | 推荐温度 |
|---|---|---|---|---|
| Markdown模板 | 文档生成、报告分析 | 结构清晰,易解析 | 对模型格式遵从度有要求 | 0.1-0.3 |
| JSON Schema | API参数生成、数据提取 | 机器可读,可校验 | 对复杂层次支持有限 | 0.0-0.1 |
| Few-shot | 推理类任务、判责 | 示范性强,无需抽象规则 | Token消耗大 | 0.1-0.3 |
| 格式验证器 | 所有场景(辅助) | 兜底保障,提高健壮性 | 增加代码复杂度 | — |
| CoT推理 | 数学推理、因果分析 | 准确率高,可调试 | 输出Token多,耗时 | 0.3-0.5 |
组合使用建议:生产级系统推荐组合“JSON Schema约束 + 格式验证器 + 必要的Few-shot示例”,这是投入产出比最高的方案组合。
八、总结:结构化约束的底层逻辑
回顾五种策略,它们的本质目标是一致的:把模型的输出空间从“无限”压缩到“有限且可预测”。
- Markdown模板通过物理结构限定输出形状
- JSON Schema通过数据类型限定输出内容
- Few-shot通过行为示范限定推理路径
- 格式验证器通过代码审查强制修正偏差
- CoT通过步骤暴露限定了思考过程
在实际项目中,建议至少使用两种以上的约束策略叠加——例如“JSON Schema + 格式验证器”的组合,可以将格式合规率从85%提升到98%以上。
最后一条建议:结构化约束不是越严格越好。过度的格式限制会抑制模型在边界场景中的灵活性。当业务允许时,保留一定的输出自由度(如允许输出“未知”或提供自由文本补充),往往能显著提升用户体验。
更多推荐

所有评论(0)