让AI不只读懂文字,还能"看懂"图片、"听懂"声音,并在同一个空间里比较它们
Multimodal Embedding是将不同模态(文本、图片、音频、视频)的数据映射到同一个高维向量空间的技术。在这个空间里,语义相关的内容——无论模态——彼此距离更近。
传统文本嵌入模型(如all-MiniLM-L6-v2)只能处理文字。输入一段话,输出一个768维向量。两个向量算余弦相似度,就知道两段话是否语义相关。
但现实世界的搜索不是这样的:
用户搜索:"红色的杯子"
传统文本搜索:只匹配含有"红色""杯子"的文本
多模态搜索:还能找到红色的杯子照片,即使照片没有任何文字描述
用户搜索:"像这种风格的界面"
传统文本搜索:懵了——"这种"是什么?
多模态搜索:用户上传一张参考图,找到视觉上相似的界面截图
多模态嵌入的基石是对比学习。核心思想:
CLIP(Contrastive Language-Image Pre-training)是OpenAI 2021年提出的经典架构:
训练流程(4亿对图文数据):
┌──────────┐ 对比损失 ┌──────────┐
│ 文本编码器 │ ←─────────→ │ 图像编码器 │
│ (Transformer) │ │ (ViT) │
└─────┬────┘ └─────┬────┘
│ │
▼ ▼
文本向量[768维] 图像向量[768维]
│ │
└──── 余弦相似度 ──────────┘
正样本 → 接近 1.0
负样本 → 接近 0.0
Hugging Face的Sentence Transformers库在2026年已经全面支持多模态:
from sentence_transformers import SentenceTransformer
# 加载多模态嵌入模型
model = SentenceTransformer("clip-ViT-B-32")
# 文本嵌入
text_emb = model.encode("一只橘猫在阳光下打盹")
# 图像嵌入
from PIL import Image
img = Image.open("cat_sleeping.jpg")
image_emb = model.encode(img)
# 跨模态比较
from sentence_transformers.util import cos_sim
similarity = cos_sim(text_emb, image_emb)
# similarity = 0.87 → 高度相关!
多模态搜索的工业级做法是两阶段检索:
阶段1 - 粗筛(Embedding模型)
目标:从百万条数据中快速筛选top-100
方法:向量相似度检索
模型:CLIP / SigLIP(速度快、内存小)
耗时:~10ms
阶段2 - 精排(Reranker模型)
目标:从top-100中精排top-10
方法:Cross-Encoder直接比较query和document
模型:ColPali / 多模态Reranker(精度高)
耗时:~100ms
为什么不用Reranker做全量?
- Cross-Encoder需要把query和每个candidate拼起来算
- 100万次Cross-Encoder计算 = 100分钟
- Embedding全量只需5秒
- 所以先Embedding粗筛,再Reranker精排
| 模型 | 模态 | 向量维度 | 特点 |
|------|------|----------|------|
| CLIP ViT-L/14 | 图文 | 768 | OpenAI出品,经典标杆 |
| SigLIP | 图文 | 768 | Google出品,CLIP的改进版 |
| ColPali | 图文(文档) | 128 | 专为文档检索设计 |
| E5-V | 图文音 | 1024 | 微软出品,三模态统一 |
| ImageBind | 6模态 | 1024 | Meta出品,最广泛的多模态 |
| Nomic-Embed-Vision | 图文 | 768 | 开源,长上下文 |
| Jina-CLIP | 图文 | 768 | 高精度,支持多语言 |
训练自己的多模态嵌入:
- Sentence Transformers >= 3.0 支持多模态微调
- 支持 vision + text + audio 任意组合
- Hugging Face 2026年4月发布完整训练教程
# openclaw agent config: multimodal-search-agent
name: multimodal-search-agent
model: auto
skills:
- name: image-search
description: "用文字描述搜索相似图片"
tools:
- image_encoder
- vector_search
- name: document-search
description: "搜索文档中的图文内容"
tools:
- document_parser
- multimodal_reranker
- name: cross-modal-query
description: "跨模态查询:用图搜图、用文搜图、用图搜文"
tools:
- embed_any
- similarity_search
memory:
type: vector
embedder: sentence-transformers/clip-ViT-B-32
storage: ~/.openclaw/multimodal-search/vectors/
from sentence_transformers import SentenceTransformer, CrossEncoder
import numpy as np
from PIL import Image
import os
class MultimodalSearch:
"""基于OpenClaw的多模态语义搜索引擎"""
def __init__(self):
# 阶段1:嵌入模型(粗筛)
self.embedder = SentenceTransformer("jina-clip-v2")
# 阶段2:Reranker(精排)
self.reranker = CrossEncoder("jina-reranker-v2-base-multilingual")
self.corpus = [] # {(text, image_path, embedding)}
self.corpus_matrix = None
def index(self, items):
"""建立索引:items = [{"text": "...", "image": "path"}, ...]"""
embeddings = []
for item in items:
if item.get("image") and os.path.exists(item["image"]):
img = Image.open(item["image"])
emb = self.embedder.encode({"text": item["text"], "image": img})
else:
emb = self.embedder.encode(item["text"])
embeddings.append(emb)
self.corpus.append(item)
self.corpus_matrix = np.array(embeddings)
print(f"✅ 索引完成:{len(self.corpus)}条记录")
def search(self, query_text=None, query_image=None, top_k=100, rerank_top=10):
"""搜索:支持文本查询、图片查询、或两者混合"""
# 编码query
if query_image and os.path.exists(query_image):
img = Image.open(query_image)
query_emb = self.embedder.encode({"text": query_text or "", "image": img})
else:
query_emb = self.embedder.encode(query_text)
# 阶段1:向量粗筛
from sentence_transformers.util import cos_sim
scores = cos_sim(query_emb, self.corpus_matrix)[0]
top_indices = np.argsort(scores.numpy())[-top_k:][::-1]
# 阶段2:Reranker精排
query_str = query_text or "[image query]"
pairs = [(query_str, self.corpus[i]["text"]) for i in top_indices]
rerank_scores = self.reranker.predict(pairs)
# 合并分数
results = []
for idx, (corpus_idx, score) in enumerate(zip(top_indices, rerank_scores)):
results.append({
"item": self.corpus[corpus_idx],
"rerank_score": float(score),
"embedding_score": float(scores[corpus_idx])
})
results.sort(key=lambda x: -x["rerank_score"])
return results[:rerank_top]
# 使用示例
search = MultimodalSearch()
# 索引:混合图文内容
search.index([
{"text": "OpenClaw Agent配置示例", "image": None},
{"text": "MCP协议架构图", "image": "docs/mcp-architecture.png"},
{"text": "多Agent协作流程", "image": "docs/multi-agent-flow.png"},
{"text": "VLA机器人控制流程", "image": "docs/vla-robot.png"},
])
# 文本搜索图片
results = search.search(query_text="智能体架构图", top_k=50, rerank_top=5)
for r in results:
print(f"🔍 {r['item']['text']} (score: {r['rerank_score']:.3f})")
# 图片搜索图片(以图搜图)
results = search.search(query_image="reference-architecture.png", top_k=50, rerank_top=5)
for r in results:
print(f"🖼️ {r['item']['text']} (score: {r['rerank_score']:.3f})")