利用 vLLM 实现多模态RAG 系统
本文将深入探讨如何使用 vLLM 构建多模态信息检索与生成(Multimodal RAG)系统,以实现对包含文本、图像和表格的文档的有效处理和智能问答。
如果您想了解更多关于自然语言处理或其他技术领域的信息,请关注我们的公众号 柏企科技圈。
一、多模态 RAG 概述
多模态 RAG 是一种先进的信息检索和生成方法,它整合了多种内容类型,主要是文本和图像。与传统仅依赖文本的 RAG 系统不同,多模态 RAG 充分发挥了文本和视觉信息的优势,为生成响应提供了更全面、更具上下文的基础。
许多文档,如研究论文、商业报告等,都包含文本、图像、图表和表格的混合。通过将视觉元素纳入检索和生成过程,多模态 RAG 系统能够:
- 捕捉纯文本分析中丢失的细微差别;
- 提供更准确、与上下文相关的响应;
- 通过视觉辅助增强对复杂概念的理解;
- 提高生成内容的整体质量和深度。
二、多模态 RAG 的实现策略
实现多模态 RAG 管道有多种方法,各有其优势和考虑因素:
联合嵌入和检索
- 利用 CLIP(对比语言 - 图像预训练)或 ALIGN(大规模图像和噪声文本嵌入)等模型为文本和图像创建统一的嵌入。
- 使用 FAISS 或 Annoy 等库实现近似最近邻搜索,以进行高效检索。
- 将检索到的多模态内容(原始图像和文本块)输入到多模态大语言模型(如 LLaVa、Pixtral 12B、GPT - 4V、Qwen - VL)中进行答案生成。
图像到文本转换
- 使用 LLaVA 或 FUYU - 8b 等模型从图像生成摘要。
- 使用基于文本的嵌入模型(如 Sentence - BERT)为原始文本和图像标题创建嵌入。
- 将文本块传递给大语言模型进行最终答案合成。
原始图像访问的混合检索
- 采用多模态大语言模型从图像生成文本摘要。
- 将这些摘要与原始图像的引用以及其他文本块一起嵌入和检索。这可以通过带有 Chroma、Milvus 等向量数据库的多向量检索器来实现,这些数据库用于存储原始文本、图像及其摘要以便检索。
- 对于最终答案生成,使用能够同时处理文本和原始图像输入的多模态模型,如 Pixtral 12B、LLaVa、GPT - 4V、Qwen - VL。
在本文中,我们将探索第三种方法,利用一系列强大的工具组合来创建一个高效的多模态 RAG 系统。
三、系统实现步骤
工具准备
- Unstructured:用于解析各种文档格式(包括 PDF)中的图像、文本和表格。
- LLaVa via vLLM:由 vLLM 服务引擎驱动,使用名为 LLaVA(llava - hf/llava - 1.5 - 7b - hf)的视觉语言模型来处理文本/表格摘要以及多模态任务,如图像摘要和从集成的文本和视觉输入生成答案。虽然不是最先进的模型,但 LLaVA 效率高且计算成本低。借助 vLLM,它可以无缝部署在 CPU 上,对于那些希望在性能和资源效率之间取得平衡的人来说是一种理想的、具有成本效益的解决方案。
- Chroma DB:作为我们的向量数据库,用于存储文本块、表格摘要、图像摘要以及它们的原始图像。结合其多向量检索器功能,它为我们的多模态系统提供了强大的存储和检索系统。
- LangChain:作为协调工具,将这些组件无缝集成在一起。
通过结合这些工具,我们将展示如何构建一个强大的多模态 RAG 系统,该系统能够处理不同类型的文档,生成高质量的摘要,并生成利用文本和视觉信息的全面答案。
- 数据下载
我们将使用这篇博客文章作为文档源,因为它包含以图像形式呈现的图表和表格中的有价值信息。
import os
import io
import re
import uuid
import base64
import shutil
import requests
from tqdm import tqdm
from PIL import Image
import matplotlib.pyplot as plt
from IPython.display import HTML, display
from unstructured.partition.pdf import partition_pdf
from langchain_core.documents import Document
from langchain_text_splitters import CharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain.chains.llm import LLMChain, PromptTemplate
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts.chat import (ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate)
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain.retrievers.multi_vector import MultiVectorRetriever
from openai import OpenAI as OpenAI_vLLM
from langchain_community.llms.vllm import VLLMOpenAI
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name='BAAI/bge-large-en')
os.mkdir("data")
shutil.move("gtm_benchmarks_2024.pdf", "data")
- 从 PDF 文档中提取文本、表格和图像
下载 PDF 后,我们将利用 unstructured.io 库处理文档并提取内容。
def extract_pdf_elements(path, fname):
"""
从 PDF 文件中提取图像、表格和文本块。
path: 文件路径,用于存储图像(.jpg)
fname: 文件名
"""
return partition_pdf(
filename=path + fname,
extract_images_in_pdf=True,
infer_table_structure=True,
chunking_strategy="by_title",
max_characters=4000,
new_after_n_chars=3800,
combine_text_under_n_chars=2000,
image_output_dir_path=path
)
def categorize_elements(raw_pdf_elements):
"""
将从 PDF 中提取的元素分类为表格和文本。
raw_pdf_elements: unstructured.documents.elements 列表
"""
tables = []
texts = []
for element in raw_pdf_elements:
if "unstructured.documents.elements.Table" in str(type(element)):
tables.append(str(element))
elif "unstructured.documents.elements.CompositeElement" in str(type(element)):
texts.append(str(element))
return texts, tables
folder_path = "./data/"
file_name = "gtm_benchmarks_2024.pdf"
raw_pdf_elements = extract_pdf_elements(folder_path, file_name)
texts, tables = categorize_elements(raw_pdf_elements)
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size = 1000, chunk_overlap = 0
)
joined_texts = " ".join(texts)
texts_token = text_splitter.split_text(joined_texts)
print("文本块数量:", len(texts))
print("表格元素数量:", len(tables))
print("分词后的文本块数量:", len(texts_token))
- 生成表格摘要
我们将使用在 CPU 机器上运行的 vLLM 引擎来驱动 7B 参数的 LLaVA 模型(llava - hf/llava - 1.5 - 7b - hf)生成表格摘要。我们也可以像在任何 RAG 系统中通常那样使用基于文本的大语言模型,但在这里我们将使用能够处理文本和图像的 LLaVa 模型本身。
生成表格摘要是为了增强自然语言检索,这些摘要对于高效检索原始表格和文本块至关重要。
llm_client = VLLMOpenAI(
base_url = "http://localhost:8000/v1",
api_key = "dummy",
model_name = "llava-hf/llava-1.5-7b-hf",
temperature = 1.0,
max_tokens = 300
)
def generate_text_summaries(texts, tables, summarize_texts=False):
"""
总结文本元素
texts: 字符串列表
tables: 字符串列表
summarize_texts: 是否总结文本
"""
prompt_text = """你是一个负责总结表格以便检索的助手。
给出一个简洁的、针对检索进行优化的表格摘要。确保捕捉到所有细节。
输入: {element} """
prompt = ChatPromptTemplate.from_template(prompt_text)
summarize_chain = {"element": lambda x: x} | prompt | llm_client | StrOutputParser()
text_summaries = []
table_summaries = []
if texts and summarize_texts:
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 3})
elif texts:
text_summaries = texts
if tables:
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 3})
return text_summaries, table_summaries
text_summaries, table_summaries = generate_text_summaries(
texts_token, tables, summarize_texts=False
)
print("文本摘要数量:", len(text_summaries))
print("表格摘要数量:", len(table_summaries))
- 生成图像摘要
现在,我们将使用视觉语言模型(VLM)生成图像摘要。
注意:图像可以通过两种主要方式提供给模型:传递图像链接或在请求中直接传递 base64 编码的图像。
api_key = "dummy"
base_url = "http://localhost:8000/v1"
vlm_client = OpenAI_vLLM(
api_key = api_key,
base_url = base_url
)
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_response = vlm_client.chat.completions.create(
model="llava-hf/llava-1.5-7b-hf",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": prompt},
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{img_base64}",
},
},
],
stream=False
)
return chat_response.choices[0].message.content.strip()
def generate_img_summaries(path):
"""
为图像生成摘要和 base64 编码字符串
path: Unstructured 提取的.jpg 文件路径列表
"""
img_base64_list = []
image_summaries = []
prompt = """你是一个负责总结图像以便最佳检索的助手。
这些摘要将被嵌入并用于检索原始图像。
编写一个清晰简洁的摘要,捕捉所有重要信息,包括图像中的任何统计数据或关键点。"""
for img_file in tqdm(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)
generated_summary = image_summarize(base64_image, prompt)
print(generated_summary)
image_summaries.append(generated_summary)
return img_base64_list, image_summaries
img_base64_list, image_summaries = generate_img_summaries(folder_path)
assert len(img_base64_list) == len(image_summaries)
- 存储和索引文档摘要
为了配置多向量检索器,我们将原始文档(包括文本、表格和图像)存储在文档存储中,同时在向量存储中索引它们的摘要,以提高语义检索效率。
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
)
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_rag_vectorstore", embedding_function=embeddings, persist_directory="./chroma_db"
)
retriever_multi_vector_img = create_multi_vector_retriever(
vectorstore,
text_summaries,
texts,
table_summaries,
tables,
image_summaries,
img_base64_list
)
- 多向量检索设置
接下来,我们定义处理和处理文本数据和 base64 编码图像的函数和配置,包括调整图像大小和格式化模型提示。它设置了一个多模态检索和生成(RAG)上下文链,以集成和分析文本和图像数据来回答用户查询。
由于我们使用 vLLM 的 HTTP 服务器为视觉语言模型提供服务,该服务器与 OpenAI 视觉 API(聊天完成 API)兼容,因此为了设置模型的上下文,我们遵循特定的聊天模板。
def plt_img_base64(img_base64):
"""显示 base64 编码字符串为图像"""
image_html = f'<img src="data:image/jpeg;base64,{img_base64}" />'
display(HTML(image_html))
def looks_like_base64(sb):
"""检查字符串是否看起来像 base64"""
return re.match("^[A-Za-z0-9+/]+[=]{0,2}$", sb) is not None
def is_image_data(b64data):
"""
通过查看数据开头检查 base64 数据是否为图像
"""
image_signatures = {
b"\xff\xd8\xff": "jpg",
b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a": "png",
b"\x47\x49\x46\x38": "gif",
b"\x52\x49\x46\x46": "webp"
}
try:
header = base64.b64decode(b64data)[:8]
for sig, format in image_signatures.items():
if header.startswith(sig):
return True
return False
except Exception:
return False
def resize_base64_image(base64_string, size=(64, 64)):
"""
调整 base64 编码图像的大小
"""
img_data = base64.b64decode(base64_string)
img = Image.open(io.BytesIO(img_data))
resized_img = img.resize(size, Image.LANCZOS)
buffered = io.BytesIO()
resized_img.save(buffered, format=img.format)
return base64.b64encode(buffered.getvalue()).decode("utf-8")
def split_image_text_types(docs):
"""
拆分 base64 编码图像和文本
"""
b64_images = []
texts = []
for doc in docs:
if isinstance(doc, Document):
doc = doc.page_content
if looks_like_base64(doc) and is_image_data(doc):
doc = resize_base64_image(doc, size=(64, 64))
b64_images.append(doc)
else:
texts.append(doc)
return {"images": b64_images, "texts": texts}
def img_prompt_func(data_dict):
"""
将上下文合并为单个字符串
"""
formatted_texts = "\n".join(data_dict["context"]["texts"])
messages = []
text_message = {
"type": "text",
"text": (
"你是一个在金融和商业指标方面有专长的助手。\n"
"你将获得可能包括与业务绩效和行业趋势相关的文本、表格和图表的信息
## 四、检索测试
我们提出问题:“从 2020 年到 2024 年,公共 SaaS 公司的年收入同比中位数增长率发生了怎样的变化?”
query = "How has the median YoY ARR growth rate for public SaaS companies changed from 2020 to 2024?"
docs = retriever_multi_vector_img.invoke(query)
plt_img_base64(docs[0])
## 五、运行 RAG 管道生成答案
由于我们当前的模型不支持非常长的上下文以及每个文本提示包含多个多模态项目,所以我们将修改检索到的上下文并测试最终答案合成部分。
context = chain_multimodal_context.invoke(query)[0].content
context = [
{
'type': 'text',
'text': "You are an AI assistant with expertise in finance and business metrics.\nYou will be given information that may include text, tables, and charts related to business performance and industry trends.\nYour task is to analyze this information and provide a clear, concise answer to the user's question.\nFocus on the most relevant data points and insights that directly address the user's query.\nUser's question: How has the median YoY ARR growth rate for public SaaS companies changed from 2020 to 2024?"
},
{
'type': 'image_url',
'image_url': {'url': 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABAAEADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3wRoQOP1o8pPT9abFKX3Axuu1toJH3vce1SbvY0AN8pPT9axf7Zn+0zQDQr/MblRIQAjjdjcDnp0P0zW5u9jUVxCtxCY2MqAkHdG20j8RQBiW2uXM0sUcvh7UIfMkCbiAVQZI3Me3Az3re8pPT9aZbwi2iEatK4BJzI248+9S7vY0AN8pPT9arXcy20lqnlFvPl8rO7G35Sc+/TH41b3exqKWJJWV2D5UHGGIH5U1bqJ3toLHMj7gA3yNtOVI5x29arpq9hI1uqXSObgssW3kOV+8AfaoLxhLc2ssDXDyQSMhjiYKmSv/AC0J7fnyaZDY3SpEPNsLZUZjtt7fkA/3STgHpk45pDLFpq1tdRW7MHt5JywjhnXa7bevH61cWVHUMjBlPQryDWYbG5hlhZJ4bsRlvmu1/eLn+66jj8qr2TPZzWdnElvZRIJDLbM5cv3Bjbv6n69KAN3cPf8AI0bh7/karWWoW2oWkdzA+Y5ASu4bTwcdDVncv94UAG4e/wCRpCwIPXp6Uu5fUUhYEHkdKAESRGBCupKna2D0PoadkeopqKgBKquSctgd/enYHoKADI9RUU8ENygWVQcHKnOCp9QexqXA9BRgegoAxZLWeG6tp50N60CuqXKHa8efVBw31Hp0qXT724lhtPKkiv4WV/OuVYKVPVRt/TtWrgegqtPp1pczpPLCDMgIWRSVYA+457UAV4NTnn+yg6dcRmeN2bfj90R0DfXtSQXGpyS2wntYIY3iYzjzdzI+eAvqP8aeukQARhpryRUUrh7lyD06889Kkt9MsbIA21pDEyIUDKg3BeOM9ew/KgCxHDGm4qigudzEDqfWn7V9BVKS33TJJFcCNScyLyd/AHrx0pi2so8vdfBsZ3/Ljd+vFAGhtX0FG1fQVnrayjZuvw2M7vlxu/XihbWYeXuvw20Hf8uN3p34oA0Nq+go2r6Cs9bWYbN1+DgHd8v3vTvxQtrMNmb8HCkN8uNx7HrxjigDQ2r6CkKgA8DpVAWsw2ZvwcKQ3y9Tzg9eMcflTo7eVHjZr0OFB3rtxuPPPXjt+VAH/9k='
}
]
chat_response = vlm_client.chat.completions.create(
model="llava-hf/llava-1.5-7b-hf",
messages=[{
"role": "user",
"content": context
}],
stream=True
)
for chunk in chat_response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
基于提供的用户问题以及检索到的文本片段和图像,模型现在将开始流式输出其响应。
## 六、注意事项
为了演示图像检索,最初生成了较大(4k 令牌)的文本块,然后进行了总结。然而,情况并非总是如此,可能需要其他方法来确保准确和高效的分块和索引。
总结和答案质量似乎对图像大小和分辨率敏感。
当前使用的视觉语言模型仅支持单图像输入。
模型可能难以理解某些视觉元素,如图表或复杂的流程图。
在某些情况下,模型可能会生成错误的描述或标题。例如,在询问统计问题时可能会提供错误信息。
如果图像中的文本不清晰或模糊,模型将尽力解释,但结果可能不太准确。
## 七、未来展望
使用具有更长上下文窗口且支持每条消息传递多个图像和/或传递多轮对话的视觉语言模型(如 Pixtral - 12B)测试准确性。
实现文本和图像模态之间更复杂的交互,使模型能够根据问题动态地优先考虑视觉或文本信息。
引入更精细的视觉内容总结技术,以生成更好的图像语义表示。
由于我们使用 vLLM 来提供模型服务,研究在 CPU 和 GPU 上使用不同优化运行相同模型时性能的变化将是很有趣的。
最后但同样重要的是,使用更好的分块和检索机制。
> 如果您对文中的技术细节或代码实现有任何疑问,欢迎随时交流探讨。同时,我们也将持续关注 RAG 技术的发展动态,为您带来更多的前沿资讯和深度分析。
>
> 以上就是本文的全部内容,希望对您有所帮助!如果您想了解更多关于自然语言处理或其他技术领域的信息,请关注我们的公众号 **柏企科技圈**。
## 参考文献
- https://blog.langchain.dev/semi-structured-multi-modal-rag/
- https://github.com/langchain-ai/langchain
- https://python.langchain.com/docs/how_to/multi_vector/
- https://docs.vllm.ai/en/latest/models/vlm.html
- https://platform.openai.com/docs/guides/vision
- https://cs.stanford.edu/~myasu/blog/racm3/
# 推荐阅读
[1. 专家混合(MoE)大语言模型:免费的嵌入模型新宠](https://mp.weixin.qq.com/s/XwgigFEuyD-ED3IunlSItw?token=2113630118&lang=zh_CN)
[2. LLM大模型架构专栏|| 从NLP基础谈起](https://mp.weixin.qq.com/s/MYx5V29WczQzxPybKBbT7Q?token=2113630118&lang=zh_CN)
[3. AI Agent 架构新变革:构建自己的 Plan-and-Execute Agent](https://mp.weixin.qq.com/s/NBlp058THkckTKFscjPhiw?token=2113630118&lang=zh_CN)
[4. 探索 AI 智能体工作流设计模式](https://mp.weixin.qq.com/s/gQuxYo7LiKuAWr04hzIjQg?token=2113630118&lang=zh_CN)
[5. 探秘 GraphRAG:知识图谱赋能的RAG技术新突破](https://mp.weixin.qq.com/s/MtTju5IQ9alTwZP_xAK-bg?token=1679189915&lang=zh_CN)
[6. 解锁 RAG 技术:企业数据与大模型的完美融合之道](https://mp.weixin.qq.com/s/9KdLmYj7WbbkMfljWMXvTg?token=1679189915&lang=zh_CN)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。