RAG召回常见问题 。
一、RAG召回最常遇到的6类问题 1. 语义不匹配(最常见) 用户提问是自然语言,库中文档是关键词/专业表述,字面不匹配、语义匹配 ,导致召回不到正确文档。 例:
用户问:“电脑连不上网怎么办”
库中是:“以太网连接故障排查” → 传统关键词检索完全搜不到。
2. 召回噪声大(无关内容多) 召回了一堆不相关的片段,大模型被干扰,回答跑偏、 hallucination(幻觉)。
3. 召回不完整(漏关键信息) 长文档被切分后,上下文断裂 ,只召回碎片,缺关键前提/结论。
4. 向量漂移/过时 文档更新后向量没重算,旧向量和新内容不匹配;高频更新场景尤其严重。
5. 检索速度慢 数据量大(10w+文档)时,暴力检索/全量相似度计算耗时高。
6. 多路召回融合差 同时用关键词+向量+规则召回,结果权重混乱,最终质量不稳定。
二、Go 语言如何处理这些问题 Go 适合做 RAG 召回层:高性能、并发强、内存占用低 ,非常适合企业级服务。
问题1:语义不匹配 Go 解决方案 : 使用嵌入模型(Embedding)生成向量,用 向量数据库 做相似度检索。
Go 常用栈
嵌入:github.com/liukanshan/go-embedding / 调用本地 Ollama / 通义千问/文心一言API
向量库:Milvus (Go SDK)、Chroma 、Pinecone
相似度:余弦相似度
Go 极简示例(向量召回)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package mainimport ( "context" "fmt" "github.com/milvus-io/milvus-sdk-go/v2/client" "github.com/milvus-io/milvus-sdk-go/v2/entity" ) func vectorSearch (queryEmbedding []float32 ) ([]string , error ) { c, err := client.NewGrpcClient(context.Background(), "localhost:19530" ) if err != nil { return nil , err } defer c.Close() searchParam := entity.NewIndexFlatSearchParam(10 ) vectors := []entity.Vector{entity.FloatVector(queryEmbedding)} result, err := c.Search( context.Background(), "rag_collection" , "" , []string {"content" }, "" , vectors, "vector" , entity.COSINE, 5 , searchParam, ) if err != nil { return nil , err } var docs []string for _, rs := range result { for _, field := range rs.Fields { if field.Name == "content" { docs = append (docs, field.Values.([]string )...) } } } return docs, nil }
问题2:召回噪声大(无关内容) Go 解决方案 :
多路召回 + 重排序(Rerank)
相似度阈值过滤
元数据过滤(Metadata Filtering)
Go 实现:阈值过滤 + Rerank
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func rerankAndFilter (query string , docs []string , threshold float64 ) []string { var filtered []string for _, doc := range docs { score := computeSimilarity(query, doc) if score >= threshold { filtered = append (filtered, doc) } } sort.Slice(filtered, func (i, j int ) bool { return computeSimilarity(query, filtered[i]) > computeSimilarity(query, filtered[j]) }) return filtered }
企业级方案 : 使用 bge-rerank / Cohere Rerank 模型,Go 直接调用 API 做二次精排。
问题3:召回不完整(上下文断裂) Go 解决方案 :
智能分块(Smart Chunking) :按段落/标题/语义切分,不硬切固定长度
窗口召回 :召回目标块的上一块 + 下一块 ,补全上下文
Go 智能分块示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func chunkByParagraph (text string ) []string { paragraphs := regexp.MustCompile(`\n\s*\n` ).Split(text, -1 ) var chunks []string for _, p := range paragraphs { trimmed := strings.TrimSpace(p) if len (trimmed) > 0 { chunks = append (chunks, trimmed) } } return chunks }
问题4:向量漂移、数据过时 Go 解决方案 :
增量更新向量
文档哈希校验 :内容变了才重新生成向量
定时任务更新 :Go 原生 time/ticker 做定时刷新
Go 增量更新向量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func updateEmbeddingIfNeeded (docID string , content string ) error { hash := sha256.Sum256([]byte (content)) hashStr := fmt.Sprintf("%x" , hash) oldHash, err := getDocHashFromDB(docID) if err != nil { return err } if hashStr == oldHash { return nil } emb, err := generateEmbedding(content) if err != nil { return err } return saveEmbeddingToMilvus(docID, emb, hashStr) }
问题5:检索速度慢 Go 解决方案 :
向量库建索引 (IVF_FLAT / HNSW)
Go 并发预处理 :嵌入、检索并发执行
内存缓存 :热点 Query 缓存召回结果
Go 并发生成嵌入(提速)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func batchGenerateEmbeddings (docs []string ) ([][]float32 , error ) { ch := make (chan []float32 , len (docs)) errCh := make (chan error , len (docs)) for _, doc := range docs { go func (d string ) { emb, err := callEmbeddingAPI(d) if err != nil { errCh <- err return } ch <- emb }(doc) } var embeddings [][]float32 for i := 0 ; i < len (docs); i++ { select { case emb := <-ch: embeddings = append (embeddings, emb) case err := <-errCh: return nil , err } } return embeddings, nil }
问题6:多路召回融合差 Go 解决方案 :
向量召回(语义)
关键词召回(BM25)
元数据过滤(权限、分类、时间)
加权融合(RRF 算法)
Go 非常适合做多路召回的调度层 。
RRF 融合公式(Go 实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func rrfFuse (ranks ...[]string ) []string { score := make (map [string ]float64 ) const k = 60 for _, rank := range ranks { for i, doc := range rank { score[doc] += 1.0 / (float64 (i+1 ) + k) } } type pair struct { doc string score float64 } var pairs []pair for d, s := range score { pairs = append (pairs, pair{d, s}) } sort.Slice(pairs, func (i, j int ) bool { return pairs[i].score > pairs[j].score }) var res []string for _, p := range pairs { res = append (res, p.doc) } return res }
三、总结 1 2 3 4 5 6 嵌入模型:Ollama (bge-small) / 云厂商API 向量数据库:Milvus(Go SDK 最完善) 关键词检索:Bleve(Go 原生 BM25) 重排序:bge-rerank-large 缓存:Redis / 内存LRU 调度:Go 原生并发 + 协程
纯向量的问题? 纯向量:容易漏招、错招(比如用户搜 “TCP 三次握手”,向量可能召回 “UDP 连接”) 工业级 RAG 标准做法:向量(Dense)+ 关键词(Sparse/BM25)多路召回 + RRF 融合 【RRF = Reciprocal Rank Fusion,倒数排名融合,是 RAG 里最常用、实现最简单、效果稳定的多路召回结果合并算法。】 MySQL 现在自带 BM25 + 全文检索,功能足够覆盖 RAG 召回。