通过eino-ext如何正常indexer RAG?

通过eino-ext如何正常indexer RAG?

整体架构

basg 复制代码
文档文本 ──→ ARK Embedder(向量化)──→ DocumentConverter(格式转换)──→ Milvus Indexer(写入)
                ↑                                                        ↑
        doubao-embedding-vision                              BinaryVector + HAMMING

安装配置

安装 向量数据库 - Milvus
yml 复制代码
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
bash 复制代码
docker compose up -d

这会启动 4 个服务:

  • etcd:Milvus 的元数据存储
  • MinIO:对象存储,用于持久化向量数据
  • Milvus standalone :向量数据库本身,端口 19530
  • Attu (可选):Milvus 可视化管理界面,端口 8000

访问 localhost:8000 可进入Attu 这是Milvus是可视化操作页面

配置相关文件

方舟控制台:来购买向量模型 (主要目前只有多模态向量模型)

env 复制代码
# 用于访问ARK服务的API密钥
ARK_API_KEY=
# 指定使用的向量模型
EMBEDDER=doubao-embedding-vision-251215    # 坑爹模型

代码介绍

Milvus 客户端初始化

go 复制代码
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

go 复制代码
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,运行时会报类型不匹配错误:

bash 复制代码
invalid type, expected []float32, got []uint8

dim 的单位是比特:

Milvus BinaryVectordim 参数单位是 比特(bit),不是字节(byte)。

项目
模型输出维度 2048 个值(每个 0~255)
字节数 2048 bytes
比特数 2048 × 8 = 16384 bits
Schema dim 应填 16384

如果错误地填了 2048(以为是字节数),Milvus 会认为每条数据的向量只有 2048 bit = 256 byte,但实际传入 2048 byte,导致报错:

bash 复制代码
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)

go 复制代码
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 端点:

go 复制代码
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 的结构体指针:

go 复制代码
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

go 复制代码
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,            // 自定义转换器
})

写入时逐条存入(也可以批量)

go 复制代码
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])
}

入口文件与文档定义

go 复制代码
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)
}
相关推荐
思麟呀2 小时前
网络层IP协议
linux·服务器·网络·网络协议·tcp/ip·计算机网络
donecoding3 小时前
遗嘱、水管与抢救室:TS 切入 Go 的流程控制、接口与并发
javascript·typescript·go
SilentSamsara3 小时前
TLS/HTTPS 实战:证书链、握手与生产配置
网络·数据库·网络协议·http·https
U盘失踪了3 小时前
URL 统一资源定位符详解
网络
爱学习的小囧3 小时前
ESXi/vCenter 批量开关虚拟机完整教程 | PowerCLI 一键 + 原生脚本循环,新手也能落地
运维·网络·数据库·esxi
bbq粉刷匠3 小时前
网络基础概念
网络·tcp/ip·计算机网络
studyForMokey3 小时前
【Android面试】动画 & Bitmap
android·面试·职场和发展
网域小星球3 小时前
C++ 从 0 入门(五)|C++ 面试必知:静态成员、友元、const 成员(高频考点)
开发语言·c++·面试·静态成员·友元函数
扉页的墨3 小时前
Go 协程泄漏排查实战:我是如何把线上内存从 500MB 压到 20MB 的
go