ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Claim抽取实战:先定Chunk再Hybrid的完整技术路线

Claim抽取实战:先定Chunk再Hybrid的完整技术路线 项目标题Claim 抽取先定 Chunk 再 Hybrid做 Claim 抽取也就是从文本里抽“可验证的主张/论断”的时候很多团队习惯一上来就调大模型甚至把整份合同、整篇论文直接喂给模型去抽。我在这个方向上踩过一轮坑现在的流程基本固定成了三步先定 Chunk再搭 Hybrid 检索最后才轮到抽取模型本身。这个顺序一旦搞反后面调 Prompt、换模型、调温度都像是在给一口漏水的锅补盖子。这篇文章把这条技术路线完整拆开讲适合正在做知识抽取、事件抽取、文档智能处理或者准备把大模型接进生产环境做信息提取的团队参考。1. 为什么要“先定 Chunk 再 Hybrid”1.1 任务理解Claim 抽取到底在抽什么先对齐一个概念。Claim 抽取和传统的“实体识别”“关系抽取”不是一回事它更接近“论点级抽取”。比如从一篇临床试验报告里抽“该药物在X人群中降低了30%的复发率”从一份合同里抽“甲方应在收到发票后10个工作日内付款”从一条新闻里抽“某公司宣布以XX亿元收购某团队”。这些内容都带有主体、行为、条件、结论而且通常散布在文档的不同位置。一个 Claim 的证据链往往跨越多句话甚至多个段落这就决定了“把上下文完整地带给模型”比“抽模型本身有多强”更关键。事件抽取是这个任务家族里最常见的一个子类。比如金融领域的“并购事件”、医疗领域的“不良反应事件”、舆情领域的“灾害事件”它们的共同点是事件触发词出现在前半段论元分散在后续段落里如果只给模型一小段碎片论元一定抽不全。换句话说Claim 抽取的上限取决于你递给模型的上下文有多完整。1.2 顺序背后的逻辑检索上下文决定了抽取上限为什么强调“先定 Chunk 再 Hybrid”因为这两件事决定了抽取模型能“看到”什么。假设你有一个 5000 词的文档库抽取模型能接受的上下文窗口是 2000 词。你不可能把整个文档库塞进去只能先切块Chunking再从这些块里找出和待抽取目标最相关的几个块Hybrid Retrieval最后把“查询条件 候选块列表”拼进 Prompt。这个链路里Chunk 切得好不好直接决定了候选块里“有没有料”Hybrid 检索好不好决定了“有料的块能不能被捞出来”。两个环节互相依赖但 Chunk 是上游中的上游。Chunk 边界切断了一个 Claim 的证据链再强的检索也捞不回来完整的上下文检索漏召回了相关块抽取模型就只能凭残缺信息硬猜。我见过一个典型的反面案例某团队做合同条款抽取直接把整个合同按 512 字符硬切结果“付款条件”和“违约责任”被切到两个 Chunk 里模型每次都只看到一半抽出来的 Claim 不是缺主语就是缺时间。后来他们花了两周调 Prompt、换模型效果始终上不去。最后我把切分策略改成“按条款边界切 200 字符重叠”同样一个模型F1 直接涨了十几个点。这就是顺序的意义先修好输入的地基再谈模型的天花板。1.3 常见错误路径先调抽取模型再回头切片的代价很多团队的实际路径是先拿整篇文档跑抽取 → 发现长文档超限 → 改为“前 N 个字符”截断 → 用截断结果调 Prompt → 效果不行 → 怀疑模型 → 换更大的模型 → 成本翻倍效果还是不行。这条路径最大的问题在于把本该由上游解决的问题全部堆积到了下游的模型身上。模型上下文窗口是有限的即使强行塞得下也会因为无关信息过多产生“注意力稀释”导致关键约束被忽略。你让模型在 8000 字的上下文里找一个 “验收合格后15日内” 的付款条件它往往会被中间的大段技术描述带跑。相比之下先把 Chunk 边界规划好再让检索只召回 2-3 个高相关片段模型看到的每句话都紧扣主题抽取准确率自然高。从成本和响应速度来看这个顺序同样划算。固定 Chunk Hybrid 之后每次抽取请求只需要携带少量候选片段Token 成本稳定可控接口延迟也从“处理整篇文档”的几十秒降到了“处理几个片段”的几秒。对于生产环境来说稳定比“偶尔聪明”重要得多。2. 第一步Chunk 切分策略的设计与参数选择2.1 按长度切分 vs 按语义边界切分Chunk 切分不是简单地把文本剁成等长段落。按固定长度切比如每 500 个 Token 一刀实现简单但问题很多一个完整句子可能被拦腰切断一个条件句的“如果”和“那么”分到了两个块里模型拿到的块全是残句。对于检索来说这种残句的语义向量质量也很差因为 embedding 模型在编码“不完整的意思”时生成的向量会偏向字面统计而不是真正含义。更可靠的做法是按语义边界切。具体来说优先级从高到低是文档结构边界 段落边界 句子边界 固定长度兜底。比如一份合同先按“第X条”切一份技术文档先按 Markdown 标题和列表切一篇论文先按“引言/方法/结果/讨论”的章节切。段落和句子作为二级边界最后才用 Token 长度做上限控制。这样切出来的每个 Chunk内部语义相对完整外部边界也比较清晰后续无论是向量化还是关键词索引质量都会高不少。中文文本切分有个额外注意点英文按空格和标点切句子很自然中文必须按句号、问号、感叹号、分号做断句。我见过有人按字符长度硬切中文结果一个完整论断被切成两半模型抽取时频繁出现“张冠李戴”的主语。切分器里一定要内置中文标点的断句逻辑。2.2 关键参数chunk_size、overlap 与滑动窗口切分器有三个参数需要认真斟酌chunk_size块大小、chunk_overlap块重叠、滑动步长stride。chunk_size单块的最大长度。建议按 Token 数设置而不是按字符数。中文场景下 1 个字约等于 0.6-1 个 Token取决于分词器如果按字符切 500 字实际 Token 数会超很容易在拼接上下文时爆窗口。我常用的起点是 chunk_size800 Token大概对应 600-800 个汉字。chunk_overlap相邻两个块相互重叠的长度。作用是避免切分边界刚好卡在关键信息中间。重叠太小没用太大则会造成大量冗余检索结果。我的经验值是 100-200 Token相当于块长度的 1/8 到 1/4。滑动步长等于 chunk_size - chunk_overlap。比如 chunk_size800overlap150那 stride650也就是说每 650 Token 切一个新块。这个公式可以帮助你在调整参数时保持逻辑一致。为什么需要 overlap我举个例子。我们抽取金融公告里的“担保事项”时经常看到这样的句子“……本次担保金额为 2.5 亿元占公司最近一期经审计净资产的比例为 12.7%。该担保事项已经公司董事会审议通过。”如果硬切第一块很可能只包含“担保金额为 2.5 亿元”第二块从“占公司最近一期”开始模型看到第一块时不知道这是谁担保的看到第二块时又不知道对象是什么。加了 overlap 之后两块都同时包含主语和金额召回哪块都能抽对。2.3 不同类型文本的切分配方不同领域文本切分策略不能一刀切。我整理了自己常用的几套配方文本类型首选边界推荐 chunk_size推荐 overlap备注合同/法律文书条款编号第X条600-800 Token100-150同时保留条款标题作为元数据新闻/资讯段落句子500-700 Token50-100新闻信息密度高块别太大论文/技术文档章节标题800-1200 Token150-200方法部分通常连贯性强对话/客服记录话轮边界300-500 Token50按角色拼接不要切断一问一答公告/财报章节段落800-1000 Token150数字和比例关系容易被截断这里有个容易被忽略的细节切分时最好把块的元数据也带上比如来源文件名、章节标题、页码。后面做 Hybrid 检索时这些元数据既能用于过滤也能在抽取结果里作为“证据出处”展示。我在实际项目里把这个信息拼进每个 Chunk 的前缀效果比单纯切文本好很多因为大模型在处理带来源标签的内容时会更倾向于忠实原文而非自由发挥。注意chunk_size 不是越大越好。块越大包含的无关信息越多检索召回的“精确度”会下降块越小单块内上下文越不完整召回后需要拼接的块也越多。800 Token 只是一个常用的平衡点具体要以你自己的数据分布和测试结果为准。3. 第二步Hybrid 混合检索的设计与实现3.1 向量检索能干什么、不能干什么Chunk 切好之后接下来是检索。为什么纯粹用向量检索不够因为向量检索擅长的是语义相似不是字面匹配。当你要找“付款时间”相关的内容用户/上游系统输入的查询词可能是“结算周期”向量检索能把这两个不同写法联系起来这是它的优势。但向量检索有两个明显的短板。第一专有名词、编号、金额这些精确信息向量相似度往往不敏感。比如你要抽取的是“合同编号 HT-2024-0815”对应的付款条件查询词里带上这个编号去搜向量检索很可能把它当成一个普通词组召回排名反而靠后。第二向量检索对查询词本身的噪声非常敏感查询词里多一个无关词结果就可能完全跑偏。尤其在中文场景里同义词差异大、实体表述多样纯靠 embedding 容易漏掉那些“字面完全一致但语义向量距离较远”的关键片段。纯粹用 BM25 这类关键词检索也不行。它无法理解同义表达比如“付款”和“结算”在 BM25 里是完全不同的词文档里只出现“结算”时你用“付款”去查就查不到。关键词检索还处理不了“以不同句式表达同一含义”的情况这是要 Hybrid 混合检索的根本原因两者互补不是二选一。3.2 BM25 关键词检索弥补了什么BM25 是经典的关键词检索算法核心思路是一个词在文档里出现得越多文档越相关但这个词在所有文档里出现得越频繁它的区分度就越低权重会被抑制。它对精确匹配、业务编号、事件触发词非常有效。我一般把查询词拆成两部分喂给 BM25一是原样的用户查询词二是从查询词里提取的实体/关键词列表。比如用户要查“XX公司2024年对外担保中的关联交易”那么关键词列表可以是“XX公司”“对外担保”“关联交易”“2024年”。BM25 会把这些词和 Chunk 里的词做精确匹配把包含这些词的块排到前面。这里有个实践技巧BM25 索引建立时要对 Chunk 做同样的分词和去停用词处理。中文要用 jieba 或其他分词器先分词否则 BM25 按单字匹配会产生大量噪声。英文则要注意词干化和大小写归一化。这些预处理决定了关键词检索的上限。还有一点不要把整个长查询句丢给 BM25查询句越长包含的虚词越多分数越被稀释。从长句里提取出 3-5 个核心关键词检索效果会明显更好。3.3 分数融合与候选片段重排Hybrid 的难点在“怎么把两类检索结果合并成一个排序列表”。常见的两个方案加权分数归一化和RFFReciprocal Rank Fusion。加权分数归一化先把向量相似度分数和 BM25 分数各自归一化到 0-1然后按权重加权final_score alpha * vector_score (1 - alpha) * bm25_score。这个方案的好处是可以精细调权重但对分数尺度敏感需要保证两边的分数分布一致否则强的那个会把弱的完全压掉。RFF 方案不管原始分数大小只看每个 Chunk 在两个检索结果里的排名计算公式是score 1/(k rank)k 一般取 60。比如一个块在向量检索里排第 1在 BM25 里排第 5那它的 RFF 分就是1/61 1/65 ≈ 0.0318。RFF 的优势是鲁棒两个检索系统的分数分布再不一样也不影响融合我把它作为默认方案。融合之后一般还会做一步重排Rerank。方法很直接把候选的前 10-20 个 Chunk 拼成一小段上下文用一个大模型或者交叉编码器Cross-Encoder按“和查询词的相关程度”重新打分。我用大模型做重排的经验是单次重排输入控制在 2000 Token 以内让模型输出“相关/不相关/部分相关”三分类再用部分相关的内容辅助判断效果比直接按分数截断要好。召回环节宁可多一些候选Recall 优先把精确筛选的工作留给重排和抽取模型。实操心得Alpha 权重别一上来就调。先从 alpha0.5 开始跑基线统计召回的 Top5 里有多少是向量独有的、多少是 BM25 独有的、多少是两者都召回的共同块。如果两类独有块里都有有效信息说明两者都有贡献保持 0.5 就够如果向量独有块里全是噪声才把 alpha 往 0.3 以下调反之调高。纯靠跑一组实验看整体指标很难定位问题在哪个环节。4. 第三步基于召回片段做 Claim 抽取4.1 抽取任务建模从整篇到片段级Chunk 和检索都准备好之后抽取任务的形态就变了不再是把“整篇文档”塞给模型而是把“查询条件 召回的 3-5 个 Chunk”作为输入让模型输出结构化 Claim。这种“片段级抽取”在实现上有几个好处。首先每个 Chunk 内部语义相对完整模型不需要在超长上下文里大海捞针。其次当多个 Chunk 同时作为输入时需要让模型分别对每个 Chunk 抽取再做一次合并去重而不是把所有 Chunk 混在一起一次性抽取。原因是混在一起时模型容易混淆信息归属把 A 块里的时间配到 B 块的金额上。我在实际项目里让模型按 Chunk 逐个输出 JSON再在代码层合并相同 Claim错误率大幅下降。举个例子我们做“公司对外担保事件抽取”时输出 schema 是固定结构每个 Claim 包含subject担保方、object被担保方、amount金额、ratio占净资产比例、time公告时间、event_type担保类型、evidence证据原文。模型对单个 Chunk 输出这样的 JSON 非常稳定一旦把 5 个 Chunk 全塞进去就会开始漏字段或者张冠李戴。4.2 Prompt 设计与结构化输出Prompt 设计上我推荐“角色 任务 schema 负面约束”四段式。角色定义可以写“你是一个严谨的金融文档信息抽取器”任务描述要说明“从给定的文本片段中抽取所有关于担保事件的 Claim”schema 要用 JSON 格式明确列出每个字段的含义负面约束很重要比如“如果没有找到金额信息输出 null不要猜测”“如果文本片段不包含任何担保信息输出空数组不要生成虚假内容”。结构化输出有两种实现方式如果你的模型 API 支持 JSON Mode / Function Calling就强制返回 JSON如果不支持就用“提取 JSON”类的 Prompt 让模型只输出 JSON 块代码层再用解析器处理。实测下来Function Calling 模式最稳至少保证输出格式不崩。此外温度参数建议调到 0这类抽取任务不需要创造性温度越高字段漏填和数值篡改的概率越大。关于证据字段这个值得多说一句每次抽取都必须把“证据原文”原样返回。这不仅是给下游审核用的也是调试用的。当团队复核抽取结果时如果发现某个 Claim 的 subject 和原文对不上可以直接定位是 Chunk 切分的问题、召回的问题还是模型理解的问题。没有证据字段的抽取结果错了都不知道错在哪一步。4.3 与 Kettle 等集成工具对接的分页抽取思路很多实际的抽取任务并不只是跑一次模型而是要落到数据流水线里定时处理新增文档。这个时候绕不开 ETL 工具。我们用的方案是KettlePentaho Data Integration定时轮询接口分页拉取待抽取文档然后送入抽取服务。具体流程可以拆成这样在 Kettle 里建一个“HTTP Client”步骤调用后端的待处理文档列表接口。接口支持分页参数比如page0size500。Kettle 里用“循环”或者“变量递增”的方式每次翻页直到返回的数据条数小于 page size 为止。拉回来的每条记录取其 id、标题、正文内容写入中间库的pending_docs表。启动一个抽取调度任务批量从pending_docs里取未处理的文档按第 2 节和第 3 节的方案做 Chunk 切分和 Hybrid 检索再调用大模型抽取。结果写回claim_results表同时把pending_docs.status更新为 processed。这里容易被忽略的是分页状态记录。不要每次从头开始翻页要记录上次处理到的页码和主键位置否则文档量大了之后全量翻页的耗时和接口压力都不可接受。Kettle 里可以用“表里的 max(processed_id)”作为下一页的起点比用 page 翻页更稳。另外抽取服务要设计成“幂等”的同一个文档重复调用也能得到相同结果这样重跑作业时不会产生重复 Claim。顺带提一句如果你的团队使用的是像 oneke 这类大模型知识抽取框架我的建议是框架帮你封装好了模型推理、schema 解析和部分评估逻辑这是好事但 Chunk 切分和 Hybrid 检索这两层一定要自己控制。因为这两个环节强依赖你的业务文档结构框架里的默认实现很难适配每一种文本。实践中比较顺的用法是用框架的抽取接口自己把候选 Chunk 拼好传进去而不是把整个库交给框架去切。5. 常见问题与排查调优实录5.1 切分不当导致抽取结果漂移现象抽取出的 Claim 字段齐全、格式正确但内容张冠李戴——比如把 B 公司的担保金额安到了 A 公司头上。这种问题 90% 出在 Chunk 切分上而不是模型上。排查思路是先看证据字段。如果模型返回的证据原文本就不包含 A 公司说明问题在上游如果证据里包含 A 公司但和 Claim 里的金额来自不同位置说明 Chunk 内混杂了多个公司的信息。这时需要把切分边界调整到“按每个公司的段落边界”切或者缩小 chunk_size避免多个主题被包进一个块。还有一种常见漂移模型的输出和原文数字不一致多发生在金额、日期、比例上。原因通常是 Prompt 里没有强调“忠实原文”或者温度没调到 0。我后面直接在负面约束里加了“所有数字必须与原文完全一致禁止换算、四舍五入和推断”这个问题基本消失。5.2 Hybrid 检索权重怎么调都打不过单一检索有时候你会发现无论怎么调 alphaHybrid 的效果都不如纯向量检索或者不如纯 BM25。这种情况通常是两类检索的候选集合本身就有问题而不是权重问题。先排查向量侧embedding 模型和文档领域是否匹配。我们用通用中文 embedding 模型在金融公告上效果一般换成在财经语料微调过的模型后向量召回准确率明显提升。再排查 BM25 侧分词是否正确。金融文本里“担保”这种词没问题但“对价”“交割”这类专业词如果被切成单字BM25 的匹配就废了。要做的是在分词器的自定义词典里加入领域专有词BM25 的效果立刻不一样。如果两边的单侧检索质量都没问题但融合后仍然没有提升可以考虑先用 RFF 替代加权分数归一化。我遇到过一次案例向量分数和 BM25 分数的量纲差距太大alpha0.5 时加权结果几乎完全由向量主导切到 RFF 后稳定性和效果都上去了。这类问题在盲调参数的时候很难发现换成排名融合一看就清楚了。还有一个不起眼但常见的原因查询词和 Chunk 属于不同层级。比如查询词是“2024年担保情况汇总”但 Chunk 里根本没有“汇总”这种词只有具体条目。这时应该把查询扩展成多个子查询如“2024年担保”“具体担保明细”“担保金额”把多个子查询的结果合并去重再交给重排。一个查询词打天下召回集合很容易偏窄。5.3 生产环境里两个容易踩的坑坑一把整篇文档重复塞进每个请求。我见过一个生产系统每个文档切了 30 个 Chunk按理应该只调 30 次抽取接口但开发人员图省事把所有 Chunk 全拼进一个 Prompt一次性抽取。结果是 Token 消耗暴涨响应时间从 3 秒变成 30 秒抽取准确率反而下降。原因很明显模型注意力有限无关 Chunk 越多关键信息越容易被淹没。坑二没有缓存 Chunk 和向量结果。同一个文档在首次处理之后切分结果和 embedding 向量应该持久化存储比如 ES 向量索引下一次查询直接检索即可。如果每次查询都重新切分、重新向量化系统在文档量增长后会越来越慢。我们在生产环境里把 Chunk 文本、向量、元数据都缓存起来检索响应从秒级落到了毫秒级。排查问题的时候我建议建立一套端到端样例集挑 30-50 条具有代表性的文档人工标注出每个 Claim 对应的证据片段位置。每次改完 Chunk 参数、检索权重或 Prompt都拿这套样例跑一遍定位变化来自哪个环节。没有这套基准任何调优都是凭感觉做出来的系统很难稳定上线。最后再分享一条个人经验这套“先定 Chunk 再 Hybrid”的思路不只是给大模型抽取用的。我后来做文本分类、摘要生成、甚至问答系统都沿用了同样的原则——先保证输入片段边界合理再考虑检索和生成。数据喂给模型之前多花一小时切分后面省下来的调参时间可能是几十倍。
返回列表