1. 问题背景:Prompt写不好,模型就成了“人工智障”
做金融NLP的同学都懂,公告文本的“事件抽取”是个老难题。项目要求从上市公司公告中提取“交易标的”、“交易金额”、“支付方式”等结构化字段,用于下游风控系统。
最初我直接用GPT-4(版本号:gpt-4-0613)做零样本调用,效果惨不忍睹——事件类型判断准确率只有37%,金额抽取经常把“1.2亿元”抽成“1.2”,更离谱的是把“受让”和“转让”方向搞反。
经过排查,问题不在模型,在我的Prompt太随意。写的是“请提取公告中的事件信息”,这种指令对于大模型来说等同于“你看着办”。于是我开始系统性实验Prompt模板,本文记录整个过程。
2. 环境与版本:固定条件才能对比
所有实验在同一环境完成,确保可比性:
- 模型:GPT-4 (gpt-4-0613),temperature=0.2,max_tokens=1500
- 框架:LangChain 0.1.0 + Python 3.10
- 数据集:50条人工标注的并购重组公告(测试集)
- 评估指标:字段级精确匹配率(即抽取出的字段值与标注完全一致的比例)
- 成本核算:按OpenAI标准定价(输入$0.03/1K tokens,输出$0.06/1K tokens)
我需要强调,temperature必须固定——有次我调了0.7导致结果抖动超过20%,差点得出错误结论。
3. 方案设计:四个Prompt迭代方向
我制定了四个递进式的实验方案:
V1 零样本基础版:一句话指令,无任何格式约束
V2 少样本示例版:给2个正例,暗示输出格式
V3 思维链+角色版:设定“金融专家”角色,要求模型先推理再输出
V4 模板约束+反例版:在V3基础上,强制JSON输出格式,并加入1个错误反例
关键设计是V4的“反例”——把模型容易犯的错误(比如方向搞反)明确写进Prompt里,告诉它“不要这样做”。
核心代码框架如下:
# prompt_template.py
from langchain.prompts import PromptTemplate
# V4版本:模板约束 + 反例
V4_TEMPLATE = """
你是一个严格的金融公告信息抽取系统。你的任务是提取并购重组事件中的结构化字段。
【公告原文】
{text}
【抽取规则】
1. 事件类型必须是以下枚举值之一:股权收购、资产收购、股权转让、资产转让、吸收合并
2. 交易方向务必注意:A公司收购B公司股权,交易方向是"买入";反之是"卖出"
3. 金额字段必须为纯数字,单位统一为"万元"。例如"1.2亿元"应输出"12000"
【错误反例】
以下抽取结果是错误的,禁止模仿:
- 错误:{{"event_type": "交易", "direction": "未知"}}
- 原因:事件类型不具体,方向不明
【输出格式】
必须严格输出JSON对象,不允许包含任何其他文字:
{{
"event_type": "string",
"target_company": "string",
"direction": "买入|卖出",
"amount_wan": "number"
}}
现在开始抽取:
"""
def build_prompt(text: str) -> str:
return PromptTemplate.from_template(V4_TEMPLATE).format(text=text)
4. 核心实现:跑通对比实验
我写了一个统一的实验脚本,循环跑四个版本的Prompt,记录准确率和Token消耗。
# experiment_runner.py
import json
import openai
from prompt_template import V1_TEMPLATE, V2_TEMPLATE, V3_TEMPLATE, V4_TEMPLATE
openai.api_key = "sk-xxx"
def run_extraction(prompt_text: str, content: str) -> tuple:
"""返回 (抽取结果dict, token消耗)"""
resp = openai.ChatCompletion.create(
model="gpt-4-0613",
temperature=0.2,
max_tokens=1500,
messages=[
{"role": "system", "content": "You are a precise data extraction engine."},
{"role": "user", "content": prompt_text.format(text=content)}
]
)
raw_output = resp["choices"][0]["message"]["content"]
usage = resp["usage"]["total_tokens"]
# 解析JSON,失败则返回空dict
try:
return json.loads(raw_output), usage
except json.JSONDecodeError:
return {}, usage
# 主循环:50条测试数据,分别跑V1-V4
versions = {"V1": V1_TEMPLATE, "V2": V2_TEMPLATE, "V3": V3_TEMPLATE, "V4": V4_TEMPLATE}
results = {v: {"acc": 0, "tokens": 0} for v in versions}
for i, item in enumerate(test_data):
for ver, template in versions.items():
result, tokens = run_extraction(template, item["text"])
# 字段级匹配
acc = 0
for field, value in item["label"].items():
if result.get(field) == value:
acc += 1
acc /= len(item["label"])
results[ver]["acc"] += acc
results[ver]["tokens"] += tokens
# 输出汇总
for ver, data in results.items():
print(f"{ver}: 平均准确率={data['acc']/50:.2%}, 平均Token消耗={data['tokens']/50:.0f}")
这个脚本跑完大约消耗了12万Token,成本约5美元,换来的是清晰的数据对比。
5. 踩坑与优化:那些让我想砸键盘的坑
坑1:JSON输出不稳定
V3版本要求输出JSON,但模型经常在JSON前后加“好的,这是结果:”这种废话。我用了两次json.loads失败后,才意识到必须用response_format={"type": "json_object"}参数——GPT-4 0613支持这个参数,加上之后解析成功率从68%直接到99%。
坑2:少样本示例的“副作用”
V2给了2个正例,准确率反而比V1低。我检查后发现,示例中的“交易金额”字段格式不统一,一个写了“亿”一个写了“万”,模型学会了这种混乱。少样本示例必须保持格式高度一致性,否则不如不给。
坑3:方向字段的隐性知识
V3加入了“先推理再抽取”的思维链,但模型在推理阶段会引入外部知识(比如“腾讯收购”它知道是买入),导致对公告原文的依赖降低。最终方案是禁止推理,改为“直接提取原文中的动词判断方向”。
坑4:Token消耗的隐性膨胀
V3思维链虽然准确率高了,但每次调用平均消耗1876 Token,比V1多了42%。对于生产环境每天10万次调用,成本差异巨大。V4通过精炼模板(去掉冗余角色描述),把Token压到1042,同时准确率反而提升。
6. 效果数据:最终对比
| 版本 | 准确率 | 平均Token消耗 | 单条成本(元) | 失败模式 |
|---|---|---|---|---|
| V1 零样本 | 37.2% | 1321 | ¥0.028 | 格式混乱,类型泛化 |
| V2 少样本 | 41.8% | 1458 | ¥0.031 | 格式不一致误导 |
| V3 思维链 | 76.5% | 1876 | ¥0.040 | 推理干扰原文 |
| V4 模板+反例 | 82.3% | 1042 | ¥0.022 | 极少数金额单位错误 |
V4的准确率比V3提升了5.8个百分点,Token消耗反而下降了44%。关键贡献点是“错误反例”——模型在Few-shot场景下,正例是学习的下限,反例是上限。如果你不给反例,模型容易在边界情况上翻车。
7. 总结:Prompt Engineering是系统工程
这次实验让我深刻意识到,Prompt不是“写一句话”,而是设计一个信息约束系统。几个可复用的经验:
- 输出格式必须强约束:用JSON Schema或枚举列表,比自然语言描述“请输出结构化数据”有效10倍
- 反例比正例更重要:模型对错误的敏感度远高于正确,尤其在边界逻辑(如方向判断)
- Token消耗与准确率不是正相关:冗长的角色设定和思维链往往无效,简洁+精确的规则才是关键
- 版本号要锁死:GPT-4从0613到1106,行为变化明显,生产环境必须固定模型版本
如果你想复现实验,注意把response_format参数加上,否则你会被JSON解析折磨到怀疑人生。后续我会继续研究多轮对话式抽取,把用户反馈纳入Prompt上下文,理论上能进一步压到90%以上。欢迎评论区交流。