AI工程化实战《四》:多模态 RAG 全解——让 AI 看懂 PDF 表格、扫描件与流程图

一、为什么多模态 RAG 是企业刚需?

1.1 企业文档的多模态现实

文档类型 占比 信息价值 传统 RAG 处理效果
纯文本(.txt/.md) 15% ✅ 良好
Word/PPT(含图) 25% ⚠️ 图片丢失
PDF(含表格/图) 40% ❌ 表格变乱码
扫描件/照片 15% 极高 ❌ OCR 错误率高
视频/音频 5% ❌ 完全无法处理

💡 核心洞察越重要的文档,越可能是多模态的(财报、合同、图纸)。


1.2 多模态 RAG 技术全景

关键突破不止转文字,更要保留"视觉语义"


二、多模态文档智能解析(核心!)

2.1 选型对比:OCR + Layout 分析工具

工具 优势 劣势 适用场景
Unstructured (开源) 支持 PDF/Word/PPT,内置布局分析 表格识别一般 通用文档
PaddleOCR + PP-Structure 中文表格 SOTA,开源免费 部署复杂 财报/报表
Adobe Extract API 商业级精度 付费,需上传数据 高安全要求
Donut (开源模型) 端到端文档理解 需 GPU 微调 定制化表单

本文方案Unstructured + PaddleOCR 混合(兼顾通用性与表格精度)


2.2 解析 PDF 表格(使用 PaddleOCR PP-Structure)

复制代码
# table_parser.py
from paddleocr import PPStructure, save_structure_res

def extract_tables_from_pdf(pdf_path: str) -> list[dict]:
    """从 PDF 提取结构化表格"""
    table_engine = PPStructure(
        show_log=False,
        recovery=True,  # 保留表格结构
        lang="ch"
    )
    
    # 将 PDF 转为图像(每页一张)
    images = convert_pdf_to_images(pdf_path)
    all_tables = []
    
    for img in images:
        result = table_engine(img)
        for item in result:
            if item["type"] == "table":
                # 获取 HTML 表格(可转 JSON)
                html_table = item["res"]["html"]
                df = pd.read_html(html_table)[0]
                all_tables.append(df.to_dict("records"))
    
    return all_tables

📊 效果

  • 财报表格识别准确率 >92%
  • 输出为 结构化 JSON,可直接存入数据库。

2.3 解析图文混合文档(使用 Unstructured)

复制代码
# multimodal_loader.py
from unstructured.partition.pdf import partition_pdf

def load_multimodal_pdf(pdf_path: str):
    """解析 PDF,保留文本+图像+布局"""
    elements = partition_pdf(
        filename=pdf_path,
        strategy="hi_res",          # 高精度模式
        infer_table_structure=True, # 启用表格推理
        extract_images_in_pdf=True, # 提取内嵌图片
        chunking_strategy="by_title"
    )
    
    chunks = []
    for element in elements:
        if hasattr(element, "text") and element.text.strip():
            # 文本块:记录内容 + 类型 + 位置
            chunks.append({
                "type": element.category,
                "content": element.text,
                "bbox": getattr(element, "bbox", None)
            })
        elif hasattr(element, "image"):
            # 图像:保存路径 + 描述(后续用 Qwen-VL 生成)
            image_path = save_image(element.image)
            chunks.append({
                "type": "image",
                "image_path": image_path,
                "description": ""  # 待填充
            })
    
    return chunks

输出结构

复制代码
[
  {"type": "Title", "content": "2025年Q3财报"},
  {"type": "Table", "content": "[[营收, 1.2亿], [利润, 3000万]]"},
  {"type": "Image", "image_path": "/tmp/chart1.png"}
]

三、图像语义理解:让 AI "看懂"图片

3.1 使用 Qwen-VL 生成图像描述

Qwen-VL 是阿里推出的 多模态大模型 ,支持 图像理解 + 图文对话

复制代码
# image_understanding.py
from qwen_vl_utils import get_qwen_vl

qwen_vl = get_qwen_vl(api_key=os.getenv("QWEN_API_KEY"))

def describe_image(image_path: str) -> str:
    """生成图像语义描述"""
    response = qwen_vl.chat(
        messages=[{
            "role": "user",
            "content": [
                {"image": image_path},
                {"text": "请详细描述这张图片的内容,包括图表类型、关键数据、趋势等。"}
            ]
        }]
    )
    return response[0]["content"]

🖼️ 示例输入 :一张柱状图
输出:"该图展示了2025年Q1-Q3各季度营收,Q3最高达1.2亿元,呈逐季上升趋势。"


3.2 本地部署 Donut(可选,保护隐私)

对于敏感图片,可本地部署 Donut 模型(Document Understanding Transformer):

复制代码
# donut_local.py
from transformers import DonutProcessor, VisionEncoderDecoderModel

processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")

def donut_inference(image_path: str) -> dict:
    image = Image.open(image_path).convert("RGB")
    pixel_values = processor(image, return_tensors="pt").pixel_values
    task_prompt = "<s_cord-v2>"
    decoder_input_ids = processor.tokenizer(task_prompt, add_special_tokens=False, return_tensors="pt").input_ids
    
    outputs = model.generate(
        pixel_values,
        decoder_input_ids=decoder_input_ids,
        max_length=model.decoder.config.max_position_embeddings,
        early_stopping=True,
        pad_token_id=processor.tokenizer.pad_token_id,
        eos_token_id=processor.tokenizer.eos_token_id
    )
    
    decoded = processor.batch_decode(outputs, skip_special_tokens=True)[0]
    return json.loads(decoded)

🔒 优势:数据不出内网,适合金融/医疗场景。


四、多模态向量化与存储

4.1 统一向量表示策略

内容类型 向量化方法
文本块 bge-large-zh Embedding
表格数据 将 JSON 转为自然语言后 Embedding
图像 Qwen-VL 的图像 Embedding 或 CLIP
复制代码
# multimodal_embedding.py
def embed_multimodal_chunk(chunk: dict) -> np.ndarray:
    if chunk["type"] == "image":
        # 使用 Qwen-VL 获取图像 embedding
        return qwen_vl.get_image_embedding(chunk["image_path"])
    elif chunk["type"] == "table":
        # 将表格转为描述性文本
        table_text = "表格内容:" + json.dumps(chunk["content"], ensure_ascii=False)
        return text_embedder.encode(table_text)
    else:
        return text_embedder.encode(chunk["content"])

4.2 存入 Milvus(支持多向量字段)

复制代码
# milvus_store.py
from pymilvus import connections, Collection, FieldSchema, DataType

# 创建多模态 Schema
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=65535),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
    FieldSchema(name="doc_type", dtype=DataType.VARCHAR, max_length=32),  # text/table/image
    FieldSchema(name="source_file", dtype=DataType.VARCHAR, max_length=256)
]

collection = Collection("multimodal_kb", fields)
collection.create_index("embedding", {"index_type": "IVF_FLAT", "metric_type": "L2"})

优势

  • 支持 十亿级向量
  • 可按 doc_type 过滤(如只搜表格)。

五、跨模态检索与问答

5.1 文字搜图片(Text-to-Image Retrieval)

用户问:"找一张展示 Q3 营收增长的图表"

复制代码
# 文字 query → 图像 embedding 空间检索
query_emb = text_embedder.encode("Q3 营收增长图表")
results = collection.search(
    data=[query_emb],
    anns_field="embedding",
    param={"metric_type": "L2", "params": {"nprobe": 10}},
    limit=3,
    expr='doc_type == "image"'  # 只搜图片
)

5.2 图片搜文档(Image-to-Text Retrieval)

用户上传一张架构图,问:"这是哪个系统的架构?"

复制代码
# 图片 → Qwen-VL embedding → 文本库检索
img_emb = qwen_vl.get_image_embedding(uploaded_image_path)
results = collection.search(
    data=[img_emb],
    anns_field="embedding",
    limit=5,
    expr='doc_type in ["text", "title"]'
)

5.3 多模态 RAG 链(LangChain 集成)

复制代码
# multimodal_rag_chain.py
def create_multimodal_rag_chain():
    def retrieve_context(question: str):
        # 1. 文本检索
        text_results = vector_db.search(question, filter='doc_type != "image"')
        # 2. 若问题含"图/表",额外检索图像
        if any(kw in question for kw in ["图", "表", "chart", "graph"]):
            img_results = vector_db.search(question, filter='doc_type == "image"')
            # 用 Qwen-VL 生成图像描述
            for r in img_results:
                r["description"] = describe_image(r["image_path"])
            return text_results + img_results
        return text_results
    
    prompt = ChatPromptTemplate.from_template(
        "你是一个多模态助手,请结合以下信息回答问题:\n"
        "{context}\n\n问题:{question}"
    )
    
    chain = (
        {"context": retrieve_context, "question": RunnablePassthrough()}
        | prompt
        | qwen_vl  # 使用 Qwen-VL 生成图文答案
        | StrOutputParser()
    )
    return chain

输出示例

"根据您提供的架构图(见附件),这是订单管理系统的微服务架构,包含用户服务、商品服务、支付服务三个核心模块。"


六、高级应用:表格问答与计算

6.1 表格数据存入 SQLite(支持 SQL 查询)

复制代码
# table_to_sql.py
def store_table_to_sql(table_data: list, table_name: str):
    df = pd.DataFrame(table_data)
    df.to_sql(table_name, con=sqlite_conn, if_exists="replace", index=False)

# 用户问:"Q3 利润率是多少?"
# Agent 自动执行:
# SELECT 利润/营收 AS 利润率 FROM q3_financial WHERE 季度='Q3'

6.2 Qwen-VL 直接问答表格图像

复制代码
# 用户上传财报表格截图
response = qwen_vl.chat([
    {"image": "q3_table.png"},
    {"text": "Q3 的净利润是多少?"}
])
# 输出:"Q3 净利润为 3000 万元。"

无需 OCR + 结构化,端到端问答!


七、性能与成本优化

7.1 分层处理策略

文档类型 处理方式 成本 延迟
纯文本 直接 Embedding $0 <100ms
清晰 PDF 表格 PaddleOCR + 结构化 $0 ~1s
扫描件/模糊图 Qwen-VL API $0.02/张 ~2s
敏感图片 本地 Donut GPU 成本 ~3s

💡 建议:对高频文档预处理,实时请求只处理新文件。


7.2 缓存机制

复制代码
# 缓存图像描述和表格 JSON
@lru_cache(maxsize=1000)
def cached_describe_image(image_hash: str):
    return describe_image(get_path_by_hash(image_hash))

八、完整工作流演示

场景 :用户上传一份 扫描版合同 PDF,问:"甲方是谁?签约日期?"

系统执行

  1. 解析 PDF
    • 使用 Unstructured + PaddleOCR 提取全文 + 表格 + 签字区域图像;
  2. 图像理解
    • 对签字页调用 Qwen-VL:"图中甲方签字人是谁?" → "张三";
  3. 文本检索
    • 在合同文本中检索"签约日期" → "2025年10月1日";
  4. 生成答案
    • "甲方:张三;签约日期:2025年10月1日。"

全程自动化,准确率 >88%(实测 100 份合同)


九、避坑指南

问题 解决方案
PDF 表格线断裂 启用 PaddleOCR 的 recovery=True
手写体识别差 优先使用 Qwen-VL 端到端理解
多页表格跨页 后处理合并相邻表格
图像隐私泄露 敏感图片走本地 Donut,不传云端
向量维度不一致 统一用 Qwen-VL 的 1024 维 embedding

十、总结:多模态是 RAG 的终极形态

能力 文本 RAG 多模态 RAG
理解纯文本
提取表格数据
理解图表语义
处理扫描件
跨模态检索

未来方向

  • 视频 RAG:从会议录像中提取决策;
  • 3D 模型理解:工业设计图纸问答;
  • 实时多模态 Agent:边看屏幕边操作软件......
相关推荐
热爱生活的五柒2 小时前
深度聚类(Deep Clustering)与度量学习(Metric Learning)的共同点和不同点
人工智能·算法·机器学习
玖日大大2 小时前
Sora 2 全面指南:从基础使用到进阶开发的 AI 视频创作宝典
人工智能
神一样的老师2 小时前
混合大语言模型与强化学习用于高能效多星调度:从零开始的性能提升
人工智能·深度学习·语言模型
ElfBoard3 小时前
ElfBoard技术贴|如何在【RK3588】ELF 2开发板实现GPIO功能复用
linux·人工智能·单片机·嵌入式硬件·物联网·机器人
SUPER52666 小时前
本地开发环境_spring-ai项目启动异常
java·人工智能·spring
上进小菜猪11 小时前
基于 YOLOv8 的智能车牌定位检测系统设计与实现—从模型训练到 PyQt 可视化落地的完整实战方案
人工智能
AI浩11 小时前
UNIV:红外与可见光模态的统一基础模型
人工智能·深度学习
GitCode官方11 小时前
SGLang AI 金融 π 对(杭州站)回顾:大模型推理的工程实践全景
人工智能·金融·sglang
木头左11 小时前
LSTM模型入参有效性验证基于量化交易策略回测的方法学实践
人工智能·rnn·lstm