从零开始多模态RAG系统构建:视觉文档详细分步实战(附完整代码)
摘要
以GPT-4o为基础构建视觉文档多模态RAG系统,利用Unstructured库解析PDF中的文本、表格和图像,
导言
多模态检索增强生成(RAG)架构在充分发挥大模型能力的同时,可有效抑制幻觉,解决垂直业务领域的数据整合难题。构建融合音频、视频、文档等多种数据源的多模态RAG系统,是深度集成异构信息的关键路径。本文完整演示了视觉文档多模态RAG系统的搭建步骤,并附可运行代码。
通过阅读本文,你将掌握:
- 如何构建融合视觉文档的多模态RAG管道
- 如何获取完整实现代码
概述
本教程基于OpenAI的GPT-4o模型,指导你构建一个多模态RAG聊天应用。核心内容包括:
- 多模态RAG聊天应用:实现从PDF文档中检索信息,并支持视觉问答。
- 一步式解析:利用Unstructured库同步提取文本、表格与图像。
- 性能评估:借助DeepEval库的多样化指标评测聊天机器人表现。
- Streamlit交互界面:通过Streamlit快速搭建演示前端。
为什么要阅读本文?
如果你希望利用GPT-4o等前沿基础模型的多模态能力,打造自己的AI应用,这篇文章正是为你量身定制。无论你是需要从市场研究报告中提炼洞察的营销专家,还是需分析多模态病历的医疗从业者,抑或是处理复杂法律文档的法律工作者,本文都会提供有价值的思路与可直接复用的代码。每个概念都将被彻底拆解,代码段也会逐行讲解。
多模态RAG的崛起
从纯文本RAG向多模态RAG的演进,标志着AI能力的质变。简要回顾:
- 起源:RAG概念于2021年4月提出,最初通过文本知识增强语言生成。
- 进展:2024年5月GPT-4o等模型发布后,系统能够同时处理图像、表格与文本,整合视觉信息。
- 新可能性:这一进化催生了更全面、上下文更丰富的AI应用场景。
本文将通过一个案例展示多模态RAG框架:基于本人发表于Neurocomputing的研究论文(包含文本、表格与图表),利用GPT-4o的视觉能力回答复杂问题。
目录
- 配置虚拟环境并安装Python库
- 非结构化数据预处理
- 文本、表格与图像摘要生成
- 多模态检索器构建
- 多模态RAG链实现
- 大模型评估
- Streamlit用户界面开发
配置虚拟环境并安装Python库
首先,用以下命令创建虚拟环境:
python3.10 -m venv venv
然后安装必要的包。你可以在GitHub仓库根目录下的requirements.txt中找到它们。
pip install -r requirements.txt
接着,打开一个Jupyter Notebook(例如your-project.ipynb)开始编码。至此准备工作完成,可以进入正题。
非结构化数据预处理
构建RAG应用的第一步,是将上下文加载到数据库中——这里使用的是PDF文档。由于大语言模型(LLM)的上下文窗口限制,我们不能直接把整个文档塞进提示词里,那样大概率会超过最大token数导致报错。
解决方法是提取文档中的不同元素:图像、文本和表格。这里使用Unstructured库来完成。
安装
如果尚未安装,先用pip安装(同时需要系统中已安装tesseract和poppler库,以便提取图像中的文本):
# brew install tesseract poppler (macOS上先安装这两个库) %pip install -q "unstructured[all-docs]"
分区与分块
from unstructured.partition.pdf import partition_pdf
elements = partition_pdf(
filename="TAGIV.pdf", # 必填
strategy="hi_res", # 必填,使用高分辨率策略
extract_images_in_pdf=True, # 必填,设置为True
extract_image_block_types=["Image", "Table"], # 可选
extract_image_block_to_payload=False, # 可选
extract_image_block_output_dir="sa ved_images", # 可选,仅在extract_image_block_to_payload=False时生效
)
这里使用partition_pdf模块对文档进行分区,并从TAGIV.pdf中提取不同元素。设置hi_res策略可以提取高质量的图像和表格。可选参数extract_image_block_types和extract_image_block_output_dir指定只提取图像和表格,并保存到sa ved_images目录中。
接下来,用chunk_by_title方法对元素进行分块。这个方法会根据“标题”将提取的元素分成块,对研究文章尤其适用——这类文章通常有独立的章节和子章节,比如引言、方法、结果等。
from unstructured.chunking.title import chunk_by_title
from typing import Any
chunks = chunk_by_title(elements)
# 查看文档中的不同类别
category_counts = {}
for element in chunks:
category = str(type(element))
if category in category_counts:
category_counts[category] += 1
else:
category_counts[category] = 1
# 输出:
# {"": 200,
# "": 3,
# "": 2}
分块后得到了三个类别:CompositeElements(文本集合,可能是段落、页眉、页脚、公式等)、Table(完整的表格)以及TableChunk(表格的一部分或片段)。文档中实际有四个表格,但只有三个被完整解析到了。????
过滤
接下来简化文档元素,把文本和表格数据分开处理。先定义一个Pydantic模型来规范化文档元素:
from pydantic import BaseModel
class Element(BaseModel):
type: str
text: Any
然后按类型分类:
categorized_elements = []
for element in chunks:
if "unstructured.documents.elements.CompositeElement" in str(type(element)):
categorized_elements.append(Element(type="text", text=str(element)))
elif "unstructured.documents.elements.Table" in str(type(element)):
categorized_elements.append(Element(type="table", text=str(element)))
# 分别提取文本和表格
text_elements = [e for e in categorized_elements if e.type == "text"]
table_elements = [e for e in categorized_elements if e.type == "table"]
遍历文档元素块,识别类型并添加到分类列表,最后过滤出文本列表和表格列表。预处理阶段到此结束。
文本、表格与图像摘要生成
为了后续使用多向量检索器,我们需要为文本、表格和图片元素创建摘要。这些摘要会被存储在向量存储器中,当用户输入查询时,就能实现语义搜索。
文本和表格摘要
先设置一个提示模板,让AI扮演专家研究助理,负责总结表格和文本。然后创建一个链,通过这个提示和GPT-4o模型处理每个元素,生成简洁摘要。为了提高效率,使用max_concurrency参数同时批处理五个元素。
%pip install -q langchain langchain-chroma unstructured[all-docs] pydantic lxml langchainhub langchain-openai
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 提示
prompt_text = """您是一名专家研究助理,负责总结研究文章中的表格和文本。给出文本的简洁总结。文本块:{element}"""
prompt = ChatPromptTemplate.from_template(prompt_text)
# 摘要链
model = ChatOpenAI(temperature=0, model="gpt-4o")
summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()
# 应用到文本
texts = [i.text for i in text_elements]
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})
# 应用到表格
tables = [i.text for i in table_elements]
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})
图像摘要
接下来定义三个关键函数来总结图像:encode_image、image_summarize和generate_img_summaries。
- encode_image:以二进制读取模式打开图像文件,返回base64编码字符串。
- image_summarize:使用包含提示和base64图像数据的
HumanMessage调用模型生成摘要。 - generate_img_summaries:遍历目录中的JPG图像,为每个图像生成摘要并返回base64编码列表和摘要列表。
完整代码如下:
import base64
import os
from langchain_core.messages import HumanMessage
def encode_image(image_path):
"""获取base64字符串"""
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode("utf-8")
def image_summarize(img_base64, prompt):
"""生成图像摘要"""
chat = ChatOpenAI(model="gpt-4o", max_tokens=1024)
msg = chat.invoke([
HumanMessage(content=[
{"type": "text", "text": prompt},
{"type": "image_url",
"image_url": {"url": f"data:image/jpg;base64,{img_base64}"}},
])
])
return msg.content
def generate_img_summaries(path):
"""
生成图像摘要和base64编码字符串
path: 通过Unstructured提取的.jpg文件列表的路径
"""
img_base64_list = []
image_summaries = []
prompt = """您是一名助手,负责为检索摘要图像。
这些摘要将被嵌入并用于检索原始图像。
给出一份简洁的图像摘要,以便更好地进行检索。"""
for img_file in sorted(os.listdir(path)):
if img_file.endswith(".jpg"):
img_path = os.path.join(path, img_file)
base64_image = encode_image(img_path)
img_base64_list.append(base64_image)
image_summaries.append(image_summarize(base64_image, prompt))
return img_base64_list, image_summaries
# 应用
fpath = "sa ved_images"
img_base64_list, image_summaries = generate_img_summaries(fpath)
多模态检索器
有了摘要,就可以构建多模态检索器了。
多向量检索器
我们将建立一个多向量检索器(MultiVectorRetriever),它接受向量存储、文档存储、id_key和search_kwargs作为输入。这种方法可以在向量存储中索引内容的摘要,同时在文档存储中保留原始内容,从而实现高效的检索。需要注意的是,这只是实现多模态RAG的一种方式——另一种方式是利用CLIP等多模态嵌入来嵌入文本和图像,然后把原始图像和文本块一起传给多模态LLM。这个话题留待以后探讨。????
检索器使用Chroma向量存储来存放摘要的嵌入,并用InMemoryStore存放完整内容。这样可以通过摘要进行语义搜索,同时在需要时返回对应的原始内容。每个文档都会分配一个UUID作为唯一标识符。
为了简化添加过程,我们写一个辅助函数add_documents:
import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
def create_multi_vector_retriever(
vectorstore, text_summaries, texts,
table_summaries, tables,
image_summaries, images
):
"""
创建检索器:索引摘要,但返回原始图像、表格或文本
"""
store = InMemoryStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
search_kwargs={"k": 2} # 限制返回前2个结果
)
def add_documents(retriever, doc_summaries, doc_contents):
doc_ids = [str(uuid.uuid4()) for _ in doc_contents]
summary_docs = [
Document(page_content=s, metadata={id_key: doc_ids[i]})
for i, s in enumerate(doc_summaries)
]
retriever.vectorstore.add_documents(summary_docs)
retriever.docstore.mset(list(zip(doc_ids, doc_contents)))
# 添加文本、表格和图像(非空才添加)
if text_summaries:
add_documents(retriever, text_summaries, texts)
if table_summaries:
add_documents(retriever, table_summaries, tables)
if image_summaries:
add_documents(retriever, image_summaries, images)
return retriever
创建检索器
# 用于索引摘要的向量存储
vectorstore = Chroma(
collection_name="mm_tagiv_paper",
embedding_function=OpenAIEmbeddings()
)
# 创建检索器
retriever_multi_vector_img = create_multi_vector_retriever(
vectorstore,
text_summaries, texts,
table_summaries, tables,
image_summaries, img_base64_list,
)
测试与完整代码
代码组织结构如下:
advanced-RAG-app/ │ ├── utils/ │ ├── __init__.py │ ├── image_processing.py │ ├── rag_chain.py │ ├── rag_evaluation.py │ └── retriever.py │ ├── main.py └── requirements.txt
完整代码已整理在GitHub仓库中,你可以直接下载使用(仓库地址:https://github.com/bhargobdeka/advanced-RAG-app——该仓库包含本文涉及的所有代码文件)。
接下来我们还会继续介绍多模态RAG链的构建、LLM性能评估以及Streamlit界面的具体实现,敬请期待。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。