1. 问题背景:当GPT-4o把“甲方”识别成“甲方公司”
上个月接了个法律文书结构化项目,要求从判决书中抽取原被告、案由、赔偿金额。最初用最朴素的Prompt:
请从以下文本中抽取实体:原告、被告、金额。
结果GPT-4o(版本:gpt-4o-2024-05-13)返回的JSON里,把“甲方(某建设集团)”拆成两个实体,金额“人民币10万元”识别成“10”。最离谱的是,某条文书里“原告某某公司”被识别为“被告”。这让我意识到——不是模型不行,是Prompt没给够约束。
2. 环境与版本:一套可复现的基准配置
- 模型:
gpt-4o-2024-05-13(temperature=0,top_p=0.1) - 测试集:200条人工标注的民事判决书片段,平均长度800字
- 评估指标:实体级F1(严格匹配)+ token消耗(含输入输出)
- 框架:
openaiPython包 v1.30.1,langchain0.2.1(仅用于解析输出)
所有实验采用同一批数据,只改动Prompt模板,控制变量。
3. 方案设计:四代Prompt的演进路线
我按复杂度递增设计了四个版本:
- V1零样本:一句话指令,无示例
- V2少样本:3条完整示例+输出格式定义
- V3思维链:先让模型“找线索”,再抽取
- V4结构化约束:XML标签+两步推理+禁止猜测规则
关键设计原则:把模型当实习生带,而不是当搜索引擎用。
4. 核心实现:从翻车到稳定的代码演化
4.1 V1到V2:少样本的威力与代价
第一版代码(零样本)长这样:
from openai import OpenAI
client = OpenAI(api_key="sk-xxx", base_url="...")
def extract_v1(text):
prompt = f"从文本中抽取原告、被告、金额。文本:{text}"
resp = client.chat.completions.create(
model="gpt-4o-2024-05-13",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
return resp.choices[0].message.content
跑完200条,F1只有0.45。分析错误案例发现:模型不知道“原告”的定义边界(是否包含“原审原告”),且输出格式混乱(有的返回JSON,有的返回纯文本)。
V2加入3条人工标注示例,并强制response_format={"type": "json_object"}:
def extract_v2(text):
examples = """
示例1:
文本:原告张三(男,1990年生)诉被告李四建设工程合同纠纷一案。
输出:{"原告": "张三", "被告": "李四", "金额": null}
"""
prompt = f"{examples}\n请按相同格式抽取。文本:{text}"
# ... 同上,加response_format
F1提升到0.71,但token消耗暴涨——每条示例约800 token,200条测试多烧了16万token(约0.3美元)。少样本不是免费的。
4.2 V3思维链:让模型先思考再回答
V3改用两步提示:先让模型列出所有与“原告”“被告”相关的短语,再基于这些短语生成JSON。这利用了模型的推理缓存在短文本上的优势:
def extract_v3(text):
prompt = f"""
第一步:找出文本中所有指代原告、被告的短语(包括全称、简称、代词)。
第二步:基于第一步结果,输出JSON,键为"原告"/"被告"/"金额",值为完整字符串。
文本:{text}
"""
# 输出解析:先取第一步的列表,再取第二步JSON
F1升至0.85,但出现了新问题:模型偶尔会在第二步“修改”第一步的结果(比如补全简称)。而且有些案例中,第一步输出了20多个短语,导致输出token达到4000+。
5. 踩坑与优化:那些让我抓狂的细节
坑1:金额“壹拾万元” vs “10万元”
V3模型经常把汉字大写金额原样输出,而标注里要求统一为阿拉伯数字。解决方案:在Prompt里加一条“所有金额转换为阿拉伯数字,保留两位小数”,F1直接+0.03。
坑2:被告“某某公司”指代不清
判决书常出现“被告一”“被告二”,模型会合并。我把输出格式改为数组:
{"被告": ["公司A", "公司B"]}
并在Prompt里明确“每个被告独立元素”,解决了90%的合并错误。
坑3:XML标签比Markdown更稳
V4实验发现,用``包裹输出,比用json围栏更少出现格式断裂。原因是GPT-4o对XML的约束力感知更强。最终模板:
请按以下结构输出:
...
...
...
禁止输出其他内容,缺少的字段填null。
6. 效果数据:最终成绩与成本核算
| 版本 | F1(严格匹配) | 平均token/条 | 200条总成本 |
|---|---|---|---|
| V1零样本 | 0.45 | 1200 | 0.15美元 |
| V2少样本 | 0.71 | 3200 | 0.42美元 |
| V3思维链 | 0.85 | 2500 | 0.33美元 |
| V4结构化 | 0.92 | 1800 | 0.24美元 |
V4最终Prompt核心片段(已脱敏):
你是一名法律信息抽取专家。任务:从判决书中抽取原被告及赔偿金额。
规则:
1. 原告、被告各为数组,包含所有指代(全称+简称)。
2. 金额统一为阿拉伯数字,如“壹拾万”转为“100000.00”。
3. 若文本无金额,输出null,禁止猜测。
4. 先判断“谁起诉谁”,再找金额。
输出格式(XML):
[...][...]...
7. 总结:Prompt工程的三个反直觉结论
- 更长的Prompt不一定更贵——V4比V2短,但准确率高21%,因为去了冗余示例,加了精准规则。
- “禁止猜测”比“请准确”有效得多——负面约束能显著减少幻觉。
- 输出格式用XML,别用JSON——在GPT-4o上,XML的括号结构更不容易被截断。
最后,别迷信“万能Prompt模板”。我试过网上流传的“角色+任务+格式”三段式,对法律文本效果一般。真正的prompt优化,是拿你的数据去试,记录每一次失败的原因。现在这套V4模板,我已在另一个医疗文本抽取任务上复用,F1从0.38直接到0.89——虽然仍需微调金额规则,但框架是通用的。
如果你也在做类似的信息抽取,建议直接抄V4的XML结构,把plaintiff换成你的实体名,跑一遍自己的数据,再根据错误案例加规则。有疑问欢迎评论区交流。