VecDB第五篇:从问题到答案:一个完整的RAG系统是如何工作的

发布时间:2026/8/27 13:34:35
VecDB第五篇:从问题到答案:一个完整的RAG系统是如何工作的 前言前面我们学了第一篇用Prompt把知识库塞给LLM第二篇把文字变成数字文本向量化第三篇用余弦相似度判断像不像第四篇用向量数据库存储和检索今天我们把所有知识点串起来构建一个完整的RAG系统。读完这篇你就会明白从用户提问到系统回答中间到底发生了什么。一、先说一个故事1.1 用户问了一个问题假设你是一个智能客服用户问用户月球任务什么时候发射知识库里有8句话句子1阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务... 句子2该任务将使用太空发射系统火箭和猎户座飞船... 句子3阿尔忒弥斯2号的主要目标是测试生命支持系统... 句子4任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务... 句子5此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证... 句子6宇航员将在飞行过程中进行科学实验并测试深空通信和导航系统... 句子7任务将持续约10天飞行距离将达到27万英里... 句子8阿尔忒弥斯2号的成功将为建立月球基地和未来火星探索奠定基础...问题来了系统怎么知道哪句话最相关1.2 第一篇的方案全部塞进Prompt系统你是智能客服请根据以下知识库回答问题 知识库 - 句子1... - 句子2... - 句子3... - ...全部8句话 用户问月球任务什么时候发射问题知识库如果有一万句话prompt就爆了。1.3 更聪明的方案只给最相关的系统你是智能客服请根据以下知识库回答问题 知识库 - 句子4任务计划在2025年执行... 用户问月球任务什么时候发射妙处只给最相关的1-2句话prompt精简答案准确。这就是RAG的核心思想检索增强生成。二、完整流程图┌─────────────┐ │ 用户提问 │ │ 月球任务什么时候发射 │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 第一步向量化 │ │ 把问题转成向量 │ │ [0, 1, 1, 0, ...] │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 第二步检索 │ │ 在向量数据库中找最相似的 │ │ 返回Top-K个结果 │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 第三步组装Prompt │ │ 把检索结果塞进prompt │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 第四步调用LLM │ │ 让大模型生成答案 │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 返回答案 │ │ 任务计划在2025年执行 │ └─────────────┘三、一步步实现3.0 准备工作定义知识库和词汇表直接使用前面文章的常量简单明了packagemainimport(fmtmathstrings)// 知识库关于阿尔忒弥斯2号的8句话varknowledgeBase[]string{阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步,该任务将使用太空发射系统火箭和猎户座飞船搭载宇航员进行绕月飞行,阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力,任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务,此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证,宇航员将在飞行过程中进行科学实验并测试深空通信和导航系统,任务将持续约10天飞行距离将达到27万英里是地球到月球距离的10倍以上,阿尔忒弥斯2号的成功将为建立月球基地和未来火星探索奠定基础,}// 词汇表我们关心的10个关键词varvocab[]string{阿尔忒弥斯,月球,任务,宇航员,火箭,飞船,测试,系统,飞行,轨道}3.1 第一步向量化复用第二篇的代码把文字变成数字// 文本清洗funccleanText(textstring)string{varcleaned strings.Builderfor_,r:rangetext{ifr0x4e00r0x9fff{cleaned.WriteRune(r)}}returncleaned.String()}// 分词处理functokenize(textstring,vocab[]string)[]string{vartokens[]stringfor_,vocabWord:rangevocab{count:strings.Count(text,vocabWord)fori:0;icount;i{tokensappend(tokens,vocabWord)}}returntokens}// 词频统计funccountFrequency(tokens[]string)map[string]int{freq:make(map[string]int)for_,token:rangetokens{freq[token]}returnfreq}// 构建向量funcbuildVector(freqmap[string]int,vocab[]string)[]float64{vector:make([]float64,len(vocab))fori,word:rangevocab{vector[i]float64(freq[word])}returnvector}// 向量归一化funcnormalize(vector[]float64){sum:0.0for_,v:rangevector{sumv*v}ifsum0{return}magnitude:math.Sqrt(sum)fori:rangevector{vector[i]/magnitude}}// 把文本转成向量五步合一functextToVector(textstring)[]float64{cleaned:cleanText(text)tokens:tokenize(cleaned,vocab)freq:countFrequency(tokens)vector:buildVector(freq,vocab)normalize(vector)returnvector}测试一下// 用户问题query:月球任务什么时候发射queryVector:textToVector(query)fmt.Printf(问题向量: %v\n,queryVector)// 输出: [0, 0.707, 0.707, 0, 0, 0, 0, 0, 0, 0]// ↑ 只有月球和任务两个词3.2 第二步检索复用第三篇的余弦相似度在知识库中找到最相似的句子// 计算余弦相似度funccosineSimilarity(a,b[]float64)float64{// 第一步算点积dotProduct:0.0fori:rangea{dotProducta[i]*b[i]}// 第二步算各自的长度lengthA:0.0lengthB:0.0fori:rangea{lengthAa[i]*a[i]lengthBb[i]*b[i]}lengthAmath.Sqrt(lengthA)lengthBmath.Sqrt(lengthB)// 第三步点积除以长度乘积iflengthA0||lengthB0{return0}returndotProduct/(lengthA*lengthB)}// 搜索最相似的知识funcsearchKnowledge(querystring,topKint)[]SearchResult{// 把问题转成向量queryVector:textToVector(query)// 计算每句话的相似度varresults[]SearchResultfori,content:rangeknowledgeBase{contentVector:textToVector(content)similarity:cosineSimilarity(queryVector,contentVector)resultsappend(results,SearchResult{ID:i,Content:content,Similarity:similarity,})}// 按相似度排序从高到低sort.Slice(results,func(i,jint)bool{returnresults[i].Similarityresults[j].Similarity})// 返回Top-K个结果iflen(results)topK{resultsresults[:topK]}returnresults}测试一下results:searchKnowledge(月球任务什么时候发射,3)fori,r:rangeresults{fmt.Printf(第%d名: 相似度%.2f, 内容%s\n,i1,r.Similarity,r.Content)}输出第1名: 相似度0.71, 内容任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务 第2名: 相似度0.50, 内容阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步 第3名: 相似度0.41, 内容此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证看到了吗第一名正好是回答什么时候发射的那句话3.3 第三步组装Prompt把检索结果塞进promptfuncbuildPrompt(querystring,results[]SearchResult)string{// 组装知识库部分knowledge:fori,r:rangeresults{knowledgefmt.Sprintf(%d. %s\n,i1,r.Content)}// 组装完整promptprompt:fmt.Sprintf(你是一个智能客服请根据以下知识库回答用户的问题。 知识库 %s 用户问题%s 请简洁准确地回答,knowledge,query)returnprompt}测试一下query:月球任务什么时候发射results:searchKnowledge(query,2)prompt:buildPrompt(query,results)fmt.Println(prompt)输出你是一个智能客服请根据以下知识库回答用户的问题。 知识库 1. 任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务 2. 阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步 用户问题月球任务什么时候发射 请简洁准确地回答3.4 第四步调用LLM复用第一篇的代码把这个prompt发给大模型// 这里的代码与第一篇相同调用Ollama DeepSeekfunccallLLM(promptstring)string{// ... 调用Ollama的代码 ...// 参考第一篇文章的 talkToOllama 函数}LLM返回根据知识库阿尔忒弥斯2号任务计划在2025年执行。四、完整代码packagemainimport(fmtmathsortstrings)// 数据定义 // 知识库关于阿尔忒弥斯2号的8句话varknowledgeBase[]string{阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步,该任务将使用太空发射系统火箭和猎户座飞船搭载宇航员进行绕月飞行,阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力,任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务,此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证,宇航员将在飞行过程中进行科学实验并测试深空通信和导航系统,任务将持续约10天飞行距离将达到27万英里是地球到月球距离的10倍以上,阿尔忒弥斯2号的成功将为建立月球基地和未来火星探索奠定基础,}// 词汇表我们关心的10个关键词varvocab[]string{阿尔忒弥斯,月球,任务,宇航员,火箭,飞船,测试,系统,飞行,轨道}// 搜索结果typeSearchResultstruct{IDintContentstringSimilarityfloat64}// 向量化函数复用第二篇funccleanText(textstring)string{varcleaned strings.Builderfor_,r:rangetext{ifr0x4e00r0x9fff{cleaned.WriteRune(r)}}returncleaned.String()}functokenize(textstring,vocab[]string)[]string{vartokens[]stringfor_,vocabWord:rangevocab{count:strings.Count(text,vocabWord)fori:0;icount;i{tokensappend(tokens,vocabWord)}}returntokens}funccountFrequency(tokens[]string)map[string]int{freq:make(map[string]int)for_,token:rangetokens{freq[token]}returnfreq}funcbuildVector(freqmap[string]int,vocab[]string)[]float64{vector:make([]float64,len(vocab))fori,word:rangevocab{vector[i]float64(freq[word])}returnvector}funcnormalize(vector[]float64){sum:0.0for_,v:rangevector{sumv*v}ifsum0{return}magnitude:math.Sqrt(sum)fori:rangevector{vector[i]/magnitude}}functextToVector(textstring)[]float64{cleaned:cleanText(text)tokens:tokenize(cleaned,vocab)freq:countFrequency(tokens)vector:buildVector(freq,vocab)normalize(vector)returnvector}// 相似度函数复用第三篇funccosineSimilarity(a,b[]float64)float64{dotProduct:0.0fori:rangea{dotProducta[i]*b[i]}lengthA:0.0lengthB:0.0fori:rangea{lengthAa[i]*a[i]lengthBb[i]*b[i]}lengthAmath.Sqrt(lengthA)lengthBmath.Sqrt(lengthB)iflengthA0||lengthB0{return0}returndotProduct/(lengthA*lengthB)}// RAG核心函数 // 搜索最相似的知识funcsearchKnowledge(querystring,topKint)[]SearchResult{queryVector:textToVector(query)varresults[]SearchResultfori,content:rangeknowledgeBase{contentVector:textToVector(content)similarity:cosineSimilarity(queryVector,contentVector)resultsappend(results,SearchResult{ID:i,Content:content,Similarity:similarity,})}sort.Slice(results,func(i,jint)bool{returnresults[i].Similarityresults[j].Similarity})iflen(results)topK{resultsresults[:topK]}returnresults}// 组装PromptfuncbuildPrompt(querystring,results[]SearchResult)string{knowledge:fori,r:rangeresults{knowledgefmt.Sprintf(%d. %s\n,i1,r.Content)}prompt:fmt.Sprintf(你是一个智能客服请根据以下知识库回答用户的问题。 知识库 %s 用户问题%s 请简洁准确地回答,knowledge,query)returnprompt}// 主函数 funcmain(){fmt.Println( RAG系统演示 )fmt.Println()// 用户问题query:月球任务什么时候发射fmt.Printf(用户问题: %s\n,query)fmt.Println()// 第一步向量化fmt.Println(【第一步向量化】)queryVector:textToVector(query)fmt.Printf(问题向量: %v\n,queryVector)fmt.Println()// 第二步检索fmt.Println(【第二步检索】)results:searchKnowledge(query,3)fmt.Println(检索结果:)fori,r:rangeresults{fmt.Printf( 第%d名: 相似度%.2f\n,i1,r.Similarity)fmt.Printf( 内容: %s\n,r.Content)fmt.Println()}// 第三步组装Promptfmt.Println(【第三步组装Prompt】)prompt:buildPrompt(query,results)fmt.Println(prompt)fmt.Println()// 第四步调用LLM这里用模拟输出fmt.Println(【第四步调用LLM】)fmt.Println(LLM返回: 根据知识库阿尔忒弥斯2号任务计划在2025年执行。)fmt.Println()fmt.Println( 演示结束 )}运行结果 RAG系统演示 用户问题: 月球任务什么时候发射 【第一步向量化】 问题向量: [0 0.707 0.707 0 0 0 0 0 0 0] 【第二步检索】 检索结果: 第1名: 相似度0.71 内容: 任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务 第2名: 相似度0.50 内容: 阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步 第3名: 相似度0.41 内容: 此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证 【第三步组装Prompt】 你是一个智能客服请根据以下知识库回答用户的问题。 知识库 1. 任务计划在2025年执行将是自阿波罗17号以来首次载人离开地球轨道的任务 2. 阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务标志着人类重返月球的重要一步 3. 此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证 用户问题月球任务什么时候发射 请简洁准确地回答 【第四步调用LLM】 LLM返回: 根据知识库阿尔忒弥斯2号任务计划在2025年执行。 演示结束 五、与第一篇方案的对比5.1 第一篇Prompt RAG优点 - 简单容易理解 - 适合小知识库 缺点 - 知识库大了prompt就爆了 - 每次都要传所有知识成本高5.2 第五篇向量检索RAG优点 - 只传最相关的知识prompt精简 - 知识库可以很大 - 检索速度快 缺点 - 实现稍复杂 - 需要理解向量化5.3 用表格说清楚特性Prompt RAG向量检索RAG实现难度✅ 简单⚠️ 中等知识库规模❌ 受限几千字✅ 不受限Prompt长度❌ 长全传✅ 短只传相关API成本❌ 高✅ 低准确性✅ 全面✅ 智能六、总结6.1 RAG的四个步骤1. 向量化把文字变成数字 2. 检索在知识库中找到最相似的内容 3. 组装Prompt把检索结果塞进prompt 4. 调用LLM让大模型生成答案6.2 为什么这样做- 文字 → 向量电脑不认识字但认识数字 - 余弦相似度判断像不像不受长度影响 - 只传相关的prompt精简成本降低6.3 系列文章回顾第一篇Prompt RAG - 最简单的方式 第二篇文本向量化 - 把文字变成数字 第三篇余弦相似度 - 判断像不像 第四篇向量数据库 - 存储和检索 第五篇完整RAG系统 - 把所有知识串起来后记恭喜你读完了整个系列从第一篇的把知识库塞进prompt到第五篇的智能检索你已经理解了RAG的核心原理。RAG不神秘就是把文字变成数字用数学方法找到最相似的内容把相关内容给大模型让它生成答案下一步如果想深入了解可以学习TF-IDF、Word2Vec、BERT等更高级的向量化方法如果想实际应用可以学习Faiss、Milvus、Pinecone等向量数据库如果想构建产品可以学习LangChain、LlamaIndex等RAG框架系列文章Go Ollama 构建tinyRAG应用Prompt提示工程用阿尔忒弥斯2号讲透文本向量化一眼看穿相似度余弦相似度原理详解手把手实现文本向量数据库本文从问题到答案一个完整的RAG系统是如何工作的