为什么需要多模态RAG?
传统RAG只能处理文字,但现实世界的信息是多维的。技术文档里穿插架构图、产品手册里有演示截图、研究报告里有数据图表——这些信息,纯文本RAG完全丢失了。
用户问"系统的整体架构是什么样的?",纯文本RAG只会返回一段文字描述,而无法返回架构图。这就是多模态RAG要解决的问题。
多模态RAG架构全景
┌─────────────────────────────────────────────────────────┐
│ 多模态RAG系统架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Query 分析 │ 判断用户需要图片、文字、还是图文 │
│ └──────┬──────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ [纯文本查询] [需要图片查询] │
│ │ │ │
│ ▼ ▼ │
│ 文本检索 多模态检索 │
│ (向量库) (CLIP/图片向量) │
│ │ │ │
│ └────┬────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 结果融合 │ 图文结果融合排序 │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ 多模态LLM生成回答(可理解和描述图片) │
│ │
└─────────────────────────────────────────────────────────┘
四种架构方案详解
方案1:独立双路检索(Easiest)
文本向量库 图片向量库
│ │
└──► 合并 ◄──┘
│
▼
多模态LLM生成
最简单也是最常见的方案:文本和图片分别Embedding,分别检索,然后合并结果交给多模态LLM生成回答。
from sentence_transformers import SentenceTransformer
from PIL import Image
import clip # OpenAI CLIP
# 文本Embedding
text_model = SentenceTransformer("BAAI/bge-large-zh-v1.5")
# 图片Embedding(CLIP)
clip_model, preprocess = clip.load("ViT-L/14", device="cuda")
def retrieve_text(query, top_k=5):
query_emb = text_model.encode(query)
results = text_collection.search(query_emb, top_k)
return [{"type": "text", "content": r["text"], "score": r["score"]} for r in results]
def retrieve_image(query, top_k=5):
# 用CLIP将文本查询转为图片检索向量
text_tokens = clip.tokenize([query]).to("cuda")
with torch.no_grad():
query_emb = clip_model.encode_text(text_tokens).cpu().numpy()
results = image_collection.search(query_emb, top_k)
return [{"type": "image", "url": r["url"], "caption": r["caption"], "score": r["score"]} for r in results]
def multimodal_rag(query):
# 双路检索
text_results = retrieve_text(query, top_k=3)
image_results = retrieve_image(query, top_k=2)
# 合并结果
all_results = text_results + image_results
# 按相关性排序
all_results.sort(key=lambda x: x["score"], reverse=True)
# 构建多模态上下文
context = build_multimodal_context(all_results)
# 多模态LLM生成
response = multimodal_llm.generate(query, context)
return response
优点:实现简单,各组件独立 缺点:文本和图片无法互相检索(文字查不到图片内容,图片查不到文字) 适用:图片内容简单(截图、照片),不需要跨模态理解
方案2:CLIP统一Embedding空间(Best Balance)
文本 ──► CLIP Encoder ──► 统一向量空间 ◄── CLIP Encoder ◄── 图片
│ │
└────────► 向量库 ◄────────────────────┘
CLIP的核心能力:将图片和文字映射到同一个向量空间。这意味着"一张猫的图片"和"猫"这个词在向量空间中距离很近。
import torch
import clip
from PIL import Image
# 加载CLIP模型
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-L/14", device=device)
def encode_text(texts):
"""用CLIP编码文本"""
tokens = clip.tokenize(texts).to(device)
with torch.no_grad():
text_features = model.encode_text(tokens)
return text_features.cpu().numpy()
def encode_image(image_paths):
"""用CLIP编码图片"""
images = [preprocess(Image.open(p)).unsqueeze(0).to(device) for p in image_paths]
images = torch.cat(images)
with torch.no_grad():
image_features = model.encode_image(images)
return image_features.cpu().numpy()
# 索引构建:将所有内容(文本+图片)用CLIP编码存入同一向量库
def build_unified_index(documents, images):
all_embeddings = []
all_metadata = []
# 文本编码
for doc in documents:
emb = encode_text([doc["text"]])[0]
all_embeddings.append(emb)
all_metadata.append({"type": "text", "content": doc["text"], "source": doc["source"]})
# 图片编码
for img in images:
emb = encode_image([img["path"]])[0]
all_embeddings.append(emb)
all_metadata.append({"type": "image", "path": img["path"], "caption": img["caption"]})
# 存入向量库(Milvus/Qdrant等)
vector_store.add(all_embeddings, all_metadata)
# 检索:统一接口
def unified_retrieve(query, top_k=5):
"""用CLIP统一检索文本和图片"""
query_emb = encode_text([query])[0]
results = vector_store.search(query_emb, top_k=top_k)
return results
优点:跨模态检索,文字可以查到相关图片,图片也可以查到相关文字 缺点:CLIP对长文本理解有限(只支持77个token) 适用:中等长度文本+图片的混合场景
方案3:图片Caption增强检索(Best for 图片为主)
图片 ──► 图片Captioning模型 ──► 文字描述 ──► 文本向量库
│ │
│ ▼
└──► 原始图片 ──────────────────────────► 多模态LLM上下文
核心思路:先用图片Captioning模型(如BLIP-2)将图片转为文字描述,然后只维护一个文本向量库。检索时只用文字,但返回时带上原始图片。
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from PIL import Image
# 图片Captioning
caption_processor = Blip2Processor.from_pretrained("Salesforce/blip2-opt-2.7b")
caption_model = Blip2ForConditionalGeneration.from_pretrained("Salesforce/blip2-opt-2.7b", torch_dtype=torch.float16).to("cuda")
def generate_caption(image_path):
"""为图片生成文字描述"""
image = Image.open(image_path).convert("RGB")
inputs = caption_processor(images=image, return_tensors="pt").to("cuda", torch.float16)
generated_ids = caption_model.generate(**inputs, max_length=50)
caption = caption_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
return caption
def build_image_augmented_index(images):
"""为图片生成描述后存入文本向量库"""
for img in images:
# 生成图片描述
caption = generate_caption(img["path"])
# 将描述和图片路径一起存入
doc = {
"text": caption,
"metadata": {
"type": "image_with_caption",
"image_path": img["path"],
"caption": caption,
}
}
text_vector_store.add(doc)
def retrieve_with_images(query, top_k=5):
"""检索:返回文字结果和图片结果"""
results = text_vector_store.search(query, top_k)
formatted = []
for r in results:
if r["metadata"]["type"] == "image_with_caption":
formatted.append({
"type": "image",
"image_path": r["metadata"]["image_path"],
"caption": r["metadata"]["caption"],
"score": r["score"],
})
else:
formatted.append({
"type": "text",
"content": r["text"],
"score": r["score"],
})
return formatted
优点:只用文本向量库,架构简单;图片有文字描述,可检索 缺点:Caption可能丢失图片细节;长文档中的图片不好处理 适用:图片为主的知识库,如图表库、截图库
方案4:多向量多索引(Most Flexible)
文档 ──► 分块 ──► 文本块 ──► 文本向量库
│ │
│ └──► 图片提取 ──► 图片向量库
│ │
└─────────────────────────────────────────┘
│
▼
结果融合 + 多模态LLM
最完整的方案:对文档同时维护文本索引和图片索引,用多路检索结果融合。
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import fitz # PyMuPDF for image extraction
def process_pdf_with_images(pdf_path):
"""处理PDF,同时提取文字和图片"""
# 提取文字
loader = PyPDFLoader(pdf_path)
pages = loader.load()
# 分块
splitter = RecursiveCharacterTextSplitter(chunk_size=500, overlap=50)
text_chunks = splitter.split_documents(pages)
# 提取图片
doc = fitz.open(pdf_path)
images = []
for page_num in range(len(doc)):
for img_index, img in enumerate(doc.get_page_images(page_num)):
xref = img[0]
base_image = doc.extract_image(xref)
image_bytes = base_image["image"]
# 保存图片
img_path = f"images/page{page_num}_img{img_index}.png"
with open(img_path, "wb") as f:
f.write(image_bytes)
images.append({
"path": img_path,
"page": page_num,
"parent_doc": pdf_path,
})
return text_chunks, images
def build_multivector_index(text_chunks, images):
# 文本索引
for chunk in text_chunks:
text_store.add({
"content": chunk.page_content,
"metadata": chunk.metadata,
})
# 图片索引(用CLIP)
for img in images:
img_emb = encode_image([img["path"]])[0]
image_store.add({
"embedding": img_emb,
"metadata": img,
})
2026年多模态LLM选型
多模态RAG的最后一步是多模态LLM生成,它需要能理解和描述图片。
| 模型 | 图片理解 | 图片生成 | 上下文窗口 | 推荐场景 |
|---|---|---|---|---|
| Claude 4 Sonnet | ⭐⭐⭐⭐⭐ | ❌ | 200K | 复杂图表分析 |
| GPT-5 | ⭐⭐⭐⭐⭐ | ❌ | 128K | 通用多模态 |
| Gemini 2.5 Pro | ⭐⭐⭐⭐ | ❌ | 1M | 超长图文 |
| Qwen-VL-Plus | ⭐⭐⭐⭐ | ❌ | 8K | 中文场景 |
| LLaVA-1.6 | ⭐⭐⭐ | ❌ | 4K | 本地部署 |
多模态生成示例
from anthropic import Anthropic
client = Anthropic(api_key="your-key")
def multimodal_generate(query, text_context, image_paths):
"""多模态LLM生成"""
# 构建消息:文字+图片
content = [{"type": "text", "text": f"参考资料:\n{text_context}\n\n问题:{query}"}]
# 添加图片
for img_path in image_paths:
with open(img_path, "rb") as f:
img_data = base64.b64encode(f.read()).decode()
content.append({
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": img_data,
}
})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=[{"role": "user", "content": content}],
)
return response.content[0].text
性能优化要点
1. 图片预筛选
不是所有图片都值得检索。先过滤掉:装饰性图片、重复图片、过小的图标。
def is_meaningful_image(img_path, min_size=100):
"""判断图片是否有信息价值"""
img = Image.open(img_path)
w, h = img.size
# 过滤小图标
if w < min_size or h < min_size:
return False
# 过滤纯色/装饰图片(通过图像复杂度判断)
gray = img.convert("L")
std = np.std(np.array(gray))
if std < 10: # 几乎纯色
return False
return True
2. 图文联合Embedding
对于"文字+配图"的文档,将文字和图片的Embedding拼接后进行检索:
def joint_embedding(text_emb, image_emb, fusion_method="concat"):
"""文字和图片的联合表示"""
if fusion_method == "concat":
return np.concatenate([text_emb, image_emb])
elif fusion_method == "weighted_sum":
return 0.7 * text_emb + 0.3 * image_emb # 文字权重更高
elif fusion_method == "attention":
# 用Cross-Attention融合
return cross_attention_fusion(text_emb, image_emb)
总结
多模态RAG的选型建议:
| 你的场景 | 推荐方案 |
|---|---|
| 图片是辅助(截图、图标) | 方案1:独立双路 |
| 图文并重,需要跨模态检索 | 方案2:CLIP统一空间 |
| 图片为主,需要描述 | 方案3:Caption增强 |
| 复杂文档(PDF/PPT) | 方案4:多向量多索引 |
2026年的趋势是CLIP统一空间 + 多模态LLM生成成为标准配置。核心挑战不再是模型能力不足,而是如何高效处理海量图片的Embedding和更新。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
