
一、什么是 RAG1.1 核心问题大模型虽然知识渊博但存在两个致命缺陷不知道私有数据你公司的产品文档、客服话术、内部资料大模型从未见过。容易胡编乱造幻觉遇到不知道的问题它会一本正经地编造答案。1.2 解决方案RAGRAGRetrieval-Augmented Generation检索增强生成的核心思路在向大模型提问之前先去资料库里找到最相关的资料把资料和问题一起交给大模型让它照着资料回答。1.3 对比不用 RAG vs 用 RAG场景不用 RAG用 RAG用户问“iPhone 15 Pro 用什么芯片”大模型可能编造或说不知道先检索到商品详情再让大模型根据详情回答用户问“你们退货政策是什么”大模型完全不知道先检索到退货政策文档再让大模型总结数据来源全靠大模型训练时的记忆实时从你的数据库/文档中检索1.4 RAG 完整流程图用户提问手机支持的芯片有哪些 │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ① 检索阶段Retrieval │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 分词 → 倒排索引查找 → TF-IDF 相似度计算 → Top-K 文档 │ │ │ └─────────────────────────────────────────────────────┘ │ │ 找到相关文档d1(iPhone 15 Pro A17 Pro)、d2(华为麒麟9000S) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ② 增强阶段Augmentation │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 把检索到的资料拼接到 Prompt 中 │ │ │ │ 请根据以下资料回答问题 │ │ │ │ [1] iPhone 15 Pro 搭载 A17 Pro 芯片... │ │ │ │ [2] 华为 Mate 60 Pro 搭载麒麟 9000S 芯片... │ │ │ │ 问题手机支持的芯片有哪些 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ③ 生成阶段Generation │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 大模型根据资料生成答案 │ │ │ │ 手机支持的芯片有A17 Pro、麒麟 9000S、骁龙 8 Gen 3│ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘二、RAG 的数据处理2.1 文字数据处理核心步骤分词 → 建索引 → 检索步骤说明示例分词把句子拆成词语“苹果手机” → [“苹果”, “果手”, “手机”]建索引记录每个词出现在哪些文档中“苹果” → [d1, d3]检索根据问题中的词找到对应文档问题含芯片 → 找到 d1, d2, d32.2 图片数据处理技术作用说明OCR提取图片中的文字从商品详情图中提取文字描述物体检测识别图片中的物体识别手机、电脑等商品图像识别理解图片内容看图说话描述动作、场景2.3 视频数据处理技术作用视频抽帧把视频拆成一帧帧图片图像解析用 JoyAI-VL-Interaction 等专用大模型解析画面音频转文字把视频中的语音转成文字三、代码实战最小可运行的 RAG 系统3.1 整体架构┌─────────────────────────────────────────────────────────────────┐ │ SimpleRag 系统 │ ├─────────────────────────────────────────────────────────────────┤ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ Tokenizer │ │ Retriever │ │ MockLlm │ │ │ │ 分词器 │ │ 检索器 │ │ 模拟大模型 │ │ │ │ - 中文bigram│ │ - 倒排索引 │ │ - 从上下文找答案 │ │ │ │ - 英文整词 │ │ - TF-IDF │ │ - 返回最佳句子 │ │ │ └──────────────┘ │ - 余弦相似度│ └──────────────────────┘ │ │ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ buildPrompt() │ │ │ │ 拼装Prompt │ │ │ └──────────────────┘ │ └─────────────────────────────────────────────────────────────────┘3.2 分词器 TokenizerstaticclassTokenizer{// 正则匹配英文/数字 或 连续的中文privatestaticfinalPatternPATTERNPattern.compile([a-zA-Z0-9]|[\\u4e00-\\u9fa5]);publicListStringtokenize(Stringtext){ListStringtokensnewArrayList();MatchermPATTERN.matcher(text.toLowerCase());while(m.find()){Stringsegm.group();if(seg.charAt(0)127){// 英文/数字整体作为一个词tokens.add(seg);}elseif(seg.length()1){// 单个汉字直接作为一个词tokens.add(seg);}else{// 多个汉字切成 bigram二元组// 例如苹果手机 → 苹果, 果手, 手机for(inti0;i1seg.length();i){tokens.add(seg.substring(i,i2));}}}returntokens;}}为什么用 bigram中文不像英文有空格分隔直接分词需要词典如 jieba。bigram 是一种近似分词方法不需要词典把相邻两个字组合成词。例如 “苹果手机” → [“苹果”, “果手”, “手机”]虽然没有精确分出苹果和手机但检索时仍能匹配到。3.3 文档类 DocstaticclassDoc{finalStringid;// 文档IDfinalStringtext;// 文档内容finalMapString,IntegertfnewHashMap();// 词频Term FrequencyDoc(Stringid,Stringtext){this.idid;this.texttext;}}TF词频是什么统计每个词在文档中出现了多少次。例如文档 “苹果 苹果 手机”TF {“苹果”: 2, “手机”: 1}。TF 越高说明该词对这篇文档越重要。3.4 检索器 RetrieverstaticclassRetriever{// 所有文档finalListDocdocsnewArrayList();// 倒排索引词 → 包含该词的文档ID列表finalMapString,ListIntegerinvertednewHashMap();// 文档频率词 → 出现在多少个文档中finalMapString,IntegerdfnewHashMap();// 总文档数intN0;// 建索引每添加一篇文档就分词并记录词频voidindex(Stringid,Stringtext){DocdocnewDoc(id,text);TokenizertokenizernewTokenizer();ListStringtokenstokenizer.tokenize(text);// 统计词频for(Stringt:tokens){doc.tf.put(t,doc.tf.getOrDefault(t,0)1);}// 记录倒排索引intdidocs.size();for(Stringt:doc.tf.keySet()){inverted.computeIfAbsent(t,k-newArrayList()).add(di);df.put(t,df.getOrDefault(t,0)1);}docs.add(doc);N;}// 计算 IDF逆文档频率doubleidf(Stringterm){intddf.getOrDefault(term,0);// 加1平滑防止除零returnMath.log((N1.0)/(d1.0))1;}// 计算权重 TF * IDFdoubleweight(inttf,doubleidf){returntf*idf;}// 检索返回 Top-K 相关文档ListHitsearch(Stringquestion,inttopK){// 1. 对问题分词TokenizertokenizernewTokenizer();ListStringqTokenstokenizer.tokenize(question);// 2. 计算查询词频MapString,IntegerqtfnewHashMap();for(Stringt:qTokens){qtf.put(t,qtf.getOrDefault(t,0)1);}// 3. 计算查询向量模长doubleqNormSq0;MapString,DoubleqVecnewHashMap();for(Map.EntryString,Integere:qtf.entrySet()){doublewweight(e.getValue(),idf(e.getKey()));qVec.put(e.getKey(),w);qNormSqw*w;}doubleqNormMath.sqrt(qNormSq);// 4. 候选文档只取命中查询词的文档SetIntegercandidatesnewHashSet();for(Stringt:qtf.keySet()){candidates.addAll(inverted.getOrDefault(t,List.of()));}// 5. 计算余弦相似度ListHithitsnewArrayList();for(intdi:candidates){Docdocdocs.get(di);doubledot0,dNormSq0;for(Map.EntryString,Integere:doc.tf.entrySet()){doublewdweight(e.getValue(),idf(e.getKey()));dNormSqwd*wd;DoublewqqVec.get(e.getKey());if(wq!null)dotwq*wd;}doubledNormMath.sqrt(dNormSq);if(dNorm0){hits.add(newHit(doc,dot/(qNorm*dNorm)));}}// 6. 按相似度降序排序取 Top-Khits.sort((a,b)-Double.compare(b.score(),a.score()));returnhits.subList(0,Math.min(topK,hits.size()));}}3.5 模拟大模型 MockLlminterfaceLlm{Stringgenerate(Stringquestion,ListStringcontexts);}staticclassMockLlmimplementsLlm{privatefinalTokenizertokenizernewTokenizer();OverridepublicStringgenerate(Stringquestion,ListStringcontexts){// 1. 问题分词SetStringqnewHashSet(tokenizer.tokenize(question));Stringbestnull;intbestOverlap0;// 2. 遍历所有上下文找与问题重合度最高的句子for(Stringctx:contexts){// 按标点分句for(Stringsentence:ctx.split([。\\n])){if(sentence.isBlank())continue;SetStringstnewHashSet(tokenizer.tokenize(sentence));// 计算交集st.retainAll(q);if(st.size()bestOverlap){bestOverlapst.size();bestsentence.trim();}}}// 3. 返回最佳句子returnbestnull?根据已有资料无法回答。:best。;}}MockLlm 的意义真实场景中这里应该调用 OpenAI、通义千问、Llama 等大模型 API。Mock 版本用找重合度最高的句子来模拟大模型的回答方便本地测试。3.6 Prompt 拼装staticStringbuildPrompt(Stringquestion,ListHithits){StringBuildersbnewStringBuilder();sb.append(你是一个严谨的问答助手。请仅根据下面提供的资料回答问题);sb.append(如果资料中没有答案就回答\不知道\。\n\n);sb.append(【资料】\n);for(inti0;ihits.size();i){sb.append([).append(i1).append(]).append(hits.get(i).doc().text).append(\n);}sb.append(\n【问题】).append(question).append(\n);sb.append(【回答】);returnsb.toString();}Prompt 模板示例你是一个严谨的问答助手。请仅根据下面提供的资料回答问题如果资料中没有答案就回答不知道。 【资料】 [1] 苹果公司的 iPhone 15 Pro 搭载 A17 Pro 芯片采用钛金属边框机身重量更轻。 [2] 华为 Mate 60 Pro 支持卫星通话功能搭载麒麟 9000S 芯片可以在没有地面网络的情况下拨打电话。 【问题】手机支持的芯片有哪些 【回答】3.7 主流程publicstaticvoidmain(String[]args){RetrieverretrievernewRetriever();// ① 离线分词 建索引retriever.index(d1,苹果公司的 iPhone 15 Pro 搭载 A17 Pro 芯片采用钛金属边框机身重量更轻。);retriever.index(d2,华为 Mate 60 Pro 支持卫星通话功能搭载麒麟 9000S 芯片可以在没有地面网络的情况下拨打电话。);retriever.index(d3,小米 14 搭载骁龙 8 Gen 3 处理器配备徕卡光学镜头主打影像性能。);retriever.index(d4,OPPO Find X7 采用天玑 9300 芯片支持 100W 超级闪充续航表现优秀。);// ② 在线检索Stringquestion手机支持的芯片有哪些;ListHithitsretriever.search(question,2);System.out.println( 检索结果 Top-hits.size() );for(Hith:hits){System.out.println(h.doc().id: h.doc().text (scoreh.score()));}// ③ 生成拼 Prompt 调用 LLMLlmllmnewMockLlm();StringpromptbuildPrompt(question,hits);Stringanswerllm.generate(question,hits.stream().map(h-h.doc().text).toList());System.out.println(\n 最终答案 );System.out.println(answer);}四、核心算法详解4.1 TF-IDF概念全称含义公式TFTerm Frequency词在文档中出现的频率词出现次数IDFInverse Document Frequency逆文档频率衡量词的稀有程度log((N1)/(df1)) 1TF-IDF-综合权重TF × IDFIDF 的直觉理解如果一个词在所有文档中都出现如的、“是”它的区分度很低IDF 应该很小。如果一个词只在少数文档中出现如麒麟9000S它的区分度很高IDF 应该很大。4.2 余弦相似度余弦相似度衡量两个向量的夹角值越接近 1 表示越相似。cos(θ) (A · B) / (|A| × |B|)A查询向量问题分词后的 TF-IDF 权重B文档向量文档分词后的 TF-IDF 权重点积对应位置相乘再求和模长各分量平方和开根号4.3 倒排索引倒排索引 从词到文档的映射。词文档列表苹果d1芯片d1, d2, d3, d4华为d2作用检索时只需查找问题中的词就能快速定位到相关文档无需遍历所有文档。五、完整数据流┌─────────────────────────────────────────────────────────────────────────┐ │ 离线阶段建索引 │ ├─────────────────────────────────────────────────────────────────────────┤ │ 文档1 ──▶ 分词 ──▶ 词频统计 ──▶ 倒排索引 │ │ 文档2 ──▶ 分词 ──▶ 词频统计 ──▶ 倒排索引 │ │ 文档3 ──▶ 分词 ──▶ 词频统计 ──▶ 倒排索引 │ │ 文档4 ──▶ 分词 ──▶ 词频统计 ──▶ 倒排索引 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 在线阶段检索 │ ├─────────────────────────────────────────────────────────────────────────┤ │ 用户问题 ──▶ 分词 ──▶ 计算 TF-IDF ──▶ 倒排索引查找 ──▶ 余弦相似度排序 │ │ │ │ 返回 Top-K 文档 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 生成阶段LLM │ ├─────────────────────────────────────────────────────────────────────────┤ │ 拼装 Prompt资料 问题 ──▶ 调用 LLM ──▶ 返回答案 │ └─────────────────────────────────────────────────────────────────────────┘六、RAG 的优化方向优化点方案说明分词更准使用 jieba、HanLP比 bigram 更精确检索更准向量数据库Faiss、Milvus用 Embedding 代替 TF-IDF排序更准重排序模型Cross-Encoder对 Top-K 结果二次排序上下文更长分块Chunking 重叠长文档切成小块分别检索多模态OCR 图像描述支持图片、视频检索成本优化本地模型 云端模型混合简单问题本地答复杂问题上云七、总结7.1 RAG 的核心价值让大模型带着资料回答问题解决幻觉和私有数据问题。7.2 关键流程分词 → 建索引 → 检索 → 拼 Prompt → LLM 生成7.3 技术要点技术作用bigram 分词中文近似分词无需词典TF-IDF衡量词对文档的重要性倒排索引快速定位包含查询词的文档余弦相似度衡量查询与文档的相似程度Prompt 工程把资料和问题组合成指令7.4 一句话记忆RAG 先检索找资料 再生成让大模型照着资料回答。