1. 问题背景:Prompt不是“玄学”,是工程
上周我在做一个金融舆情监控系统,需要从上市公司公告中抽取“担保人”、“被担保人”、“担保金额”和“担保期限”四个实体。最初我用了一个简易模板,结果模型把“担保金额”识别成“被担保金额”,甚至把“人民币”也当成实体值。这让我意识到:Prompt设计直接决定业务效果,而不仅靠“提示词越长越好”的直觉。
于是我做了一组对照实验,目标是回答三个问题:
- 哪种Prompt模板在实体抽取任务上F1最高?
- token消耗差异有多大?换算成成本差多少?
- 哪些“花哨”技巧(比如角色扮演)真的值得?
2. 环境与版本
- 模型:
gpt-4-turbo-1106(通过OpenAI Python SDK v1.10.0调用) - 温度:
0.2(降低随机性,保证可复现) - max_tokens:
1024 - 数据集:自建中文金融公告200条,人工标注实体(BIO格式)
- 评估指标:F1(严格匹配实体边界+类型)、单条平均token消耗(
usage.prompt_tokens + usage.completion_tokens) - 成本换算:按OpenAI官方价格(输入$0.01/1K tokens,输出$0.03/1K tokens)
所有实验均在同一个test set(50条)上进行,每类模板重复3次取均值。
3. 方案设计:5种Prompt模板
我设计了5种模板,从最朴素到最“花哨”:
| 编号 | 模板类型 | 核心指令 |
|---|---|---|
| T1 | 零样本 | 直接问“提取实体” |
| T2 | 少样本 | 给2个完整的输入-输出示例 |
| T3 | 思维链 | 要求“先思考再输出” |
| T4 | 结构化输出 | 指定JSON schema,并给约束条件 |
| T5 | 角色扮演 | “你是专业金融NLP标注员” |
注意:T3和T4都要求输出JSON,但T3增加了一步“推理过程”。T5在T4基础上加了角色前缀。
4. 核心实现:代码与关键细节
4.1 基础调用函数(所有模板共用)
# prompt_template.py
import openai
import json
client = openai.OpenAI(api_key="sk-xxx", base_url="https://api.openai.com/v1")
def call_model(system_prompt, user_prompt):
response = client.chat.completions.create(
model="gpt-4-turbo-1106",
temperature=0.2,
max_tokens=1024,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
usage = response.usage
total_tokens = usage.prompt_tokens + usage.completion_tokens
return response.choices[0].message.content, total_tokens
4.2 结构化输出模板(T4)的关键实现
T4的核心不是简单问“提取”,而是强制模型遵循一个schema,并对边界做约束。这是我在实验中效果最稳的模板。
# t4_structured.py
def build_t4_prompt(text):
schema = {
"type": "object",
"properties": {
"guarantee": {"type": "string", "description": "担保人全称,去除'公司'后缀以外的修饰词"},
"guaranteed": {"type": "string", "description": "被担保人全称"},
"amount": {"type": "number", "description": "担保金额,单位万元,纯数字"},
"period": {"type": "string", "description": "担保期限,如'1年'或'自合同生效之日起至2025年12月31日'"}
},
"required": ["guarantee", "guaranteed", "amount", "period"]
}
system_prompt = "你是实体抽取引擎。仅输出JSON,不要附加任何解释。"
user_prompt = f"""
从以下公告中抽取实体,严格按schema输出:
{json.dumps(schema, ensure_ascii=False)}
文本:
{text}
注意:金额统一转成数字(万元),日期保留原文。
"""
return system_prompt, user_prompt
5. 踩坑与优化:三个没想到的坑
坑1:T3思维链模板输出不可控
我让T3先输出“理由”再输出JSON,结果模型经常把理由写很长,导致completion_tokens从平均150暴涨到400+。更坑的是,偶尔它会输出然后答案是:这样的非JSON文本,解析时直接报错。解决方案: 在T3的prompt末尾强加只输出JSON,不要理由——但这等于把T3退化成了T4。
坑2:T2少样本模板的“示例污染”
我给了两个示例,其中一个是“担保金额为5000万元”,结果测试时模型把“5000”直接复制到输出里,即使文本中实际是“6000万元”。这是经典的“示例锚定”效应。优化: 将示例中的金额改成不常见的“12345万元”,并明确标注“示例仅展示格式,不代表真实数据”。
坑3:角色扮演模板的隐藏成本
T5加了“你是专业金融NLP标注员”之后,输出质量确实提升了,但token消耗也明显增加。原因是模型倾向于“入戏”,输出一些“根据我的专业判断”之类的废话。优化: 将角色描述从system prompt移到user prompt末尾,并且加“直接输出,不要解释”——这样既保留角色影响,又压缩冗余。
6. 效果数据:一张表看清所有差异
| 模板 | F1(严格) | 平均token/条 | 每千条成本(USD) | 输出格式稳定性 |
|---|---|---|---|---|
| T1零样本 | 0.67 | 268 | ~$7.2 | 差,经常漏字段 |
| T2少样本 | 0.75 | 356 | ~$9.5 | 中,有锚定偏差 |
| T3思维链 | 0.79 | 513 | ~$14.3 | 差,JSON解析失败率12% |
| T4结构化 | 0.82 | 412 | ~$11.1 | 高,解析失败率0% |
| T5角色扮演 | 0.85 | 687 | ~$19.0 | 高,但费用贵70% |
关键结论:
- 从T1到T4,F1提升15个百分点,token成本只增加54%。T4是性价比最优解。
- T5比T4只提升3个点F1,但token消耗暴涨67%。对于业务场景,这3个点不值得。
- 如果预算敏感,建议用T4;如果对F1有硬指标(如>0.85),可以考虑T5,但需要配合更严格的max_tokens限制。
7. 总结:Prompt工程的核心是“约束”而非“堆砌”
这次实验让我改变了一个认知:好的Prompt不是更长的prompt,而是更严格的约束。 T4之所以胜出,是因为它把输出schema、类型约束、清洗规则全部显式写清楚,让模型没有“自由发挥”的空间。
实用的建议:
- 优先使用结构化输出模板,并明确JSON schema。
- 少样本模板要小心“示例锚定”,用极端值做例子。
- 思维链(CoT)适合复杂推理,但不是抽取任务的首选。
- 角色扮演可以提质量,但必须搭配“直接输出”指令来控成本。
最后留个问题:我的实验用的是GPT-4-turbo,如果你换成Claude-3或者Llama-3-70B,结果可能完全不同。建议在业务落地前,至少测试3个不同厂商的模型,用同一套模板做个快速评测——这比盲目调prompt更高效。
代码和数据集已放GitHub(仓库名:prompt-entity-benchmark),欢迎star和提issue。