Skip to content
鼓励作者:欢迎打赏犒劳

eino整合RAG

Milvus 官方文档:https://milvus.io/docs

支持的向量数据库

根据 Eino-ext 文档,目前已集成以下方案:

数据库Eino 集成特点适用场景
Milvus✅ Indexer + Retriever云原生、分布式、GPU 加速大规模生产环境(千万级+)
Elasticsearch✅ Indexer + Retriever支持向量+全文混合检索已有 ES 基础设施的企业
OpenSearch✅ Indexer + Retriever成熟的混合查询能力日志+搜索+向量的统一平台
Qdrant✅ RetrieverRust 实现、高性能过滤需要复杂过滤条件的场景
Redis✅ Retriever超低延迟、KNN + Range 搜索实时推荐、缓存场景
PgVector社区支持PostgreSQL 扩展、ACID 事务已有 PG 主存储的场景
数据规模评估
├── <10万条 → Chroma / 内存方案(原型验证)
├── 10万~1000万 → Qdrant / PgVector / 单机 Milvus
└── >1000万条 → 分布式 Milvus / Elasticsearch / OpenSearch
查询模式评估
├── 纯向量检索 → Milvus / Qdrant(性能最优)
├── 混合检索(向量+全文) → Elasticsearch / OpenSearch / Weaviate
└── 复杂元数据过滤 → Qdrant / Weaviate(支持多级嵌套条件)

Eino 集成示例

docker安装Milvus

shell
# 下载 standalone 模式配置文件(以 v2.5.5 为例)
wget https://github.com/milvus-io/milvus/releases/download/v2.5.5/milvus-standalone-docker-compose.yml -O docker-compose.yml

# 后台启动所有容器
docker compose up -d

##  Attu 图形界面管理 Milvus  打开浏览器,访问 http://localhost:3000。
docker run -d --name attu -p 3000:3000 zilliz/attu:latest
容器名称角色定位核心功能类比说明
milvus-standalone数据库引擎负责处理所有核心的向量搜索、索引构建和数据管理请求,是整个系统的“大脑”和“执行者”。相当于 MySQL 服务本身
milvus-etcd元数据管理存储Milvus所有的“元数据”,比如:有哪些集合(Collection)、集合的 Schema 定义、分区信息等。相当于 MySQL 的 information_schema 系统表
milvus-minio数据存储负责真正存储所有向量数据和日志文件,是系统的“数据仓库”。相当于你的电脑硬盘

使用 Milvus(推荐)

由代码自动创建表,没有指定数据库,默认是库是用的default

go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strings"

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

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

	// --- 1. 初始化 Embedder ---
	embedder, err := ollama.NewEmbedder(ctx, &ollama.EmbeddingConfig{
		BaseURL: "http://localhost:11434",
		Model:   "nomic-embed-text",
	})
	if err != nil {
		log.Fatalf("创建 Embedder 失败: %v", err)
	}
	fmt.Println("✅ Embedder 初始化成功")

	// 2. 创建 Milvus 客户端
	milvusAddr := "192.168.154.131:19530"
	milvusCli, err := milvusclient.NewGrpcClient(ctx, milvusAddr)
	if err != nil {
		log.Fatalf("创建 Milvus 客户端失败: %v", err)
	}
	defer milvusCli.Close()
	fmt.Printf("✅ Milvus 客户端连接成功 (%s)\n", milvusAddr)

	// 删除已存在的集合
	collectionName := "my_knowledge"
	err = milvusCli.DropCollection(ctx, collectionName)
	if err == nil {
		fmt.Printf("已删除旧集合: %s\n", collectionName)
	}

	// 3. 创建集合
	fmt.Println("正在创建集合...")

	// 定义字段
	collSchema := &entity.Schema{
		CollectionName: collectionName,
		Fields: []*entity.Field{
			entity.NewField().
				WithName("id").
				WithDataType(entity.FieldTypeInt64).
				WithIsPrimaryKey(true).
				WithIsAutoID(true),
			entity.NewField().
				WithName("content").
				WithDataType(entity.FieldTypeVarChar).
				WithMaxLength(65535),
			entity.NewField().
				WithName("vector").
				WithDataType(entity.FieldTypeFloatVector).
				WithDim(768),
			entity.NewField().
				WithName("metadata").
				WithDataType(entity.FieldTypeJSON),
		},
	}

	// 创建集合
	err = milvusCli.CreateCollection(ctx, collSchema, 1)
	if err != nil {
		log.Fatalf("创建集合失败: %v", err)
	}
	fmt.Printf("✅ 集合 %s 创建成功\n", collectionName)

	// 创建 FLAT 索引
	idx, err := entity.NewIndexFlat(entity.L2)
	if err != nil {
		log.Fatalf("创建索引失败: %v", err)
	}
	err = milvusCli.CreateIndex(ctx, collectionName, "vector", idx, false)
	if err != nil {
		log.Fatalf("创建索引失败: %v", err)
	}
	fmt.Println("✅ FLAT 索引创建成功")

	// 加载集合
	err = milvusCli.LoadCollection(ctx, collectionName, false)
	if err != nil {
		log.Fatalf("加载集合失败: %v", err)
	}
	fmt.Println("✅ 集合加载成功")

	// 4. 准备数据
	docs := []*einoschema.Document{
		{
			Content: "Go语言由Google开发,于2009年正式发布,是一种静态强类型、编译型、并发型编程语言。",
			MetaData: map[string]any{
				"category":  "基础",
				"topic":     "Go语言历史",
				"tags":      []string{"google", "编程语言", "静态类型"},
				"create_at": "2024-01-15",
			},
		},
		{
			Content: "Go语言的goroutine是轻量级线程,只需要2KB栈空间,可以轻松创建数百万个并发任务。",
			MetaData: map[string]any{
				"category":  "并发",
				"topic":     "goroutine",
				"tags":      []string{"goroutine", "并发", "轻量级线程"},
				"create_at": "2024-02-20",
			},
		},
		{
			Content: "Eino是CloudWeGo开源的AI应用开发框架,专为Go语言设计,提供流式处理、可观测性等特性。",
			MetaData: map[string]any{
				"category":  "框架",
				"topic":     "Eino框架",
				"tags":      []string{"Eino", "CloudWeGo", "AI框架"},
				"create_at": "2024-03-10",
			},
		},
	}

	// 5. 生成向量
	texts := make([]string, len(docs))
	for i, doc := range docs {
		texts[i] = doc.Content
	}
	vectors, err := embedder.EmbedStrings(ctx, texts)
	if err != nil {
		log.Fatalf("生成向量失败: %v", err)
	}
	fmt.Printf("✅ 成功生成 %d 个向量,维度: %d\n", len(vectors), len(vectors[0]))

	// 6. 插入数据
	contentData := make([]string, len(docs))
	vectorData := make([][]float32, len(docs))
	metadataData := make([][]byte, len(docs))

	for i, doc := range docs {
		contentData[i] = doc.Content
		vectorData[i] = make([]float32, len(vectors[i]))
		for j, v := range vectors[i] {
			vectorData[i][j] = float32(v)
		}
		metadataBytes, _ := json.Marshal(doc.MetaData)
		metadataData[i] = metadataBytes
	}

	contentCol := entity.NewColumnVarChar("content", contentData)
	vectorCol := entity.NewColumnFloatVector("vector", 768, vectorData)
	metadataCol := entity.NewColumnJSONBytes("metadata", metadataData)

	_, err = milvusCli.Insert(ctx, collectionName, "", contentCol, vectorCol, metadataCol)
	if err != nil {
		log.Fatalf("插入数据失败: %v", err)
	}
	fmt.Printf("✅ 成功插入 %d 条数据\n", len(docs))

	// 刷新数据
	err = milvusCli.Flush(ctx, collectionName, false)
	if err != nil {
		log.Fatalf("刷新数据失败: %v", err)
	}
	fmt.Println("✅ 数据刷新成功")

	// 7. 初始化 Retriever
	ret, err := milvusretriever.NewRetriever(ctx, &milvusretriever.RetrieverConfig{
		Client:       milvusCli,
		Collection:   collectionName,
		Embedding:    embedder,
		MetricType:   entity.L2,
		OutputFields: []string{"content", "metadata"},
		VectorConverter: func(ctx context.Context, vectors [][]float64) ([]entity.Vector, error) {
			result := make([]entity.Vector, len(vectors))
			for i, v := range vectors {
				float32Vec := make([]float32, len(v))
				for j, val := range v {
					float32Vec[j] = float32(val)
				}
				result[i] = entity.FloatVector(float32Vec)
			}
			return result, nil
		},
		DocumentConverter: func(ctx context.Context, sr client.SearchResult) ([]*einoschema.Document, error) {
			var results []*einoschema.Document
			contentCol := sr.Fields.GetColumn("content")
			metadataCol := sr.Fields.GetColumn("metadata")
			for i := 0; i < sr.ResultCount; i++ {
				content := ""
				if contentCol != nil {
					content, _ = contentCol.GetAsString(i)
				}
				metadata := make(map[string]any)
				if metadataCol != nil {
					metadataStr, _ := metadataCol.GetAsString(i)
					if metadataStr != "" {
						json.Unmarshal([]byte(metadataStr), &metadata)
					}
				}
				doc := &einoschema.Document{
					Content:  content,
					MetaData: metadata,
				}
				doc.WithScore(float64(sr.Scores[i]))
				results = append(results, doc)
			}
			return results, nil
		},
	})
	if err != nil {
		log.Fatalf("创建 Retriever 失败: %v", err)
	}
	fmt.Println("✅ Retriever 初始化成功")

	// 8. 执行检索测试
	testQueries := []string{
		"Go语言的并发有什么特点?",
		"Go适合AI开发吗?",
		"Eino是什么框架?",
	}

	fmt.Println("\n" + strings.Repeat("=", 60))
	fmt.Println("开始检索测试")
	fmt.Println(strings.Repeat("=", 60))

	for _, query := range testQueries {
		fmt.Printf("\n📝 查询: %s\n", query)

		results, err := ret.Retrieve(ctx, query, retriever.WithEmbedding(embedder))
		if err != nil {
			log.Printf("检索失败: %v\n", err)
			continue
		}

		fmt.Printf("检索到 %d 条相关文档:\n", len(results))
		for i, doc := range results {
			fmt.Printf("\n  [%d] 相似度得分: %.4f\n", i+1, doc.Score())
			fmt.Printf("      Content: %s\n", truncateString(doc.Content, 80))
			if len(doc.MetaData) > 0 {
				fmt.Printf("      MetaData: %+v\n", doc.MetaData)
			}
		}
		fmt.Println("\n" + strings.Repeat("-", 60))
	}
}

func truncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

测试

用的是本地map的方式快速验证

所需要的模型

shell
# Embedding 模型(必需)
ollama pull nomic-embed-text

# LLM 模型(用于生成回答)
ollama pull qwen2.5:3b

测试代码

go
package main

import (
	"context"
	"fmt"
	"math"
	"sort"
	"strings"

	ollamaembed "github.com/cloudwego/eino-ext/components/embedding/ollama"
	ollamachat "github.com/cloudwego/eino-ext/components/model/ollama"
	"github.com/cloudwego/eino/schema"
)

// Document 文档结构
type Document struct {
	Content   string
	Metadata  map[string]string
	Embedding []float64
}

// VectorStore 简单的向量存储
type VectorStore struct {
	documents []Document
}

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

	// 1. 初始化 Embedder(用于文本转向量)
	fmt.Println("正在初始化 Embedder...")
	embedder, err := ollamaembed.NewEmbedder(ctx, &ollamaembed.EmbeddingConfig{
		BaseURL: "http://localhost:11434",
		Model:   "nomic-embed-text", // 专业的 embedding 模型
	})
	if err != nil {
		fmt.Printf("初始化 Embedder 失败: %v\n", err)
		fmt.Println("请先运行: ollama pull nomic-embed-text")
		return
	}

	// 2. 准备真实数据(关于 Go 语言和 AI 的知识库)
	documents := []Document{
		{
			Content:  "Go语言由Google开发,于2009年正式发布,是一种静态强类型、编译型、并发型编程语言。",
			Metadata: map[string]string{"category": "基础", "topic": "Go语言历史"},
		},
		{
			Content:  "Go语言的goroutine是轻量级线程,只需要2KB栈空间,可以轻松创建数百万个并发任务。",
			Metadata: map[string]string{"category": "并发", "topic": "goroutine"},
		},
		{
			Content:  "Go语言的channel是goroutine之间通信的主要方式,遵循'不要通过共享内存来通信,而要通过通信来共享内存'的理念。",
			Metadata: map[string]string{"category": "并发", "topic": "channel"},
		},
		{
			Content:  "在AI开发中,Python拥有TensorFlow、PyTorch等丰富框架,而Go在部署和高性能服务方面有优势。",
			Metadata: map[string]string{"category": "AI", "topic": "AI开发对比"},
		},
		{
			Content:  "Eino是CloudWeGo开源的AI应用开发框架,专为Go语言设计,提供流式处理、可观测性等特性。",
			Metadata: map[string]string{"category": "框架", "topic": "Eino框架"},
		},
		{
			Content:  "Go语言在Web服务方面性能优秀,标准库net/http简单易用,常用于构建微服务和API网关。",
			Metadata: map[string]string{"category": "Web", "topic": "Web开发"},
		},
		{
			Content:  "RAG(检索增强生成)结合了信息检索和LLM,可以基于私有知识库回答问题,避免幻觉问题。",
			Metadata: map[string]string{"category": "AI", "topic": "RAG技术"},
		},
		{
			Content:  "使用Eino框架构建RAG应用时,可以使用Indexer组件管理向量索引,Retriever组件进行检索。",
			Metadata: map[string]string{"category": "框架", "topic": "Eino RAG"},
		},
	}

	// 3. 生成所有文档的向量并存储
	fmt.Println("\n正在生成文档向量...")
	vectorStore := &VectorStore{
		documents: make([]Document, len(documents)),
	}

	for i, doc := range documents {
		embeddings, err := embedder.EmbedStrings(ctx, []string{doc.Content})
		if err != nil {
			fmt.Printf("生成文档 %d 向量失败: %v\n", i+1, err)
			return
		}
		vectorStore.documents[i] = doc
		vectorStore.documents[i].Embedding = embeddings[0]
		fmt.Printf("✓ 已处理文档 %d/%d: %s\n", i+1, len(documents), truncateString(doc.Content, 40))
	}

	// 4. 初始化 LLM 模型(用于生成回答)
	fmt.Println("\n正在初始化 LLM 模型...")
	chatModel, err := ollamachat.NewChatModel(ctx, &ollamachat.ChatModelConfig{
		BaseURL: "http://localhost:11434",
		Model:   "qwen2.5:3b",
	})
	if err != nil {
		fmt.Printf("初始化 LLM 失败: %v\n", err)
		return
	}

	// 5. 测试查询
	testQueries := []string{
		"Go语言的并发有什么特点?",
		"什么是RAG技术?",
		"Go适合AI开发吗?",
		"Eino是什么框架?",
	}

	fmt.Println("\n" + strings.Repeat("=", 60))
	fmt.Println("开始 RAG 查询测试")
	fmt.Println(strings.Repeat("=", 60))

	for _, query := range testQueries {
		fmt.Printf("\n📝 用户问题: %s\n", query)
		fmt.Println(strings.Repeat("-", 60))

		// 6. 将查询问题转向量
		queryEmbedding, err := embedder.EmbedStrings(ctx, []string{query})
		if err != nil {
			fmt.Printf("生成查询向量失败: %v\n", err)
			continue
		}

		// 7. 检索最相关的 Top-2 文档
		topDocs := vectorStore.Search(queryEmbedding[0], 2)

		fmt.Println("📚 检索到的相关文档:")
		for i, doc := range topDocs {
			fmt.Printf("  [%d] 相似度: %.4f | 分类: %s | 内容: %s\n",
				i+1, doc.Score, doc.Doc.Metadata["category"],
				truncateString(doc.Doc.Content, 50))
		}

		// 8. 构建 RAG Prompt
		context := ""
		for i, doc := range topDocs {
			context += fmt.Sprintf("文档%d: %s\n", i+1, doc.Doc.Content)
		}

		prompt := fmt.Sprintf(`基于以下已知信息,回答问题。如果无法从提供的信息中找到答案,请说明信息不足。

已知信息:
%s

问题:%s

请基于上述信息给出准确、简洁的回答:`, context, query)

		// 9. 调用 LLM 生成回答
		messages := []*schema.Message{
			schema.SystemMessage("你是一个专业的AI助手,请基于提供的上下文回答问题。"),
			schema.UserMessage(prompt),
		}

		resp, err := chatModel.Generate(ctx, messages)
		if err != nil {
			fmt.Printf("生成回答失败: %v\n", err)
			continue
		}

		fmt.Printf("🤖 AI 回答:\n%s\n", resp.Content)
		fmt.Println(strings.Repeat("=", 60))
	}
}

// Search 向量检索(余弦相似度)
func (vs *VectorStore) Search(queryEmbedding []float64, topK int) []SearchResult {
	results := make([]SearchResult, len(vs.documents))

	for i, doc := range vs.documents {
		similarity := cosineSimilarity(queryEmbedding, doc.Embedding)
		results[i] = SearchResult{
			Doc:   doc,
			Score: similarity,
			Index: i,
		}
	}

	// 按相似度排序
	sort.Slice(results, func(i, j int) bool {
		return results[i].Score > results[j].Score
	})

	// 返回 Top-K
	if topK > len(results) {
		topK = len(results)
	}
	return results[:topK]
}

// SearchResult 检索结果
type SearchResult struct {
	Doc   Document
	Score float64
	Index int
}

// 余弦相似度计算
func cosineSimilarity(a, b []float64) float64 {
	if len(a) != len(b) {
		return 0
	}

	var dotProduct, normA, normB float64
	for i := 0; i < len(a); i++ {
		dotProduct += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}

	if normA == 0 || normB == 0 {
		return 0
	}

	return dotProduct / (math.Sqrt(normA) * math.Sqrt(normB))
}

// 辅助函数:截断字符串
func truncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

// 注意:需要在文件顶部导入 strings 包
// import "strings"

结果:

text
正在初始化 Embedder...

正在生成文档向量...
✓ 已处理文档 1/8: Go语言由Google开发,于2009年正�...
✓ 已处理文档 2/8: Go语言的goroutine是轻量级线程�...
✓ 已处理文档 3/8: Go语言的channel是goroutine之间通�...
✓ 已处理文档 4/8: 在AI开发中,Python拥有TensorFlow�...
✓ 已处理文档 5/8: Eino是CloudWeGo开源的AI应用开发�...
✓ 已处理文档 6/8: Go语言在Web服务方面性能优秀�...
✓ 已处理文档 7/8: RAG(检索增强生成)结合了信�...
✓ 已处理文档 8/8: 使用Eino框架构建RAG应用时,可...

正在初始化 LLM 模型...

============================================================
开始 RAG 查询测试
============================================================

📝 用户问题: Go语言的并发有什么特点?
------------------------------------------------------------
📚 检索到的相关文档:
  [1] 相似度: 0.8040 | 分类: 框架 | 内容: Eino是CloudWeGo开源的AI应用开发框架,�...
  [2] 相似度: 0.7114 | 分类: AI | 内容: 在AI开发中,Python拥有TensorFlow、PyTorch�...
🤖 AI 回答:
信息不足。提供的信息没有提及关于Go语言并发的具体特点。
============================================================

📝 用户问题: 什么是RAG技术?
------------------------------------------------------------
📚 检索到的相关文档:
  [1] 相似度: 0.8763 | 分类: 框架 | 内容: Eino是CloudWeGo开源的AI应用开发框架,�...
  [2] 相似度: 0.7911 | 分类: 框架 | 内容: 使用Eino框架构建RAG应用时,可以使用I...
🤖 AI 回答:
RAG技术是使用Eino框架中的Indexer和Retriever组件来管理向量索引并进行检索的AI应用开发方法。
============================================================

📝 用户问题: Go适合AI开发吗?
------------------------------------------------------------
📚 检索到的相关文档:
  [1] 相似度: 0.7392 | 分类: AI | 内容: RAG(检索增强生成)结合了信息检索�...
  [2] 相似度: 0.6796 | 分类: 框架 | 内容: Eino是CloudWeGo开源的AI应用开发框架,�...
🤖 AI 回答:
信息不足。
============================================================

📝 用户问题: Eino是什么框架?
------------------------------------------------------------
📚 检索到的相关文档:
  [1] 相似度: 0.8763 | 分类: 框架 | 内容: Eino是CloudWeGo开源的AI应用开发框架,�...
  [2] 相似度: 0.7911 | 分类: 框架 | 内容: 使用Eino框架构建RAG应用时,可以使用I...
🤖 AI 回答:
Eino是CloudWeGo开源的AI应用开发框架,专为Go语言设计。
============================================================

如有转载或 CV 的请标注本站原文地址