通过eino-ext如何正常indexer RAG?
整体架构
文档文本 ──→ ARK Embedder(向量化)──→ DocumentConverter(格式转换)──→ Milvus Indexer(写入)
↑ ↑
doubao-embedding-vision BinaryVector + HAMMING
安装配置
安装 向量数据库 - Milvus
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.18
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 30s
timeout: 20s
retries: 3
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001"
- "9000:9000"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.5.10
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
start_period: 90s
timeout: 20s
retries: 3
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- "etcd"
- "minio"
# 这是新增的 Attu 服务哦!
attu:
container_name: milvus-attu
image: zilliz/attu:v2.5
ports:
- "8000:3000" # 把本地的 8000 端口映射到容器的 3000 端口 (Attu 默认端口)
environment:
# MILVUS_URL 指向 Docker 网络里的 Milvus standalone 服务
MILVUS_URL: standalone:19530
depends_on:
- standalone # 确保 Milvus 启动后再启动 Attu
networks:
default:
name: milvus
docker compose up -d
这会启动 4 个服务:
- etcd:Milvus 的元数据存储
- MinIO:对象存储,用于持久化向量数据
- Milvus standalone:向量数据库本身,端口 19530
- Attu(可选):Milvus 可视化管理界面,端口 8000
访问 localhost:8000 可进入Attu 这是Milvus是可视化操作页面
配置相关文件
方舟控制台:来购买向量模型 (主要目前只有多模态向量模型)
# 用于访问ARK服务的API密钥
ARK_API_KEY=
# 指定使用的向量模型
EMBEDDER=doubao-embedding-vision-251215 # 坑爹模型
代码介绍
Milvus 客户端初始化
var MilvusCli cli.Client
func init() {
ctx := context.Background()
client, err := cli.NewClient(ctx, cli.Config{
Address: "localhost:19530", // Milvus 默认端口
})
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
MilvusCli = client
}
使用 init() 函数在包加载时自动建立连接。连接地址与 docker-compose 中映射的端口一致。
定义 Collection Schema
var fields = []*entity.Field{
{
Name: "id",
DataType: entity.FieldTypeVarChar,
TypeParams: map[string]string{"max_length": "255"},
PrimaryKey: true,
},
{
Name: "vector",
DataType: entity.FieldTypeBinaryVector, // 二进制向量(不是 FloatVector!)
TypeParams: map[string]string{
"dim": "16384", // ⚠️ 单位是比特(bit),不是字节
},
},
{
Name: "content",
DataType: entity.FieldTypeVarChar,
TypeParams: map[string]string{"max_length": "8192"},
},
{
Name: "metadata",
DataType: entity.FieldTypeJSON,
},
}
踩坑记录
FloatVector vs BinaryVector:
ARK 的 doubao-embedding-vision-* 模型输出的是 []uint8(二进制向量),值域 0~255,而不是常见的 []float32 浮点向量。
如果用 entity.FieldTypeFloatVector,运行时会报类型不匹配错误:
invalid type, expected []float32, got []uint8
dim 的单位是比特:
Milvus BinaryVector 的 dim 参数单位是 比特(bit),不是字节(byte)。
| 项目 | 值 |
|---|---|
| 模型输出维度 | 2048 个值(每个 0~255) |
| 字节数 | 2048 bytes |
| 比特数 | 2048 × 8 = 16384 bits |
| Schema dim 应填 | 16384 |
如果错误地填了 2048(以为是字节数),Milvus 会认为每条数据的向量只有 2048 bit = 256 byte,但实际传入 2048 byte,导致报错:
the num_rows (8) of field (vector) is not equal to passed num_rows (1)
这里的 8 = 2048 ÷ 256,即 Milvus 把 1 条数据的 2048 byte 当成了 8 条 256-byte 的数据。
初始化嵌入器(Embedder)
timeout := 30 * time.Second
apiType := ark.APITypeMultiModal // ⚠️ 多模态模型必须指定
embedder, err := ark.NewEmbedder(ctx, &ark.EmbeddingConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("EMBEDDER"), // doubao-embedding-vision-251215
APIType: &apiType,
Timeout: &timeout,
})
ARK 的嵌入 API 有两个不同的 HTTP 端点:
APITypeText APIType = "text_api" # `/api/v3/embeddings` | 纯文本嵌入模型
APITypeMultiModal APIType = "multi_modal_api" # `/api/v3/embeddings/multimodal` | 多模态嵌入模型
doubao-embedding-vision-* 模型只部署在 multimodal 端点上。
如果不设置 APIType,SDK 会默认走 text 端点,导致:
doubao-embedding-vision-251215 does not support this api (status code: 400)
判断方法:看模型名字是否包含 vision,包含就必须用 APITypeMultiModal。
自定义 DocumentConverter
查看 eino-ext 源码中默认 Converter 的实现(utils.go + types.go),发现它用的是带 milvus: tag 的结构体指针:
type binaryRow struct {
ID string `json:"id" milvus:"name:id"`
Content string `json:"content" milvus:"name:content"`
Vector []byte `json:"vector" milvus:"name:vector"` // BinaryVector 标量
Metadata []byte `json:"metadata" milvus:"name:metadata"` // JSON 序列化后的字节
}
func binaryDocumentConverter(_ context.Context, docs []*schema.Document, vectors [][]float64) ([]interface{}, error) {
rows := make([]interface{}, 0, len(docs))
for i, doc := range docs {
metadata, _ := json.Marshal(doc.MetaData) // 元数据需序列化为 []byte
byteVec := make([]byte, len(vectors[i]))
for j, v := range vectors[i] {
byteVec[j] = byte(v)
}
rows = append(rows, &binaryRow{ // ⚠️ 必须返回 struct 指针
ID: doc.ID,
Content: doc.Content,
Vector: byteVec,
Metadata: metadata,
})
}
return rows, nil
}
- struct 必须带
milvus:"name:xxx"tag:告诉 SDK 每个字段对应哪个列名 - Vector 字段类型是
[]byte:对应 BinaryVector - 返回值必须是 struct 指针(
&binaryRow{...}):不能是值或 map
组装 Indexer
indexer, err := milvus.NewIndexer(ctx, &milvus.IndexerConfig{
Client: MilvusCli,
Collection: collection, // 集合名: "AwesomeEino"
Fields: fields, // 上文定义的 Schema
Embedding: embedder, // ARK 嵌入器
MetricType: milvus.MetricType(entity.HAMMING), // 二进制向量用汉明距离
DocumentConverter: binaryDocumentConverter, // 自定义转换器
})
写入时逐条存入(也可以批量)
for _, doc := range docs {
storeDoc := []*schema.Document{{ID: doc.ID, Content: doc.Content, MetaData: doc.MetaData}}
ids, err := indexer.Store(ctx, storeDoc)
if err != nil {
log.Fatalf("Failed to store documents: %v", err)
}
println("Stored document ID:", ids[0])
}
入口文件与文档定义
func main() {
godotenv.Load(".env") // 加载环境变量
docs := []*schema.Document{
{ID: "doc1", Content: "Eino 是一个开源的 AI 应用开发框架...",
MetaData: map[string]interface{}{"source": "eino-docs"}},
{ID: "doc2", Content: "Milvus 是一个高性能向量数据库...",
MetaData: map[string]interface{}{"source": "milvus-docs"}},
}
stage4.IndexerRAG(docs)
}