LLM Engineering2026年5月21日

03 RAG多模态数据处理


Gemini 多模态处理

Gemini 作为一种强大的多模态模型,具备处理和生成文本、图像、音频、视频、PDF 及代码等多种形式数据的能力。其核心优势在于:

  • 原生统一架构: 从预训练阶段就将所有模态置于同一表征空间进行学习,而非后期外挂模块,从而减少信息损耗,更完整地保留时序和细节信息。
  • 端到端推理: 采用同一组 Transformer 参数直接处理任意组合的输入,例如同时处理 CT 影像和病历文本,或将手写食谱视频直接转化为数字菜谱。
  • 上下文规模: Gemini 3 Pro 支持高达 100 万 token 的长窗口,能够一次性处理 1 小时视频或 700 页 PDF,并输出结构化报告。
  • 生成能力: 不仅能理解和分析,还能进行多模态生成,如文本生成图像、图像编辑、音频流式生成等。

Gemini API 使用示例

Step1,申请Gemini API KEY

访问 https://aistudio.google.com/app/api-keys 获取 API Key。

Step2,添加环境变量

设置 GEMINI_API_KEYGOOGLE_API_KEY

文本输出

Python
from google import genai client = genai.Client() # 文字输出 response = client.models.generate_content( model="gemini-3-flash-preview", contents="用中文解释AI大模型是如何工作的", ) print(response.text)

图像理解

Python
from PIL import Image from google import genai # Assuming genai is imported client = genai.Client() image = Image.open("dog_and_girl.jpeg") # 注意:contents 变成了一个列表,里面同时放了图片对象和文字 response = client.models.generate_content( model="gemini-3-flash-preview", contents=[image, "帮我解释下这张照片"] ) print(response.text)

视频理解

Python
import time from google import genai # Assuming genai is imported client = genai.Client() # 1. 上传视频文件 print("正在上传视频...") video_file = client.files.upload(file="car.mp4") # 汽车剐蹭视频 print(f"上传成功: {video_file.name}") # 2. 等待视频处理(关键步骤!) # 视频上传后,Google 需要几秒钟在云端进行转码。 while video_file.state.name == "PROCESSING": print("视频处理中,请稍候...") time.sleep(2) video_file = client.files.get(name=video_file.name) if video_file.state.name == "FAILED": raise ValueError("视频处理失败") print("视频就绪,开始推理...") # 3. 多模态推理 response = client.models.generate_content( model="gemini-3-flash-preview", contents=[ video_file, "详细描述视频里发生了什么?如果有对话,请把关键对话提取出来。" ] ) print(response.text)

CASE:迪士尼 RAG 助手

本案例旨在为迪士尼构建一个 7x24 小时在线的 AI 客服助手,实现自动化解答高频问题、提供准确信息并处理多模态查询。

挑战

  • 知识来源多样化: 知识库包含 PDF (官方规定)、Word (内部 FAQ)、网页公告、以及带有图片和表格的活动介绍文件。
  • 非结构化数据处理: 如何有效提取并理解 PDF 和 Word 文档中的表格与图片信息是 RAG 成功的关键。
  • 知识的有效组织: 如何将海量、零散的知识点进行切片 (Chunking) 并建立索引,确保检索的准确性。
  • 保证答案的有效性: 如何让最终生成的答案严格基于检索到的内容,避免 LLM 出现幻觉。

技术选型 (方案2 - 采用 Multimodal-Embedding)

  • Embedding 模型: Multimodal-Embedding (如阿里云通义 tongyi-embedding-vision-plus),统一处理文本、图像、视频。
  • 向量数据库/库: FAISS (用于高性能向量检索)。在生产环境中可考虑 Milvus, ChromaDB, Elasticsearch 等。
  • LLM: Qwen-flash (用于最终答案的生成)。
  • 流程编排: 直接使用底层 API (不依赖 LangChain 等框架)。

Multimodal-Embedding 使用

Multimodal-Embedding 模型将文本、图像、视频等不同模态的数据转换成统一的向量空间中的浮点数向量(例如 tongyi-embedding-vision-plus 生成 1152 维向量)。这使得在同一语义空间中进行跨模态检索和相似度计算成为可能。

Multimodal-Embedding 模型对比

模型名称 | 向量维度 | 文本长度限制 | 图片限制 | 视频片限制 | 单价(每千输入Token)

qwen2.5-vl-embedding | 2048, 1024, 768, 512 | 32,000 Token | ≤5MB,1张 | ≤50MB | 图片/视频:0.0018元 文本:0.0007元

tongyi-embedding-vision-plus | 1,152 | 1,024 Token | ≤3MB,≤8张 | ≤10MB | 0.0005元

tongyi-embedding-vision-flash | 768 | 1,024 Token | ≤3MB,≤8张 | ≤10MB | 0.00015元

multimodal-embedding-v1 | 1,024 | 512 Token | ≤3MB,1张 | ≤10MB | 图片/视频:0.0009元 文本:0.0007元

文本 Embedding 示例

Python
import dashscope import json from http import HTTPStatus text = "上海迪士尼乐园门票分为一日票、两日票和特定日票三种类型。一日票可在购买时选定日期使用,价格根据季节浮动,平日成人票475元起" input = [{'text': text}] resp = dashscope.MultiModalEmbedding.call( model="tongyi-embedding-vision-plus", input=input ) if resp.status_code == HTTPStatus.OK: result = { "status_code": resp.status_code, "request_id": getattr(resp, "request_id", ""), "code": getattr(resp, "code", ""), "message": getattr(resp, "message", ""), "output": resp.output, "usage": resp.usage } print(json.dumps(result, ensure_ascii=False, indent=4))

图片 Embedding 示例

Python
import dashscope import base64 import json from http import HTTPStatus image_path = "./disney_knowledge_base/images/1-聚在一起说奇妙.jpg" with open(image_path, "rb") as image_file: base64_image = base64.b64encode(image_file.read()).decode('utf-8') image_format = "jpg" image_data = f"data:image/{image_format};base64,{base64_image}" input = [{'image': image_data}] resp = dashscope.MultiModalEmbedding.call( model="tongyi-embedding-vision-plus", input=input ) if resp.status_code == HTTPStatus.OK: result = { "status_code": resp.status_code, "request_id": getattr(resp, "request_id", ""), "code": getattr(resp, "code", ""), "message": getattr(resp, "message", ""), "output": resp.output, "usage": resp.usage } print(json.dumps(result, ensure_ascii=False, indent=4))

视频 Embedding 示例

Python
import dashscope import json from http import HTTPStatus # 多模态向量化模型目前仅支持以URL形式输入视频文件,暂不支持直接传入本地视频。 video = "https://dataset-1255932437.cos.ap-nanjing.myqcloud.com/mp4/car.mp4" input = [{'video': video}] resp = dashscope.MultiModalEmbedding.call( model="tongyi-embedding-vision-plus", input=input ) if resp.status_code == HTTPStatus.OK: result = { "status_code": resp.status_code, "request_id": getattr(resp, "request_id", ""), "code": getattr(resp, "code", ""), "message": getattr(resp, "message", ""), "output": resp.output, "usage": resp.usage } print(json.dumps(result, ensure_ascii=False, indent=4))

格式处理相关

处理 .docx 文件

parse_docx 函数使用 python-docx 库读取 .docx 文件,遍历其所有元素(段落和表格),并提取成独立的内容块。

  • 段落处理: 提取纯文本内容,去除空白,标记为 "type": "text"
  • 表格处理: 将 Word 表格转换为 Markdown 格式,标记为 "type": "table"
Python
# disney_bot.py from docx import Document as DocxDocument import os def parse_docx(file_path): doc = DocxDocument(file_path) content_chunks = [] for element in doc.element.body: if element.tag.endswith('p'): # 段落处理 paragraph_text = "" for run in element.findall('.//w:t', {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}): paragraph_text += run.text if run.text else "" if paragraph_text.strip(): content_chunks.append({"type": "text", "content": paragraph_text.strip()}) elif element.tag.endswith('tbl'): # 表格处理 # 转换为Markdown格式 md_table = [] table = [t for t in doc.tables if t._element is element][0] if table.rows: header = [cell.text.strip() for cell in table.rows[0].cells] md_table.append("| " + " | ".join(header) + " |") md_table.append("|" + "---|"*len(header)) for row in table.rows[1:]: row_data = [cell.text.strip() for cell in row.cells] md_table.append("| " + " | ".join(row_data) + " |") table_content = "\n".join(md_table) if table_content.strip(): content_chunks.append({"type": "table", "content": table_content}) return content_chunks

处理 .pdf 文件

parse_pdf 函数使用 fitz (PyMuPDF) 库打开并逐页读取 PDF 文档,将其拆解成纯文本和独立的图片文件。

  • 文本提取: 使用 page.get_text("text") 抓取每页纯文本内容,保存为独立区块并附上页码。
  • 图片提取: 侦测并提取页面中的所有嵌入图片,保存到指定 image_dir 目录下,并记录图片路径。
Python
# disney_bot.py import fitz # PyMuPDF import os def parse_pdf(file_path, image_dir): doc = fitz.open(file_path) content_chunks = [] for page_num, page in enumerate(doc): # 提取文本 text = page.get_text("text") content_chunks.append({"type": "text", "content": text, "page": page_num + 1}) # 提取图片 for img_index, img in enumerate(page.get_images(full=True)): xref = img[0] base_image = doc.extract_image(xref) image_bytes = base_image["image"] image_ext = base_image["ext"] image_path = os.path.join(image_dir, f"{os.path.basename(file_path)}_p{page_num+1}_{img_index}.{image_ext}") with open(image_path, "wb") as f: f.write(image_bytes) content_chunks.append({"type": "image", "path": image_path, "page": page_num + 1}) return content_chunks

Faiss 索引构架 (4-disney_build_index.py)

  1. 解析文档: 使用 parse_docx() 等函数处理 Word 文档,提取文本段落和表格。

  2. 文本切分: 使用 split_text() 函数,按固定长度(例如 chunk_size=500 字符,overlap=50 字符重叠)切分文本。

  3. 多模态 Embedding:

    • 使用 tongyi-embedding-vision-plus 模型统一处理文本、图片和视频。

    • 文本直接编码。

    • 图片 Base64 编码后发送。

    • 视频进行多帧提取后取平均向量。

  4. 构建 FAISS 索引: 采用 IndexFlatL2 (L2 距离,欧氏距离) 进行精确搜索索引。

  5. 持久化存储: 将 FAISS 索引保存为 .faiss 文件 (disney_index.faiss),元数据保存为 JSON 文件 (disney_metadata.json)。

Query 查询处理 (5-disney_query.py)

  1. 加载索引: 从文件加载 FAISS 索引和元数据 JSON。

  2. Query Embedding: 将用户问题 (query) 通过 Multimodal-Embedding 模型转换为向量,使其与索引中的向量处于同一空间。

  3. 相似度检索: 检索所有记录,按 L2 距离排序,并转换为相似度 (sim = 1/(1+distance))。

  4. 媒体意图检测: 通过关键词匹配(如“图片”、“海报”、“视频”等)判断用户是否需要媒体内容。

  5. 结果筛选:

    • 文本: 无条件选取 Top-K (默认 k=3) 相似结果。

    • 图片/视频: 仅当检测到媒体意图且距离小于阈值(例如 3.0)时才采纳最近匹配的媒体。

  6. LLM 生成: 构建 Prompt (结合背景知识和用户问题),调用 qwen-flash 生成答案。

Metadata 元数据格式示例

JSON
// 文本类型 { "id": 0, "source": "退票政策.docx", "type": "text", "content": "退票内容..." } // 图片类型 { "id": 10, "source": "图片: poster.jpg", "type": "image", "path": "images/poster.jpg", "content": "[图片] poster.jpg" } // 视频类型 { "id": 15, "source": "视频: 汽车剐蹭", "type": "video", "url": "https://...", "description": "汽车剐蹭视频" }

关键参数配置

Python
# 切分参数 CHUNK_SIZE = 500 CHUNK_OVERLAP = 50 # 媒体匹配阈值 MEDIA_DISTANCE_THRESHOLD = 3.0 # 关键词检测 IMAGE_KEYWORDS = ["图片", "海报", "照片", "看看", "长什么样"] VIDEO_KEYWORDS = ["视频", "录像", "播放"] # 模型配置 EMBEDDING_MODEL = "tongyi-embedding-vision-plus" LLM_MODEL = "qwen-flash"

多模态统一向量空间与检索策略

文本、图片、视频通过同一个 Embedding 模型映射到统一的向量空间,从而实现跨模态语义检索。

检索策略:统一索引 + 后筛选

  1. 统一检索: 一次查询返回所有模态类型的结果。

  2. 意图检测: 通过关键词判断用户是否需要媒体内容。

  3. 分类筛选:

    • 文本: 无条件选取 Top-K 结果。

    • 图片/视频: 仅当检测到媒体意图且距离小于 MEDIA_DISTANCE_THRESHOLD (如 3.0) 时才采纳。

  4. LLM 生成: 文本结果无条件进入 Prompt,媒体作为附件补充,确保回答质量。

案例演示 (用户问题示例)

  • 用户问题 1: "我想了解一下迪士尼门票的退款流程"

    • 结果: 匹配到多个文本 Chunk,其中包含退票政策等信息,LLM 基于这些文本生成详细的退款流程说明。
  • 用户问题 2: "最近万圣节的活动海报是什么"

    • 结果: 匹配到文本 Chunk 和一个图片 Chunk (2-万圣节.jpeg)。由于检测到“海报”关键词,LLM 会结合文本信息描述万圣节活动,并附上图片链接。
  • 用户问题 3: "我的汽车被剐蹭了,你能看到视频么?"

    • 结果: 匹配到文本 Chunk 和一个视频 Chunk (汽车剐蹭视频 URL)。LLM 会根据迪士尼客服的身份,说明无法查看监控,但会引导用户联系客服中心,并附上相关的视频链接(如果相关)。

切片策略 (Chunking)

知识切片是 RAG 系统的核心环节,直接影响检索质量和回答准确性。

常用切片策略

  1. 固定长度切片 (有重叠)

    Code
    _核心思路_*: 按固定字符数切分文本,并优先在句子边界进行切分,避免切断句子。块之间通常有固定长度的重叠。 _优缺点_*: 实现简单,处理速度快,长度统一;适合技术文档和规范文件等需要统一处理长度、批量处理的场景。不适合语义敏感的问答。 _代码示例_*: (通过 `1-固定长度切片.py` 实现)
  2. 句子边界切片 (无重叠)

    Code
    _核心思路_*: 基于自然语言处理,按句子、段落等语义单位进行切分。保持语义完整性,避免在句子中间断开,确保每个切片都是完整的语义单元。 _优缺点_*: 语义保持好,检索准确性高;但切片长度可能不均匀。适用于自然语言文本、问答系统。不适合长句子多的文档。 _代码示例_*: (通过 `2-句子边界切片.py` 实现)
  3. LLM 语义切片

    Code
    _核心思路_*: 利用 LLM 的语义理解能力,在保持语义完整性的同时实现精确的长度控制。LLM 可以智能选择分割点。 _优缺点_*: 语义理解能力强,分割点选择智能;但依赖 GPU,成本较高。适用于高质量要求、复杂语义结构且有预算支持的项目。不适合大规模文档、成本敏感的场景。 _代码示例_*: ```python prompt = f""" 请将以下文本按照语义完整性进行切片,每个切片不超过{max_chunk_size}字符。 要求: 1. 保持语义完整性 2. 在自然的分割点切分 3. 返回JSON格式的切片列表,格式如下: {{ "chunks": [ "第一个切片内容", "第二个切片内容", ... ] }} 文本内容: {text} 请返回JSON格式的切片列表: """ # 调用LLM进行切片 ```
  4. 层次切片

    Code
    _核心思路_*: 基于文档的层次结构(标题、章节、段落)进行切分,将每个结构单元作为独立的块。 _优缺点_*: 保持文档结构,便于理解文档的逻辑关系,支持层次化查询;但依赖文档格式。适用于结构化文档(手册、规范、API 文档)。不适合无标题的纯文本。 _代码示例_*: (通过 `4-层次切片.py` 实现)
  5. 滑动窗口切片

    Code
    _核心思路_*: 使用固定大小的窗口在文本上滑动,产生重叠的切片。通过重叠机制确保上下文连续性,减少信息丢失,提高检索召回率。 _优缺点_*: 保持上下文连续性,减少信息丢失;但会产生大量重叠内容。适用于需要重叠信息、长文档处理、需要保持上下文的场景。不适合存储敏感、去重求高的场景。 _代码示例_*: (通过 `5-滑动窗口切片.py` 实现)

切片策略对比总结

方法 | 核心思路 | 重叠 | 长度均匀 | 语义完整 | 实现成本 | 适用场景 | 不适用场景

固定长度切片 | 按字符数切,句子边界优化 | 有 | 高 | 中 | 低 | 通用、批量处理、对长度有要求 | 语义敏感的问答

句子边界切片 | 按句号分句,再合并 | 无 | 低 | 高 | 低 | 自然语言文本、问答系统 | 长句子多的文档

LLM 语义切片 | LLM 理解内容后切分 | 无 | 中 | 最高 | 高 | 高质量要求、复杂语义结构 | 大规模文档、成本敏感

层次切片 | 按标题/章节切分 | 无 | 低 | 高 | 中 | 结构化文档(手册、规范、API 文档) | 无标题的纯文本

滑动窗口切片 | 固定窗口滑动,大量重叠 | 大量 | 高 | 中 | 低 | 需要上下文连续、长文档召回 | 存储敏感、去重要求高

场景选择建议

  • 通用场景: 固定长度切片(简单可靠)
  • 技术文档: 层次切片(保留结构)
  • 高质量要求: LLM 语义切片(效果最好)
  • 长文档召回: 滑动窗口切片(不漏信息)

关键要点

  • Gemini 的原生多模态能力: 具备统一理解、推理和生成文本、图像、视频等多种模态数据的能力,是构建高级 RAG 应用的基础。
  • Multimodal-Embedding 的核心作用: 将不同模态的数据映射到统一的向量空间,实现跨模态语义检索和相似度计算,简化了多模态 RAG 的实现。
  • RAG 助手工作流: 涵盖数据处理(解析、切分)、向量化(Embedding、FAISS 索引)、检索(统一检索、意图检测、筛选)和生成(LLM 答案构建)完整流程。
  • 切片策略的重要性与多样性: 不同的知识切片策略(固定长度、句子边界、LLM 语义、层次、滑动窗口)各有优缺点,需根据具体场景和需求选择最合适的策略,以提高检索质量和答案准确性。
  • 统一索引与后筛选机制: 通过构建单一多模态向量索引,结合意图检测和结果筛选,实现了高效且精准的多模态信息检索,避免了多套索引的复杂性。

embed

附件