通过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 端点,导致:

java 复制代码
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)
}
相关推荐
苏三说技术44 分钟前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎2 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha2 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn2 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425912 小时前
ShardingJDBC
后端
行者全栈架构师2 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端