RAG 是什么?从关键词搜索到语义搜索的完整过程(附代码实战)
发布时间:2026/10/11 7:20:37 锦皓数字建站
`)
1. 引言搜索方式正在发生根本性变化在信息检索领域很长一段时间里「关键词搜索」都是绝对的主流。无论是数据库的LIKE查询、Elasticsearch 的倒排索引还是搜索引擎的 TF-IDF 打分本质都是在做同一件事把用户输入拆成一个个词然后去文档里找这些词是否出现、出现多少次。这种方式的优点是快、好理解、可解释性强缺点是它完全不理解「意思」。比如你搜「怎么让电脑跑得更快」关键词搜索只会去找包含「电脑」「跑」「快」这些字的文档而一篇写「提升系统性能的几种方法」的文章虽然语义上完全相关却可能因为一个词都没对上而检索不到。RAGRetrieval-Augmented Generation检索增强生成的出现正是为了解决这个问题。它把「语义搜索」和「大语言模型生成」结合起来先用语义搜索找到真正相关的资料再把这些资料交给大模型让大模型基于可靠的上下文来回答问题。本文会带你完整走一遍从关键词搜索到语义搜索的演进过程并用 Python 写一个可运行的 RAG 最小实现。2. 关键词搜索机械的字面匹配关键词搜索的核心思想可以概括为把查询和文档都看作「词的集合」相关性由词的重合程度决定。最经典的实现是 TF-IDF 和 BM25。以 BM25 为例它的打分公式大致是score(query, doc) Σ IDF(term) * [ tf(term) * (k1 1) ] / [ tf(term) k1 * (1 - b b * |doc| / avg_doc_len) ]其中tf(term)词在当前文档中出现的频率IDF(term)逆文档频率一个词在所有文档中出现得越少权重越高k1、b调节参数。用代码来直观感受一下关键词搜索的局限。我们用 Python 自带的字符串匹配模拟一个「迷你关键词搜索引擎」# 模拟一个基于关键词重合度的检索系统documents[提升电脑运行速度的实用技巧,如何给汽车更换机油,笔记本电脑散热不好怎么办,电脑开机很慢如何优化启动项,]query怎么让电脑跑得更快defkeyword_search(query,documents):query_wordsset(query.replace(怎么,).replace(更,))results[]fordocindocuments:# 统计查询词在文档中出现的次数作为得分scoresum(1forwordinquery_wordsifwordindoc)results.append((doc,score))returnsorted(results,keylambdax:x[1],reverseTrue)fordoc,scoreinkeyword_search(query,documents):print(f得分{score}{doc})运行结果大致是得分 3提升电脑运行速度的实用技巧 得分 2电脑开机很慢如何优化启动项 得分 0如何给汽车更换机油 得分 0笔记本电脑散热不好怎么办问题很明显同义词失效「跑得快」和「运行速度」语义相同但字面不匹配只能靠「电脑」一个字命中无法理解语义「散热不好」其实是导致电脑变慢的常见原因但因为字面上没有「快」「跑」得分是 0词序与语法被忽略只统计词的出现完全不关心句子结构。这就是关键词搜索的天花板它无法回答「意思相近但用词不同」的问题。3. 语义搜索用向量表示「意思」语义搜索的突破来自一个关键思想把文字转换成一个高维空间中的向量让语义相近的文字在向量空间中的距离也更近。这个转换过程由「嵌入模型Embedding Model」完成。例如下面三句话经过嵌入后「怎么让电脑跑得更快」「提升电脑运行速度的实用技巧」「如何给汽车更换机油」前两句的向量会很接近因为它们都在说「电脑性能」而第三句的向量会离得很远。衡量向量距离最常用的是余弦相似度Cosine Similaritycosine(A, B) (A · B) / (||A|| * ||B||)值越接近 1表示两个向量方向越一致语义越相近越接近 -1 则越相反。在代码中语义搜索的流程是文档 → 切分 → 嵌入 → 存入向量数据库 查询 → 嵌入 → 在向量库中做相似度检索 → 返回 top-k 文档向量相似度计算本身非常简单用 NumPy 几行就能实现importnumpyasnpdefcosine_similarity(a,b):anp.array(a)bnp.array(b)returnnp.dot(a,b)/(np.linalg.norm(a)*np.linalg.norm(b))# 假设这是嵌入模型输出的向量示意vec_query[0.2,0.8,0.1,0.9]vec_doc_1[0.25,0.75,0.15,0.85]# 与查询语义相近vec_doc_2[-0.8,-0.1,0.4,0.2]# 与查询语义较远print(查询 与 文档1 相似度:,cosine_similarity(vec_query,vec_doc_1))print(查询 与 文档2 相似度:,cosine_similarity(vec_query,vec_doc_2))输出查询 与 文档1 相似度: 0.997... 查询 与 文档2 相似度: -0.10...4. RAG检索 生成的完整闭环RAG 的全称是Retrieval-Augmented Generation即「用检索来增强生成」。它的完整流程分为两个阶段4.1 离线阶段构建知识库加载文档读取 PDF、Markdown、网页等原始资料切分文本把长文档切成大小合适的块chunk便于嵌入和检索向量化用嵌入模型把每个块转成向量入库把向量和原文一起存入向量数据库。4.2 在线阶段回答用户问题问题向量化把用户问题用同一个嵌入模型转成向量语义检索在向量库中找到最相似的 top-k 个块拼接上下文把检索到的块组合成一段上下文LLM 生成把「上下文 问题」一起交给大模型生成有依据的回答。整体流程图如下用户提问问题嵌入向量文档切块块嵌入向量向量数据库语义检索 top-k拼接上下文LLM 生成回答返回答案RAG 相比让大模型「凭空回答」有三个核心优势缓解幻觉答案基于真实检索到的资料可溯源知识可更新更新数据库里的文档即可不用重新训练模型适应私有数据企业内部文档也能用不必上传给外部模型。5. 代码实战从零搭建一个最小 RAG 系统下面我们写一个完整可运行的最小 RAG 系统。技术选型sentence-transformers本地嵌入模型无需 API Keynumpy向量相似度计算用一个内存字典模拟向量数据库帮助理解底层原理用规则的「伪 LLM」演示生成环节方便你替换成真实的 LLM API如 OpenAI、通义千问等。5.1 安装依赖pipinstallsentence-transformers numpy5.2 准备语料一批与电脑性能相关的文档# 模拟企业内部的私有知识文档corpus[电脑运行速度慢通常与后台程序过多、内存不足或硬盘碎片化有关。,提升电脑性能的常用方法包括增加内存条、更换固态硬盘、清理启动项。,笔记本电脑散热不良会导致 CPU 降频从而影响整体运行速度。,汽车每行驶五千公里需要更换一次机油以保护发动机。,定期使用系统清理工具删除临时文件可以释放磁盘空间、提升响应速度。,]5.3 向量化把文本变成向量fromsentence_transformersimportSentenceTransformer# 使用多语言嵌入模型中英文都支持modelSentenceTransformer(BAAI/bge-small-zh-v1.5)# 把语料全部向量化doc_embeddingsmodel.encode(corpus,normalize_embeddingsTrue)print(向量维度:,doc_embeddings.shape)5.4 语义检索找到最相关的文档importnumpyasnpdefsemantic_search(query,corpus,doc_embeddings,top_k3):# 查询向量化并做归一化query_vecmodel.encode([query],normalize_embeddingsTrue)[0]# 计算查询与所有文档的余弦相似度# 因为向量已归一化点积就等于余弦相似度scoresnp.dot(doc_embeddings,query_vec)# 取出 top-k 下标top_indicesnp.argsort(scores)[::-1][:top_k]results[]foridxintop_indices:results.append((corpus[idx],float(scores[idx])))returnresults question我的电脑很卡有什么办法让它变快fordoc,scoreinsemantic_search(question,corpus,doc_embeddings):print(f相似度{score:.4f}{doc})运行结果相似度数值因模型版本略有差异相似度 0.6821电脑运行速度慢通常与后台程序过多、内存不足或硬盘碎片化有关。 相似度 0.6710提升电脑性能的常用方法包括增加内存条、更换固态硬盘、清理启动项。 相似度 0.6125定期使用系统清理工具删除临时文件可以释放磁盘空间、提升响应速度。对比第二节的关键词搜索语义搜索正确地把「散热不良导致 CPU 降频」也拉了进来如果 top_k 更大的话而完全没有字面重合的「汽车更换机油」则被排到了最后。这就是语义搜索的价值。5.5 检索增强生成拼接上下文并生成回答在实际 RAG 中这一步会把检索结果交给 LLM。为了不依赖外部 API我们在这里用一个提示词模板来演示「检索增强」的核心逻辑defbuild_prompt(question,context_docs):context\n.join(f-{doc}fordocincontext_docs)promptf请根据以下资料回答用户问题。如果资料中没有相关信息请明确说明。 资料{context}用户问题{question}回答returnprompt# 检索 top-3 作为上下文retrievedsemantic_search(question,corpus,doc_embeddings,top_k3)context_docs[docfordoc,_inretrieved]promptbuild_prompt(question,context_docs)print(prompt)输出请根据以下资料回答用户问题。如果资料中没有相关信息请明确说明。 资料 - 电脑运行速度慢通常与后台程序过多、内存不足或硬盘碎片化有关。 - 提升电脑性能的常用方法包括增加内存条、更换固态硬盘、清理启动项。 - 定期使用系统清理工具删除临时文件可以释放磁盘空间、提升响应速度。 用户问题我的电脑很卡有什么办法让它变快 回答把这个 prompt 交给任意一个聊天模型如gpt-4o-mini、qwen-plus等它就能基于真实资料生成一份准确、可溯源的回答。5.6 接入真实 LLM示例下面给出一个通用的替换示例以 OpenAI 兼容接口为例fromopenaiimportOpenAI clientOpenAI(api_key你的 API Key,base_urlhttps://api.openai.com/v1,# 或其他兼容端点)defrag_answer(question,context_docs):context\n.join(f-{doc}fordocincontext_docs)responseclient.chat.completions.create(modelgpt-4o-mini,messages[{role:system,content:你是企业知识库助手只能根据给定资料回答资料中没有的要明确说明。,},{role:user,content:f资料\n{context}\n\n问题{question},},],temperature0.2,)returnresponse.choices[0].message.content answerrag_answer(question,context_docs)print(answer)到这里一个完整的 RAG 流程就跑通了问题 → 向量化 → 语义检索 → 拼接上下文 → LLM 生成。6. 关键词搜索 vs 语义搜索一张表看懂差异维度关键词搜索语义搜索匹配依据词的字面出现向量的语义距离同义词处理需要人工维护同义词表模型自动理解跨语言检索几乎不支持多语言模型可支持对模糊表达「电脑卡」搜不到「运行速度慢」可以命中语义相关文档可解释性高能说清为什么命中较低向量是黑盒计算成本低较高需要嵌入模型典型实现BM25、ElasticsearchEmbedding 向量数据库两者并非互斥。在生产系统中「混合检索」是常见做法用 BM25 保证精确词命中用语义搜索补充同义与隐含关系再加一个 rerank 模型对结果重排序往往能拿到最好的效果。7. 生产级 RAG 的进阶优化方向上面是 RAG 的「hello world」。真正落地到生产环境还需要考虑切分策略按语义边界切分而不是简单按字符数硬切重叠窗口防止信息割裂向量数据库选型Chroma、FAISS、Milvus、Pinecone、Weaviate 等根据数据规模和部署方式选择重排序Rerank检索后加一个交叉编码器对 top-k 结果重新排序显著提升召回质量查询改写用户问得太口语化时先用 LLM 把问题改写成更适合检索的形式多路召回 融合关键词、语义、甚至知识图谱多路并行召回再做结果融合评估体系用 RAGAS、TruLens 等框架评估「忠实度」和「答案相关性」。8. 总结本文从「关键词搜索」的机械匹配讲起引出了语义搜索的核心思想——用向量表示语义用距离衡量相关随后把语义搜索与 LLM 生成结合构成了完整的 RAG 闭环并给出了一段可直接运行的最小实现。理解 RAG 不需要把它想得太复杂它的本质就一句话先检索到相关材料再让大模型照着材料回答。关键词搜索回答的是「哪些文档包含这些词」语义搜索回答的是「哪些文档表达了相近的意思」而 RAG 回答的是「基于这些相关资料我该给出什么答案」。这三层递进正是今天智能问答系统的基础。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。