如果你是做智能搜索的,那肯定绕不开向量检索这个坎。不管是图片相似性搜索、文本语义匹配,还是音频指纹比对,核心都是把非结构化数据转换成向量,再从海量向量里快速找到最相似的那些。但问题来了,直接操作向量数据库底层接口太麻烦,不同语言的开发者又有各自的技术栈 —— 这时候,Milvus SDK 就成了救命稻草。
不管你用哪种语言,在开始之前,有几个通用的坑必须先填上,不然后面步步是坎。
为啥 Python SDK 最火?因为做 AI、做智能搜索的开发者,十有八九在用 Python。Milvus 的 Python SDK 叫 pymilvus,社区活跃,文档也最完善,新手入门首选它准没错。
connections.connect(alias="default", host="127.0.0.1", port="19530")
vector_field = FieldSchema (name="vector", dtype=DataType.FLOAT_VECTOR, dim=768)
schema = CollectionSchema (fields=[id_field, vector_field], description="文本向量集合")
collection = Collection (name="text_vectors", schema=schema, using="default")
ids = list(range(100))
vectors = [[random.random() for _ in range(768)] for _ in range(100)]
insert_result = collection.insert([ids, vectors])
"index_type": "IVF_FLAT",
"metric_type": "L2", # 距离度量方式,文本向量常用 L2 或 IP
"params": {"nlist": 128} # nlist 是聚类数量,一般设为数据量的开方
}
collection.create_index (field_name="vector", index_params=index_params)
query_vector = [random.random () for _ in range (768)] # 你的查询向量
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = collection.search (
data=[query_vector],
anns_field="vector",
param=search_params,
limit=10, # 返回 top 10 相似结果
output_fields=["id"] # 要返回的其他字段
)
print (f"ID: {hit.id}, 距离: {hit.distance}")
Java 开发者看过来!Milvus Java SDK 虽然不如 Python 那么轻量,但胜在稳定,特别适合企业级应用 —— 毕竟很多大型系统都是 Java 栈,总不能为了个向量数据库换语言吧?
import io.milvus.param.;
.withHost ("127.0.0.1")
.withPort (19530)
// 有用户名密码的话加上
//.withUsername ("your_username")
//.withPassword ("your_password")
.build ();
MilvusClient client = new MilvusServiceClient (connectParam);
.withName("id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(false)
.build();
.withName ("vector")
.withDataType (DataType.FloatVector)
.withDimension (768) // 同样要和向量维度一致
.build ();
.withCollectionName("text_vectors_java")
.withFieldTypes(idField, vectorField)
.build();
if (createResponse.getStatus () != R.Status.Success.getCode ()) {
System.err.println ("创建集合失败:" + createResponse.getMessage ());
return;
}
- > 格式,每个子列表是一个向量。
List
- > vectors = new ArrayList<>();
for (long i = 0; i < 100; i++) {
ids.add(i);
List
for (int j = 0; j < 768; j++) {
vec.add((float) Math.random());
}
vectors.add(vec);
}
.withCollectionName("text_vectors_java")
.withFields("id", "vector")
.withValues(ids, vectors)
.build();
if (insertResponse.getStatus () != R.Status.Success.getCode ()) {
System.err.println ("插入失败:" + insertResponse.getMessage ());
}
FlushParam flushParam = FlushParam.newBuilder ()
.addCollectionName ("text_vectors_java")
.build ();
client.flush (flushParam);
""index_type": "IVF_FLAT"," +
""metric_type": "L2"," +
""params": {"nlist": 128}" +
"}";
.withCollectionName("text_vectors_java")
.withFieldName("vector")
.withIndexParam(indexParamJson)
.build();
LoadCollectionParam loadParam = LoadCollectionParam.newBuilder ()
.withCollectionName ("text_vectors_java")
.build ();
client.loadCollection (loadParam);
List
- > queryVectors = new ArrayList<>();
List
for (int i = 0; i < 768; i++) {
queryVec.add((float) Math.random());
}
queryVectors.add(queryVec);
String searchParamJson = "{" +
""metric_type": "L2"," +
""params": {"nprobe": 10}" +
"}";
.withCollectionName("text_vectors_java")
.withAnnField("vector")
.withVectors(queryVectors)
.withParam(searchParamJson)
.withLimit(10)
.withOutputFields("id")
.build();
if (searchResponse.getStatus () == R.Status.Success.getCode ()) {
SearchResultsData resultsData = searchResponse.getData ();
for (SearchResultsData.ResultWrapper result : resultsData.getResults ()) {
System.out.println ("ID:" + result.getFieldValue ("id") + ", 距离:" + result.getScore ());
}
}
ReleaseCollectionParam releaseParam = ReleaseCollectionParam.newBuilder ()
.withCollectionName ("text_vectors_java")
.build ();
client.releaseCollection (releaseParam);
如果你的智能搜索应用对性能要求特别高,比如需要每秒处理几千上万次搜索请求,那 Go SDK 绝对是首选。Go 语言的并发模型太适合这种场景了,Milvus Go SDK 又做得足够轻量,资源占用低,响应速度快。
"context"
"fmt"
"github.com/milvus-io/milvus-sdk-go/v2/client"
"github.com/milvus-io/milvus-sdk-go/v2/entity"
)
c, err := client.NewClient (ctx, client.Config {
Address: "127.0.0.1:19530",
// 用户名密码在这里加:
// Username: "your_username",
// Password: "your_password",
})
if err != nil {
panic (fmt.Sprintf ("连接失败: % v", err))
}
defer c.Close () // 记得最后关闭连接
vectorField := entity.NewFieldSchema ("vector", entity.FieldTypeFloatVector, false)
vectorField.WithDim (768) // 设置向量维度
if err != nil {
panic (fmt.Sprintf ("创建集合失败: % v", err))
}
vectors := make([][]float32, 100)
for i := 0; i < 100; i++ {
ids[i] = int64(i)
vec := make([]float32, 768)
for j := 0; j < 768; j++ {
vec[j] = float32(rand.Float64())
}
vectors[i] = vec
}
vecCol, err := entity.NewColumnFloatVector("vector", 768, vectors)
if err != nil {
panic(err)
}
if err != nil {
panic (fmt.Sprintf ("插入失败: % v", err))
}
err = c.Flush(ctx, "text_vectors_go", false)
if err != nil {
panic(err)
}
"nlist": 128,
})
if err != nil {
panic(err)
}
if err != nil {
panic (fmt.Sprintf ("创建索引失败: % v", err))
}
err = c.LoadCollection (ctx, "text_vectors_go", false)
if err != nil {
panic (err)
}
queryVec := make ([] float32, 768)
for i := 0; i < 768; i++ {
queryVec [i] = float32 (rand.Float64 ())
}
sp, err := entity.NewIndexIVFFlatSearchParam (10) //nprobe=10
if err != nil {
panic (err)
}
ctx,
"text_vectors_go", // 集合名
"", // 分区名,不指定查所有
[] string {"id"}, // 返回字段
[] entity.Vector {entity.FloatVector (queryVec)}, // 查询向量
"vector", // 向量字段名
entity.MetricTypeL2,
10, // 返回 top 10
sp,
)
if err != nil {
panic (fmt.Sprintf ("搜索失败: % v", err))
}
for _, hits := range res {
for _, hit := range hits {
id, ok := hit.Entity.GetField ("id").(int64)
if ok {
fmt.Printf ("ID: % d, 距离: % f\n", id, hit.Score)
}
}
}
err = c.ReleaseCollection (ctx, "text_vectors_go")
if err != nil {
panic (err)
}
以前总觉得向量数据库是后端的事,前端开发者插不上手?Milvus JavaScript SDK 来了,不管是 Node.js 后端,还是浏览器端,都能直接调用 Milvus,前端开发者也能轻松玩转智能搜索!
cors:
enabled: true
allow_origins:
- "" # 开发环境可以用,生产环境指定具体域名
const client = new MilvusClient({
address: 'localhost:19530',
// 用户名密码:
//username: 'your_username',
//password: 'your_password',
});
await client.createCollection ({
collection_name: 'text_vectors_js',
fields: [
{
name: 'id',
data_type: 5, // 5 是 INT64
is_primary_key: true,
auto_id: false,
},
{
name: 'vector',
data_type: 101, // 101 是 FLOAT_VECTOR
dim: 768,
},
],
});
const ids = Array.from ({length: 100}, (_, i) => i);
const vectors = Array.from ({ length: 100 }, () =>
Array.from ({ length: 768 }, () => Math.random ())
);
collection_name: 'text_vectors_js',
fields_data: [
{
field_name: 'id',
data: ids,
},
{
field_name: 'vector',
data: vectors,
},
],
});
await client.flush({
collection_names: ['text_vectors_js'],
});
await client.createIndex ({
collection_name: 'text_vectors_js',
field_name: 'vector',
index_name: 'vector_index',
index_type: 'IVF_FLAT',
metric_type: 'L2',
params: JSON.stringify ({nlist: 128}),
});
await client.loadCollection ({
collection_name: 'text_vectors_js',
});
const queryVector = Array.from ({length: 768}, () => Math.random ());
const searchResult = await client.search ({
collection_name: 'text_vectors_js',
field_name: 'vector',
vectors: [queryVector],
limit: 10,
params: JSON.stringify ({ nprobe: 10 }),
output_fields: ['id'],
});
await client.releaseCollection ({
collection_name: 'text_vectors_js',
});
}
import { useEffect, useState } from 'react';
const [client, setClient] = useState(null);
const [searchResult, setSearchResult] = useState([]);
// 初始化客户端
const milvusClient = new MilvusClient ({
address: 'http://your-milvus-server:9091', // 注意:浏览器端用 HTTP 端口 9091,不是 19530
});
setClient (milvusClient);
}, []);
if (!client) return;
const queryVector = Array.from ({length: 768}, () => Math.random ());
collection_name: 'text_vectors_js',
field_name: 'vector',
vectors: [queryVector],
limit: 10,
params: JSON.stringify({ nprobe: 10 }),
output_fields: ['id'],
});
};
<div>
<button onClick={handleSearch}>执行搜索</button>
<div>
{searchResult.map((res, idx) => (
<div key={idx}>
{res.map((hit, i) => (
<p key={i}>ID: {hit.id}, 距离: {hit.score}</p>
))}
</div>
))}
</div>
</div>
);
踩过的坑多了,就知道哪些地方必须小心。这 5 个雷区,不管用哪种语言的 SDK,都可能遇到,记好了能少走很多弯路!
这是新手最常犯的错!创建集合时定义的向量维度是 768,结果插入的向量有 512 维的,或者查询向量是 1024 维的,直接报错。解决办法:在代码里加校验,比如插入前检查每个向量的长度是否等于集合定义的维度,不等就过滤或报错。别指望 Milvus 会帮你自动处理,它只会严格校验。
有些人插入数据后直接搜,结果发现几十万条数据就要搜几百毫秒,还以为是 Milvus 不行。错了!没建索引的话,Milvus 会做全量扫描,速度当然慢。记住:数据量超过 1 万条,一定要建索引,哪怕是最简单的 IVF_FLAT 也行。建索引后,速度能提升几十倍甚至上百倍。
比如 IVF_FLAT 的 nlist 设得太小(比如 10),或者 HNSW 的 M 参数设得太大(比如 100),都会影响精度或速度。正确的做法:nlist 一般设为数据量的开方(比如 10 万条数据设 300-500),HNSW 的 M 设 16-32 就行,别瞎调。不确定的话,先用默认参数,再慢慢调优。
Milvus 的集合数据默认存在磁盘上,搜索前必须调用 load 加载到内存(除了用 Local 模式的 Milvus Lite)。有些人插入数据、建了索引,直接搜,结果返回空,还以为数据丢了。其实就是没 load!解决办法:搜索前先检查集合是否已加载,没加载就调用 load 方法。
特别是用 Python 这种动态语言,开 100 个线程同时插入数据,很容易导致 Milvus 服务内存飙升,甚至 OOM。Milvus 虽然支持并发,但也要控制力度。建议:单客户端并发数不超过 10,或者用批量插入(每次插 1 万 - 10 万条,根据数据大小调整),比频繁小批量插入高效得多。
光说不练假把式,来个实战案例 —— 用 Milvus SDK 构建一个 “拍图找同款” 的商品图片智能搜索系统。不管你用 Python 还是 Java,思路都一样,这里以 Python 为例。
先爬一批商品图片(比如 10 万张),用预训练的图像模型(比如 ResNet50、CLIP)转换成 512 维向量。代码示例(用 CLIP):
import torch
from transformers import CLIPModel, CLIPProcessor
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
image = Image.open(image_path)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
embeddings = model.get_image_features(**inputs)
归一化向量(推荐,让搜索更稳定)
return embeddings.squeeze().tolist()
创建集合时,除了向量字段,还要加商品 ID、商品名、图片 URL 等标量字段,方便搜索后返回详细信息:
连接 Milvus
定义 schema:商品 ID、名称、图片 URL、向量
name_field = FieldSchema (name="name", dtype=DataType.VARCHAR, max_length=200)
url_field = FieldSchema (name="image_url", dtype=DataType.VARCHAR, max_length=500)
vector_field = FieldSchema (name="vector", dtype=DataType.FLOAT_VECTOR, dim=512) # CLIP 图像向量是 512 维
fields=[id_field, name_field, url_field, vector_field],
description="商品图片向量集合"
)
批量插入(每次插 1 万条,避免内存不够)
total = 100000 # 总数据量
end = min (i + batch_size, total)
product_ids = list (range (i, end))
names = [f"商品_{j}" for j in range (i, end)]
urls = [f"https://example.com/images/{j}.jpg" for j in range (i, end)]
vectors = [image_to_vector (f"images/{j}.jpg") for j in range (i, end)] # 假设图片存在本地
print (f"已插入 {end}/{total} 条数据")
图片向量常用余弦相似度(Cosine),对应的 metric_type 是 "COSINE"。索引用 HNSW,因为它在高维向量搜索中速度和精度都不错:
"index_type": "HNSW",
"metric_type": "COSINE",
"params": {"M": 16, "efConstruction": 200} # M 是邻居数量,efConstruction 控制建索引精度
}
collection.load()
用 FastAPI 写个简单的接口,接收图片,转向量,调用 Milvus 搜索:
import uvicorn
async def search_similar(file: UploadFile = File(...)):
保存上传的图片
f.write(await file.read())
转向量
搜索
"metric_type": "COSINE",
"params": {"ef": 64} # ef 控制搜索精度,越大越准但越慢
}
data=[query_vector],
anns_field="vector",
param=search_params,
limit=10,
output_fields=["product_id", "name", "image_url"]
)
整理结果
for hit in results [0]:
similar_products.append ({
"product_id": hit.entity.get ("product_id"),
"name": hit.entity.get ("name"),
"image_url": hit.entity.get ("image_url"),
"similarity": 1 - hit.distance # 余弦距离转相似度(1 - 距离)
})
uvicorn.run("main:app", host="0.0.0.0", port=8000)
希望这篇关于通过 Milvus SDK 构建智能搜索应用及多语言支持的攻略,能帮你在开发过程中少走弯路。若你对其中某些步骤或内容有疑问,欢迎随时交流。