长上下文与大文档处理:200页PDF怎么喂给AI,我踩了4个坑

客户甩过来一份200页的产品手册PDF:"帮我们做个能查这个的AI助手。"

我的第一反应:简单,第3篇RAG搭过了------切chunk、存向量库、检索问答,搞定。

结果上线第一天就被投诉:查表单数据查不到,跨章节的对比问题答不对,"帮我总结全文"直接超出token限制。

200页文档和10页文档,不是"量变",是"质变"。 长文档处理的坑,远比想象中多。


先说结论

问题 错误做法 正确做法
文档太长token超限 整篇丢给LLM 分层处理:先检索再生成
表格/图表提取丢失 纯文本提取 专用PDF解析工具
跨章节问题答不准 只看top-3 chunk 父文档检索+上下文窗口
全文摘要不全 从头到尾摘要 Map-Reduce分片摘要

Java人的理解:长文档 ≈ 大数据量查询。 你不会把100万条记录全SELECT出来再处理,而是先WHERE过滤再聚合。AI处理长文档也是:先检索筛选再生成,不是一股脑全塞。


坑1:PDF提取出来全是乱码和断行

翻车现场

用PyPDF2提取200页PDF:

ini 复制代码
from PyPDF2 import PdfReader
​
reader = PdfReader("product-manual.pdf")
text = ""
for page in reader.pages:
    text += page.extract_text()

结果:

diff 复制代码
产品名称   型号    价格
----------  -----  -----
智能门锁    A200   899
    (续表)
颜色:深空灰    重量:2.3kg

表格被打散成文本流,跨页表格直接断开,多栏排版左右混排。 用这种数据做RAG,检索效果可想而知。

正确做法:用专用PDF解析工具

ini 复制代码
# 方案1:unstructured(推荐,支持表格/图片/布局)
from unstructured.partition.pdf import partition_pdf
​
elements = partition_pdf(
    "product-manual.pdf",
    strategy="hi_res",           # 高精度模式
    infer_table_structure=True,   # 识别表格结构
    extract_images_in_pdf=True,   # 提取图片
)
​
# 按类型处理
for element in elements:
    if element.category == "Table":
        # 表格保留HTML结构
        table_html = element.metadata.text_as_html
        # 转成Markdown表格更好读
        table_md = html_table_to_markdown(table_html)
    elif element.category == "Title":
        # 标题信息
        title = element.text
    elif element.category == "NarrativeText":
        # 正文
        text = element.text

方案2:Marker(PDF转Markdown,效果最好)

ini 复制代码
# pip install marker-pdf
from marker.converters.pdf import PdfConverter
from marker.models import create_model
from marker.output import text_from_rendered
​
converter = PdfConverter(artifact_dict=create_model())
rendered = converter("product-manual.pdf")
markdown_text, metadata = text_from_rendered(rendered)

Marker的优势: 把PDF转成结构化Markdown,表格保留为Markdown表格,标题层级完整,代码块完整。直接用第23篇的Markdown结构切分就行。

成本对比:

工具 速度 效果 免费
PyPDF2 快 差(表格/布局丢失) 是
unstructured (hi_res) 慢(需OCR模型) 好 是
Marker 中等 最好 是
商业API(TextIn/百度) 快 好 否(按页收费)

坑2:跨章节问题答不准------检索只看片段不看上下文

翻车现场

用户问:"A200和A300有什么区别?"

A200的参数在第3章,A300的参数在第5章。检索top-4 chunk:

ini 复制代码
Chunk 1: "A200智能门锁,支持指纹解锁..." (第3章,sim=0.78)
Chunk 2: "A200技术参数:重量2.3kg..."   (第3章,sim=0.72)
Chunk 3: "常见问题:如何重置密码..."     (FAQ章,sim=0.45)
Chunk 4: "售后服务政策..."               (第8章,sim=0.41)

A300的信息根本没检索到! 因为用户的问法里只提了"A200和A300",而A300那个chunk的相似度没进top-4。

正确做法:父文档检索(Parent Document Retriever)

核心思想:用小chunk做检索(精准),命中后返回大chunk(完整上下文)给LLM。

ini 复制代码
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
​
# 小chunk:用于精准检索
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
​
# 大chunk:给LLM看完整上下文
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=800)
​
# 向量库存小chunk,文档库存大chunk
vectorstore = Chroma(embedding_function=embeddings)
docstore = InMemoryStore()
​
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=docstore,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)
​
# 检索时:用小chunk匹配,返回对应的大chunk
docs = retriever.invoke("A200和A300有什么区别?")
# 返回的是包含A200和A300信息的完整段落

为什么有效? 小chunk(200字符)匹配更精准------"A300"这个关键词在小chunk里密度更高。匹配到后,把包含这个小chunk的大chunk(800字符)取出来,大chunk里有A300的完整参数。

进阶:多向量检索------一个文档存多个检索向量

ini 复制代码
from langchain.retrievers.multi_vector import MultiVectorRetriever
​
# 对每个大chunk生成摘要,用摘要做检索向量
summaries = []
for doc in large_chunks:
    summary = llm.invoke(f"用一句话总结:{doc.page_content}")
    summaries.append(summary)
​
# 向量库存摘要向量,文档库存原文
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=docstore,
    id_key="doc_id",
)
​
# 摘要向量 → 检索命中 → 返回对应原文

效果: 用户问"A200和A300区别",摘要向量里"A200和A300智能门锁对比"精准命中,返回包含两者完整参数的原文。


坑3:全文摘要------token超限或摘要不全

翻车现场

用户问"帮我总结一下这份手册的核心内容"。

方案1:把200页全文丢给LLM → token超限(200页≈50万token) 方案2:只取前10页摘要 → 漏掉后半部分内容 方案3:分章摘要再合并 → 每章摘要还行,合并时又超限

正确做法:Map-Reduce分片摘要

ini 复制代码
from langchain.chains.summarize import load_summarize_chain
​
# Map-Reduce:先分片摘要,再合并
chain = load_summarize_chain(
    llm,
    chain_type="map_reduce",
    verbose=True
)
​
# 自动处理:分片→各片摘要→合并摘要
summary = chain.run(docs)

Map-Reduce的问题: 合并时可能丢失细节。更好的方案是Refine(渐进式精炼):

ini 复制代码
chain = load_summarize_chain(
    llm,
    chain_type="refine",
    verbose=True
)
​
# Refine逻辑:
# 第1个chunk → 生成初始摘要
# 第2个chunk + 当前摘要 → 更新摘要
# 第3个chunk + 当前摘要 → 更新摘要
# ...
# 最终摘要不断被"精炼",越来越完整

Refine vs Map-Reduce:

维度 Map-Reduce Refine
速度 快(分片并行) 慢(串行精炼)
完整性 合并时可能丢信息 逐步累积,更完整
token消耗 多(每个chunk独立摘要) 少(只在摘要上追加)
适用 大量短文档 一个长文档

200页手册用Refine更合适: 串行处理虽然慢,但摘要更完整。

实际优化: 先用Map-Reduce做每章摘要,再用Refine合并章节摘要------两阶段处理:

ini 复制代码
# 阶段1:每章独立摘要(可并行)
chapter_summaries = []
for chapter in chapters:
    summary = llm.invoke(f"用3句话总结以下内容:\n{chapter}")
    chapter_summaries.append(summary)
​
# 阶段2:合并章节摘要(Refine)
final_summary = chapter_summaries[0]
for i in range(1, len(chapter_summaries)):
    final_summary = llm.invoke(
        f"当前摘要:{final_summary}\n\n"
        f"新内容:{chapter_summaries[i]}\n\n"
        f"将新内容融入当前摘要,保持完整性和连贯性。"
    )

坑4:长上下文模型的陷阱------能塞进去不等于能用

翻车现场

2025年模型上下文越来越长:GPT-4o 128K,Claude 200K,Gemini 1M。

"那我直接把200页文档塞进prompt不就行了?不用RAG了!"

ini 复制代码
llm = ChatOpenAI(model="gpt-4o-128k")
response = llm.invoke(f"根据以下文档回答问题:\n{full_document}\n\n问题:{query}")

3个问题:

  1. 成本爆炸: 200页≈50万token输入,GPT-4o的输入价格 2.5/百万token→一次查询2.5/百万token → 一次查询 2.5/百万token→一次查询1.25
  2. 中间内容被忽略: 研究表明,LLM对长上下文中间部分的信息"注意力衰减"------像读一本长篇小说,中间章节记不清
  3. 响应变慢: 50万token输入,推理时间10-30秒

正确做法:长上下文≠不用RAG,而是RAG+长上下文互补

ini 复制代码
# 最佳实践:先用RAG筛选相关片段,再用长上下文模型做深度理解
​
# Step 1: RAG检索top-10(比平时多取一些)
retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
relevant_chunks = retriever.invoke(query)
​
# Step 2: 用长上下文模型处理多个chunk(不用截断)
context = "\n\n---\n\n".join([chunk.page_content for chunk in relevant_chunks])
​
llm = ChatOpenAI(model="qwen-long")  # 长上下文模型,便宜
response = llm.invoke(
    f"根据以下文档片段回答问题,如果多个片段有冲突请指出:\n\n"
    f"{context}\n\n"
    f"问题:{query}"
)

什么时候用长上下文模型替代RAG?

场景 用RAG 用长上下文
文档<50页 都行 都行
文档50-200页 推荐 成本高
精确查询("XX参数是多少") 推荐 杀鸡用牛刀
全文理解("总结全文"/"对比分析") 不够 推荐
多文档交叉分析 推荐 适合

实用建议: 用便宜的长上下文模型(qwen-long、deepseek)做RAG的后端,比用贵的短上下文模型+大量chunk更划算。


回头看

坑 一句话总结
PDF提取乱码 别用PyPDF2,用unstructured或Marker,表格结构不丢
跨章节答不准 父文档检索:小chunk精准匹配,大chunk给完整上下文
全文摘要不全 Refine渐进式精炼,或两阶段Map-Reduce+Refine
长上下文不是万能药 RAG筛选+长上下文深度理解,互补才是正解

长文档处理的核心原则和Java处理大数据量一样:不要全量加载,先过滤再处理。 RAG就是AI应用的"WHERE条件",长上下文模型是"聚合函数",两者结合才能高效处理大文档。

相关推荐
极限挑战哥2 小时前
共享电单车联营平台分账架构实践:二清合规与自动分账零人工方案
java·大数据·人工智能·微信小程序·架构·微信公众平台
皮皮林5512 小时前
比 VMware 更快,更轻的虚拟机,横空出世!
java
databook2 小时前
Pandas 与 Polars:语法、性能和资源占用方面的对比
python·数据分析·pandas
威哥爱编程2 小时前
Python 智能体开发实战 10:本地跑得好好的,一并发就崩?把它做成一个能上线的服务
python·agent
威哥爱编程2 小时前
Python 智能体开发实战 09:改了提示词,到底是变好还是变坏?搭一套能打分的离线评测
python·agent
Thomas.Sir2 小时前
第13课:MyBatis-Plus整合SpringBoot4.x,分页、条件查询、逻辑删除实战
java·spring boot
meiying88383 小时前
多快递公司快递单号批量查询接口怎么对接?Python 聚合查询实现
java·大数据·python
古法安卓3 小时前
Android-GPU压测工具
android·java·android studio
Hello_Pyhx3 小时前
Deep-Live-Cam 源码配置思路:环境、模型与执行后端分开排查
python