跳转到内容

Prompt 工程教程:五要素法 + Few-shot + CoT + JSON 输出

学完这一节,你能:

  • 写出结构化 Prompt(五要素框架),不再写“帮我整理下客户反馈”这种让模型抓瞎的话
  • Few-shot 示例CoT 思维链 提升复杂任务准确率,还能讲清“为啥这样有效”
  • 让模型稳定输出 JSON(这是 FDE 把 AI 从“聊天玩具”变成“工程系统”的关键)

提示词工程在 FDE JD 出现率 55%,是技术栈第一项。后面所有模块(RAG / Agent / 评估)都建立在它之上。

写 Prompt = 点外卖。

回想你点外卖填的那张单子,为啥要勾那么多选项?因为一句“我要吃饭”没法让店家知道你到底要啥。一份清楚的外卖单,有五块东西:

  1. 给谁下单(角色):给厨师下单,还是给跑腿小哥下单?
  2. 现在啥情况(背景):朋友聚餐?一个人吃?赶时间?
  3. 具体要什么(任务):两份番茄炒蛋、一份米饭
  4. 不要什么(约束):不要辣、不要香菜
  5. 怎么给你(输出格式):打包盒、要不要餐具

少一块会怎样?少“背景”,店家不知道份量做多大;少“约束”,可能做出你吃不了的味道;少“输出格式”,直接给你端盘子里——可你在家没法用。写 Prompt 也一样,五块都给齐,模型才稳。

把点外卖那五块,翻译成写 Prompt:

点外卖 Prompt 五要素 邮件分类举例
给谁下单 角色 你是客服邮件分类专家
现在啥情况 背景 我们是卖电子产品的电商
具体要什么 任务 把每封邮件归到 5 类之一
不要什么 约束 只能归一类,不确定归“其他”
怎么给你 输出格式 JSON: {category, confidence}

最容易搞混的是背景和任务。一句话记住:背景是状态,任务是动作。“我们是电商”是背景(陈述现状),“请分类”是任务(动词开头)。背景告诉模型“在什么场景下判断”,任务告诉模型“具体产出什么”。

复杂任务,光描述规则模型常常学不会。这时别再费劲解释了——直接拿几个成品给它看

就像师傅带徒弟:你口头描述“什么叫好的木匠活”,徒弟半天听不懂;但你塞给他三把做好的椅子,他照着刨,立马就会了。模型也一样,这招叫 Few-shot(少样本):在 Prompt 里塞 2-5 个“输入→输出”的成品例子,它会照着模式模仿。

例子1: "这手机太好用了!" → positive
例子2: "质量差,后悔买" → negative
例子3: "还行吧,一般般" → neutral
现在判断: "续航惊人但拍照糊" → ?

为什么是 3-5 个,不是 1 个也不是 30 个?

  • 太少(1-2 个):模型只见过一次,没抓准规律,可能蒙
  • 正好(3-5 个):模型看几遍就总结出套路,稳定模仿
  • 太多(>10 个):一是费钱(每个例子都算 token),二是模型反而会“死记例子”,换个稍不同的说法就懵(过拟合)

CoT(思维链):让模型把计算过程写出来

Section titled “CoT(思维链):让模型把计算过程写出来”

数学题、逻辑推理、多步决策这类任务,你直接问答案,模型容易蒙错。让它先把过程写出来再给答案,准确率能从 50% 跳到 90%+。

类比:考试时把计算过程写出来,vs 心算直接写答案。心算错一步整道题全错;写过程的话,每步都能检查,而且写着写着思路就顺了。

# 不用 CoT(容易蒙错)
prompt = "小明有 5 个苹果,吃了 2 个,又买了 3 个,现在有几个?"
# 用 CoT(准确率高)
prompt = """小明有 5 个苹果,吃了 2 个,又买了 3 个,现在有几个?
请一步步思考:
步骤1:
步骤2:
最终答案:"""

但说实话:模型并没有真的“在思考”。 它本质是个“字接字”的语言模型,CoT 只是逼它先输出一段推理文字。重点是:模型生成下一个字时,前面所有字(包括它自己刚写的推理)都会被一起算进去当输入。所以“先写过程 → 再下结论”这个顺序,会让最终结论落在更合理的字上。看起来像思考,本质是“输出文字 → 这些文字又帮下一句输出得更准”的滚雪球效应。

什么时候用 CoT?推理类任务(数学、逻辑、多步决策)。分类、抽取这种简单任务别用,白白多花 token 还变慢。

结构化 JSON 输出:三种“约束程度”

Section titled “结构化 JSON 输出:三种“约束程度””

企业应用里,模型输出不能是一段话,必须是代码能解析的 JSON。三种做法,按“管得多严”排序:

方法 1:口头要求(松,可能违约) —— 在 Prompt 里写一句“请输出 JSON”

system = "你是数据抽取器。输出必须是合法 JSON,不要任何解释。格式:{name, age, city}"
user = "张三今年 28 岁,住在上海"
# 大部分时候输出 JSON,偶尔会调皮加一句"好的,这是结果:"

像口头约定:大多数时候守约,偶尔违约。简单任务够用,但你不能 100% 信它

方法 2:response_format(中,基本不违约) —— 调用时强制 JSON 模式

response = client.chat.completions.create(
model="glm-4-flash",
messages=[...],
response_format={"type": "json_object"} # 像签合同:必须输出 JSON
)

像签合同:模型底层被强制走 JSON 模式,基本不会违约。生产环境首选

方法 3:Function Calling(严,直接锁死格式) —— 定义一个“工具”,让模型按工具的参数 schema 输出。像装了把锁:模型必须按你定义的字段名、类型填,一点不差。模块 3 详讲,现在知道有这招就行。

怎么选? 小练习用方法 1;做产品先上方法 2;要跟其他系统对接、字段必须严丝合缝,上方法 3。

🛠️ 动手实操:做一个邮件分类器(分三步加难度)

Section titled “🛠️ 动手实操:做一个邮件分类器(分三步加难度)”

我们做 FDE 入门最经典的小项目——客服邮件分类器,但分三步迭代:先写五要素版,再加 Few-shot 提准确率,最后用 JSON 强制输出。你会直观看到“每加一层,效果稳一点”。

确保 llm.py 还在 ~/fde-learn 目录里(上节封装的函数)。这节站在它肩膀上。

新建 email_classifier.py:

from llm import chat
import json
# 五要素版的 system(行尾【】标了对应哪个要素,方便对照)
SYSTEM_V1 = """你是客服邮件分类专家。【角色】
我们是卖电子产品的电商,每天收几百封客户邮件。【背景】
请把每封邮件归到下面 5 类中的 1 类:【任务】
- complaint:投诉(不满、索赔、情绪激动)
- inquiry:咨询(问功能、价格、流程)
- return:退货退款
- technical:技术问题(bug、报错、不能用)
- other:其他
每封只能归一类,不确定归 other。【约束】
输出 JSON: {"category": "类别", "confidence": 0.0-1.0, "reason": "一句话理由"}【输出格式】"""
def classify(email: str) -> dict:
"""分类一封邮件"""
result = chat(email, system=SYSTEM_V1)
try:
return json.loads(result)
except json.JSONDecodeError:
return {"category": "other", "confidence": 0.0, "reason": "解析失败"}
if __name__ == "__main__":
test_emails = [
"我上周买的耳机充不进电,要求换货!", # 期望 return / technical
"请问你们的企业版支持多少人同时用?", # 期望 inquiry
"服务太差了!等了三天没人理,要投诉!", # 期望 complaint
]
for email in test_emails:
print(f"邮件: {email}")
print(f"分类: {classify(email)}\n")

运行 python email_classifier.py

✅ 大概率分类对。但遇到边界邮件(比如“产品不能用,要求退款”)会左右摇摆——这时候该 Few-shot 上场。

Step 2:加 Few-shot(让边界邮件也稳)

Section titled “Step 2:加 Few-shot(让边界邮件也稳)”

把 SYSTEM 换成 V2,在五要素末尾追加几个“输入→输出”的成品例子:

# Few-shot 版:五要素 + 3 个成品例子
SYSTEM_V2 = SYSTEM_V1 + """
参考下面 3 个例子,照这个模式分类:
例子1: "耳机用了三天就坏了,充不进电,要求换货" → {"category": "return", "confidence": 0.9, "reason": "明确要求换货"}
例子2: "你们的 Pro 版和 Max 版有啥区别?" → {"category": "inquiry", "confidence": 0.95, "reason": "询问产品差异"}
例子3: "app 一直闪退,根本登录不进去" → {"category": "technical", "confidence": 0.9, "reason": "报 bug 不能用"}"""
def classify_v2(email: str) -> dict:
"""Few-shot 版分类"""
result = chat(email, system=SYSTEM_V2)
try:
return json.loads(result)
except json.JSONDecodeError:
return {"category": "other", "confidence": 0.0, "reason": "解析失败"}
# 把 main 里的 classify(email) 换成 classify_v2(email) 再跑一遍

✅ 边界邮件会明显稳——这就是“师傅带徒弟看几个成品”的效果。3 个例子足够,加到 10 个反而会让模型只认这几个特定说法。

Step 3:JSON 强制输出(让解析不再失败)

Section titled “Step 3:JSON 强制输出(让解析不再失败)”

Step 1/2 偶尔会解析失败(模型加了废话)。从源头解决:在 llm.py 末尾追加一个 chat_json() 函数,用方法 2 强制 JSON。

# 加到 llm.py 末尾
def chat_json(prompt: str, system: str = "你是中文助手", model: str = "glm-4-flash") -> str:
"""
强制 JSON 输出版 chat。
response_format={"type": "json_object"} 让模型底层走 JSON 模式,
不会再加"好的,这是结果:"这种废话。
"""
client = OpenAI(
api_key=os.getenv("GLM_API_KEY"),
base_url="https://open.bigmodel.cn/api/paas/v4/"
)
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt}
],
response_format={"type": "json_object"} # 签合同:必须 JSON
)
return response.choices[0].message.content

然后 email_classifier.pyfrom llm import chat 改成 from llm import chat_json,classify_v2 里调用换成 chat_json,再跑一遍。

✅ 这次解析失败几乎不再出现。生产环境永远用 response_format 或 Function Calling,别赌模型自觉。

问题 原因 解决
模型不输出 JSON,带一堆解释 只口头要求 升级到 response_format={"type": "json_object"}(方法 2)
JSON 解析失败 模型加了 ```json 标记 result.strip('\’).replace(‘json\n’,‘’)` 清洗,或上方法 2
分类不准 / 左右摇摆 规则模糊 加 3-5 个 Few-shot 例子,把边界说清
简单任务用 CoT 反而慢 不需要推理却加了“一步步思考” 分类、抽取别用 CoT,只推理任务用
Few-shot 加 10+ 个更差 过拟合 + 费 token 砍到 3-5 个,挑边界例子
1. 五要素里,"背景" 和 "任务" 有啥区别?各举一个邮件分类里的例子。

背景 是“现在啥情况”(陈述现状)。例如:“我们是电商,每天收几百封邮件”。 任务 是“具体要模型干啥”(动词开头)。例如:“把每封邮件归到 5 类之一”。 一句话:背景是状态,任务是动作

2. Few-shot 为什么 3-5 个最好?给 30 个会怎样?

太少(1-2 个)模型没抓准规律;3-5 个模型看完能总结出套路,稳定模仿;太多(30 个)一是费钱(每个例子都算 token),二是模型容易“死记例子”,换个稍不同的输入就懵(过拟合)。

3. CoT 提升准确率的真正原理是什么?模型真的"在思考"吗?

不是真思考。模型本质是“字接字”的语言模型,CoT 只是逼它先生成一段推理文字。重点是:模型每次生成下一个字时,前面所有的字(包括它自己刚生成的推理)都会被一起算进去当输入。所以“先写过程 → 再下结论”这个输出顺序,会让最终结论落在更合理的字上。看起来像思考,本质是“输出文字 → 这些文字又帮助下一句输出得更准”的滚雪球效应。

🚀 实战小项目:结构化信息抽取

Section titled “🚀 实战小项目:结构化信息抽取”

选一份真实文档(简历 / 产品说明 / 合同条款),用 五要素 Prompt + JSON 输出,抽取结构化字段。验收:对 10 份文档,字段准确率 ≥ 80%(自己人工核对)。进阶:准确率不够的字段,加 Few-shot 例子补强。

3 句话回顾:

  1. Prompt 五要素 = 点外卖:角色 / 背景 / 任务 / 约束 / 输出格式,五块都给齐模型才稳
  2. Few-shot(看成品)和 CoT(写过程)分别解决“规则难描述”和“推理复杂”,前提是用对地方
  3. JSON 输出三档约束:口头要求(松)→ response_format(中,生产首选)→ Function Calling(严)

下一节:进入模块 2,用低代码平台(Coze / Dify) 把这些能力快速做成能给客户用的应用——30 分钟交付。


← 上一节:第一次调通 API | 下一节:Coze 与 Dify 低代码