痛点:把“文本内容”转成向量后,如何在本地 Redis 里做近似向量搜索(KNN),而不依赖外部向量数据库?
方案:
sentence-transformers/all-MiniLM-L6-v2
生成 384 维 Float32 向量;FTCreate / FTSearch / FTAggregate
)完成端到端流程。# 初始化 Go Module
go mod init vecexample
# 安装依赖
go get github.com/redis/go-redis/v9
go get github.com/henomis/lingoose/embedder/huggingface
⚠️ Redis 服务器
- 启动模块:
redis-stack
、redis-stack-server
或自行加载redisearch.so
+rejson.so
- go-redis 建议 Protocol: 2 (RESP2),避免 RESP3 生僻结构的解析开销。
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Protocol: 2, // RESP2 更省心
})
_, _ = rdb.FTDropIndexWithArgs(ctx,
"vector_idx",
&redis.FTDropIndexOptions{DeleteDocs: true},
) // 防止重复
_, err := rdb.FTCreate(ctx, "vector_idx",
&redis.FTCreateOptions{
OnHash: true,
Prefix: []any{"doc:"}, // 监听 doc:* 键
},
&redis.FieldSchema{FieldName: "content", FieldType: redis.SearchFieldTypeText},
&redis.FieldSchema{FieldName: "genre", FieldType: redis.SearchFieldTypeTag},
&redis.FieldSchema{
FieldName: "embedding",
FieldType: redis.SearchFieldTypeVector,
VectorArgs: &redis.FTVectorArgs{
HNSWOptions: &redis.FTHNSWOptions{
Dim: 384, Type: "FLOAT32", DistanceMetric: "L2",
},
},
},
).Result()
if err != nil { panic(err) }
hf := huggingfaceembedder.New().
WithToken(os.Getenv("HF_TOKEN")). // HuggingFace 访问令牌
WithModel("sentence-transformers/all-MiniLM-L6-v2")
sentences := []string{
"That is a very happy person",
"That is a happy dog",
"Today is a sunny day",
}
tags := []string{"persons", "pets", "weather"}
embs, _ := hf.Embed(ctx, sentences)
func floatsToBytes(fs []float32) []byte {
buf := make([]byte, len(fs)*4)
for i, f := range fs {
binary.LittleEndian.PutUint32(buf[i*4:], math.Float32bits(f))
}
return buf
}
for i, emb := range embs {
_, _ = rdb.HSet(ctx, fmt.Sprintf("doc:%d", i), map[string]any{
"content": sentences[i],
"genre": tags[i],
"embedding": floatsToBytes(emb.ToFloat32()),
}).Result()
}
qEmb, _ := hf.Embed(ctx, []string{"That is a happy person"})
buf := floatsToBytes(qEmb[0].ToFloat32())
res, _ := rdb.FTSearchWithArgs(ctx,
"vector_idx",
"*=>[KNN 3 @embedding $vec AS score]",
&redis.FTSearchOptions{
Return: []redis.FTSearchReturn{
{FieldName: "score"},
{FieldName: "content"},
},
Params: map[string]any{"vec": buf},
DialectVersion: 2,
},
).Result()
for _, d := range res.Docs {
fmt.Printf("%s\t%v\n", d.Fields["content"], d.Fields["score"])
}
输出示例
That is a very happy person 0.114...
That is a happy dog 0.610...
Today is a sunny day 1.486...
agg, _ := rdb.FTAggregateWithArgs(ctx,
"vector_idx", "*",
&redis.FTAggregateOptions{
GroupBy: []redis.FTAggregateGroupBy{
{
Fields: []any{"@genre"},
Reduce: []redis.FTAggregateReducer{
{Reducer: redis.SearchCount, As: "cnt"},
},
},
},
},
).Result()
for _, row := range agg.Rows {
fmt.Printf("%s : %v\n", row.Fields["genre"], row.Fields["cnt"])
}
步骤 | Hash | JSON |
---|---|---|
FTCreate | OnHash:true |
OnJSON:true ;字段用 $.path + As |
写入方式 | HSet("doc:*", ...) + 字节串 |
JSONSet("jdoc:*", "$", ...) + []float32 原样 |
查询参数 | 依旧传字节串 | 依旧传字节串(参数统一编码) |
结果字段 | 字段直接展开 | 字段在 Fields["$"] 内或按别名返回 |
_, _ = rdb.FTCreate(ctx, "vector_json_idx",
&redis.FTCreateOptions{OnJSON: true, Prefix: []any{"jdoc:"}},
&redis.FieldSchema{FieldName: "$.content", As: "content", FieldType: redis.SearchFieldTypeText},
&redis.FieldSchema{FieldName: "$.genre", As: "genre", FieldType: redis.SearchFieldTypeTag},
&redis.FieldSchema{FieldName: "$.embedding", As: "embedding",
FieldType: redis.SearchFieldTypeVector,
VectorArgs: &redis.FTVectorArgs{
HNSWOptions: &redis.FTHNSWOptions{Dim: 384, Type: "FLOAT32", DistanceMetric: "L2"},
},
},
).Result()
for i, emb := range embs {
_, _ = rdb.JSONSet(ctx, fmt.Sprintf("jdoc:%d", i), "$", map[string]any{
"content": sentences[i],
"genre": tags[i],
"embedding": emb.ToFloat32(), // 直接存数组!
}).Result()
}
问题 | 可能原因 / 解决 |
---|---|
ERR unknown index name |
忘记先 FT.CREATE 或 Index 名写错 |
查询报 Property is not a vector |
向量字段未被识别:检查 FieldType、Dim、Type |
向量搜索速度慢 | 调整 HNSW 参数 EF_CONSTRUCTION / M ,或增加内存 |
RESP3 解析困难 | 使用 Protocol:2 ,或调用 RawResult() 自行解析 |
通过 go-redis + RediSearch,你可以在本地 Redis 轻松实现:
这使得 Redis 成为“小而全”的 实时语义检索引擎。复制本文代码,即刻在你的业务中解锁 AI 搜索能力吧!