知识库要支持按图片内容检索,不能只靠 OCR、文件名或人工打标,也不能在查询时临时看图。正确做法是在入库阶段用 LingBot-Vision 把图片编码成语义向量,与文本向量放进同一个向量数据库,并用 doc_id/chunk_id 把图片挂到原文档上。图片和文本共用同一个 collection,查询时才能一次召回两类结果。
适用场景:现有知识库已有多模态内容,且查询词包含画面语义。操作动作:给图片生成归一化 embedding,写入与文本相同的 collection,并带 type 字段区分。验证方式:造一组同时含图片唯一信息与文本唯一信息的测试查询,逐条对比召回项。风险边界:LingBot-Vision 的向量与文本向量是否同空间,需要先用相似度分布确认;跨模型拼接可能造成检索失真。
设计图文统一索引的结构
图片索引不能单独建库,否则查询时要搜两次再合并,阈值和排序会很难处理。建议把图片作为文档在 chunk 维度上的补充记录,而不是独立文档。统一索引至少需要这些字段:
- id:doc_id + chunk_id + 序号,保证图片和文本的记录不冲突;
- doc_id:原文档 ID,用于追溯来源;
- chunk_id:文本分段 ID,用于定位图片在文档中的位置;
- type:image 或 text,用于区分两类向量;
- embedding:L2 归一化后的向量;
- metadata:图片路径、页面序号、OCR 文本等附属信息。
映射关系上,一张图片可以对应一个或多个 chunk_id。如果图片只属于某个段落,chunk_id 用该段落 ID;如果图片覆盖整篇文档,可以单独建一个 image 专用 chunk_id。文本记录不保存图片字段,只靠 type 区分,查询时才能用同一个 collection 统一召回。
用LingBot-Vision生成图片语义向量
LingBot-Vision 在入库阶段只负责把图片编码成向量,不做检测或识别。需要先确认接口返回的是语义向量,不是分类 logits 或编码后的 base64。下面是通用接入骨架,端点地址和返回字段按实际环境替换:
import requests
def get_image_embedding(image_path: str, endpoint: str = 'http://your-lingbot-vision:8000/embed') -> list:
with open(image_path, 'rb') as f:
resp = requests.post(endpoint, files={'file': f}, timeout=30)
resp.raise_for_status()
vec = resp.json().get('embedding')
if not vec or len(vec) == 0:
raise ValueError('embedding is empty')
norm = sum(x * x for x in vec) ** 0.5
if norm == 0:
raise ValueError('zero vector')
return [x / norm for x in vec]如果走本地推理,则取模型最后一个池化层的输出,并在写库前做同样的 L2 归一化。归一化不是可选项,后续用余弦距离检索时必须保证所有向量处于同一量纲。
写入向量数据库并建立混合索引
推荐使用余弦距离,因为图片向量和文本向量都已经归一化。Chroma 配置示例:
import chromadb
client = chromadb.PersistentClient(path='./kb_data')
collection = client.get_or_create_collection(
name='unified_knowledge',
metadata={'hnsw:space': 'cosine'}
)
# 写入图片向量
collection.upsert(
ids=[f'{doc_id}_{chunk_id}_img'],
embeddings=[image_vec],
metadatas=[{
'doc_id': doc_id,
'chunk_id': chunk_id,
'type': 'image',
'image_path': image_path
}],
documents=[caption or '']
)
# 写入文本向量
collection.upsert(
ids=[f'{doc_id}_{chunk_id}_txt'],
embeddings=[text_vec],
metadatas=[{'doc_id': doc_id, 'chunk_id': chunk_id, 'type': 'text'}],
documents=[text]
)如果使用 Milvus,collection schema 也按同样的字段设计,向量字段用 FLOAT_VECTOR,度量类型用 COSINE,不需要为图片单独建 collection。写入时把 type、doc_id、chunk_id 作为 scalar field 传入。
检索效果验证与调优
验证阶段先不要看指标,先看召回内容是否匹配业务查询。准备一组测试查询,每条至少包含一个只有图片才能回答的条件,例如“第二页架构图里数据库节点标了什么颜色”“安全手册那张示意图的操作顺序”。同时用同样的查询跑一个纯文本索引做对照,纯文本索引只需把 where 条件改成只保留 type=text。
query_vec = text_embedding('安全手册示意图的操作顺序')
hits = collection.query(
query_embeddings=[query_vec],
n_results=10,
where={'type': {'$in': ['image', 'text']}}
)
for meta in hits['metadatas'][0]:
print(meta['doc_id'], meta['chunk_id'], meta['type'])对比两条索引的返回列表:如果混合索引中图片相关结果出现在前几位,而纯文本索引搜不到,说明图文索引有效;如果图片结果整体靠后,可以先给 type=image 的结果加一个稳定的排序加权项,或者在查询端对图片类型单独放大权重。阈值不要先定死,先收集一轮查询的距离分布,再看保留多少结果能覆盖业务需求。需要注意,如果发现图片语义向量和文本向量距离普遍偏大,说明两个模型不在同一个向量空间中,需要检查文本向量是否也由同一套模型产出,或考虑用同一编码器统一返回。