技术分享会刚结束,架构师老赵就堵住了老李。
“你们那个知识库问答系统,我刚才问了句‘微服务架构长什么样’,它回答‘文档中未包含架构图’。我上个月才上传了《系统架构设计 v3》,里面有完整的架构图!你们的 AI 是不是瞎?”
老李端着保温杯,一脸无辜地看向小王。
小王叹了口气:“老李,咱们批量导入文档的时候,你写了个脚本把 PDF 里的文字全提出来了——图片呢?表格呢?全扔了吧?”
老李愣住了:“图片……那些不就是点缀吗?技术文档核心是文字啊。”
“老李你想啊,你买房看户型图,是看文字描述‘主卧朝南,客厅面宽四米二’更直观,还是直接看那张图更直观?”
老李没接话。他知道答案。
“技术文档里的架构图、流程图、ER 图、对比表格——这些不是‘点缀’,是知识不可分割的部分。你把它们丢了,等于只记住了菜谱的文字步骤,把成品图扔了。用户问‘这道菜做出来长什么样’,你只能干瞪眼。”
多模态嵌入:让文字和图片住在同一个空间
“文字能向量化,图片也能。”小王走到白板前,画了两个并排的框。
“普通的嵌入模型只理解文字。你把一句话变成 1024 个浮点数,这个向量就代表这句话的语义。但如果你把一张架构图也变成 1024 个浮点数,而且这两个向量在同一个空间里——那么一张‘微服务架构图’的向量,就应该跟‘微服务架构’这段文字的向量靠得很近。”
“这就是多模态嵌入的核心思想。文字和图片共享同一个语义空间。你搜文字,能匹配图片;你搜图片,能匹配文字。”
老李皱起眉:“这怎么做到的?文字是一串词,图片是一堆像素,怎么映射到同一个空间?”
“CLIP 模型就是这么设计的。”小王在终端里调出了 CLIP 的架构图,“它用了两套编码器——一个文字编码器,一个图像编码器。训练的时候,给它看四亿对图文配对——比如一张猫的照片配‘一只橘猫在睡觉’。训练目标是让配对图文在语义空间里尽量靠近,不配对的尽量远离。训完之后,两套编码器输出的向量就在同一个空间里了。”
图:CLIP 的两套编码器,把图文映射到同一个空间
实战:用自然语言搜图片
“别光讲理论,”老李催促道,“你跑一个试试。”
小王打开笔记本,写了一段代码,用的是 HuggingFace 上的 CLIP 模型:
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch
# 加载 CLIP 模型(中文版)
model = CLIPModel.from_pretrained("OFA-Sys/chinese-clip-vit-base-patch16")
processor = CLIPProcessor.from_pretrained("OFA-Sys/chinese-clip-vit-base-patch16")
# 准备几张公司架构图(模拟)
images = [
Image.open("微服务架构图_v3.png"),
Image.open("数据库ER图.png"),
Image.open("部署拓扑图.png"),
]
queries = ["微服务架构设计", "数据库表关系", "服务器部署结构"]
# 分别编码文字和图片
image_inputs = processor(images=images, return_tensors="pt")
text_inputs = processor(text=queries, return_tensors="pt", padding=True)
with torch.no_grad():
image_embeds = model.get_image_features(**image_inputs)
text_embeds = model.get_text_features(**text_inputs)
# 计算相似度矩阵
similarity = torch.matmul(text_embeds, image_embeds.T)
for i, query in enumerate(queries):
best_match = similarity[i].argmax().item()
print(f"'{query}' → 最匹配图片索引: {best_match}")
# 输出:
# '微服务架构设计' → 最匹配图片索引: 0
# '数据库表关系' → 最匹配图片索引: 1
# '服务器部署结构' → 最匹配图片索引: 2老李看着输出,保温杯在手里慢慢转:“所以,用户问‘微服务架构长什么样’,系统先把他这句话向量化,然后在图片向量库找最接近的架构图,返回给用户?”
“对。而且不只是精确匹配——用户说‘服务怎么拆分的’、‘那个有好多方块的图’,CLIP 都能把它映射到微服务架构图附近。因为它在四亿组训练数据里见过各种描述图片的方式。”
表格:最难处理的“四不像”
“那表格呢?”老李追问,“上周的《API 性能对比表》,那是一张嵌在文档里的表格。文字提取出来是一堆乱序的数字,图的话背景是白色,CLIP 能看懂表格吗?”
“表格是多模态知识库里最棘手的东西。”小王诚实地点头,“它既不是纯文字——把单元格按顺序提出来会丢失行列关系;也不是纯图片——白底黑字让图像编码器很难提取语义。”
“处理表格,通常走两条路。”
小王在玻璃上画了两个箭头:“第一条路,结构化提取。用专门的表格解析工具把表格转成 JSON 或 DataFrame,保留行列结构。检索的时候,根据行列标题做精确匹配。”
import pandas as pd
# 方法一:结构化提取(用 camelot 或 tabula 从 PDF 中提取表格)
# 假设已经提取成 DataFrame
df = pd.DataFrame({
"API名称": ["用户认证", "订单查询", "支付回调"],
"QPS上限": [5000, 2000, 1000],
"平均延迟": ["50ms", "80ms", "120ms"],
"数据来源": ["MySQL", "Redis", "第三方"]
})
def search_table(df, condition_col, value):
"""在表格中按条件查找"""
result = df[df[condition_col].str.contains(value)]
return result.to_dict(orient="records")
# 用户问:"支付回调的QPS上限是多少?"
print(search_table(df, "API名称", "支付"))
# 输出:[{'API名称': '支付回调', 'QPS上限': 1000, '平均延迟': '120ms', '数据来源': '第三方'}]“第二条路,把表格转成描述性文本再向量化。比如把上面那张表转成‘用户认证 API 的 QPS 上限是 5000,平均延迟 50 毫秒,数据来源 MySQL’。这样可以用普通的文字嵌入模型处理。缺点是转换过程中可能会丢失细节。”
老李若有所思:“那我们公司的技术文档,应该是两条路都走——结构化提取保证精确查询,同时转成文字做模糊检索。”
“对。这就是‘混合表处理’。结构化部分负责精确问答,向量化部分负责语义发现。”
多模态 RAG:一个查询,三种结果
“所以,”老李把保温杯放下,“一个完整的 RAG 系统,检索的不应该只是文字。”
小王点点头,在屏幕上画了一个新的检索架构:
图:多模态 RAG——一次查询,同时检索文字、图片、表格
“用户问‘API 网关的限流策略’,系统可能同时返回——文字说明限流算法是令牌桶,一张流程图展示限流判断逻辑,一张表格对比不同阈值下的 QPS 表现。大模型把这三者融合成一段连贯的回答,最后附上图片和表格。”
老李看着这张图,沉默了一会儿。然后问了一个直击灵魂的问题:“成本呢?”
成本警告:多模态的代价
“我就知道你要问这个。”小王调出一张对比表。
“纯文字嵌入,一张 A4 文档大概 1000 个 token,用 text-embedding-3-small 编码,成本不到一分钱。一张图片用 CLIP 编码,模型推理一次要跑 Vision Transformer,算力需求高几十倍,而且存储的向量维度通常也更高。表格的结构化提取需要额外的解析流水线,维护成本也不低。”
“所以多模态不是免费的午餐。它适合的场景是——知识本身就包含大量非文字信息。技术文档、医学报告、建筑设计、电商商品描述。如果你们公司的知识主要是纯政策文件,那纯文字就够。”
老李靠回椅背,拧开保温杯,枸杞的香气飘了出来。
“我们那批技术文档,架构图至少有三百张,对比表格更多。这些如果不索引进去,知识库就是个半残废。”他顿了顿,“下周把多模态检索加进去。先上图片,表格用结构化提取先跑着。”
他站起来,走到门口,又转过身来:“那什么,你说 CLIP 是看四亿组图文对训练的。那咱们公司的架构图风格跟互联网图片差很多,它真的能理解吗?需不需要用我们自己的数据微调一下?你再给我讲讲怎么用少量样本做领域适配。”
小王咧嘴笑了。老李从“图片不就是点缀吗”到“需不需要微调”,这中间的认知跨度,比 CLIP 的语义空间还大。
“行,我给你讲 LoRA 微调 CLIP 的方案——其实就跟你上次搞的那个一样,冻结主干,加几个小矩阵……”
保温杯搁在桌上,枸杞沉沉浮浮,像多模态语义空间里那些相互靠近的向量——文字、图片、表格,终于开始共享同一个维度。

