Eino+Milvus实现简单的知识检索

40 阅读3分钟

Milvus安装

Milvus教程:milvus.io/docs/zh/

本地化界面工具:zilliz.com.cn/attu

安装Milvus(docker方式)

milvus.io/docs/zh/ins…

// 下载安装脚本
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh

// 启动docker容器
bash standalone_embed.sh start

启动后:

  1. 服务端口:19530
  2. Web访问:http://127.0.0.1:9091/webui/

安装远程连接工具

  1. 根据自己的系统选择对应版本下载 github.com/zilliztech/…

  2. 登录,首次访问正常不需要账号密码,如果提示可以使用下面的账号密码: user: root password: Milvus

  3. 登录成功后记得先修改密码:

截图20260109095104112.png

Eino开发

使用上面安装的Milvus作为向量数据库,Eino的RAG相关components已经集成了Milvus,可以很方便的使用Milvus实现Embedding、索引和检索功能。

Embedding

这里的Embedding模型使用Ollama下的qwen3-embedding:4b。

func getEmbedder(ctx context.Context) *ollama.Embedder {
    embedder, err := ollama.NewEmbedder(ctx, &ollama.EmbeddingConfig{
        BaseURL: ollamaBaseURL,
        Model:   ollamaEmbeddingModel,
        Timeout: 10 * time.Second,
    })
    if err != nil {
        log.Fatalf("NewEmbedder of ollama error: %v", err)
    }
    log.Printf("===== call Embedder directly =====")
    return embedder
}

func embedding(ctx context.Context, text string) {
    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }

    vectors, err := embedder.EmbedStrings(ctx, []string{text})
    if err != nil {
        log.Fatalf("EmbedStrings of Ollama failed, err=%v", err)
    }
    log.Printf("vectors : %v", vectors)
}

Indexer

将文档先进行向量化,再将向量结果存储到Mlivus并构建索引。

func indexer(ctx context.Context, docs []*schema.Document) {

    cli, err := client.NewClient(ctx, client.Config{
        Address:  milvusAddr,
        Username: milvusUsername,
        Password: milvusPassword,
    })
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer cli.Close()

    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }

    indexer, err := milvus.NewIndexer(ctx, &milvus.IndexerConfig{
        Client:    cli,
        Embedding: embedder,
    })
    if err != nil {
        log.Fatalf("Failed to create indexer: %v", err)
        return
    }
    log.Printf("Indexer created success")

    ids, err := indexer.Store(ctx, docs)
    if err != nil {
        log.Fatalf("Failed to store: %v", err)
    }
    log.Printf("Store success, ids: %v", ids)
}

Retriever

通过关键词实现基于向量相似度的文档检索

func retriever(ctx context.Context, query string) {
    cli, err := client.NewClient(ctx, client.Config{
        Address:  milvusAddr,
        Username: milvusUsername,
        Password: milvusPassword,
    })
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
        return
    }
    defer cli.Close()

    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }
    // Create a retriever
    retriever, err := milvusretriever.NewRetriever(ctx, &milvusretriever.RetrieverConfig{
        Client:      cli,
        Collection:  "",
        Partition:   nil,
        VectorField: "",
        OutputFields: []string{
            "id",
            "content",
            "metadata",
        },
        DocumentConverter: nil,
        MetricType:        "",
        TopK:              0,
        ScoreThreshold:    5,
        Sp:                nil,
        Embedding:         embedder,
    })
    if err != nil {
        log.Fatalf("Failed to create retriever: %v", err)
        return
    }

    // Retrieve documents
    documents, err := retriever.Retrieve(ctx, query)
    if err != nil {
        log.Fatalf("Failed to retrieve: %v", err)
        return
    }

    // Print the documents
    for i, doc := range documents {
        fmt.Printf("Document %d:\n", i)
        fmt.Printf("title: %s\n", doc.ID)
        fmt.Printf("content: %s\n", doc.Content)
        fmt.Printf("metadata: %v\n", doc.MetaData)
    }
}

完整代码

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/cloudwego/eino-ext/components/embedding/ollama"
    "github.com/cloudwego/eino-ext/components/indexer/milvus"
    milvusretriever "github.com/cloudwego/eino-ext/components/retriever/milvus"
    "github.com/cloudwego/eino/schema"
    "github.com/milvus-io/milvus-sdk-go/v2/client"
)

var (
    milvusAddr     = os.Getenv("MILVUS_ADDR")     // http://112.126.xx.xxx:19530
    milvusUsername = os.Getenv("MILVUS_USERNAME") // root
    milvusPassword = os.Getenv("MILVUS_PASSWORD") // password123

    ollamaBaseURL        = os.Getenv("OLLAMA_BASE_URL")    //  http://localhost:11434
    ollamaEmbeddingModel = os.Getenv("OLLAMA_EMBED_MODEL") // qwen3-embedding:4b
)

func getEmbedder(ctx context.Context) *ollama.Embedder {
    embedder, err := ollama.NewEmbedder(ctx, &ollama.EmbeddingConfig{
        BaseURL: ollamaBaseURL,
        Model:   ollamaEmbeddingModel,
        Timeout: 10 * time.Second,
    })
    if err != nil {
        log.Fatalf("NewEmbedder of ollama error: %v", err)
    }
    log.Printf("===== call Embedder directly =====")
    return embedder
}

func embedding(ctx context.Context, text string) {
    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }

    vectors, err := embedder.EmbedStrings(ctx, []string{text})
    if err != nil {
        log.Fatalf("EmbedStrings of Ollama failed, err=%v", err)
    }
    log.Printf("vectors : %v", vectors)
}

func indexer(ctx context.Context, docs []*schema.Document) {

    cli, err := client.NewClient(ctx, client.Config{
        Address:  milvusAddr,
        Username: milvusUsername,
        Password: milvusPassword,
    })
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer cli.Close()

    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }

    indexer, err := milvus.NewIndexer(ctx, &milvus.IndexerConfig{
        Client:    cli,
        Embedding: embedder,
    })
    if err != nil {
        log.Fatalf("Failed to create indexer: %v", err)
        return
    }
    log.Printf("Indexer created success")

    ids, err := indexer.Store(ctx, docs)
    if err != nil {
        log.Fatalf("Failed to store: %v", err)
    }
    log.Printf("Store success, ids: %v", ids)
}

func retriever(ctx context.Context, query string) {
    cli, err := client.NewClient(ctx, client.Config{
        Address:  milvusAddr,
        Username: milvusUsername,
        Password: milvusPassword,
    })
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
        return
    }
    defer cli.Close()

    embedder := getEmbedder(ctx)
    if embedder == nil {
        return
    }
    // Create a retriever
    retriever, err := milvusretriever.NewRetriever(ctx, &milvusretriever.RetrieverConfig{
        Client:      cli,
        Collection:  "",
        Partition:   nil,
        VectorField: "",
        OutputFields: []string{
            "id",
            "content",
            "metadata",
        },
        DocumentConverter: nil,
        MetricType:        "",
        TopK:              0,
        ScoreThreshold:    5,
        Sp:                nil,
        Embedding:         embedder,
    })
    if err != nil {
        log.Fatalf("Failed to create retriever: %v", err)
        return
    }

    // Retrieve documents
    documents, err := retriever.Retrieve(ctx, query)
    if err != nil {
        log.Fatalf("Failed to retrieve: %v", err)
        return
    }

    // Print the documents
    for i, doc := range documents {
        fmt.Printf("Document %d:\n", i)
        fmt.Printf("title: %s\n", doc.ID)
        fmt.Printf("content: %s\n", doc.Content)
        fmt.Printf("metadata: %v\n", doc.MetaData)
    }
}


func main() {
    ctx := context.Background()

    // Embedding:将文本转换成向量
    text := "你好,听说你是房产经纪人 可以推荐我一些适合的房源吗?"
    embedding(ctx, text)

    // 将文档先转换成向量再索引到 Milvus 中
    indexer(ctx, []*schema.Document{
        {
            ID:      "milvus-1",
            Content: "milvus is an open-source vector database",
            MetaData: map[string]any{
                "h1": "milvus",
                "h2": "open-source",
                "h3": "vector database",
            },
        },
        {
            ID:      "milvus-2",
            Content: "milvus is a distributed vector database",
        },
    })

    // 从 Milvus 中检索向量
    retriever(ctx, "milvus")
}