Skip to content

手把手教你用 Go + Eino 搭建一个企业级 RAG 知识库(含代码与踩坑)

RAG(检索增强生成)是目前解决大模型幻觉最有效的手段。但网上的教程大多是 Python + LangChain 的 Demo,一到生产环境就各种问题。

本文将基于 字节跳动 Eino 框架和 Milvus 向量数据库,手把手带你用 Go 语言实现一个支持混合检索、文档切分、向量化的企业级 RAG 系统。

一、 为什么你的 RAG 效果很差?

很多同学照着网上的教程写了个 RAG,结果发现效果惨不忍睹:

  1. 切分太粗:把整段文本直接向量化,导致检索时丢失细节。
  2. 检索不准:只用向量搜索(Dense Search),搜“Java 高级”却出来“Java 入门”。
  3. 数据陈旧:知识库更新慢,甚至不支持实时插入。

企业级 RAG 的核心在于:精细化的 ETL + 混合检索策略。


二、 架构设计:Eino RAG 链路

在 Eino 中,RAG 不再是简单的 Function Call,而是一套标准的流水线:

#bytemd-mermaid-1769399415309-0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#bytemd-mermaid-1769399415309-0 .error-icon{fill:#552222;}#bytemd-mermaid-1769399415309-0 .error-text{fill:#552222;stroke:#552222;}#bytemd-mermaid-1769399415309-0 .edge-thickness-normal{stroke-width:2px;}#bytemd-mermaid-1769399415309-0 .edge-thickness-thick{stroke-width:3.5px;}#bytemd-mermaid-1769399415309-0 .edge-pattern-solid{stroke-dasharray:0;}#bytemd-mermaid-1769399415309-0 .edge-pattern-dashed{stroke-dasharray:3;}#bytemd-mermaid-1769399415309-0 .edge-pattern-dotted{stroke-dasharray:2;}#bytemd-mermaid-1769399415309-0 .marker{fill:#333333;stroke:#333333;}#bytemd-mermaid-1769399415309-0 .marker.cross{stroke:#333333;}#bytemd-mermaid-1769399415309-0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#bytemd-mermaid-1769399415309-0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#bytemd-mermaid-1769399415309-0 .cluster-label text{fill:#333;}#bytemd-mermaid-1769399415309-0 .cluster-label span{color:#333;}#bytemd-mermaid-1769399415309-0 .label text,#bytemd-mermaid-1769399415309-0 span{fill:#333;color:#333;}#bytemd-mermaid-1769399415309-0 .node rect,#bytemd-mermaid-1769399415309-0 .node circle,#bytemd-mermaid-1769399415309-0 .node ellipse,#bytemd-mermaid-1769399415309-0 .node polygon,#bytemd-mermaid-1769399415309-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#bytemd-mermaid-1769399415309-0 .node .label{text-align:center;}#bytemd-mermaid-1769399415309-0 .node.clickable{cursor:pointer;}#bytemd-mermaid-1769399415309-0 .arrowheadPath{fill:#333333;}#bytemd-mermaid-1769399415309-0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#bytemd-mermaid-1769399415309-0 .flowchart-link{stroke:#333333;fill:none;}#bytemd-mermaid-1769399415309-0 .edgeLabel{background-color:#e8e8e8;text-align:center;}#bytemd-mermaid-1769399415309-0 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#bytemd-mermaid-1769399415309-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#bytemd-mermaid-1769399415309-0 .cluster text{fill:#333;}#bytemd-mermaid-1769399415309-0 .cluster span{color:#333;}#bytemd-mermaid-1769399415309-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#bytemd-mermaid-1769399415309-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#bytemd-mermaid-1769399415309-0 :root

Markdown/PDF文档

文档切分器

向量化模型

Milvus 向量库

用户提问

意图识别/Filter构建

混合检索器

大模型生成


三、 实战:从文档到检索

3.1 步骤一:文档切分(Chunking)

我们不能把一整本书丢给大模型。 在项目中,我实现了一个基于 Markdown 语义 的切分器。

go
// backend/internal/eino/milvus/splitter/markdown.go // 核心逻辑:按 H1/H2 标题进行切分,保留上下文 func SplitMarkdown(content string) []string {     chunks := make([]string, 0)     // ... 正则匹配 # 标题     // ... 递归切分     return chunks }

经验:不要用固定字符数(如 500字)切分!一定要按 语义(段落/标题) 切分,否则会把完整的逻辑打断。

3.2 步骤二:向量化与入库

我们使用 text-embedding-3-small 模型进行向量化,存入 Milvus。

go
// backend/internal/eino/milvus/importer.go func ImportKnowledge(ctx context.Context, files []string) error {     for _, file := range files {         // 1. 读取并切分         chunks := splitter.Split(file)                  // 2. 批量向量化 (Batch Embedding)         vectors, _ := embeddingModel.EmbedStrings(ctx, chunks)                  // 3. 存入 Milvus         // 注意:我们同时存储了 Metadata(如语言、难度、分类)         milvusClient.Insert(ctx, collectionName, "", columns...)     }     return nil }

这是 RAG 效果好坏的关键! 我们不能只查向量,必须结合标量过滤

场景:用户问“给我出一道 Redis高级 面试题”。 如果只查向量,可能会搜出“Redis 基础命令”。 必须加上过滤条件:category == 'Redis' && difficulty == 'Hard'

go
// backend/internal/eino/milvus/retrieval/retriever.go func (s *RetrieverService) Retrieve(ctx context.Context, query string) ([]*schema.Document, error) {     // 1. 动态构建 Filter     // 这里其实可以用一个小模型先做意图识别,提取 Filter 条件     expr := "category == 'Redis' && difficulty == 'Hard'"          // 2. 调用 Milvus 进行混合检索     // Eino 的 Retriever 接口完美支持这种高级操作     docs, err := s.client.Search(ctx, s.collection, expr, queryVector)          return docs, nil }

四、 效果对比

指标

传统 RAG (LangChain 默认)

Eino 企业级 RAG (本项目)

检索准确率

约 60%

> 95% (含混合检索)

响应速度

慢 (Python 串行处理)

(Go 并发处理)

代码可维护性

差 (黑盒)

(强类型接口)


五、 源码送给你

为了让大家少走弯路,我把这套 Go + Eino + Milvus 的 RAG 系统源码开源出来了。 它包含:

  1. Markdown/PDF 解析器
  2. Milvus 客户端封装
  3. Hybrid Search 实现逻辑
  4. 知识库导入脚本

👉 获取方式:

关注掘金专栏,或关注公众号【王中阳】,回复“面试吧”,即可获取完整源码和部署文档。

私信我的绿泡泡:wangzhongyang1993,备注“掘金”,拉你进技术交流群,手把手教你搭建自己的企业知识库!

🚀 学习遇到瓶颈?想进大厂?

看完这篇技术文章,如果还是觉得不够系统,或者想在实战中快速提升?
王中阳的就业陪跑训练营,提供定制化学习路线 + 企业级实战项目 + 简历优化 + 模拟面试。

了解训练营详情