背景

我当前有一个业务系统,希望能添加一个机器人助手。直接使用大模型,由于缺少相关的业务数据,效果并不理想,了解一下 RAG

什么是 RAG

RAG(Retrieval Augmented Generation),搜索引擎 + 大模型

简单来说就是从一个数据源中先捞出一部分数据,有个前置的筛选操作(通常是向量数据库),然后将搜索出的数据组成 prompt 喂给大模型,最终获取大模型的返回值,进行过滤输出。

Top-K 结果
用户输入问题
查询向量化
Embedding Model
向量数据库
近似搜索 ANN
检索片段
+ 原始文本
构建提示词
Prompt Engineering
系统指令 + 上下文 + 问题
大模型生成答案
LLM Generation
返回带引用的回答
Sources / 页码

什么时候要用到 RAG

其实大多数的业务系统可以不上 RAG,通常情况下的业务系统都是通过数据库记录数据,很少有需要做数据推理、解释、总结的相关功能,如果真遇到了需要语义匹配(例如:任务表中有任务描述)等刚需场景再考虑上。

  • 数据规模大:需要参考的知识库太大,超过了模型上下文限制。
  • 时效性与准确性:数据经常更新,每次更新都要重新训练模型,成本就太高了。
  • 多租户/版本数据隔离:非公开数据(例:常见的SaaS系统都有用户角色控制权限,数据仅某些角色可见)。
  • 长尾:出现频率低、种类多的一些罕见任务或小众需求。

题外话:发现了一个开源库 vanna 可以直接和数据库进行对话。

这个我也测试了一下,其原理简单说就是

  1. 训练:数据库结构(DDL)、字段说明、示例 SQL 等扔进向量库,建成私有知识库。
  2. 提问:用自然语言问问题时,系统先检索最相关的上下文,再喂给 LLM 生成可直接执行的 SQL,本地运行并返回结果/图表。整个过程数据不出本地,且每次成功查询会自动回注向量库,持续自我优化。

CODE SHOW

使用 AI 编程简单做了一个小 demogithub源码

技术架构

客户端请求
Go API服务 :8080
本地嵌入服务 :5000
本地LLM服务 :5001
MySQL数据库 :3306
Qdrant向量数据库 :6333
BGE-M3嵌入模型
Ollama + Llama3/DeepSeek
用户表
APK表
权限表
向量存储
权限过滤

核心特性

  • 完全本地化:使用本地嵌入模型和LLM,无需依赖外部API
  • 权限控制:基于用户角色和单位的多级权限管理
  • 向量检索:使用Qdrant向量数据库进行语义搜索
  • 智能问答:结合检索到的上下文进行个性化回答

核心实现

RAG服务核心逻辑

// 处理用户查询的核心流程
func (r *RAGService) Query(userID int, question string) (string, error) {
    // 1. 获取用户信息和权限
    user, err := r.authSvc.GetUserByID(userID)
    userAccess := r.getUserAccessContext(user)
    
    // 2. 个性化查询重写
    personalizedQuery := r.personalizeQuery(question, user)
    
    // 3. 向量检索(带权限过滤)
    apkIDs, contexts, err := r.retrieveAPKs(personalizedQuery, userAccess)
    
    // 4. 构建提示词并生成答案
    prompt := r.buildPrompt(user, question, contexts)
    return r.generateAnswer(prompt)
}

// 个性化查询处理
func (r *RAGService) personalizeQuery(query string, user *User) string {
    if strings.Contains(query, "我") || strings.Contains(query, "我的") {
        return fmt.Sprintf("%s 上传者ID:%d", query, user.ID)
    }
    if strings.Contains(query, "我们单位") {
        return fmt.Sprintf("%s 单位ID:%d", query, user.UnitID)
    }
    return query
}

向量检索与权限过滤

// 带权限过滤的向量检索
func (r *RAGService) retrieveAPKs(query string, userAccess []string) ([]int, []string, error) {
    vector, err := r.embeddingSvc.GetEmbedding(query)
    
    // 构建权限过滤器
    filter := &qdrant.Filter{
        Must: []*qdrant.Condition{{
            ConditionOneOf: &qdrant.Condition_Field{
                Field: &qdrant.FieldCondition{
                    Key: "access_scope",
                    Match: &qdrant.Match{
                        MatchValue: &qdrant.Match_Keywords{
                            Keywords: &qdrant.RepeatedStrings{Strings: userAccess},
                        },
                    },
                },
            },
        }},
    }

    // 执行向量搜索
    resp, err := pointsClient.Search(ctx, &qdrant.SearchPoints{
        CollectionName: "apk_vectors",
        Vector:         vector,
        Filter:         filter,
        Limit:          3,
    })
    
    // 处理搜索结果...
}

本地服务集成

// 嵌入服务调用
func (e *EmbeddingService) GetEmbedding(text string) ([]float32, error) {
    requestBody, _ := json.Marshal(map[string][]string{"texts": {text}})
    resp, _ := http.Post(e.baseURL+"/embed", "application/json", bytes.NewBuffer(requestBody))
    
    var result struct { Embeddings [][]float32 `json:"embeddings"` }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.Embeddings[0], nil
}

// LLM服务调用
func (r *RAGService) generateAnswer(prompt string) (string, error) {
    requestBody, _ := json.Marshal(map[string]interface{}{
        "prompt": prompt,
        "model":  "deepseek-coder:6.7b",
    })
    
    resp, _ := http.Post("http://localhost:5001/generate", 
                        "application/json", bytes.NewBuffer(requestBody))
    
    var result struct { Response string `json:"response"` }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.Response, nil
}

API接口示例

# 添加APK
curl -X POST http://localhost:8080/apks \
  -H "Content-Type: application/json" \
  -d '{"name": "支付宝", "uploader_id": 1, "visible_units": [101, 102]}'

# 智能问答
curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{"user_id": 1, "question": "我在哪天上传了支付宝?"}'

快速启动

# 1. 启动依赖服务
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=apk_rag -d mysql:latest

# 2. 启动本地模型服务
ollama serve && ollama pull llama3
python local_embedding.py &
python local_llm.py &

# 3. 启动Go服务
go run .

技术栈

组件 技术选型 作用
后端服务 Go + Gin API服务和业务逻辑
嵌入模型 BGE-M3 文本向量化
LLM服务 Ollama + Llama3/DeepSeek 文本生成
向量数据库 Qdrant 向量存储和检索
关系数据库 MySQL 结构化数据存储

实际做 RAG 开发中的一些感悟

其实大多数业务系统是不需要使用 RAG 的,先搞清楚自己到底要不要上 RAG

上述 demo 极为简单,是丐版,离真正的生产使用还差了好远。如果真的考虑做一个 RAG 系统,可以考虑考虑以下问题(实际生产中的问题更多):

  1. RAG 有一步是数据向量化,是不是可以不用向量化,我直接通过 elastic search 之类的服务做存储,然后搜出来数据,自己组装 prompt 丢给大模型。向量化有什么作用?
  2. 什么是 embedding
  3. 向量存储方案选型?
  4. 模型怎么选,选哪个?
  5. 文档怎么切?
  6. 如何同当前系统进行结合?
  7. 输出结果不理想怎么办,如何调优?
  8. 如何去评估 RAG 的效果好不好?
  9. 拒答阈值怎么定?
  10. 生产级加固,成本和延迟,可观测性?

最难的点还是在于如何精准的搜索到最相关的上下文

总结

实际生产中,首先得再问一下,是否真的有必要上 RAG

大模型的 RAG 入门并不难,难的是各种细节的调整(数据处理等)。

撸了一个丐版的 RAGdemo,向量化 → 近似召回 → Prompt 拼装 → 大模型生成。

最后提一嘴,最后调用大模型的参数量越大越好,上下文长度越长越好。

参考

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐