RAG 从入门到放弃?丐版 demo 实战笔记(go+python)

背景

我当前有一个业务系统,希望能添加一个机器人助手。直接使用大模型,由于缺少相关的业务数据,效果并不理想,了解一下 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服务核心逻辑

go 复制代码
// 处理用户查询的核心流程
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
}

向量检索与权限过滤

go 复制代码
// 带权限过滤的向量检索
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,
    })
    
    // 处理搜索结果...
}

本地服务集成

go 复制代码
// 嵌入服务调用
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接口示例

bash 复制代码
# 添加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": "我在哪天上传了支付宝?"}'

快速启动

bash 复制代码
# 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 拼装 → 大模型生成。

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

参考

相关推荐
悠哉悠哉愿意27 分钟前
【ROS2学习笔记】 TF 坐标系
笔记·学习·ros2
li星野4 小时前
打工人日报#20251005
笔记·程序人生·fpga开发·学习方法
JJJJ_iii5 小时前
【深度学习01】快速上手 PyTorch:环境 + IDE+Dataset
pytorch·笔记·python·深度学习·学习·jupyter
悠哉悠哉愿意6 小时前
【ROS2学习笔记】RViz 三维可视化
笔记·学习·机器人·ros2
序属秋秋秋8 小时前
《C++进阶之C++11》【智能指针】(下)
c++·笔记·学习·面试·c++11·智能指针·新特性
丰锋ff9 小时前
2024 年真题配套词汇单词笔记(考研真相)
笔记·考研
序属秋秋秋9 小时前
《C++进阶之C++11》【智能指针】(上)
c++·笔记·学习·面试·c++11·智能指针·新特性
十安_数学好题速析10 小时前
根式方程:结构联想巧用三角代换
笔记·学习·高考
弘毅 失败的 mian10 小时前
利用 VsCode + EIDE 进行嵌入式开发(保姆级教程)
经验分享·笔记·vscode·stm32·单片机·嵌入式硬件·eide
li星野10 小时前
打工人日报#20251002
笔记·程序人生·学习方法