当前位置: 首页 > news >正文

基于 Eino 框架从零搭建一个 RAG

基于 CloudWeGo Eino 从零搭建 Go 语言 RAG 检索增强系统

前言

大语言模型(LLM)在落地企业场景时,普遍面临知识时效性差、专业领域内容缺失、生成幻觉等问题。检索增强生成(RAG)通过「先检索外部知识库、再基于参考内容生成答案」的思路,低成本且高效地解决了上述痛点,已成为构建企业级 AI 应用的核心技术方案。

CloudWeGo 推出的Eino是一款 Go 生态的大模型应用开发框架,它对主流大模型服务、向量数据库、文档处理工具做了统一抽象与封装,开发者无需对接各类异构 SDK,仅通过几行代码即可组装出完整的 RAG 链路。本文将基于 Eino 框架,从组件拆解到完整落地,一步步搭建一个可运行的 RAG 知识库系统。


一、RAG 核心组件拆解

在 Eino 的设计中,一个完整的 RAG 系统由以下核心组件构成,各司其职且可灵活插拔:

组件核心作用通俗类比
ChatModel与大语言模型直接交互,负责最终答案生成答题的「考生」
ChatTemplate结构化 Prompt 模板,统一管理系统提示、上下文、检索结果标准化答题卡 + 答题规则
Embedding将文本转换为高维向量,是语义检索的基础把文字翻译成「向量语言」
Indexer将文档向量化后存入向量数据库,建立索引整理并装订「小抄本」
Retriever根据用户问题从向量库召回最相关的文档片段从「小抄本」里翻找对应知识点
Transformer文档预处理:分割、过滤、格式转换、合并等把长文档裁成适合翻阅的小片段
Tool大模型可调用的外部工具(扩展能力)考生可以使用的计算器、词典

整条 RAG 的核心链路为:
原始文档 → Transformer 切片 → Embedding 向量化 → Indexer 入库 → 用户提问 → Retriever 检索 → ChatTemplate 组装 Prompt → ChatModel 生成答案


二、前置准备

2.1 环境与依赖

  • Go 1.21 及以上版本
  • 已部署的 Milvus 向量数据库(本文使用 Milvus 2.x 独立部署)
  • 火山方舟(字节跳动大模型平台)API Key(用于调用豆包大模型与 Embedding 服务)

2.2 项目初始化

创建项目并安装核心依赖:

mkdirEino_frame&&cdEino_frame go mod init Eino_frame# 安装 Eino 核心与扩展包go get github.com/cloudwego/eino go get github.com/cloudwego/eino-ext go get github.com/milvus-io/milvus-sdk-go/v2 go get github.com/joho/godotenv

2.3 环境变量配置

在项目根目录创建.env文件,统一管理密钥与模型配置:

# .env ARK_API_KEY=your_ark_api_key_here MODEL=doubao-pro-32k # 对话模型名称 EMBEDDER=doubao-embedding-text-240515 # 向量嵌入模型名称

2.4 项目结构

最终项目目录结构如下:

Eino_frame/ ├── main.go # 主程序入口,串联全流程 ├── .env # 环境变量 ├── RAG_introduction.md # 测试知识库文档 └── rag/ ├── milvus_client.go # Milvus 客户端初始化 ├── embedding.go # 向量嵌入组件 ├── transformer.go # 文档分割转换组件 ├── indexer.go # 索引存储组件 ├── retriever.go # 检索组件 ├── chat_model.go # 大模型对话组件 └── prompt.go # ChatTemplate 提示词模板

三、核心组件代码实现

我们将所有 RAG 能力封装在rag包下,保证职责单一、可复用。

3.1 Milvus 客户端初始化

所有向量操作都依赖 Milvus 客户端,做全局单例初始化。

rag/milvus_client.go

packageragimport("context""github.com/milvus-io/milvus-sdk-go/v2/client""log")// MilvusCli 全局 Milvus 客户端实例varMilvusCli client.Client// InitMilvusClient 初始化 Milvus 连接funcInitMilvusClient(){ctx:=context.Background()cli,err:=client.NewClient(ctx,client.Config{Address:"39.105.40.22:19530",// Milvus 服务地址DBName:"EinoFrame",// 目标数据库名})iferr!=nil{log.Fatalf("初始化 Milvus 客户端失败: %v",err)}MilvusCli=cli log.Println("Milvus 客户端初始化成功")}

💡 注意:正式创建 Indexer 前,必须确保 Milvus 服务已启动,且对应数据库已提前创建。

3.2 Embedding 文本向量化组件

Embedding 负责将文本转换为高维向量,是实现语义相似度匹配的基础。这里我们使用火山方舟的 Embedding 服务。

rag/embedding.go

packageragimport("context""github.com/cloudwego/eino-ext/components/embedding/ark""os""log")// NewArkEmbedding 创建火山方舟向量嵌入器funcNewArkEmbedding(ctx context.Context)*ark.Embedder{apiType:=ark.APITypeMultiModal embedder,err:=ark.NewEmbedder(ctx,&ark.EmbeddingConfig{APIKey:os.Getenv("ARK_API_KEY"),Model:os.Getenv("EMBEDDER"),APIType:&apiType,})iferr!=nil{log.Printf("创建 Embedding 失败: %v",err)returnnil}returnembedder}

3.3 Transformer 文档分割组件

长文档无法直接向量化和精准检索,需要先按语义切分为多个小块。Eino 内置了 Markdown 标题分割器,非常适合技术文档、博客等结构化内容。

rag/transformer.go

packageragimport("context""github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown""github.com/cloudwego/eino/components/document""log")// NewArkTransformer 创建 Markdown 标题分割器// 按 #、##、### 标题层级切割文档,保留标题结构funcNewArkTransformer(ctx context.Context)document.Transformer{splitter,err:=markdown.NewHeaderSplitter(ctx,&markdown.HeaderConfig{Headers:map[string]string{"#":"h1","##":"h2","###":"h3",},TrimHeaders:false,// 保留标题在片段内容中})iferr!=nil{log.Printf("创建文档分割器失败: %v",err)returnnil}returnsplitter}

3.4 Indexer 索引存储组件

Indexer 是「做小抄」的核心组件:它自动调用 Embedding 将文档片段向量化,然后写入 Milvus 集合中。

⚠️ 重要:正式创建 Indexer 之前,一定要先定义好集合的字段结构,字段类型、维度必须与 Embedding 输出严格匹配。

rag/indexer.go

packageragimport("context""github.com/cloudwego/eino-ext/components/embedding/ark""github.com/cloudwego/eino-ext/components/indexer/milvus""github.com/milvus-io/milvus-sdk-go/v2/entity""log")// NewArkIndexer 创建 Milvus 索引器funcNewArkIndexer(ctx context.Context,embedder*ark.Embedder)*milvus.Indexer{collectionName:="test"// 定义集合字段结构fields:=[]*entity.Field{{Name:"id",DataType:entity.FieldTypeVarChar,TypeParams:map[string]string{"max_length":"255"},PrimaryKey:true,},{Name:"vector",// 向量字段名,需与 Retriever 配置一致DataType:entity.FieldTypeFloatVector,// 普通浮点向量;二值向量使用 BinaryVectorTypeParams:map[string]string{"dim":"1536"},// 维度必须与 Embedding 模型输出匹配},{Name:"content",DataType:entity.FieldTypeVarChar,TypeParams:map[string]string{"max_length":"8192"},},{Name:"metadata",DataType:entity.FieldTypeJSON,},}indexer,err:=milvus.NewIndexer(ctx,&milvus.IndexerConfig{Client:MilvusCli,Collection:collectionName,Fields:fields,Embedding:embedder,// 绑定嵌入器,入库时自动完成向量化})iferr!=nil{log.Printf("创建 Indexer 失败: %v",err)returnnil}log.Println("Indexer 创建成功")returnindexer}

3.5 Retriever 检索组件

Retriever 是「翻小抄」的组件:将用户问题向量化后,在 Milvus 中做相似度搜索,召回最相关的 Top K 个文档片段。

rag/retriever.go

packageragimport("context""github.com/cloudwego/eino-ext/components/embedding/ark""github.com/cloudwego/eino-ext/components/retriever/milvus""log")// NewArkRetriever 创建 Milvus 检索器funcNewArkRetriever(ctx context.Context,embedder*ark.Embedder)*milvus.Retriever{retriever,err:=milvus.NewRetriever(ctx,&milvus.RetrieverConfig{Client:MilvusCli,Collection:"test",VectorField:"vector",// 指定用于相似度计算的向量字段OutputFields:[]string{// 检索返回的字段"id","content","metadata",},TopK:3,// 召回最相关的 3 个片段Embedding:embedder,})iferr!=nil{log.Printf("创建 Retriever 失败: %v",err)returnnil}returnretriever}

3.6 ChatModel 对话模型组件

ChatModel 封装了与大模型的交互逻辑,负责基于组装好的 Prompt 生成最终答案。

rag/chat_model.go

packageragimport("context""github.com/cloudwego/eino-ext/components/model/ark""os""log")// NewArkModel 创建火山方舟对话模型funcNewArkModel(ctx context.Context)*ark.ChatModel{model,err:=ark.NewChatModel(ctx,&ark.ChatModelConfig{APIKey:os.Getenv("ARK_API_KEY"),Model:os.Getenv("MODEL"),})iferr!=nil{log.Printf("创建 ChatModel 失败: %v",err)returnnil}returnmodel}

3.7 ChatTemplate 提示词模板

ChatTemplate 用于结构化地管理和组装 Prompt。我们将检索到的知识库内容注入到系统提示词中,约束模型仅基于参考内容回答,从源头减少幻觉。

新增rag/prompt.go

packageragimport("github.com/cloudwego/eino/components/prompt""github.com/cloudwego/eino/schema")// BuildRAGPrompt 构建 RAG 对话模板// 将检索到的参考资料注入 system prompt,随用户问题一起喂给大模型funcBuildRAGPrompt()prompt.ChatTemplate{systemPrompt:=`你是一个专业的知识问答助手,请严格基于下面的参考资料回答用户的问题。 如果参考资料中没有相关信息,请如实回答"暂未找到相关资料",不要编造内容。 参考资料: {{.references}}`returnprompt.FromMessages(schema.SystemMessage(systemPrompt),schema.UserMessage("{{.question}}"),).WithVariables("references","question")}

四、完整 RAG 流程串联

现在我们在main.go中把所有组件串起来,实现两个核心流程:

  1. 文档入库:加载 Markdown 文档 → 分割 → 向量化 → 存入 Milvus
  2. 检索问答:接收用户问题 → 检索相关片段 → 组装 Prompt → 调用大模型生成答案

main.go

packagemainimport("Eino_frame/rag""context""fmt""github.com/cloudwego/eino/schema""github.com/joho/godotenv""log""os""strconv""strings")funcmain(){ctx:=context.Background()// 加载环境变量(全局加载一次即可)err:=godotenv.Load(".env")iferr!=nil{log.Fatalf("加载 .env 文件失败: %v",err)}// 1. 初始化 Milvus 客户端rag.InitMilvusClient()// 2. 初始化所有基础组件embedder:=rag.NewArkEmbedding(ctx)indexer:=rag.NewArkIndexer(ctx,embedder)retriever:=rag.NewArkRetriever(ctx,embedder)splitter:=rag.NewArkTransformer(ctx)chatModel:=rag.NewArkModel(ctx)promptTemplate:=rag.BuildRAGPrompt()// ========== 流程一:文档入库 ==========log.Println("===== 开始文档入库 =====")// 读取本地 Markdown 知识库文档bs,err:=os.ReadFile("./RAG_introduction.md")iferr!=nil{log.Fatalf("读取文档失败: %v",err)}// 构造 Eino 标准文档对象docs:=[]*schema.Document{{ID:"doc_rag_intro",Content:string(bs),},}// 按标题分割文档splitDocs,err:=splitter.Transform(ctx,docs)iferr!=nil{log.Fatalf("文档分割失败: %v",err)}log.Printf("文档分割完成,共 %d 个片段",len(splitDocs))// 为每个片段设置唯一 IDfori,doc:=rangesplitDocs{doc.ID=docs[0].ID+"_chunk_"+strconv.Itoa(i)}// 自动向量化并写入 Milvusids,err:=indexer.Store(ctx,splitDocs)iferr!=nil{log.Fatalf("文档入库失败: %v",err)}log.Printf("文档入库成功,ID 列表: %v",ids)// ========== 流程二:RAG 检索问答 ==========log.Println("\n===== 开始 RAG 问答 =====")userQuestion:="RAG 技术主要解决了大语言模型的哪些问题?"log.Printf("用户问题: %s",userQuestion)// 1. 从向量库检索相关文档片段results,err:=retriever.Retrieve(ctx,userQuestion)iferr!=nil{log.Fatalf("检索失败: %v",err)}log.Printf("检索到 %d 条相关片段",len(results))// 2. 拼接参考资料文本varrefBuilder strings.Builderfori,res:=rangeresults{refBuilder.WriteString(fmt.Sprintf("[片段%d]\n%s\n\n",i+1,res.Content))}references:=refBuilder.String()// 3. 用模板格式化最终 Promptmessages,err:=promptTemplate.Format(ctx,map[string]any{"references":references,"question":userQuestion,})iferr!=nil{log.Fatalf("格式化 Prompt 失败: %v",err)}// 4. 调用大模型生成答案resp,err:=chatModel.Generate(ctx,messages)iferr!=nil{log.Fatalf("调用模型失败: %v",err)}log.Println("\n===== 最终答案 =====")fmt.Println(resp.Content)}

五、运行与验证

  1. 在项目根目录准备测试文档RAG_introduction.md,写入 RAG 相关的技术介绍内容
  2. 执行主程序:
go run main.go
  1. 预期输出顺序:
    • Milvus 客户端初始化成功
    • 文档分割完成,输出片段数量
    • 文档入库成功,输出所有片段 ID
    • 输出检索到的相关片段数量
    • 打印大模型基于参考资料生成的最终答案

六、优化与扩展方向

  1. 增量入库与去重:生产环境增加文档指纹校验,避免重复写入;支持批量入库提升性能
  2. 检索后重排序(Rerank):在向量粗召回后增加重排序模型,进一步提升检索准确率
  3. 多格式文档支持:接入 PDF、Word、网页、PPT 等格式的 Transformer,扩展知识库来源
  4. 多轮对话记忆:增加会话上下文管理,实现带历史记忆的 RAG 对话
  5. 工具调用能力:基于 Eino 的 Tool 组件,让大模型可调用数据库、外部 API 等工具

七、总结

Eino 框架通过统一的组件抽象,极大降低了 Go 语言下 RAG 系统的开发门槛。开发者只需关注业务逻辑本身,无需花费大量精力对接不同厂商的 SDK、处理向量数据库的底层细节。从文档切片到最终生成答案,整条链路清晰可扩展,非常适合快速落地企业内部知识库、智能客服、技术问答等场景。

http://www.cnnetsun.cn/news/3492725.html

相关文章:

  • 你是我的眼
  • PCB收放板机的机械手选型——三轴与六轴的适用边界
  • 谷歌 AI 笔记应用更名 Gemini Notebook,新增代码编写执行功能
  • Wayca-scheduler部署教程:从源码编译到生产环境安装的完整流程
  • 三步告别机械键盘连击:免费开源工具让你的键盘重获新生
  • 2026去水印小程序有哪些?实测实用去水印工具教程
  • 2026年下半年量化软件定位,先区分学习开发与执行
  • 近期AI量化工具评估,按概念代码回测模拟推进
  • TacentView图像查看器完整指南:游戏开发者的纹理查看终极工具
  • 【读书笔记】《一个世纪的人生课》
  • Spring知识梳理(4)
  • 转向软硬件交界处的微光:我的 Triton 踩坑与蜕变之旅
  • ncmdump终极指南:3步解锁网易云音乐NCM加密格式,实现音乐自由
  • 2026证件照换底色详细操作方法:手机、网页、电脑PS全场景教程
  • Codex+DeepSeek FDE落地案例分享:从模型内卷到落地为王:FDE 为什么突然火了?
  • Flutter第六节-----Flutter部分基础组件解析到自定义组件
  • 360AI搜索响应延迟超2.3s?性能压测报告首曝:3种Query优化策略实测QPS提升417%
  • 深入解析TI MibSPI控制寄存器:ECC诊断与中断配置实战指南
  • 怎样快速搞定Windows苹果驱动:3步精简安装终极指南
  • coding-ethos 实战:把 AI 编码规则变成可回读的 policy 与 remediation
  • 成年人的顶级能力:允许自己搞砸
  • Python TDD实战:红绿重构循环驱动高质量代码开发
  • CyberChef:网络安全分析师的终极数据处理神器
  • C++轻量级状态机TinyFSM:从原理到实战,告别if-else混乱
  • 70V高耐压USB过压保护芯片PW1600——欧盟Type-C 42V认证首选方案
  • 2026 AI副业爆发倒计时:3类零代码技能+2个合规变现闭环,90%程序员尚未察觉
  • C++异步线程池:从原理到工业级实现
  • 从零实现C++ STL set:深入理解红黑树与迭代器封装
  • Linux C读写锁原理与实战:从pthread_rwlock_t到高并发缓存优化
  • C++与OpenCV实现车牌定位:边缘与颜色融合的实战指南