**

一、问题背景:为什么我要死磕这个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设计要匹配任务复杂度:

  1. 字段抽取类任务,Schema约束 > 长篇CoT。把枚举值、判定规则、输出格式一次性写死,比让模型"自己思考"更稳定也更便宜。
  2. token要算总账。P3单条看着只贵0.0002美元,乘上业务量就是真金白银。
  3. 别迷信temperature=0。评估至少跑3轮,否则你看到的提升可能只是噪声。
  4. 示例要防污染。Few-shot的示例分布必须覆盖真实数据的全部分布,否则模型会"抄偏"。

下一步我打算试试把P4的Schema换成Pydantic模型自动生成,再对比一下手工Prompt和自动生成Prompt的差异——如果效果好,后续维护成本能再降一截。有兴趣的同学可以蹲个后续。