多模态 RAG 实战:结合视觉大模型的文档智能问答

1. 引言

传统 RAG(Retrieval-Augmented Generation)只能处理文本,但现实中的文档充满了图表、表格和示意图。本文将构建一个多模态 RAG 系统,能够同时理解文档中的文字和图像,实现真正的文档智能问答。

技术栈:

  • 文档解析:PyMuPDF + Unstructured
  • 图像理解:GPT-4o / LLaVA
  • 向量数据库:ChromaDB
  • 文本嵌入:OpenAI text-embedding-3-small
  • 编排框架:LangChain

2. 系统架构

文档输入 → 解析(文本+图像) → 多模态嵌入 → 向量存储
                                              ↓
用户提问 → 检索(文本+图像) → 多模态LLM生成 → 回答

3. 文档解析:提取文本与图像

import fitz  # PyMuPDF
import os
from pathlib import Path

def extract_content(pdf_path: str, output_dir: str = "extracted"):
    """从 PDF 中提取文本块和图像"""
    os.makedirs(output_dir, exist_ok=True)
    doc = fitz.open(pdf_path)

    contents = []

    for page_idx, page in enumerate(doc):
        # 提取文本
        text_blocks = page.get_text("blocks")
        for block in text_blocks:
            if block[6] == 0:  # 文本块
                contents.append({
                    "type": "text",
                    "content": block[4].strip(),
                    "page": page_idx,
                    "bbox": block[:4],
                })

        # 提取图像
        image_list = page.get_images(full=True)
        for img_idx, img in enumerate(image_list):
            xref = img[0]
            base_image = doc.extract_image(xref)
            img_path = f"{output_dir}/page{page_idx}_img{img_idx}.png"
            with open(img_path, "wb") as f:
                f.write(base_image["image"])

            contents.append({
                "type": "image",
                "content": img_path,
                "page": page_idx,
                "bbox": None,
            })

    doc.close()
    return contents

4. 多模态描述生成

使用视觉大模型为图像生成文本描述:

import base64
from openai import OpenAI

client = OpenAI()

def describe_image(image_path: str) -> str:
    """用 GPT-4o 为图像生成描述"""
    with open(image_path, "rb") as f:
        img_base64 = base64.b64encode(f.read()).decode()

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text":
                    "请详细描述这张图片的内容,包括所有可见的文字、"
                    "数据、图表类型、关键趋势和结论。"
                    "如果是表格,提取所有行列数据。"},
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{img_base64}"
                }},
            ],
        }],
        max_tokens=1000,
    )
    return response.choices[0].message.content

5. 向量化与存储

from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document

def build_vectorstore(contents: list, persist_dir: str = "chroma_db"):
    """构建多模态向量数据库"""
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500, chunk_overlap=50
    )

    documents = []
    for item in contents:
        if item["type"] == "text":
            chunks = splitter.split_text(item["content"])
            for chunk in chunks:
                documents.append(Document(
                    page_content=chunk,
                    metadata={"page": item["page"], "source": "text"}
                ))
        elif item["type"] == "image":
            description = describe_image(item["content"])
            documents.append(Document(
                page_content=description,
                metadata={
                    "page": item["page"],
                    "source": "image",
                    "image_path": item["content"],
                }
            ))

    vectorstore = Chroma.from_documents(
        documents, embeddings, persist_directory=persist_dir
    )
    return vectorstore

6. 多模态 RAG 查询链

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

def multimodal_query(vectorstore, question: str) -> str:
    """多模态 RAG 查询"""
    retriever = vectorstore.as_retriever(
        search_type="mmr",       # 最大边际相关性
        search_kwargs={"k": 5}
    )

    # 检索相关文档
    docs = retriever.invoke(question)

    # 分离文本和图像上下文
    text_context = []
    image_descriptions = []
    for doc in docs:
        if doc.metadata["source"] == "image":
            image_descriptions.append(
                f"[图片描述 - 第{doc.metadata['page']}页]: "
                f"{doc.page_content}"
            )
        else:
            text_context.append(doc.page_content)

    # 构造多模态 prompt
    context = "\n\n".join(text_context + image_descriptions)

    llm = ChatOpenAI(model="gpt-4o", temperature=0)
    response = llm.invoke(
        f"基于以下上下文回答问题。如果涉及图表数据,请引用具体数值。\n\n"
        f"上下文:\n{context}\n\n"
        f"问题:{question}"
    )
    return response.content

7. 完整使用示例

# 1. 解析文档
contents = extract_content("annual_report.pdf")
print(f"提取了 {len(contents)} 个内容块")

# 2. 构建向量库
vectorstore = build_vectorstore(contents)

# 3. 查询
answer = multimodal_query(
    vectorstore,
    "2024年Q3的营收增长率是多少?与Q2相比趋势如何?"
)
print(answer)

输出示例:

根据文档中的财务图表,2024年Q3营收为3.2亿元,同比增长28.5%。
Q2营收为2.8亿元,同比增长22.1%。Q3较Q2增长了14.3%,
整体呈加速增长趋势。

8. 性能优化

优化方向 方法 效果
图像去重 pHash 感知哈希去重 减少 30% 冗余图像
嵌入缓存 SQLite 缓存已嵌入文本 避免重复 API 调用
分块策略 按语义分块而非固定长度 提升检索精度
重排序 Cohere Reranker mAP@5 提升 15%

9. 总结

多模态 RAG 的核心在于:

  1. 解析层:PDF → 文本 + 图像的完整提取
  2. 理解层:视觉大模型将图像转化为可检索的文本描述
  3. 检索层:统一的向量空间,同时检索文本和图像内容
  4. 生成层:大模型综合文本与图像信息生成准确回答

生产环境中建议增加查询改写(Query Rewriting)和答案校验(Groundedness Check)环节。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐