RAG从原理到实战:用客服项目讲透检索增强生成全链路
发布时间:2026/10/2 15:51:39来源:尧图网络
上周有位读者找我说面试官让他用一分钟解释RAG他背了几天八股还是卡壳。这事我特别理解RAG概念本身不难难的是你手里没有一条完整的实现链路脑子里只有“向量检索 大模型”六个字自然说不清楚。今天这篇我换种讲法用一个真实客服项目的故事把RAG从头到尾讲透每一步都配代码和参数。面试上再被问你就直接讲故事从为什么需要RAG讲到切分、向量化、检索、生成一条线拉通比任何定义都管用。1. RAG到底在解决什么问题先从一个翻车案例说起1.1 一个没有RAG的客服机器人假设你是一家电商公司的后台开发某天老板兴冲冲告诉你“咱们上个大模型客服把售后问题全自动扛下来。”你第一版怎么做大概率是写一大段prompt扔给大模型“你是一个客服请回答用户问题。”然后直接上线。上线当天就翻车。用户问“你们家七天无理由退货怎么操作” 模型可能回答得头头是道但你仔细一看退换货流程和公司实际规则完全对不上。更离谱的是用户问“支持花呗分期吗”模型开始编造“我们支持12期免息首月只需xxx元”。你在公司内部文档里搜了一圈根本没有这条政策。这就是大模型最常见的两个问题幻觉和知识更新滞后。模型训练时只见过截止到某个时间点的语料公司内部的售后政策、新品说明、库存规则完全不在它脑子里。你没法靠换prompt解决这个问题因为参数化的知识一旦训练完就固化了你改不了。RAG要解决的就是这个“让AI学会查阅资料再说话”的问题。它不是一个神秘的技术思路可以类比成考试开卷大模型不会直接作答而是先让你从参考书里找到相关段落再结合段落内容写答案。检索增强生成核心就是“检索”和“生成”两条腿走路。1.2 RAG的核心链路五步走面试时讲RAG最好先把这条链路画出来不用多五步加载把文档、网页、数据库记录读进来。切分把长文本切成小块方便后续检索。向量化把每一块文本转成向量也就是一组数字表示它的语义。存储与检索把向量存进向量数据库用户提问时把问题转成向量通过相似度计算找出最相关的几个片段。生成把检索到的片段拼进prompt交给大模型生成答案。有了这条主线面试官问“RAG有哪些环节”就不会卡住。但如果你只背到这一步还是不够因为真正让候选人拉开差距的都在第二步和第四步的细节里。接下来我用故事把每个环节串起来。1.3 为什么不用微调和微调的对比面试几乎必问“RAG和微调有什么区别我微调一个模型不也能知道这些知识吗”这个问题要答得好需要分清两个层次。RAG解决的是“模型不知道但能查阅”的知识微调解决的是“模型的表达方式、任务能力、风格”问题。我见过一个很形象的类比RAG是让员工查公司制度手册再办事微调是给员工培训话术和姿态。手册上有的内容查一下就能答对但员工办事的风格不会变培训能改变员工的基本素养但制度改了培训成果就过期了。两者不是互相替代的关系而是互补关系。对比维度RAG微调知识来源外部知识库可动态更新模型参数需要重新训练更新成本换文档、重建索引即可需要准备数据集、训练算力幻觉问题可溯源能降低但不保证根除难以溯源依旧可能编造适合场景私有知识、问答、客服、实时数据风格迁移、格式约定、领域能力强化开发成本中等依赖检索链路质量高依赖数据标注和训练面试回答时不要只说“RAG成本低”要补一句RAG并不能替代微调两者可以组合使用比如先微调让模型学会某种业务口吻再挂RAG知识库回答实际问题。这个组合思路在真实项目中很常见。2. 故事拆解给老板做智能客服背后的RAG细节2.1 文档加载不是所有格式都能直接喂给模型回到客服项目。我当时拿到的是PDF、Word、Excel混合的售后文档还有几个网页版公告。第一反应是“直接把文档全部读进来”结果发现坑很多。PDF看着正常但很多是从扫描件转出来的文字复制出来全是乱码Excel表格读进来一行一行变成了长字符串把表格结构完全打乱网页公告里全是导航和页脚噪音。如果你没做文档加载和清洗后面整个RAG链条都会被带崩。所以第一步“加载清洗”要有明确策略。常见做法是PDF优先用能保留版式的解析库比如PyMuPDF、pdfplumber表格型PDF要用表格解析。Word用docx2txt或者python-docx读取段落注意忽略页眉页脚。网页用BeautifulSoup抽取正文去掉script、style、nav这些标签。Excel按单元格结构化读取把每一行转成一个“记录文本”不要简单合并所有单元格。代码示例加载本地文本文件from langchain_community.document_loaders import TextLoader, PyPDFLoader loader TextLoader(help_center.txt, encodingutf-8) docs loader.load() # 如果还有PDF可以这样加载 # pdf_loader PyPDFLoader(faq.pdf) # docs pdf_loader.load()实际生产里文档加载往往被低估。我建议面试时主动提一句“文档清洗比模型选择更能影响效果”这句话会显得你踩过坑。2.2 文本切分chunk size和overlap是调参第一关文本切分是RAG最容易翻车的环节也是最容易在面试里问出深度的点。我一开始用的是暴力切法按固定长度每200字切一块。结果核心问题来了一段话里前半句说“用户申请退款”后半句说“需满足条件X”被切到了两个chunk里。用户问退款条件检索到的chunk只有“用户申请退款”几个字没有任何条件细节生成答案自然错。所以切分不能只看长度要看语义边界。最常用的是递归字符切分优先按段落切段落太长再按句子切句子太长再按标点切。这样能最大程度保住完整语义。LangChain里的RecursiveCharacterTextSplitter参数上主要调三个chunk_size每块文本的目标长度中英文混排场景我一般先试300-500字。chunk_overlap相邻块之间的重叠长度通常设在chunk_size的10%-20%防止关键句正好落在切割边界。separators分隔符优先级中文场景我通常配[\n\n, \n, 。, , ]。一个常用配置from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap60, separators[\n\n, \n, 。, , , , ], length_functionlen, ) chunks splitter.split_documents(docs)很多人忽略切分后的“质量检查”。我当时写了一个小脚本把每个chunk的首尾50个字符打印出来人工扫一遍有没有语义断裂。切分不是一次调好就不管了不同文档的粒度不一样售后FAQ适合小块产品说明适合大块。2.3 向量化与检索相似度计算和topk的秘密切分之后每个chunk要变成向量。这里涉及一个关键概念嵌入模型embedding model。你可以把向量化理解为“给文本打坐标”。语义相近的句子在坐标空间里靠得近语义无关的句子里得远。中文场景我常用BAAI/bge-small-zh-v1.5它是一个开源的中文embedding模型效果不错体积也小。如果完全离线,也可以用Ollama跑本地embedding服务避免数据出网。嵌入模型调用示例from langchain_community.embeddings import HuggingFaceEmbeddings embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, encode_kwargs{normalize_embeddings: True} ) query_vector embedding_model.embed_query(七天无理由退货怎么操作) chunk_vectors embedding_model.embed_documents([c.page_content for c in chunks])向量化之后就是检索。检索最基础的方式是余弦相似度。面试这里别只说“算相似度”要能说出计算逻辑余弦相似度 两个向量的点积 / 两个向量长度的乘积结果接近1表示方向越一致语义越相近。如果embedding模型输出时已经做了归一化那余弦相似度直接等于点积速度快很多。检索时还要设一个参数top_k也就是返回最相关的几个chunk。top_k太小可能漏掉关键信息太大会把无关噪声塞进prompt模型容易被带偏。经验上先设4-6再根据效果调整。3. 手写一套极简RAG从零开始的Python实现3.1 没有LangChain也能跑核心代码很多人一学RAG就扎进LangChain结果被各种抽象类绕晕。其实RAG的核心逻辑用一个小脚本就能跑通。我把这个最小实现拆给大家看面试时你甚至可以直接默写。核心思路把文本切分后用中文分词统计词频构造成向量检索时用余弦相似度选top_k最后把结果拼进prompt。这一步虽然不够工业级但足以说明原理。# -*- coding: utf-8 -*- import jieba import math from collections import Counter def cut(text): # 去掉纯空格和多余符号 return [w for w in jieba.cut(text) if w.strip() and w not in 。、\n] def tf_vector(text, vocabulary): words cut(text) freq Counter(words) vec [freq.get(word, 0) for word in vocabulary] return vec def cosine_sim(a, b): dot sum(x * y for x, y in zip(a, b)) norm_a math.sqrt(sum(x * x for x in a)) norm_b math.sqrt(sum(y * y for y in b)) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b) # 模拟语料 docs [ 七天无理由退货要求商品保持完好且不影响二次销售。, 如果商品存在质量问题可以在签收后48小时内申请售后。, 花呗分期目前支持3期、6期和12期。, 优惠券只能在支付时使用过期不补发。, ] vocabulary sorted(set(w for d in docs for w in cut(d))) doc_vecs [tf_vector(d, vocabulary) for d in docs] query 商品有质量问题怎么申请售后 query_vec tf_vector(query, vocabulary) scores [cosine_sim(query_vec, vec) for vec in doc_vecs] top_indices sorted(range(len(scores)), keylambda i: scores[i], reverseTrue)[:2] for i in top_indices: print(fscore{scores[i]:.4f} text{docs[i]})这段代码不需要安装任何深度学习库只需要jieba做中文分词。它演示了一个关键点RAG本质上不是复杂模型而是把文本组织成可检索结构。面试你完全可以拿这个例子说明“我理解RAG底层在做什么”。当然用词频向量检索的效果远不如向量数据库因为“质量问题”和“售后”这种语义关联靠词形匹配很难发现。真实项目里要换成embedding模型。3.2 用LangChain让代码更工程化如果面试官问“你项目里是不是直接调LangChain”你就要展示工程化的写法。LangChain的价值不是“魔法”而是把加载、切分、embedding、存储、检索、LLM调用串成标准流程。一个典型的RAG流程如下from langchain_community.vectorstores import FAISS from langchain_community.llms import Ollama from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough # 1. 加载与切分 chunks splitter.split_documents(docs) # 2. 向量化入库 vectorstore FAISS.from_documents(chunks, embedding_model) retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 3. 定义提示词模板 template 你是电商客服助手请根据下面提供的参考材料回答问题。 如果参考材料里没有答案就明确说不知道不要编造。 参考材料 {context} 用户问题{question} 请用简洁的中文回答 prompt ChatPromptTemplate.from_template(template) # 4. 本地大模型Ollama llm Ollama(modelqwen2.5:7b) # 5. 组装RAG链路 def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm ) answer rag_chain.invoke(质量问题怎么申请售后) print(answer)这里有几个细节要注意FAISS是纯内存向量库适合演示和小规模场景大规模生产建议上Milvus、Qdrant或pgvector。search_kwargs{k: 4}表示取4个chunk太小容易漏信息太大容易串知识。提示词里加“没有答案就别说不知道”是压制幻觉最便宜的手段。面试时能把这条链路讲清楚已经超过大多数只会背概念的人。3.3 让链路可观测加日志和评估工程化RAG不只在于“能跑通”还在于“能排查问题”。我强烈建议在开发时给RAG加日志把每一步记录下来import logging logging.basicConfig(levellogging.INFO) # 检索环节打印召回结果 retrieved retriever.invoke(query) for i, doc in enumerate(retrieved): logging.info(f[retrieved-{i}] score-later {doc.metadata.get(source)} :: {doc.page_content[:80]})打印这些内容有什么用当你发现模型答错了先看是不是漏召回。如果相关chunk根本没有被检索到那问题在embedding或切分而不是大模型如果chunk检索到了但答案还是错那问题在prompt或生成。这个排查思路面试时直接讲比“我会调参”有说服力得多。4. 面试追问实战RAG相关的高频问题与应对4.1 常见八大坑检索质量差、切分不合理、幻觉残留面试官很喜欢问“你实际做RAG时遇到过什么问题”。下面这几个坑是我真实踩过的你可以直接拿来当案例。检索命中率低相关chunk没被召回。常见原因是chunk切太小导致语义不完整或者query与文档表达差异大。对策是调整切分参数或者对query做改写query rewriting。相似度分数普遍偏低可能是embedding模型不适合你的领域。比如医疗场景用通用embedding术语语义抓不准需要找领域微调过的模型。召回内容互相矛盾两个chunk的信息是不同版本的规则答案出现“左右互搏”。对策是在切分时保留文档来源和更新时间生成时让模型优先参考最新来源。答案冗长但没信息量往往是因为top_k过大模型被无关chunk干扰。把top_k调小或在prompt里强调“只依据可用信息回答”。幻觉依旧存在RAG不是银弹当检索结果本身不完整时模型会脑补缺失部分。除了加强prompt约束更根本的是提升召回质量。重复内容大量召回同一个问题在不同FAQ里重复出现。做去重或按来源分组避免模型被重复信息带偏。切分边界切断句子这是最常见的低质量召回元凶。用带overlap的切分以及递归式分隔符能减少但无法完全消除。向量库数据更新后效果反而变差新增了错误或格式不一致的文档带坏了检索结果。需要建文档准入规范新文档先清洗和试跑再入库。这里我建议面试时讲一个具体的badcase不用多一个就够。比如“某次用户问补差价规则因为文档里‘差价’这个词频繁出现检索回来的是另一个含‘差价’但不相关的内容模型便答了错误政策。后来我加了query改写和关键词加权才解决。”这类回答非常落地。4.2 面试官爱问的深层问题RAG与Agentic RAG、GraphRAG、Ontology RAG最近很多面试开始问“RAG的下一步是什么”。你需要知道几个关键词Agentic RAG、GraphRAG、Ontology RAG。Agentic RAG传统RAG是“每次提问无脑检索一次”。Agentic RAG则让大模型作为agent自主判断“这个问题需不需要检索”“检索一次够不够”“需要多步检索还是换个方式检索”。它适合复杂问题比如“对比A产品和B产品的售后条款”一次检索很难覆盖需要agent拆解任务多次调取。GraphRAG把文档里的实体和关系抽出来构成知识图谱再结合图结构做检索。解决“多跳问题”比如“谁在哪个部门负责这个流程”需要跨多个文档关联知识。Ontology RAG在知识图谱之上再加一层本体ontology定义概念、属性和关系的语义规范。这样能让机器更明确“产品”和“订单”之间的关系而不是只靠向量相似度。回答时我建议这样说传统RAG是“让模型会查资料”Agentic RAG是“让模型学会找资料”GraphRAG和Ontology RAG是“让资料之间有结构”。这就能让面试官知道你不仅知道名词还理解演进逻辑。如果你有时间可以简单画个关系图类型核心思路适合场景朴素RAG向量相似度召回片段FAQ、简单问答多路召回RAG关键词向量结构化数据并行召回精排前提升召回率Agentic RAG模型自主决策检索方式复杂多步问题GraphRAG实体关系图谱辅助检索多跳关系、关联分析Ontology RAG用本体规范概念语义垂直领域、术语严格的场景4.3 评估指标Hit Rate、MRR等面试如果问你“怎么知道RAG做得好不好”别只回答“看回答准不准”。你要提指标。Hit Rate命中率在所有测试问题中至少有一个golden chunk被成功召回的比例。它反映“检索器有没有把正确答案捞回来”。MRRMean Reciprocal Rank第一个正确答案在排序中的位置的倒数取平均。它反映“正确答案是不是排在最前面”。Faithfulness忠实度生成答案是不是严格基于检索到的内容有没有说瞎话。Answer Relevancy答案相关性答案和问题的相关程度。比如你有100道测试题每道题都预先标注了应该命中的chunk。检索后90道题的候选里包含正确chunk那Hit Rate就是90%。而如果正确答案往往排在第二位MRR就会偏低。MRR更高意味着用户问问题系统更快给出正确依据。我在项目里通常先调Hit Rate再调MRR最后看Faithfulness。因为召回的准确性决定了RAG地上限生成质量只是在这个上限内发挥。这个判断可以说到面试官心坎里。5. 写在最后一些个人经验聊了这么多回到开头的故事。那个读者后来告诉我他面试时不再背定义而是讲他调chunk_size、加overlap、量Hit Rate的过程面试官额外追问了很多细节他反而越聊越稳最后拿到了offer。我自己的体会是RAG的入门门槛远低于微调但要做好拼的不是会调库而是对“切分、召回、生成”三个环节每个细节的理解。面试你不需要讲得多高深但一定要让人感觉到你是“亲手做过”的而不是“背过面经”的。最后再分享一个小技巧面试前真的自己动手写一遍文中的极简RAG用公司的内部文档做一个小知识库跑通一轮“提问-检索-生成”再把一个badcase的排查过程整理成三句话。这比准备十篇八股都有用。RAG这东西代码一跑故事就有了故事有了面试就稳了。
网站建设高端定制企业官网