一、问题背景:为什么我决定重写Prompt
上个月接手一个电商评论分析模块,需求很朴素:把用户评论分成「好评 / 中评 / 差评」三类,输出置信度和关键理由。
第一版Prompt我随手写的,大概是这样:
prompt = f"请判断这条评论是好评、中评还是差评:{comment}"
上线后准确率只有78.3%,而且输出格式极其不稳定——有时候返回"好评",有时候返回"这条评论属于好评类别",有时候还会自己加一段解释。后端正则解析直接崩了。
更麻烦的是成本。当时用的是Few-shot,塞了8个示例,单次请求input token接近600,按每天5万次调用算,一个月光这一项就要$18.7。老板问我能不能优化,我说能,于是有了下面这轮实验。
二、环境与版本
- 模型:
gpt-4o-mini-2024-07-18 - SDK:
openai==1.35.0 - Python:3.11.6
- 测试集:人工标注的1200条电商评论(好评512 / 中评341 / 差评347)
- 评测脚本:本地跑,准确率 + 格式合规率 + 平均token
- 关键参数:
temperature=0,max_tokens=200,response_format按实验分组
为什么选gpt-4o-mini而不是gpt-4o?因为这是要上生产的批量任务,成本敏感。gpt-4o-mini的定价是$0.15/1M input、$0.60/1M output,gpt-4o是它的十几倍。实测在这个任务上,只要Prompt设计得当,mini完全够用。
三、方案设计:四组Prompt对比
我设计了四组策略,控制变量,只改Prompt:
| 组别 | 策略 | 示例数 | 输出约束 |
|---|---|---|---|
| A | Zero-shot | 0 | 无 |
| B | Few-shot | 4 | 无 |
| C | Few-shot + CoT | 4 | 无 |
| D | Few-shot + CoT + JSON Schema | 4 | json_object |
A组就是基线,测模型裸能力。B组加4个代表性示例(每类至少1个,含1个易混淆的中评)。C组在B基础上要求模型先输出判断依据再给结论。D组在C基础上强制JSON输出。
示例的选择很关键。我一开始随机抽了4条,效果一般。后来改成"边界样本优先"——专门挑那种"质量还行但物流慢"的中评,模型对边界的判断明显变好。
四、核心实现
先看统一调用封装:
import json
import time
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
def call_model(prompt: str, use_json: bool = False) -> dict:
kwargs = {
"model": "gpt-4o-mini-2024-07-18",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0,
"max_tokens": 200,
}
if use_json:
kwargs["response_format"] = {"type": "json_object"}
start = time.time()
resp = client.chat.completions.create(**kwargs)
latency = time.time() - start
return {
"content": resp.choices[0].message.content,
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"latency": latency,
}
D组的Prompt模板,这是最终胜出的版本:
SYSTEM_PROMPT = """你是电商评论情感分类专家。请严格按以下规则分类:
分类标准:
- 好评(pos):整体满意,无明显抱怨
- 中评(neu):褒贬参半,或问题轻微但被提及
- 差评(neg):核心体验受损,明确表达不满
判断步骤:
1. 提取评论中的正面点与负面点
2. 判断负面点是否影响核心体验(质量/功能/效果)
3. 若仅物流/包装问题且产品本身好评,归为neu
4. 输出JSON
输出格式(必须严格遵守):
{"label": "pos|neu|neg", "confidence": 0.0-1.0, "reason": "20字内理由"}
"""
def build_prompt(comment: str, examples: list) -> str:
ex_text = "\n".join(
f"评论:{e['text']}\n输出:{json.dumps(e['output'], ensure_ascii=False)}"
for e in examples
)
return f"""{SYSTEM_PROMPT}
参考示例:
{ex_text}
待分类评论:{comment}
输出:"""
评测脚本:
def evaluate(dataset, prompt_builder, use_json=False):
correct, total = 0, 0
fmt_ok = 0
total_tokens = 0
for item in dataset:
prompt = prompt_builder(item["text"])
result = call_model(prompt, use_json=use_json)
total_tokens += result["prompt_tokens"] + result["completion_tokens"]
try:
parsed = json.loads(result["content"])
fmt_ok += 1
pred = parsed["label"]
except Exception:
pred = result["content"].strip()[:3]
if pred == item["label"]:
correct += 1
total += 1
return {
"accuracy": correct / total,
"format_rate": fmt_ok / total,
"avg_tokens": total_tokens / total,
}
五、踩坑与优化
坑1:response_format=json_object 必须Prompt里出现"JSON"字样。 我一开始只在system里写了输出格式,没显式提"JSON"这个词,API直接报400。官方文档有写,但很容易忽略。
坑2:temperature=0 不等于完全确定性。 在1200条测试集上重跑两次,D组仍有7条结果不一致。所以生产环境我加了结果缓存,同一条评论用hash做key,避免重复调用。
坑3:CoT让output token涨了3倍。 C组平均completion token从18涨到61,虽然准确率上去了,但成本也上去了。D组通过"reason限制20字"把completion压回31,这是成本和效果的平衡点。
坑4:示例顺序有影响。 把差评示例放最后,模型对差评的召回率提升约2个百分点(可能是recency bias)。最终我把易混淆的中评放中间,差评放最后。
六、效果数据
1200条测试集,temperature=0,跑3次取平均:
| 组别 | 准确率 | 格式合规率 | 平均Token | 月成本(5万/天) |
|---|---|---|---|---|
| A | 78.3% | 71.2% | 42 | $2.4 |
| B | 89.7% | 88.5% | 187 | $10.1 |
| C | 94.2% | 90.1% | 243 | $13.6 |
| D | 96.1% | 99.8% | 31 | $4.2 |
几个值得说的点:
- D组比C组准确率高1.9个点,token反而少了87%。原因是JSON Schema约束让模型不再啰嗦,直接给结论。
- 格式合规率从71.2%到99.8%,这是工程上最有价值的提升——后端解析再也不用写一堆fallback。
- 按每天5万次调用算,D组月成本$4.2,比最初的B组方案省了77%。
- 延迟方面,D组平均1.2s,比B组的1.8s还快,因为output短了。
分类别看,差评的F1从0.81提升到0.94,中评从0.72提升到0.91。中评是最难的一类,CoT的推理步骤对它的帮助最大。
七、总结
这轮实验下来,我的几个结论:
第一,结构化输出约束的性价比极高。JSON Schema既提升准确率又降token,没有理由不用。
第二,Few-shot的示例质量比数量重要。4个精心挑选的边界样本,效果超过8个随机样本。别堆示例,要挑示例。
第三,CoT不是免费的。它提升准确率,但token涨得厉害。如果任务本身不难(比如二分类),CoT可能不划算。要算ROI。
第四,Prompt Engineering是可量化的工程。别凭感觉调,建测试集、跑指标、看token,跟调模型超参一样对待。
最后附上我现在的默认模板思路:System里写清分类标准和判断步骤,Few-shot给4个边界样本,强制JSON输出,reason限长。这套组合在电商情感分类上稳定在96%左右,成本可控,格式稳定。
如果你也在做类似任务,建议先跑一遍这四组对比,你会对"Prompt值多少钱"有非常直观的感受。