最近在做一个项目,需要从合同文本里抽日期、金额、甲方乙方这些字段。本地部署了Qwen2.5-7B,用sglang跑起来,发现prompt稍微换一下表达方式,抽取结果就飘忽不定。比如用“提取”还是“输出”结果就有差异,加了JSON格式约束也会偶尔抽疯,返回一些不在原文里的字段名。我知道RAG那套对抽取帮助不大,但总不能每次都给few-shot吧,几十个字段写全了token又爆炸。有没有老哥做过类似的?你们是直接写死system prompt,还是用了什么特殊的分隔符技巧?或者干脆微调了?求个方向,孩子快被这破格式逼疯了。
有大佬用Qwen搞过结构化信息抽取吗?感觉prompt怎么写都不稳
全部回复
共 69 条说实话我也被这玩意儿折磨过一阵,后来发现直接让模型输出纯文本+自定义分隔符比强上JSON稳得多,比如用“字段名||值”这种,再加个“只输出原文有的内容”的system约束。few-shot确实不能给全,我一般就挑两个最难的字段各给一个例子,剩下的靠格式约束硬扛。另外你试试把温度调到0.1以下,有时候飘就是采样随机性闹的。微调暂时别碰,7B基座搞抽取用LoRA也得攒不少标注,性价比不高。
试试把字段定义写进system prompt里用XML标签包起来,比JSON稳很多,7B对格式敏感但语义理解还行。
微调吧,7B用sglang跑few-shot确实容易飘,我试过把字段名写进schema约束能稳一点。
说实话你这个情况太典型了,Qwen2.5-7B在抽取任务上对指令的敏感度确实高得离谱,我拿它做法律文书抽取也踩过一模一样的坑。后来我试了一圈,发现与其跟prompt较劲,不如把输出格式彻底锁死——比如在system prompt里直接给一个残缺的JSON模板,只留字段名和空值,让它“填空”而不是“生成”,稳定性会好很多。但你这几十个字段全塞进去token肯定爆炸,所以更建议按字段类型分组抽,比如先抽日期金额这类定长数字,再单独跑一轮抽甲乙双方,每轮只给5-6个字段的模板。至于微调,除非你有几百条带标注的真实合同,否则7B模型学不到什么泛化能力,反而容易过拟合到你的样例格式上。另外你提到它偶尔编造原文里没有的字段名,这个多半是模型在补全它训练时见过的合同模板,可以试试在prompt末尾强调“只能使用给定字段列表,禁止新增”,然后配合采样参数把temperature调到0.1以下。还有个土办法,反正你用了sglang,可以在后端做个输出校验,如果返回的JSON里出现了不在白名单里的key,就直接强制重采样一次,虽然不能根治但能拦住大部分抽风。最后想说,如果项目周期紧,别在纯prompt上死磕,用规则预处理把明显结构化的段落切出来,再让模型只抽那几行,效果可能反而比让它读全文强。
同款项目路过,合同抽取这块Qwen2.5-7B确实容易在措辞上翻车,我试过把“提取”改成“识别”结果日期格式都变了。后来发现一个笨办法,就是system prompt里用极简的伪代码描述任务,比如“输入文本,返回字段列表,值必须原文子串”,比纯自然语言稳定很多。JSON约束那个我一开始也踩坑,后来不用response_format,改成在prompt里给一个残缺的JSON模板,比如“甲方:,乙方:,金额:”,让它只填空,反而很少乱造字段。不过你这几十个字段确实难搞,few-shot放不下的话,可以试试把字段定义拆成两轮,第一轮先让它判断哪些字段存在,第二轮再抽具体值,能省不少token。微调我还没敢碰,但看社区里有人用几百条标注数据把7B调完,说准确率能上95%,你要是项目周期长,这条路可能比死磕prompt值。还有个偏方,对金额和日期这种格式敏感的,可以单独写个正则兜底,模型输出后强行校验一遍,不符合就重抽,虽然丑但能救命。
我之前搞过类似的事,也是Qwen系列,后来发现关键不在prompt措辞,而是让它先输出一个“空字段模板”,再填值,抽风概率会低不少。另外你试过把输出格式定义成XML而不是JSON吗?对7B来说XML的括号约束反而更稳定。微调的话,如果你能攒几百条真实合同样本,lora跑一下效果会质变,但要是字段总变,那还是写死模板加正则兜底更省心。
这问题太真实了,Qwen对指令的措辞敏感度确实高,我试过用XML标签把字段包起来,比纯JSON稳一点,但偶尔还是会编值。你可以试试把system prompt里所有字段定义成“如果原文没有就返回null”,再给一个极简的固定模板样例,别写完整JSON结构。另外别用“提取”这种动词,换成“从原文中复制对应内容”,效果会好不少。真要几十个字段还想稳定,7B不微调基本到头了,我后来换了Qwen2.5-14B,幻觉少很多,但速度又下来了,看你能不能忍。
我上个月刚踩过一模一样的坑,Qwen2.5-7B做合同字段抽取,prompt换个说法结果就变,后来发现是sglang那边默认的采样温度没调,冷启动时特别明显,设成0之后稳定性好了不少。但json约束还是会偶发抽风,尤其是字段名映射,模型会自己发明一些看起来很合理但原文没有的key,后来我在system里加了一句“字段名必须从以下列表中选,不允许新增”,效果比单纯写json schema好。few-shot确实token爆炸,我试过只给两个最难的字段做示例,其他字段靠描述约束,勉强能跑。另外强烈建议上outlines或者xgrammar这种结构化解码,比纯prompt约束靠谱太多,sglang本身也支持。如果字段量真的几十个,微调可能反而是省心的路,LoRA训个几百条就比prompt调半天强。
用sglang的话可以试试outlines或者xgrammar做约束解码,比在prompt里写JSON schema靠谱多了,字段名直接从语法层面锁死。Qwen2.5本身对tool call格式的遵循还行,你可以把抽取任务包装成function calling,schema里把字段和类型都定死,比纯文本prompt稳不少。few-shot不用全字段都写,挑三五个典型难例放进去就够,token压力没那么大。实在要微调的话LoRA几百条标注数据就能见效,但优先试试约束解码这条路线。