**
一、问题背景:为什么我要死磕这个Prompt
事情起因很简单:我们做的是一个电商商品页的"评论洞察"模块,需要把用户评论抽成结构化数据,喂给下游的BI看板。目标字段有5个:
sentiment:情感极性(positive / neutral / negative)category:问题归类(quality / logistics / service / price / other)keywords:关键词数组(最多3个)has_refund_intent:是否有退款/退货意图(bool)summary:一句话摘要(不超过30字)
一开始我图省事,写了个最朴素的Prompt就上线了。结果运营反馈:"差评被识别成正面的""退款意图全漏了"。我拉了一批badcase一看,问题全出在Prompt上——模型不是不会,是我没把要求说清楚。
于是就有了这次系统性的对比实验。我的目标很明确:在成本可控的前提下,把字段级准确率做到90%以上。
二、环境与版本
先把实验环境交代清楚,避免有人说"你这结果不可复现":
- Python 3.11.6
- openai SDK 1.35.10
- 模型:
gpt-4o-mini(快照gpt-4o-mini-2024-07-18) - 参数:
temperature=0,max_tokens=512,top_p=1 - 测试集:从真实评论中人工标注的200条样本,覆盖5个类目、中英混排、含emoji和错别字
- 评估方式:字段级精确匹配(
keywords按集合重合率≥0.6算对),人工复核争议样本
token统计用tiktoken 0.7.0,按cl100k_base编码计算。价格按官方gpt-4o-mini输入$0.15/1M token、输出$0.60/1M token估算。
三、方案设计:四类Prompt的对比思路
我设计了4组Prompt,核心变量是"给模型的约束强度和示例数量":
| 方案 | 描述 | 预期 |
|---|---|---|
| P1 零样本指令 | 只给字段说明,要求输出JSON | 最省token,质量存疑 |
| P2 Few-shot | 加3条标注示例 | 质量提升,token上升 |
| P3 CoT | 要求先分析再输出,附推理步骤 | 质量最高,token最贵 |
| P4 Schema约束JSON | 用JSON Schema描述字段+枚举值,配合response_format |
格式最稳,token中等 |
关键设计点:P4用了OpenAI的response_format={"type": "json_object"},强制模型输出合法JSON,这个在后面踩坑部分会细说。
四、核心实现(含代码)
先看统一调用函数和token统计:
import json, tiktoken
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
enc = tiktoken.get_encoding("cl100k_base")
def count_tokens(text: str) -> int:
return len(enc.encode(text))
def call_llm(prompt: str, use_json_mode: bool = False) -> dict:
kwargs = dict(
model="gpt-4o-mini-2024-07-18",
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=512,
)
if use_json_mode:
kwargs["response_format"] = {"type": "json_object"}
resp = client.chat.completions.create(**kwargs)
content = resp.choices[0].message.content
usage = resp.usage
return {
"raw": content,
"prompt_tokens": usage.prompt_tokens,
"completion_tokens": usage.completion_tokens,
"total_tokens": usage.total_tokens,
}
再看P4(Schema约束版)的Prompt构造,这是我认为性价比最高的方案:
SCHEMA_PROMPT = """你是电商评论信息抽取引擎。请严格按以下JSON Schema抽取,只输出JSON,不要解释。
Schema:
{{
"sentiment": "positive|neutral|negative",
"category": "quality|logistics|service|price|other",
"keywords": ["最多3个关键词"],
"has_refund_intent": true|false,
"summary": "不超过30字的中文摘要"
}}
判定规则:
1. 含"退货/退款/换货/不想要了"等表述,has_refund_intent=true;
2. category 只能选一个,优先选用户抱怨最多的维度;
3. 若评论无明显情感倾向,sentiment=neutral。
评论:{review}
"""
def extract_p4(review: str) -> dict:
prompt = SCHEMA_PROMPT.format(review=review)
return call_llm(prompt, use_json_mode=True)
对比一下P1(零样本)有多"简陋":
P1_PROMPT = """抽取评论信息,输出JSON,包含字段:
sentiment, category, keywords, has_refund_intent, summary。
评论:{review}
"""
说实话,写完P1我就预感到要翻车——它连枚举值都没给,模型只能瞎猜。
五、踩坑与优化
坑1:json_object模式不是万能的。 我一开始在P4里只写了"输出JSON",结果gpt-4o-mini偶尔会输出带markdown代码块的json包裹内容,导致json.loads直接炸。后来在Prompt里明确写"只输出JSON,不要解释",并配合response_format,才稳定下来。注意:json_object模式要求Prompt里必须出现"JSON"字样,否则API会报400。
坑2:Few-shot示例的"污染"。 P2里我放了3条示例,结果模型把示例里的关键词风格也学过去了——示例全是"质量""物流",导致新评论的keywords大量出现这两个词。优化办法是示例覆盖全部5个category,且关键词风格多样化。
坑3:CoT让token翻倍但收益递减。 P3要求模型先写推理再输出,completion_tokens从平均80涨到平均420,但准确率只比P4高1.2个百分点。结论:CoT适合复杂推理任务,这种字段抽取用Schema约束更划算。
坑4:temperature=0也不完全确定。 同一批样本跑两次,约3%的样本输出有差异。所以评估时我跑了3轮取平均,避免被单次波动误导。
六、效果数据
200条样本、3轮平均的结果如下(token为单次请求均值):
| 方案 | 平均总token | 字段准确率 | 格式合法率 | 单条成本(估算) |
|---|---|---|---|---|
| P1 零样本 | 580 | 71.3% | 88.5% | $0.00012 |
| P2 Few-shot | 1240 | 86.7% | 96.0% | $0.00026 |
| P3 CoT | 2100 | 94.6% | 97.5% | $0.00041 |
| P4 Schema约束 | 980 | 93.4% | 99.5% | $0.00021 |
几个值得说的细节:
- P1在
has_refund_intent上准确率只有52%,基本等于抛硬币,印证了"没说清规则模型就摆烂"。 - P4的
格式合法率99.5%是最大亮点,200条里只有1条因为评论超长被截断导致JSON不完整。 - P3和P4准确率接近,但P3的token是P4的2.14倍。按日均10万条评论算,P3每天多花约$20,一年就是7000多美元——这笔账不得不算。
- 最终我选了P4作为线上方案,并对
summary字段单独加了一条"超过30字则截断"的后处理,把准确率又拉到94.1%。
七、总结
这次实验最大的收获不是"哪个Prompt最好",而是Prompt设计要匹配任务复杂度:
- 字段抽取类任务,Schema约束 > 长篇CoT。把枚举值、判定规则、输出格式一次性写死,比让模型"自己思考"更稳定也更便宜。
- token要算总账。P3单条看着只贵0.0002美元,乘上业务量就是真金白银。
- 别迷信temperature=0。评估至少跑3轮,否则你看到的提升可能只是噪声。
- 示例要防污染。Few-shot的示例分布必须覆盖真实数据的全部分布,否则模型会"抄偏"。
下一步我打算试试把P4的Schema换成Pydantic模型自动生成,再对比一下手工Prompt和自动生成Prompt的差异——如果效果好,后续维护成本能再降一截。有兴趣的同学可以蹲个后续。