资讯动态

长文本信息提取的输入侧工程化:切分、定位与重组实战

发布时间:2026/10/18 11:19:50 来源:尧图企业网站定制
LLM深入思考一做LLM应用做得越久我越觉得真正决定效果上限的往往不是模型本身的推理能力而是我们喂给它的输入方式。输入的文本怎么组织、关键信息怎么分布、上下文怎么取舍这些看起来不怎么“技术”的细节实际对结果的稳定性和准确率影响极其巨大。尤其当你处理的任务是从一段很长的文本里提取信息、做判断、或者做结构化输出的时候——模型的什么“深层推理能力”反而不是瓶颈文本的组织方式才是。这篇文章我会用一个非常典型的场景——长文本信息提取——来拆解我踩过的坑和最终沉淀下来的方案。这不是一篇泛泛讲原理的文章而是把我怎么分析问题、怎么设计输入结构、怎么一步步调试、最后得到什么样的稳定效果全流程拆开来说清楚。按照这个系列规划的节奏第一篇先聚焦在“输入侧”的工程化处理后面几篇再逐步深入到Prompt设计、输出约束、评测和稳定性的细节。1. 为什么要单独讨论“输入侧”1.1 一个很反直觉的发现模型“读”长文本的能力比你想象的差很多人在第一次接触大语言模型的时候都会有一个错觉既然模型的上下文窗口能到几十万token那直接把整份文档丢进去不就行了我一开始也这么干。但在实际跑过几千次测试之后我得说上下文窗口大不等于模型能有效利用这么大窗口里的所有内容。这里面的差距比大多数人想象的要大得多。我做过一个很简单的实验一份大概几万字的行业分析报告我把全文直接塞进模型然后问它几个细节性问题——比如“报告中提到的第三季度增长速度是多少”。数据本身在文本里出现得清清楚楚但模型给出的回答时对时错而且错误的方式非常迷惑它有时候会把其他季度的数据当作第三季度的有时候会把预测值当成实际值。这个问题反复出现逼着我去重新审视“模型是怎么读长文本的”。从工程角度去理解模型在生成回复的时候并不是像人一样从头到尾把资料读一遍再思考而是在生成每个token的时候用注意力机制去“检索”输入序列里跟当前生成最相关的部分。当文本特别长的时候这种“检索”的精度会明显下降尤其是那些藏在一大堆干扰信息中间的关键内容很容易被模型忽略。这个发现改变了我的整个处理思路。从那以后我处理所有长文本任务第一原则已经变成了能不全量塞就不全量塞能裁剪就裁剪能分块就分块。上下文窗口是拿来救急的不是拿来当默认方案的。1.2 长文本场景的两类典型问题表现跟不同团队的开发者交流下来长文本场景翻车的情况其实非常集中大概就两大类。第一类是“检索失败型”。文本太长关键信息被淹没在大量无关内容里模型根本没注意到关键段落。表现出来就是回答看似合理但数据和事实是错的——或者编造了原文根本没有的内容。这类问题最隐蔽因为模型的回答往往是流畅的、完整的如果不去仔细核对原文你根本发现不了它是在“一本正经地胡说八道”。我见过太多测试者被这种回答带偏以为模型推理能力强实际上模型只是生成能力强。第二类是“结构坍塌型”。把长文本分块之后分批喂给模型但分块没有考虑内容语义的完整性把一个完整的章节拦腰截断导致模型拿到的每一块上下文都残缺不全。表现出来就是模型反复要求澄清、回答不完整、提取的字段总是缺胳膊少腿。这两类问题如果你只是简单地调整Prompt措辞几乎没有任何效果。它们本质上是输入结构的问题不是指令理解的问题。这也解释了我为什么要单独写一篇讨论输入侧的处理——因为很多人在这个环节就输了后面怎么调Prompt都是空的。2. 核心方案从“让模型读全文”到“帮模型找到内容”2.1 换一个思路直接压缩需要模型“读”的文本量我在多次尝试之后确定下来一套处理长文本任务的核心框架分成三个阶段第一阶段预切分。不是拿到文本就丢给模型而是先做结构化切分。这里有一个很重要的原则切分要跟着内容的自然边界走不能按固定的字符数硬切。比如一份报告它天然有章节、小节、段落的层级按这些层级切出来每一块的语义是相对完整的。我见过很多人用固定长度切分切出来的块往往在句子中间断开这种块喂给模型信息是残破的提取效果会大打折扣。第二阶段定位与评分。切分之后先做一轮轻量级的快速筛选把跟目标任务相关的块找出来。这个过程不依赖复杂的推理只做关键词匹配和相关性打分。这里的核心思路是让模型或者规则系统先做“粗筛”把不相关的段落直接过滤掉。打个比方你要在一本书里找某个人物的出场描写你不会重新读一遍全书再回答你会先查索引、翻到对应页、再精读那一页。模型面对长文本也应该是这个逻辑但我们得替它把“翻到对应页”这个动作做掉。第三阶段重组与精读。把筛选出来的高相关分块按照原文顺序重新拼装成一个精炼的上下文包再喂给模型做最终的信息提取。这个上下文包通常只有原始文本的十分之一甚至更少但信息密度极高。这套框架跑通之后我的任务成功率有了非常明显的提升。第三阶段的输入变短模型注意力更集中输出的准确率和格式稳定性都上了一个台阶。2.2 为什么不直接用向量检索看到这里你可能会问这不就是RAG检索增强生成的思路吗用向量检索找相关块不就行了我对这个问题的回答是向量检索可以做但在这个场景里它不是最优解。向量检索适合的是开放式的问答场景——问题跟你不太确定答案在哪篇文档里需要从整个知识库里找线索。它的问题是检索的召回结果不稳定相关性的判断标准跟任务目标不完全一致。你查“第三季度的增长数据”向量检索可能给你召回的是所有谈到“增长”的段落而不是具体数据所在的那个表格。更关键的是当我明确知道“我就需要这份文档里的这几个字段”那么我可以直接用规则来定位根本不需要模糊匹配。确定性场景用确定性方法这是工程上一条非常重要的原则。规则定位的好处是你可以完全预测系统行为——哪些块会被选中哪些不会每一块被选中的逻辑是什么。而向量检索的筛选逻辑是黑盒的出了问题你很难排查。2.3 一个必须做的前置动作清洗原始材料在切分之前有一个步骤千万别省——清洗原始材料。从我处理各类文档的经验来看原始文本里普遍存在两类问题。一类是干扰性噪音页眉页脚、目录、重复的标题、图表说明文字混在正文里。这些内容会稀释真正有效的信息密度我见过最夸张的一份文本清洗之前有三万多字清洗之后只剩不到两万字——也就是说三分之一的内容都是页眉页脚和目录。另一类是格式转换导致的乱码和碎片。我之前处理过一份PDF转出来的文本表格全部错位字段和数值完全对不上拿这种数据去做切分和提取结果根本不可能是对的。所以我现在处理任何文档都会先走一遍清洗流程去空行、去页眉页脚、统一换行符、识别并剥离常见噪音段落。这个步骤不产生“智能”价值但它的价值恰恰在于避免后面所有环节被脏数据污染。就像炒菜之前先洗菜切菜每个人都会做但不同的人洗得干净程度不一样最后菜的味道就不一样。3. 实操拆解一个完整的长文提取任务3.1 场景设定与准备我用一个具体的例子来把这套流程完整走一遍。假设我要做的是一个“批量处理企业年报”的功能输入是一份PDF格式的年报需要从里面提取几个固定字段公司名称、报告期、营业收入、净利润、同比增长率。这看起来是个很简单的任务。但难点在于年报这份文档很长通常几万字而且数据分散在不同章节——营业收入可能在“经营情况讨论与分析”里出现多次财报附注里也有摘要里也有。如果我直接让模型全文读取它往往会抓到错误位置的数据。为了测试方便我把样例文档准备好做了两个版本一个是完整的原始版一个是我把关键段落做了标记的“标准答案版”——这个是我用来核对模型提取是否准确的参照物。3.2 切分不要按字符数硬切要按语义边界切切分这一步我用的策略是先按段落切再把段落合并成块每块控制在一个合适的规模。具体做法是这样先把整个文档按空行拆成段落列表然后从第一个段落开始逐个累加等累计长度达到阈值时就切成一块。这个阈值我一般设置在1000到1500字之间——既能保证每块有足够的上下文语义又不会因为太长而稀释了关键信息。这里最关键的调整是——不要把阈值当成硬边界。如果一个段落累积到阈值边缘而下一个段落跟当前内容明显属于同一个主题比如正在讲同一个业务板块我就把这个段落也吞进来。反之如果分段的时候正好停在一个大标题前面那就提前切。我写切分逻辑的时候特别注意保留一个信息每个块的起始段落序号和它在原文中的大致位置。这个信息在后面做定位和重排的时候非常有用。下面是切分逻辑的关键代码框架def split_document_into_chunks(text, max_chars1200): # 先按空行把文档切成段落 paragraphs [p.strip() for p in re.split(r\n\s*\n, text) if p.strip()] chunks [] # 当前块的段落容器和字数字段 current_paragraphs [] current_length 0 start_idx 0 # 记录块在原文中的起始段落位置 for i, para in enumerate(paragraphs): # 如果单个段落本身超过阈值就单独成块避免因为合并导致块过长 if len(para) max_chars: # 先把已有内容落地为一个块 if current_paragraphs: chunks.append({ start_para: start_idx, end_para: i - 1, content: \n.join(current_paragraphs) }) current_paragraphs [] current_length 0 chunks.append({ start_para: i, end_para: i, content: para }) start_idx i 1 continue # 判断加入当前段后是否溢出 if current_length len(para) max_chars and current_paragraphs: # 看看段首是否像标题如果是标题则优先让它开启新块 if looks_like_heading(para): chunks.append({ start_para: start_idx, end_para: i - 1, content: \n.join(current_paragraphs) }) current_paragraphs [para] current_length len(para) start_idx i else: chunks.append({ start_para: start_idx, end_para: i - 1, content: \n.join(current_paragraphs) }) current_paragraphs [para] current_length len(para) start_idx i else: current_paragraphs.append(para) current_length len(para) # 落盘最后一块 if current_paragraphs: chunks.append({ start_para: start_idx, end_para: len(paragraphs) - 1, content: \n.join(current_paragraphs) }) return chunks这段代码里有几个细节值得说。looks_like_heading这个判断是我后来补的——我观察到一个现象如果一段末尾是标题但块容量刚好满了把标题吞进上一块会导致下一块开头十分突兀模型对“下一段在讲什么”缺少引导。所以我会用正则去匹配类似“第X节”“X”“一、”这种模式遇到这种段落宁可让上一块少一点也要让标题开启新块。处理完切分之后我的年报文本从两万多字变成了二十多个块每个块1200字上下并且保存了每块在原文的段落位置索引。这些块就是后续所有处理的基本单元。3.3 定位用打分器过滤无关段落切分只是准备工作。真正的核心是第二步——定位。我要从二十多个块里找出哪些块最可能包含目标字段。针对“提取财报数据”这个场景我不需要向量检索我直接用关键词规则就能做得很准。核心逻辑是给每个块计算一个得分得分由多个信号叠加。比如我要提取“营业收入”那么块里出现“营业收入”这个词——加高分块里出现“利润”“净利润”“同比增长”等关联词——加中分块里出现货币符号、数字百分比等财务特征——加辅助分块里出现明确的排除词——比如“不重要”“不适用”“参见”等——减分。然后我把每个块的得分加起来按从高到低排序取Top-N的块作为精读上下文。但这里有一个很重要的调优经历一开始N选得太少我遇到过一次“关键信息漏召回”的翻车。原因是我把目标字段的候选块限制为Top 3但“营业收入”在年报里分散出现在“第三节管理层讨论”“第七节财务报告”“附注”等好几个位置Top 3根本覆盖不全。后来我把策略改成按字段单独评分——每个目标字段有自己的候选块集合——然后再合并去重。这样“营业收入”这个字段会找出它自己的前五个候选块“净利润”也找出自己的前五个合并之后再去重覆盖情况就完整多了。这个改动带来了实际效果的提升之前偶尔会漏掉某个字段的数据改完之后基本稳定。3.4 重组让模型“只看该看的”最后一步把选出的块按原文顺序重新拼装形成一个精读上下文。这里安排一个很重要的处理细节给每个块前面加一个位置标注。比如“[原文章节 C第12段至第15段]”然后才接块内容。这个标注的目的是给模型一个“空间锚点”——让它知道当前读到的内容在文档里大概处于什么位置减少因为内容跳转而造成的困惑。实测下来加了位置标注之后模型输出的数据对应的章节归属明显更清晰了。拼装完成之后我再用一个结构化的Prompt让模型做提取。这个Prompt里面我做了几个约束每条数据输出的格式要符合一张固定表格的结构提取不到时不要猜测返回“未找到”如果多个位置出现了不同数值以“财务报告附注”章节的数值为准并标注数据来源。最终得到的效果同样的任务之前在全文塞入方式下的准确率大概是七成左右——每次总有那么一两个字段出错。切到这套“切分-定位-重组”流程之后三十份测试文档的字段提取准确率稳定在九成五以上而且每份文档的处理耗时还缩短了。4. 测试中反复踩到的坑和解决实录4.1 数据来源矛盾的问题年报文本里经常出现同一个数据在多个章节出现且数值不一致的情况。它们是“同比去年增长15%”有的地方写成“同比增长15.2%”。之前模型面对这种矛盾会随机挑一个导致结果不稳定。我的处理办法在Prompt里明确数据冲突的应对规则给数据来源设定优先级并让模型输出数据来源位置。成本几乎为零但对稳定性提升非常明显。给模型指定规则去选择而不是让它自由发挥这是在设计任务时必须想清楚的边界。4.2 切分边界把表格切断有几次翻车原因非常隐蔽——块内容里包含半截表格。年报表格是有特定结构的如果切分时正好把一个表格从中间切断这两个半截表在语义上完全丧失模型提取出来的数据就是错的。解决办法是在切分之前预扫描表格区域。因为表格在原始文本里通常有特征比如连续的竖线、制表符或者“单位万元”这类表格头标识我会把这些区域识别出来整体作为一个“超长块”保留不允许在中间拆分。4.3 过滤规则把关键块误杀了我调关键词规则的时候出现过一次误伤有一个目标字段的候选块评分始终偏低找来找去才发现文档里这个字段一直用简称表达我过滤的关键词用的是全称自然匹配不到。这给我一个教训关键词规则不要只做字符串精确匹配要做同义扩展。给每个目标字段维护一个小型的同义词词典把常见简称、英文缩写、表格表头变体都列进去。这个词典规模不需要很大但带来的召回覆盖提升非常可观。5. 一个更实际的坑你不可能永远只处理年报我前面举的例子是年报提取数据但同样的框架完全可以迁移到其他长文本场景——合同关键条款提取、论文要点总结、尽调报告信息整合、访谈记录结构化等等。区别主要在切分粒度和定位信号上。合同类文本适合按条款切分定位信号可以用“第X条”“违约责任”“付款条件”这类结构化短语。论文按章节切分定位信号主要是“摘要”“方法”“实验”“结论”。访谈记录按问答对切分定位信号是提问者的固定身份标识。核心框架不变变的只是语义边界识别和关键词信号配置。这也是我把这套方案沉淀为通用处理层的原因——以后接任何长文提取任务我只需要写一套适配任务的规则配置不需要重新发明流程。6. 关于效果评测不能只看几次“感觉还行”最后说一个很多人忽略的问题——评测方法。我在做这套方案验证的时候用的是一组标准答案对照法提前人工标注出每个目标字段在原文里的准确值然后跑程序提取逐字对照。评测结果用“字段级准确率”来量化所有目标字段里程序提取结果跟标准答案完全一致的字段占比。为什么要强调这个因为大语言模型应用太容易被“感觉”欺骗了。某个字段答对了你就觉得效果很好另一个字段答错了你以为是偶发问题。只有把所有字段的成败记录下来你才能看到真正的水平分布。我在项目后期养成了一个习惯每改一个环节就全量跑一遍测试集把字段级准确率对比表拉出来任何低于95%的字段都必须找到原因。这个习惯帮助我发现了好几处隐蔽问题比如某个字段因为数据来源规则没写清楚而导致准确率只有八成——不测是永远发现不了的。7. 一个值得记住的调试结论这套方案跑通之后我最大的体会有两点。第一大模型应用工程的很多问题不是模型不够聪明而是你没把问题变成模型容易回答的形态。输入侧的切分、过滤、重组本质上是在替模型降低任务的感知难度。这比反复修改Prompt的作用大得多。第二先让文本变短再让模型变强。在所有外部辅助手段里把输入控制在合理长度是成本最低、效果最稳的优化手段。它的原理不复杂——就像给人派活一次性给三十页资料要你找出三个数字和你把三个数字所在的三段放到他面前让他摘抄后者的稳定性和可靠度显然高得多。模型也是一样。下一篇我会继续聊Prompt设计层的经验——当输入已经压缩到位之后怎么样构造指令让模型做得更稳。到时候见。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑