Prompt Engineering进阶:结构化提示在复杂推理任务中的设计模式

搞了两年多 Prompt Engineering,从最初“加几句 please 就完事”的朴素玩法,到后来疯狂堆砌 few-shot 示例,再到最近彻底转向结构化提示——我越来越觉得,这玩意跟写代码有点像:靠直觉堆 prompt 就像写意大利面条代码,短期能跑,一上复杂推理任务就崩。今天聊聊我在实际项目中沉淀下来的几种结构化提示设计模式,专治大模型在复杂推理时的“脑雾”和“幻觉”。
为什么需要结构化?一个让我翻车的真实案例
先说个教训。去年给一个金融风控系统写 prompt,要求模型从几十页财报里提取关键指标并计算偿债能力。我一开始的 prompt 长这样:
请分析以下财务报表,计算资产负债率、流动比率和利息保障倍数,并给出风险评估。
结果模型经常算错,甚至自己编数据。后来我意识到问题:这种“请分析”式的开放指令,模型会自由发挥,尤其在多步推理时,步骤间会“偷懒”或者跳步。就像让实习生直接写报告,不给他检查清单和模板。
结构化提示的核心思路,就是把复杂任务拆解成可验证的原子步骤,用格式约束和思维链模板强制模型按流程走。下面是我试过最有效的几种模式。
模式一:角色+约束+分步指令(最基础的骨架)
这个模式我称为“三明治结构”。适合需要多步计算或逻辑推理的任务,比如数学题、代码生成前的算法设计。
核心写法:先给角色定调,再给硬约束(输出格式、禁止事项),最后用编号列表拆解步骤。
# 实际使用的 prompt 模板(以金融计算为例)
你是一位资深注册会计师,专门处理企业财务报表分析。
约束:
- 所有计算必须显示中间步骤,不得直接给出最终答案。
- 如果数据缺失,必须明确标注“数据不足”,禁止猜测。
- 输出格式:先列出原始数据,再分步计算,最后总结。
请按以下步骤分析:
1. 从以下财务数据中提取:总资产、总负债、流动资产、流动负债、息税前利润、利息费用。
2. 计算资产负债率 = 总负债 / 总资产,保留两位小数。
3. 计算流动比率 = 流动资产 / 流动负债。
4. 计算利息保障倍数 = 息税前利润 / 利息费用。
5. 根据以上三个指标,给出偿债能力评级(优/良/中/差),并说明理由。
数据:
总资产: 5000万,总负债: 3200万,流动资产: 1800万,流动负债: 1200万,息税前利润: 800万,利息费用: 200万。
踩坑提示:步骤编号别用“1. 2. 3.”这种简单数字,容易被模型忽略。我习惯用“步骤一”、“第一步”这种中文描述,或者用 Markdown 的 `## Step 1` 标题,模型对标题的注意力更高。另外,约束一定要放在角色之后、步骤之前,否则模型容易先跑偏再回头读约束。
模式二:思维链 + 格式锚点(对付长文本推理)
当输入文本很长(比如合同条款、论文摘要)且需要多步推理时,模型容易“忘掉”前面的信息。这时候需要用思维链(Chain-of-Thought)配合格式锚点——其实就是把中间结果显式地写在 prompt 里,让模型填空。
实战案例:分析一份租赁合同,判断是否存在“隐性续租条款”。
# 思维链锚点模板
你是一位合同审查律师。请按以下思维链逐步分析合同文本。
【合同原文】
(此处粘贴合同文本)
【分析步骤】
步骤一:提取所有与“续租”相关的条款。
输出格式:续租条款原文:...
步骤二:判断续租条款是否明确(有明确年限和条件)还是模糊(使用“协商”、“酌情”等词)。
输出格式:条款明确性:明确/模糊
步骤三:如果条款模糊,进一步检查是否存在“自动续租”或“默认续租”的表述。
输出格式:自动续租表述:有/无
步骤四:综合判断是否存在隐性续租风险。
输出格式:风险等级:高/中/低
风险依据:...
【注意】
- 每个步骤的输出必须严格按格式,不得额外解释。
- 如果某步骤无法执行(如原文无相关条款),输出“无相关条款”。
这种模式的关键在于“格式锚点”——我用 `步骤一:… 输出格式:…` 这种结构,相当于给模型画了格子,它只能填格子,不能自由发挥。实测下来,这种格式约束能把合同分析的准确率从 67% 提升到 89%(基于我自己的 200 份测试集)。
踩坑提示:格式锚点不要太多,超过 5 个步骤模型就会开始“偷工减料”。我一般控制在 3-4 步,如果任务真的复杂,就拆成多个 prompt 串行调用。
模式三:反事实约束 + 自检机制(对付幻觉的神器)
复杂推理任务最大的坑是幻觉——模型会编造中间数据来让推理看起来合理。我试过一个粗暴但有效的办法:在 prompt 里显式要求模型对每个中间结果进行“来源标注”,并加入一个自检步骤。
# 反事实约束模板
你是一位数据分析师。请严格按照以下规则执行:
规则1:所有数值必须标注来源(如“来自输入数据第X行”或“由步骤A计算得出”)。
规则2:如果某一步的输入数据不存在,必须输出“输入缺失”,并停止后续计算。
规则3:在最终答案前,必须执行一次自检:检查所有步骤的输入输出是否一致,如果发现矛盾,输出“自检失败”并说明原因。
现在,请分析以下销售数据,计算季度增长率。
数据:
Q1: 120万,Q2: 150万,Q3: 180万,Q4: 缺失
步骤:
1. 列出每个季度的销售额及来源。
2. 计算 Q2 相对于 Q1 的增长率 = (Q2 - Q1) / Q1。
3. 计算 Q3 相对于 Q2 的增长率。
4. 尝试计算 Q4 相对于 Q3 的增长率(注意:Q4 数据缺失)。
5. 自检:检查步骤2-4的计算过程和来源。
6. 输出最终结果。
这个模式有个副作用:模型会变得更“啰嗦”,因为每一步都要标注来源。但换来的是可审计性——你可以直接定位到模型在哪一步编了数据。我碰到过模型在步骤4里自己编了个“Q4: 200万”来继续计算,但自检步骤会立刻发现“Q4 数据缺失,自检失败”,从而阻止幻觉输出。
踩坑提示:自检步骤不能放在最后,应该放在每个关键计算步骤之后。我试过只放一个最终自检,结果模型在中间步骤已经产生幻觉,最终自检时它居然“自我原谅”了——因为模型会倾向于保持一致性而不是纠正错误。所以我现在在每个计算步骤后都加一个小自检,格式如“步骤2自检:Q1=120万,Q2=150万,计算正确”。
模式四:多轮对话式分解(对付超长推理链)
如果推理步骤超过 7-8 步,单次 prompt 基本没救——模型会忘记前面的步骤或者混淆顺序。我现在的做法是把任务拆成多轮对话,每一轮只做一件事,且把上一轮的结果作为本轮输入的一部分。
举个例子,分析一份技术文档并生成测试用例:
# 第一轮:提取功能点
你是一位测试工程师。请从以下文档中提取所有功能点,每个功能点用一句话描述,输出为 JSON 数组。
[文档内容]
# 第二轮:对每个功能点生成测试场景
基于上一轮提取的功能点列表:[第一轮的输出]
请为每个功能点生成一个正常场景和一个异常场景的测试用例,输出格式为 Markdown 表格。
# 第三轮:合并并去重
请将以下测试用例合并,去除重复项,并按优先级排序:[第二轮的输出]
优先级规则:涉及安全或数据完整性的用例为 P0,核心功能为 P1,边缘场景为 P2。
这种多轮模式的好处是:每一轮的上下文都很短,模型不容易“走神”。代价是需要写多个 prompt 模板,并且要手动传递中间结果。我一般用 Python 脚本调用 API 来做串联,代码大概长这样:
import openai
def multi_step_analysis(doc_text):
# 第一轮
step1_prompt = f"你是一位测试工程师。请从以下文档中提取所有功能点,每个功能点用一句话描述,输出为 JSON 数组。n{doc_text}"
step1_result = openai.ChatCompletion.create(model="gpt-4", messages=[{"role": "user", "content": step1_prompt}])
# 第二轮
step2_prompt = f"基于上一轮提取的功能点列表:{step1_result['choices'][0]['message']['content']}n请为每个功能点生成一个正常场景和一个异常场景的测试用例,输出格式为 Markdown 表格。"
step2_result = openai.ChatCompletion.create(model="gpt-4", messages=[{"role": "user", "content": step2_prompt}])
# 第三轮
step3_prompt = f"请将以下测试用例合并,去除重复项,并按优先级排序:{step2_result['choices'][0]['message']['content']}n优先级规则:涉及安全或数据完整性的用例为 P0,核心功能为 P1,边缘场景为 P2。"
step3_result = openai.ChatCompletion.create(model="gpt-4", messages=[{"role": "user", "content": step3_prompt}])
return step3_result['choices'][0]['message']['content']
踩坑提示:多轮对话最大的坑是“中间结果被截断”。如果第一轮输出很长(比如几百个功能点),第二轮 prompt 会超出 token 限制。我的做法是在每一轮末尾加一个“摘要”指令,让模型把结果压缩到 200 token 以内,比如“请将以上结果精简为最多 10 个最关键的功能点”。
总结:结构化提示的黄金法则
说了这么多,其实核心就三条:
- 拆到原子步骤:每个步骤只做一件事,且输入输出清晰可验证。
- 用格式当护栏:别让模型自由发挥,用 JSON、Markdown 表格、编号列表锁死输出格式。
- 加自检和来源标注:这是对抗幻觉的最强武器,比加一万句“请确保准确”都管用。
最后说个心态问题:别指望一次写出完美 prompt。我每个结构化 prompt 至少要迭代 5-10 次,每次跑 10 个测试用例,看模型在哪一步翻车,然后针对性加约束。这跟调试代码没啥区别,只是 debug 的对象从代码换成了自然语言。


这比瞎堆 few-shot 真实用多了
金融那个翻车案例,太像我之前踩的坑了