最近在做一个项目,需要从合同文本里抽日期、金额、甲方乙方这些字段。本地部署了Qwen2.5-7B,用sglang跑起来,发现prompt稍微换一下表达方式,抽取结果就飘忽不定。比如用“提取”还是“输出”结果就有差异,加了JSON格式约束也会偶尔抽疯,返回一些不在原文里的字段名。我知道RAG那套对抽取帮助不大,但总不能每次都给few-shot吧,几十个字段写全了token又爆炸。有没有老哥做过类似的?你们是直接写死system prompt,还是用了什么特殊的分隔符技巧?或者干脆微调了?求个方向,孩子快被这破格式逼疯了。
有大佬用Qwen搞过结构化信息抽取吗?感觉prompt怎么写都不稳
全部回复
共 60 条我之前搞过类似的事儿,也是合同抽取,最后发现7B这级别对指令跟随的稳定性就是差点意思。你试试把输出格式定义成纯自然语言描述,别给JSON骨架,让它自己生成,反而会稳一些。
另外字段名不一致的抽疯,大概率是模型在瞎编,我后来是把所有可能出现的别名直接列在prompt里,比如“甲方”后面加个括号(委托方/买方),这样能压住不少幻觉。
微调确实是最优解,但要是图省事,可以试试把few-shot压缩成只给一个最难的例子,其他字段靠规则后处理兜底,token能省不少。
你要是实在被逼急了,可以换个思路,先用Qwen抽出粗粒度块,再用正则去抠细字段,两边结合一下,比死磕一个prompt强。
同款痛点,我之前用Qwen抽合同也这样。后来发现别跟它纠结用词,直接在system prompt里把输出格式定义成一行一个字段的纯文本模板,比JSON稳很多,而且字段名用原文里的词,别自己发明缩写。另外可以试试把sglang的采样参数调低点,temperature设0.1,top_p也压一压,飘的情况能少不少。至于few-shot,我留了三个极端例子做锚点,但token问题你可以在prompt里塞一个“未知”兜底选项,让模型拿不准就填这个,比硬编字段名省心。微调暂时没试,但听人说用几百条伪标注数据跑个LoRA,效果比堆prompt靠谱。
同病相怜,我之前搞过一阵子供应商合同抽取,Qwen2.5-7B对指令的敏感度确实高得离谱,“提取”和“输出”这种词都算好的,我后来发现连标点符号都能影响结果,比如句号换成换行符,字段就漏了。你试过在system prompt里把每个字段的“定义”写清楚吗?比如“甲方乙方”别只给标签,加一句“乙方为合同中承担付款义务的一方”,这种语义锚点对7B模型比格式约束管用得多。
至于few-shot,token爆炸我太理解了,我最后是折中方案:只对最难的3-4个字段给例子,比如金额和日期,其他的靠正则预清洗兜底,让模型只负责填充缺失值,这样稳定性提升不少。sglang的话,你可以试试在采样参数里把temperature调低到0.1以下,并且打开repetition_penalty,我这么做之后至少不会平白无故编造字段名了。
微调我是真没敢碰,数据标注成本太高,但如果你合同模板比较固定,也许可以搞十几条伪样本做QLoRA,效果可能比死磕prompt靠谱。另外你说“返回不在原文里的字段名”,我怀疑是模型把上下文里的其他词当成了schema,你可以试试在输入末尾加一句“禁止输出原文中未出现的实体类型”,虽然不能根治,但能减少抽风频率。
还有个土办法,用sglang的structured generation功能,把输出schema用JSON Schema定义死,配合regex约束,虽然不能解决语义漂移,但至少保证格式合法,你再拿结果去校验,不合格就重跑一次。先别急着全用few-shot,试试混合策略,让模型只做“定位”和“归一化”,别让它做“判断”,能省不少心。
我之前搞过类似的,也是Qwen2.5,合同抽取这块真的玄学。我后来是把JSON格式直接写死在system prompt里,并且用XML标签把每个字段包起来,比如
我之前搞过类似的,也是Qwen2.5,后来发现问题不在prompt本身,而是模型对指令的“语义边界”特别敏感。你可以试试把所有字段定义成固定的中文标签,然后用XML标签包起来,比如<日期>...,比纯JSON稳很多。另外,few-shot别给全,每个字段给一个示例就够,token压力会小很多。微调的话,7B参数量其实值得试,LoRA跑一下也就一两天的事,效果比折腾prompt强多了。
同病相怜,我之前用Qwen做法律文书抽取也踩过这坑。你提到的“提取”和“输出”导致结果漂移,本质是模型对指令动词的敏感度太高,跟sglang的采样参数也有关系,试试把temperature调到0.1以下,top_p设0.9,能压住一部分随机性。但别指望纯靠prompt工程解决,7B模型对格式的语义理解上限就在那,尤其你字段多的时候,它很容易把“甲方”和“乙方”的语义边界搞混。我后来是折中方案:system prompt里只写清任务定义和输出规范,字段名全部用双尖括号括起来,比如“《甲方名称》”,然后每个字段后面跟一个示例值,但不给完整few-shot,这样token消耗可控,准确率能提升不少。你试过用函数调用模式吗?Qwen2.5支持tool calling,把字段定义成JSON schema传进去,让它走结构化生成路径,比裸prompt稳得多,只是需要稍微改下sglang的接口逻辑。要是字段实在太多,微调还是终极解,但别一上来就全量微调,先用100条标注数据做LoRA,针对你的合同领域调一下,效果立竿见影。另外检查下你的解码参数里有没有开repetition_penalty,有时候模型为了凑格式会自己造词,这个参数能抑制。
试试把输出格式定义成纯文本模板再让模型填空,别直接给JSON,7B对括号引号太敏感了。
试试把输出格式整个挪到system prompt里,user只给原文,字段定义用XML标签包起来,比纯JSON稳很多。另外温度调到0.1以下,采样参数别用默认的,7B对格式敏感的时候这招挺管用。要是还飘,建议只微调LoRA,几十个字段也就几百条标注数据,比折腾prompt省心多了。
说实话你这个情况我太熟了,当时做财报抽取差点被Qwen搞到怀疑人生。我的经验是system prompt里别用自然语言描述任务,直接给一个固定的字段映射模板,比如“输入合同文本,输出如下JSON,缺失填null”,然后所有字段名用英文小写加下划线,能明显减少幻觉。另外你试试把输出格式要求放在用户消息末尾,而不是系统提示里,我发现Qwen对位置很敏感,放前面容易被它当成对话历史忽略掉。分隔符的话,我用过XML标签那种,像
我之前也踩过这坑,Qwen对指令的措辞敏感得离谱,后来干脆把system prompt里所有动词统一成“抽取”,然后强制要求输出纯JSON数组,连字段名都提前在prompt里定义好。few-shot别全给,每个字段给一个正例一个反例,token能省一半。要是还是飘,建议直接上微调,LoRA跑几十条标注数据比调prompt省心多了。另外检查下是不是sglang的采样参数问题,temperature调低到0.1能稳不少。
同感,7B模型对指令的敏感度确实离谱,我试过用XML标签把字段包起来,比纯JSON稳定点,但遇到长文本还是会漏抽。你要是字段固定,干脆把system prompt写成填空式模板,让模型只填值,token占用其实可控。微调倒是终极方案,但成本摆在那,先用正则把原文里明显的日期金额预处理掉,再让模型补全其他字段,能省不少事。
同感,Qwen2.5对指令的表述方式特别敏感,我之前用“字段:值”这种格式约束也翻过车。后来试了下把要抽的字段名全部大写加粗,然后在system里明确写“只输出原文出现的实体,禁止推断”,稳定性好了不少。另外你可以试试用正则把返回结果里的非法字段直接过滤掉,比硬调prompt省心。微调的话数据量没几百条效果也不明显,建议先拿few-shot跑通再考虑。
试试在prompt里把字段定义成固定编号+用XML标签包起来,我之前这么搞稳多了,7B对格式敏感但认结构。
试试把输出格式直接写进system prompt里,用XML标签包住字段,比JSON稳多了,还省token。
我这边也是Qwen2.5,抽合同字段直接上微调了,LoRA跑20个样本就够,prompt再折腾也就那样。
我之前也踩过这坑,Qwen对指令格式确实敏感,后来发现把要抽的字段和类型写进system prompt里,再加个固定模板“原文:xxx 输出:json”能稳不少。少样本别全给,每个字段给一两个正反例就够了,多了反而干扰。你要是字段固定,不如试试用函数调用或者约束解码,sglang本身支持json schema,能硬卡输出格式,比prompt硬磨靠谱。微调倒是终极方案,但先别急,拿十几个典型合同跑跑看是不是模型本身理解问题。
试试把输出格式改成纯文本加固定分隔符,别让模型自由发挥JSON,能稳不少。
微调才是正道,7B用LoRA搞个几百条标注数据,比调prompt省心多了。
同感,Qwen2.5-7B对指令的措辞敏感度确实高,我之前做法律文书抽取也踩过这坑,“提取”和“抽取”结果能差出好几个字段。后来试了把输出格式直接写进system prompt里,用XML标签把每个字段包起来,比如
试试把输出格式定义成带正则约束的伪代码,比纯JSON稳很多,再配个函数调用的模板。
说实话你这情况我太熟了,之前用Qwen抽物流单据也是这德行,系统提示词里写“返回JSON”和“请以JSON格式输出”结果都能差出去十万八千里。后来我试了个偏方,把字段定义直接嵌进输入文本里,比如“合同签署日期是_,甲方全称是_,付款金额是____”,让模型做填空而不是抽取,稳定性一下子高了不少。另外你提到几十个字段,我建议先跑两轮粗筛,第一轮只抽高频核心字段,第二轮再基于第一轮的结果去补全剩余字段,这样token压力小,逻辑上也更可控。微调的话除非你有几百条高质量标注样本,不然7B模型很容易过拟合到你的模板上,换个写法又崩,性价比其实不高。还有个土办法,把原文里可能出现的日期格式、金额单位先正则预处理一遍,能兜住一部分底,模型抽歪了你还能靠规则纠偏。反正别指望一个prompt通吃,结构化抽取这活儿本质上是工程问题,不是纯提示词能解决的。
同款项目,被Qwen的字段幻觉折磨过,后来试了试把抽取目标定义成带编号的列表,每个字段后面加一行“原文中不存在则返回空字符串”,效果比JSON约束稳不少。还有个小技巧,system prompt里强调“严格基于输入文本,不得添加任何额外内容”,中文表述比英文指令管用。微调暂时没碰,但感觉如果字段特别固定,用几十条样本做LoRA可能是终极方案,token压力也能接受。你这几十个字段确实难搞,不如先按业务优先级拆成几个子任务,每个只抽一小类,分步跑,稳定性会好很多。