AI图鉴

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

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

来源:掘金 · 发布时间:11-02 · 由 AI图鉴每小时自动聚合

客户甩过来一份200页的产品手册PDF:"帮我们做个能查这个的AI助手。" 我的第一反应:简单,第3篇RAG搭过了——切chunk、存向量库、检索问答,搞定。 结果上线第一天就被投诉:查表单数据查不到

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

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

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

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

先说结论

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

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

翻车现场

用PyPDF2提取200页PDF:

from PyPDF2 import PdfReader

reader = PdfReader("product-manual.pdf")

text = ""

for page in reader.pages:

text += page.extract_text()

结果:

产品名称 型号 价格

---------- ----- -----

智能门锁 A200 899

(续表)

颜色:深空灰 重量:2.3kg

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

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

# 方案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,效果最好)

# 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结构切分就行。

成本对比:

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

翻车现场

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

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

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。

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的完整参数。

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

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分片摘要

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(渐进式精炼):

chain = load_summarize_chain(

llm,

chain_type="refine",

verbose=True

)

# Refine逻辑:

# 第1个chunk → 生成初始摘要

# 第2个chunk + 当前摘要 → 更新摘要

# 第3个chunk + 当前摘要 → 更新摘要

# ...

# 最终摘要不断被"精炼",越来越完整

Refine vs Map-Reduce:

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

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

# 阶段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了!"

llm = ChatOpenAI(model="gpt-4o-128k")

response = llm.invoke(f"根据以下文档回答问题:\n{full_document}\n\n问题:{query}")

3个问题:

- 成本爆炸: 200页≈50万token输入,GPT-4o的输入价格\(2.5/百万token → 一次查询\)1.25

- 中间内容被忽略: 研究表明,LLM对长上下文中间部分的信息"注意力衰减"——像读一本长篇小说,中间章节记不清

- 响应变慢: 50万token输入,推理时间10-30秒

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

# 最佳实践:先用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?

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

回头看

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

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 607 款 AI 工具分类导航 · 每日 AI 热门项目与动态