大模型应用实战:从0到1教你构建多模态 RAG(图文 + 表格混合检索)系统

Qwen3-VL-30B

Qwen3-VL-30B

图文对话
Qwen3-VL

Qwen3-VL是迄今为止 Qwen 系列中最强大的视觉-语言模型,这一代在各个方面都进行了全面升级:更优秀的文本理解和生成、更深入的视觉感知和推理、扩展的上下文长度、增强的空间和视频动态理解能力,以及更强的代理交互能力

大模型应用实战:构建多模态 RAG(图文 + 表格混合检索)系统

你好,欢迎来到我的技术博客!

你是否曾对那个只能“读”懂文字,却看不见图片、理不清表格的 RAG (检索增强生成) 系统感到一丝失望?当你的用户在产品手册的智能问答系统中输入“咖啡机显示 E22 故障码,我该怎么办?”时,你多么希望系统不仅能告诉他“这是传感器故障”,更能直接甩出说明书里那张关键的“传感器位置与维修示意图”。

如果这说中了你的心声,那么恭喜你,来对地方了。

本篇教程,将是这个问题的终结者。我们将彻底告别“纯文本”的束缚,从一个空白的文件夹开始,逐行代码、每个命令地去构建一个能够理解图像、解析表格,并进行智能图文混合检索的多模态 RAG 系统。我将像一个向导,牵着你的手,解释每一步的“为什么”与“怎么做”。

学习完本教程,你将收获的不仅仅是一个酷炫的、能运行的项目,更是一套处理多模态数据、设计跨模态检索策略的核心思想与实战技巧。准备好了吗?让我们一起,让你的 RAG 系统“开眼看世界”。

一、为什么是多模态 RAG?—— 我们要解决的真实问题

让我们先抛开复杂的术语,聚焦于一个真实的、触手可及的场景。

想象一下,我们手里有一份《智能咖啡机产品手册》的电子版(Markdown 格式),内容包含:

  • 普通描述性文本: “本款咖啡机采用最新的 Aroma+ 技术…”
  • 故障代码表格:
    故障代码故障描述解决方案
    E21水箱缺水请为水箱加水
    E22传感器通讯故障检查传感器连接线,参考图 3-1
  • 维修示意图: 一张名为 sensor_diagram.jpg 的图片,清晰地展示了传感器的位置(即图 3-1)。

传统 RAG 系统的窘境

如果我们用传统的 RAG 方法来处理这份手册,系统会将所有文本内容(包括表格里的文字)切碎、向量化后存入数据库。当用户提问:“显示 E22 错误怎么办?

系统会检索到与“E22”最相关的文本块,也就是表格中的那一行。它可能会返回:“E22, 传感器通讯故障, 检查传感器连接线,参考图 3-1”。

这个结果不能说错,但绝对不够好。用户看到了“参考图 3-1”,但他去哪里找这张图呢?他不得不再次手动翻阅手册,体验大打折扣。问题的根源在于,传统 RAG 是一个“睁眼瞎”,它处理了图像的描述文本,却不理解图像本身的内容

多模态 RAG 的威力

而我们今天将要构建的多模态 RAG 系统,则完全不同。当面对同样的问题时,它将给出如下的理想答复:

查询: 显示 E22 错误怎么办?

系统返回:

  • 相关文本: 根据手册,E22 错误表示 传感器通讯故障
  • 解决方案: 您需要 检查传感器连接线
  • 相关图示:

看到了吗?系统不仅理解了文本,还“看”懂了用户需要的图片,并将它与相关的文本信息一起推送出来。这才是真正智能、高效的信息服务。我们的核心目标,就是实现这种文本、图像、表格信息的协同检索与呈现。

二、项目整体架构与技术选型

在动手之前,我们先从宏观上理解一下整个系统的运作流程。一个清晰的架构图是最好的导航。

查询与检索
数据处理与存储
查询处理
用户查询: 'E22 故障'
文本嵌入模型 m3e-base
查询向量 (768维)
FAISS 相似度检索
返回图文混合结果
Streamlit 前端展示
数据预处理
数据源: coffee_manual.md
文本块
表格行
图像路径
文本嵌入模型 m3e-base
图像特征模型 CLIP
文本/表格向量 (768维)
图像向量 (768维)
向量数据库 FAISS

架构解读:

  1. 数据处理阶段(左侧): 我们首先解析输入的 Markdown 手册,将其分解为三种基本信息单元:普通文本块、表格的每一行,以及图片。
  2. 特征提取与存储: 我们使用两种不同的模型将这些信息“翻译”成机器能理解的数学语言——向量。
    • 文本和表格行,使用文本嵌入模型(如 m3e-base)转换成向量。
    • 图像,则使用专门的图像特征模型(CLIP)转换成向量。
    • 关键在于,我们选择的模型能确保所有向量都是同一维度(例如 768 维),这样它们才能在同一个“语义空间”中进行比较。
    • 最后,我们将原始信息和它们对应的向量一同存入 FAISS 向量数据库。
  3. 查询检索阶段(右侧):
    • 当用户输入查询时,我们用相同的文本嵌入模型将查询也转换为一个向量。
    • 然后,用这个查询向量去 FAISS 数据库中寻找“距离”最近、也就是语义最相似的向量。
    • 由于数据库中同时存有图、文、表的向量,这次检索自然就能找到所有相关模态的信息。
    • 最后,我们将检索到的原始信息(文本、图片路径等)在前端界面上进行渲染和展示。

技术栈解读(为什么选它?):

  • 核心框架:LangChain: 它是构建 LLM 应用的“瑞士军刀”,提供了大量开箱即用的模块,如文档加载器、切分器、向量存储接口等,能极大简化我们的开发流程。
  • 图像理解:CLIP (Contrastive Language–Image Pre-training): 这是 OpenAI 的一个经典模型,它的核心能力就是将文本和图像映射到同一个高维语义空间。简单来说,在它的世界里,“一只正在奔跑的狗”的文本向量,会和一张真实的小狗奔跑照片的图像向量非常接近。这正是我们实现跨模态检索的基石。
  • 向量数据库:FAISS: 由 Facebook AI 开发,是一个极其高效的相似性搜索库。它非常轻量,无需部署复杂的数据库服务,可以直接在本地文件系统上运行,对于入门学习和中小型项目来说是完美的选择。
  • 前端交互:Streamlit: 如果你厌倦了复杂的前端技术,那么一定会爱上 Streamlit。它允许你只用 Python 代码,就能像写脚本一样快速构建出一个漂亮、可交互的 Web 应用,用于展示我们的 RAG 系统再合适不过。

三、手把手实战:从零到一构建系统

理论讲完,让我们卷起袖子,开始敲代码!

步骤〇:环境准备与项目初始化

一个干净、独立的项目环境是后续一切顺利进行的前提。

  1. 创建项目文件夹:打开你的终端(Terminal 或 PowerShell),执行以下命令。

    # 创建一个名为 multimodal-rag 的文件夹
    mkdir multimodal-rag
    # 进入这个文件夹
    cd multimodal-rag
    

    为什么? 为我们的项目创建一个专属的“家”,所有相关文件都将存放在这里,保持整洁。

  2. 创建并激活 Python 虚拟环境

    # 使用 Python 内置的 venv 模块创建一个名为 .venv 的虚拟环境
    python -m venv .venv
    
    • 在 macOS 或 Linux 上激活:
      source .venv/bin/activate
      
    • 在 Windows 上激活:
      .venv\Scripts\activate
      

    激活成功后,你会看到终端提示符前面多了 (.venv) 的字样。
    为什么? 虚拟环境可以隔离不同项目所需的依赖库,避免版本冲突。比如 A 项目需要 requests 1.0 版本,B 项目需要 2.0 版本,虚拟环境能让它们和平共处。

  3. 安装核心依赖
    创建一个名为 requirements.txt 的文件,并填入以下内容:

    # requirements.txt
    
    langchain
    langchain-community
    langchain-core
    # 用于图像处理
    Pillow
    # 图像特征提取模型
    torch
    torchvision
    transformers
    # 文本嵌入模型
    sentence-transformers
    # 向量数据库
    faiss-cpu
    # 前端界面
    streamlit
    # Markdown表格解析
    unstructured
    

    然后执行安装命令:

    pip install -r requirements.txt
    

    为什么? requirements.txt 文件是项目的“依赖清单”。pip install -r 命令会读取这个清单,并自动下载安装所有需要的库,方便项目的分享和复现。

步骤一:多模态数据处理 —— 让机器读懂图与表
  1. 1.1 数据准备
    在项目根目录 multimodal-rag/下,创建一个名为 data 的文件夹。

    mkdir data
    

    data 文件夹中,放入一张你找到的关于电路板或机器内部的图片,并重命名为 sensor_diagram.jpg
    然后,在 data 文件夹中创建一个名为 coffee_manual.md 的文件,内容如下:

    # 智能咖啡机 Pro 使用手册
    
    ## 第一章:产品介绍
    
    本款咖啡机采用最新的 Aroma+ 技术,能够实现毫秒级的温度控制和研磨精度调节,为您带来大师级的咖啡体验。
    
    ## 第二章:故障排查
    
    当您遇到问题时,请参考下表进行故障排查。
    
    | 故障代码 | 故障描述 | 解决方案 |
    | :--- | :--- | :--- |
    | E21 | 水箱缺水 | 请为水箱加水至最低刻度线以上。 |
    | E22 | 传感器通讯故障 | 检查位于设备背部的传感器连接线是否松动。具体位置请参考图 3-1。 |
    | E45 | 加热模块异常 | 请联系售后服务进行检修。 |
    
    **图 3-1:传感器连接线位置示意图**
    ![传感器示意图](./sensor_diagram.jpg)
    
    ## 第三章:日常维护
    
    为了保证咖啡机的最佳性能,请每周进行一次内部清洁。
    
  2. 1.2 多模态数据解析与切分
    现在,我们需要编写一个脚本来读取这个 Markdown 文件,并智能地将其拆分成文本、表格和图像三种类型。LangChain 的文档加载器和切分器可以帮助我们。

    在项目根目录创建一个名为 data_processing.py 的文件,并写入以下代码:

    # data_processing.py
    
    import os
    from typing import Any, Dict, List, Optional
    from langchain_core.documents import Document
    from langchain.document_loaders import UnstructuredMarkdownLoader
    from langchain.text_splitter import MarkdownHeaderTextSplitter
    
    def split_markdown_documents(file_path: str) -> List[Document]:
        """
        加载并切分 Markdown 文件,智能区分文本、表格和图像。
        
        Args:
            file_path: Markdown 文件的路径。
        
        Returns:
            一个包含 Document 对象的列表,每个对象代表一个数据块。
        """
        # 使用 Unstructured 加载器,它能够较好地处理复杂的 Markdown 结构
        loader = UnstructuredMarkdownLoader(file_path, mode="elements")
        docs = loader.load()
        
        processed_docs = []
        for element in docs:
            if element.metadata.get('category') == 'Table':
                # 如果是表格,将其内容(通常是 HTML 格式)和一些元数据存起来
                # 这里我们简化处理,直接用表格的文本表示,并添加类型元数据
                table_text = element.page_content
                processed_docs.append(Document(
                    page_content=table_text,
                    metadata={'type': 'table', 'source': file_path}
                ))
            elif 'image' in element.metadata.get('filename', ''):
                 # Unstructured 通常会将图片解析为一个特殊的 element
                 # 这里我们简化逻辑,假设 Markdown 中的图片链接是我们的处理目标
                 # 一个更鲁棒的方法是正则匹配 markdown 图片语法
                 pass # 我们将在主流程中用正则直接提取图片
            else:
                 # 对于普通文本,可以进一步切分
                 headers_to_split_on = [
                     ("#", "Header 1"),
                     ("##", "Header 2"),
                 ]
                 text_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
                 splits = text_splitter.split_text(element.page_content)
                 for split in splits:
                     split.metadata['type'] = 'text'
                     split.metadata['source'] = file_path
                     processed_docs.append(split)
    
        # 单独用正则表达式提取图片链接
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        import re
        # 匹配 Markdown 图片语法: ![alt_text](path)
        image_matches = re.findall(r"!\[(.*?)\]\((.*?)\)", content)
        for alt_text, path in image_matches:
            # 确保是本地文件路径
            if not path.startswith('http'):
                # 构建图片的完整路径
                full_path = os.path.join(os.path.dirname(file_path), path)
                processed_docs.append(Document(
                    page_content=f"图片描述: {alt_text}", # 使用 alt text 作为内容
                    metadata={'type': 'image', 'path': full_path, 'source': file_path}
                ))
    
        return processed_docs
    
    if __name__ == '__main__':
        # 用于测试
        file_path = './data/coffee_manual.md'
        documents = split_markdown_documents(file_path)
        
        for doc in documents:
            print(f"--- 类型: {doc.metadata['type']} ---")
            print(doc.page_content)
            if 'path' in doc.metadata:
                print(f"路径: {doc.metadata['path']}")
            print("\n")
    
    

    代码解释

    • 我们使用了 UnstructuredMarkdownLoader,它比普通的文本加载器更强大,能识别出 Markdown 中的表格等元素。
    • 对于表格元素,我们将其内容单独创建一个 Document,并打上 'type': 'table' 的元数据标签。
    • 对于普通文本,我们使用 MarkdownHeaderTextSplitter 按标题进行切分,使得每个文本块的上下文更完整。
    • 关键:我们额外使用正则表达式 !\[(.*?)\]\((.*?)\) 来精确地捕获 Markdown 文件中所有的图片链接。对于每个匹配到的图片,我们创建一个 Document,内容可以是图片的描述文字,同时在元数据中保存它的 'type': 'image' 和真实的文件路径 'path'。这是后续处理图像的关键。
    • 运行 python data_processing.py,你可以看到手册被清晰地拆分为了文本、表格和图片信息块。
步骤二:特征提取与向量存储 —— 建立统一的“语义地图”

现在,我们要将这些处理好的数据块,全部转换成向量。

在项目根目录下创建 vector_store.py 文件:

# vector_store.py

import torch
from PIL import Image
from transformers import CLIPProcessor, CLIPModel
from sentence_transformers import SentenceTransformer
from data_processing import split_markdown_documents
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.schema import Document
import numpy as np
import os

# --- 全局模型加载 ---
# 选择一个计算设备
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {DEVICE}")

# 1. 加载文本嵌入模型 (m3e-base)
# 这个模型会输出 768 维的向量,与 CLIP 的图像特征维度一致
print("Loading text embedding model...")
text_embed_model = HuggingFaceEmbeddings(model_name='moka-ai/m3e-base', model_kwargs={'device': DEVICE})

# 2. 加载图像特征提取模型 (CLIP)
print("Loading image feature model (CLIP)...")
clip_model_name = "openai/clip-vit-base-patch32"
clip_model = CLIPModel.from_pretrained(clip_model_name).to(DEVICE)
clip_processor = CLIPProcessor.from_pretrained(clip_model_name)
print("Models loaded successfully.")


def get_image_embedding(image_path: str) -> List[float]:
    """使用 CLIP 模型为图片生成特征向量"""
    try:
        image = Image.open(image_path).convert("RGB")
        inputs = clip_processor(images=image, return_tensors="pt").to(DEVICE)
        with torch.no_grad():
            image_features = clip_model.get_image_features(**inputs)
        return image_features.cpu().numpy().flatten().tolist()
    except Exception as e:
        print(f"Error processing image {image_path}: {e}")
        return None

def build_vector_store(docs: List[Document], db_path: str = "faiss_index"):
    """构建并保存向量数据库"""
    if os.path.exists(db_path):
        print(f"Vector store at {db_path} already exists. Skipping build.")
        return FAISS.load_local(db_path, text_embed_model, allow_dangerous_deserialization=True)

    texts = []
    text_embeddings = []
    image_embeddings = []
    metadata = []
    
    print("Processing documents and generating embeddings...")
    for doc in docs:
        doc_type = doc.metadata.get('type', 'text')
        
        if doc_type == 'text' or doc_type == 'table':
            texts.append(doc.page_content)
            metadata.append(doc.metadata)
        elif doc_type == 'image':
            image_path = doc.metadata.get('path')
            if image_path and os.path.exists(image_path):
                img_embedding = get_image_embedding(image_path)
                if img_embedding:
                    # 我们需要一个"占位"文本来与图像向量关联
                    # 这里我们使用图片的描述文字
                    texts.append(doc.page_content) 
                    # 将图像向量伪装成文本向量存入,实现混合检索
                    image_embeddings.append(img_embedding)
                    metadata.append(doc.metadata)

    # 批量生成文本/表格的嵌入
    if texts:
        text_embeddings = text_embed_model.embed_documents(texts)

    # 将文本向量和图像向量合并
    # 注意:这里的实现是一个简化,LangChain 的 MultiVectorRetriever 提供了更优雅的方案
    # 但为了教学目的,我们手动合并,更容易理解
    all_embeddings = []
    final_texts = []
    final_metadata = []

    # 添加文本和表格
    for i in range(len(text_embeddings)):
        # 确保这个文本不是图像的占位符
        if metadata[i]['type'] != 'image':
             all_embeddings.append(text_embeddings[i])
             final_texts.append(texts[i])
             final_metadata.append(metadata[i])
    
    # 添加图像
    image_index_start = len(text_embeddings) - len(image_embeddings)
    for i in range(len(image_embeddings)):
        all_embeddings.append(image_embeddings[i])
        final_texts.append(texts[image_index_start + i]) # 图像的占位文本
        final_metadata.append(metadata[image_index_start + i]) # 图像的元数据


    # 创建 FAISS 向量数据库
    print("Creating FAISS vector store...")
    # FAISS 需要将 embedding 转换为 numpy 数组
    embedding_array = np.array(all_embeddings, dtype=np.float32)

    # 使用 LangChain 的 FAISS 类从文本和嵌入构建数据库
    # text_embedding_pairs = list(zip(final_texts, all_embeddings))
    vector_store = FAISS.from_embeddings(text_embeddings=list(zip(final_texts, all_embeddings)), embedding=text_embed_model, metadatas=final_metadata)

    print(f"Saving FAISS index to {db_path}...")
    vector_store.save_local(db_path)
    print("Vector store built and saved successfully.")
    return vector_store


if __name__ == '__main__':
    # 整个流程串起来
    file_path = './data/coffee_manual.md'
    
    # 1. 切分文档
    print("Step 1: Splitting documents...")
    documents = split_markdown_documents(file_path)
    
    # 2. 构建向量数据库
    print("Step 2: Building vector store...")
    build_vector_store(documents)

代码解释:

  • 模型加载:我们在脚本开头就加载了 m3e-base 文本模型和 CLIP 图文模型。这是一个好习惯,避免在函数调用时重复加载,提高效率。我们特意选择了 m3e-base,因为它输出的 768 维向量恰好和 CLIP 模型输出的图像特征向量维度相同,省去了复杂的维度对齐步骤。
  • get_image_embedding 函数:这个函数是图像处理的核心。它接收一个图片路径,使用 Pillow 库打开图片,然后通过 CLIP 模型和处理器,将图片转换成一个 768 维的向量(一个浮点数列表)。
  • build_vector_store 函数
    • 它遍历所有 Document 块。
    • 如果是文本或表格,就将其内容加入一个待处理列表。
    • 如果是图片,就调用 get_image_embedding 函数生成图像向量。
    • 核心技巧:我们用一个 texts 列表存储所有文本内容(包括图片的描述文字),另一个 image_embeddings 列表专门存储图像向量。
    • 然后,我们一次性调用 text_embed_model.embed_documents() 为所有文本和表格生成向量,这比单条生成效率高得多。
    • 最后,我们将文本向量和图像向量合并在一起,连同它们对应的原始文本和元数据,一起喂给 FAISS.from_embeddings(),创建出我们的多模态向量数据库,并保存到本地。

现在,运行这个脚本:python vector_store.py。它会下载所需的模型(首次运行会比较慢),然后处理数据,最终在项目根目录下生成一个名为 faiss_index 的文件夹,这就是我们宝贵的知识库!

步骤三与四:检索逻辑、结果融合与前端展示

万事俱备,只欠东风。最后一步,我们来搭建一个可以交互的 Web 界面,并实现最终的检索逻辑。

在项目根目录创建 app.py 文件:

# app.py

import streamlit as st
from langchain.vectorstores import FAISS
from vector_store import text_embed_model # 导入我们已经加载的文本嵌入模型
import os

# --- 全局加载向量数据库 ---
DB_PATH = "faiss_index"

@st.cache_resource
def load_vector_store():
    """加载本地的 FAISS 向量数据库"""
    if not os.path.exists(DB_PATH):
        st.error("错误:向量数据库 'faiss_index' 未找到。请先运行 vector_store.py 来构建数据库。")
        return None
    # 加载时需要指定所用的 embedding model
    # 同时允许"不安全"的反序列化,这是 FAISS 加载本地文件所必需的
    return FAISS.load_local(DB_PATH, text_embed_model, allow_dangerous_deserialization=True)

db = load_vector_store()

# --- Streamlit 界面设计 ---
st.set_page_config(page_title="多模态咖啡机手册 RAG", layout="wide")
st.title("智能咖啡机产品手册 - 多模态检索系统")

# 初始化 session state
if "messages" not in st.session_state:
    st.session_state.messages = []

# 显示历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        # 检查消息内容是文本还是图片路径
        if message["type"] == "text":
            st.markdown(message["content"])
        elif message["type"] == "image":
            st.image(message["content"])


# 获取用户输入
query = st.chat_input("请输入您的问题 (例如: E22 错误是什么?)")

if query and db:
    # 显示用户问题
    st.chat_message("user").markdown(query)
    st.session_state.messages.append({"role": "user", "type": "text", "content": query})

    # --- 核心检索逻辑 ---
    # k=3 表示返回最相关的3个结果
    results = db.similarity_search(query, k=3)

    # --- 结果展示 ---
    with st.chat_message("assistant"):
        st.markdown("根据您的手册,我找到了以下相关信息:")
        
        # 用于防止重复显示相同内容
        displayed_content = set()

        for doc in results:
            content = doc.page_content
            if content in displayed_content:
                continue
            displayed_content.add(content)
            
            doc_type = doc.metadata.get('type', 'text')

            if doc_type == 'image':
                image_path = doc.metadata.get('path')
                st.markdown("---")
                st.markdown(f"**相关图示:** {content}") # 显示图片描述
                if os.path.exists(image_path):
                    st.image(image_path)
                    # 将图片添加到 session state
                    st.session_state.messages.append({"role": "assistant", "type": "image", "content": image_path})
                else:
                    st.warning(f"图片文件未找到: {image_path}")
            
            elif doc_type == 'table':
                st.markdown("---")
                st.markdown("**相关表格信息:**")
                st.markdown(content) # Streamlit 可以很好地渲染 Markdown 表格
                st.session_state.messages.append({"role": "assistant", "type": "text", "content": content})

            else: # 默认为文本
                st.markdown("---")
                st.markdown("**相关文本描述:**")
                st.markdown(content)
                st.session_state.messages.append({"role": "assistant", "type": "text", "content": content})

代码解释:

  • @st.cache_resource:这是一个 Streamlit 的装饰器,它的作用是缓存加载函数的返回结果。这意味着向量数据库 db 在整个应用生命周期中只会被加载一次,即使用户多次查询,也不会重复执行加载操作,极大地提高了性能。
  • 界面布局:我们使用了 st.title 创建标题,st.chat_input 创建一个位于页面底部的聊天输入框,并用 st.session_state 来保存聊天记录,实现了简单的多轮对话界面。
  • 核心检索逻辑:只有一行关键代码 db.similarity_search(query, k=3)。它接收用户的查询字符串,自动将其向量化,然后在 FAISS 数据库中执行相似度搜索,返回最相关的 3 个 Document 对象。
  • 智能结果展示:我们遍历返回的 results,检查每个 Document 的元数据 metadata['type']
    • 如果是 'image',我们就从元数据中取出路径 metadata['path'],使用 st.image() 将图片直接显示出来。
    • 如果是 'table''text',我们就用 st.markdown() 来展示内容。Streamlit 对 Markdown 的支持非常好,可以自动将表格文本渲染成漂亮的格式。

运行你的应用!

在终端中,确保你的虚拟环境已激活,然后运行:

streamlit run app.py

你的浏览器会自动打开一个新页面,展示出我们的应用界面。现在,尝试输入那个经典问题:“E22 错误是什么意思,怎么修?

四、落地场景演示与效果对比

见证奇迹的时刻到了!

传统 RAG 的效果(想象图):

(这是一个模拟图,展示了纯文本输出的局限性)

我们的多模态 RAG 效果(真实截图):

(这是一个真实的应用截图,展示了图文并茂的理想输出)

效果分析:
对比一目了然。传统 RAG 只能给出干巴巴的文字线索,用户获取信息的链路是中断的。而我们的多模态 RAG 系统,真正理解了查询的深层意图,将解决问题所需的所有信息——故障描述文本、表格化的解决方案、以及最关键的视觉图示——一次性、完整地呈现在用户面前,实现了信息服务的闭环。

五、总结与展望:你的多模态之旅才刚刚开始

恭喜你!从一个空白文件夹到功能完善的多模态 RAG 应用,你已经亲手走完了全程。我们一起学习了如何解析图文表混合的 Markdown 文档,如何使用 CLIP 和文本嵌入模型为不同模态的数据生成统一的语义向量,如何构建和查询 FAISS 向量数据库,并最终用 Streamlit 搭建了一个漂亮的前端界面。

这不仅仅是一个玩具项目,它背后的思想可以被应用到各种真实的场景中,比如:

  • 企业内部知识库: 检索包含流程图、架构图和配置表格的技术文档。
  • 电商智能客服: 当用户问“这款衣服的蓝色是什么样的?”,可以直接返回商品图片。
  • 医疗辅助诊断: 根据病症描述,检索相关的文本病例和影像图片。

当然,我们的系统还有很大的优化空间,这也是你下一步可以探索的方向:

  • 优化检索质量: 引入一个重排(Re-ranking)模型,对初步检索出的结果进行二次排序,将最精准的答案顶到最前面。
  • 扩展更多模态: 尝试加入音频或视频片段的检索能力。
  • 处理复杂文档: 目前我们处理的是结构清晰的 Markdown,你可以挑战一下如何从 PDF 或 Word 文档中精准地提取图、文、表信息。

您可能感兴趣的与本文相关的镜像

Qwen3-VL-30B

Qwen3-VL-30B

图文对话
Qwen3-VL

Qwen3-VL是迄今为止 Qwen 系列中最强大的视觉-语言模型,这一代在各个方面都进行了全面升级:更优秀的文本理解和生成、更深入的视觉感知和推理、扩展的上下文长度、增强的空间和视频动态理解能力,以及更强的代理交互能力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

THMAIL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值