1. 项目概述
这个项目展示了如何利用Langchain和Chroma构建一个能够处理多模态PDF文档的检索增强生成(RAG)系统。与传统的纯文本RAG不同,我们的系统需要处理PDF中常见的三种内容形态:文本段落、表格数据和图片信息。
在实际业务场景中,PDF文档往往包含丰富的内容形式。根据我的项目经验,纯文本的RAG系统在处理这类复杂文档时,会遇到几个典型问题:
- 表格数据被拆分成零散文本后失去结构性语义
2.图片信息完全丢失
3.跨模态内容间的关联关系断裂
我们的解决方案通过以下创新点解决这些问题:
- 对文本、表格、图片采用差异化的预处理策略
- 保留原始非文本数据的完整形态
- 通过语义描述建立多模态内容的统一表示
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心设计思路
2.1 多模态数据处理流程
整个系统的核心在于如何处理PDF中的异构数据。我们采用分而治之的策略:
-
文本内容:
- 直接进行语义切分和向量化
- 使用滑动窗口(chunk_overlap)保持上下文连贯性
- 典型chunk_size设为500字符以平衡语义完整性和检索效率
-
表格数据:
- 转换为HTML格式保留结构信息
- 使用LLM生成表格内容的语义摘要
- 只对摘要进行向量化,原始表格存入文档存储
-
图片内容:
- 使用多模态LLM生成图片描述
- 将图片编码为base64格式
- 只对描述文本进行向量化,原始图片存入文档存储
提示:表格和图片的摘要质量直接影响后续检索效果,建议使用较强的LLM如GPT-4或Claude3来完成这项任务。
2.2 向量数据库设计
我们选择Chroma作为向量数据库主要基于以下考虑:
- 轻量级且易于集成
- 支持持久化存储
- 与Langchain生态无缝衔接
文档存储设计采用键值存储结构,关键字段包括:
python复制{
"doc_id": "唯一标识符",
"type": "内容类型(text/table/image)",
"content": "原始内容",
"summary": "语义摘要(仅table/image需要)"
}
这种设计实现了:
- 向量库只存储语义信息,保持高效检索
- 原始内容独立存储,避免向量库膨胀
- 通过doc_id建立双向关联
3. 关键技术实现
3.1 PDF解析与预处理
使用Unstructured库进行PDF解析时,有几个关键参数需要注意:
python复制elements = partition_pdf(
filename="your_pdf_data",
strategy="auto", # 自动选择解析策略
languages=["chi_sim", "eng"], # 支持中英文混合
extract_images_in_pdf=True, # 必须开启图片提取
infer_table_structure=True # 必须开启表格识别
)
常见问题处理:
- 中文解析乱码:确保系统字体包含中文字体
- 表格识别错误:可以尝试调整strategy为"hi_res"获得更精确的结果
- 图片提取失败:检查PDF是否使用特殊编码,可先用PDF工具转换
3.2 内容分块策略
文本分块采用递归字符分割器:
python复制text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""], # 按段落优先分割
chunk_size=500,
chunk_overlap=50 # 保持上下文连贯
)
表格和图片则保持完整不分块,因为:
- 表格拆分会破坏数据结构
- 图片分块会导致语义碎片化
- LLM生成的摘要已经包含压缩的语义信息
3.3 多模态内容处理
表格处理实现细节:
python复制table_summaries = []
for table in self.tables:
doc_id = str(uuid.uuid4())
table_md = table.metadata.text_as_html
# 使用结构化提示词提高摘要质量
prompt = f"""
请根据以下HTML表格内容,生成包含以下信息的描述:
1. 表格主题(不超过10个字)
2. 行列数量(如"3行5列")
3. 关键数据摘要(指出最突出的3个数据点)
4. 整体结论(不超过20个字)
表格内容:
{table_md}
"""
summary = self.llm.invoke(prompt).content
self.docstore[doc_id] = {
"type": "table",
"content": table_md
}
table_summaries.append({
"doc_id": doc_id,
"summary": summary
})
图片处理实现细节:
python复制image_summaries = []
for img in self.images:
doc_id = str(uuid.uuid4())
raw_image_path = img.metadata.image_path
image_b64 = self.image_to_base64(raw_image_path)
# 多模态提示词设计
prompt = """
请详细描述这张图片,包括:
1. 主要物体/人物及其位置关系
2. 颜色和风格特征
3. 文字内容(如果有)
4. 可能的用途或场景
"""
caption = self.llm.invoke([
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpg;base64,{image_b64}"}
}
]
}
]).content
self.docstore[doc_id] = {
"type": "image",
"content": raw_image_path
}
image_summaries.append({
"doc_id": doc_id,
"summary": caption
})
3.4 向量数据库构建
Chroma数据库的初始化需要注意几个关键点:
python复制client = chromadb.PersistentClient(path=data_path)
vectorstore = Chroma.from_documents(
documents=docs,
collection_name=collection_name,
embedding=embedding_model, # 推荐使用bge-small-zh-v1.5中文模型
persist_directory=data_path,
client=client
)
嵌入模型选择建议:
- 中文内容:bge-small-zh系列
- 英文内容:text-embedding-3-small
- 多语言混合:paraphrase-multilingual-mpnet-base-v2
4. 检索与生成优化
4.1 混合检索策略
检索过程需要处理不同类型的内容:
python复制hits = self.vectorstore.similarity_search(query, k=k)
results = []
for h in hits:
doc_type = h.metadata["type"]
if doc_type == "text":
results.append({
"type": "text",
"content": h.page_content
})
else:
doc_id = h.metadata["doc_id"]
raw = self.docstore[doc_id]
results.append({
"type": raw["type"],
"content": raw["content"]
})
4.2 重排序实现
新增的重排序功能可以显著提升结果质量:
python复制candidates = self.retrieve(query, k=10)
pairs = [[query, candidate["summary"]] for candidate in candidates]
scores = rerank_model.predict(pairs)
sorted_pairs = sorted(zip(scores, candidates), key=lambda x: x[0], reverse=True)
res = []
for score, candidate in sorted_pairs[:top_k]:
candidate['score'] = float(score)
res.append(candidate)
推荐的重排序模型:
- bge-reranker-base中文版
- CohereRerank(英文效果更好)
- 本地部署的MiniLM-L6-v2
4.3 多模态答案生成
最终答案生成需要处理混合内容:
python复制context = ""
img = []
for r in result:
if r["type"] == "table":
context += f"\n表格内容:\n{r['content']}\n"
elif r["type"] == "image":
base64_img = image_to_base64(r['content'])
context += f"\n图片内容(Base64编码):\n"
img.append({
"type": "image_url",
"image_url": {"url": f"data:image/jpg;base64,{base64_img}"}
})
else:
context += f"\n文本内容:\n{r['content']}\n"
prompt = f"""
你是一个专业助手,请根据以下已知信息回答问题。
如果信息不足,请明确说明"根据提供的信息无法确定"。
用户问题:{query}
已知内容:
{context}
"""
message_content = [{"type": "text", "text": prompt}]
message_content.extend(img) # 合并文本和图片
response = chat.invoke([
HumanMessage(content=message_content)
])
5. 部署与优化建议
5.1 性能优化技巧
- 批量处理:对大量PDF可以先批量解析,再统一处理
- 缓存机制:对已处理的文档建立哈希校验,避免重复处理
- 异步处理:图片和表格的摘要生成可以并行执行
- 分级存储:热数据放内存,冷数据存磁盘
5.2 常见问题排查
-
表格识别不全:
- 检查PDF是否为扫描件
- 尝试调整unstructured的ocr策略
- 考虑使用专门的表格识别工具如Camelot
-
图片描述不准确:
- 确保图片分辨率足够(建议300dpi以上)
- 尝试不同的多模态模型(GPT-4V通常效果最好)
- 添加更详细的提示词约束
-
检索结果不相关:
- 检查嵌入模型是否匹配内容语言
- 调整chunk_size和chunk_overlap参数
- 添加query扩展或重写步骤
5.3 扩展方向
- 支持更多格式:Word、Excel、PPT等Office文档
- 增强表格理解:添加表格问答专用模块
- 视觉问答:结合目标检测等CV技术
- 知识图谱整合:将提取的实体关系结构化
6. 实用资源推荐
-
PDF解析工具对比:
工具名称 优点 缺点 Unstructured 多模态支持好 对复杂表格处理一般 PyPDF2 轻量快速 只能提取文本 pdfplumber 表格提取精准 内存消耗大 -
多模态LLM选择:
- GPT-4V:效果最好但成本高
- LLaVA:开源可本地部署
- Qwen-VL:中文场景表现优秀
-
中文嵌入模型推荐:
- bge-small-zh-v1.5:平衡速度和效果
- m3e-base:中文任务表现优异
- text2vec-large:效果最好但资源消耗大
在实际部署时,我发现以下几个配置组合效果最佳:
- 中文场景:bge-small-zh + Qwen-VL + bge-reranker
- 英文场景:text-embedding-3-small + GPT-4V + CohereRerank
- 混合场景:paraphrase-multilingual + LLaVA + MiniLM-reranker
对于想要快速上手的开发者,建议先从阿里云的百炼平台开始,它提供了完整的API生态和免费的额度,足够用于原型验证。当系统需要投入生产环境时,再考虑私有化部署方案。
