从零手写RAG:大模型外挂知识库,解决幻觉与私有知识接入

发布时间:2026/10/18 10:07:35

从零手写RAG:大模型外挂知识库,解决幻觉与私有知识接入 你有没有问过大模型一个专业问题它语气笃定地回答了一大篇你转手一查资料发现关键数据全是在编我见过不少刚接触大模型的程序员第一批产品Demo都栽在这上面。这个问题的学名叫“幻觉”而目前工程上最主流的解法就是RAG——检索增强生成。RAG的原理一句话就能说清让大模型在回答之前先到外部知识库里把相关内容“查”出来再结合查到的内容作答。对小白程序员来说它往往是进阶大模型应用的第一个正儿八经的项目在大模型应用架构里它也是把私有知识接入通用模型的最轻量方式。这篇内容是我从零学习和落地RAG的实践整理面向跟我一样从传统业务开发转过来的程序员不堆论文公式尽量把原理讲明白把能跑的Demo给出来再把实际项目中踩过的坑也一并说了。读完你能回答三个问题RAG是什么、怎么跑通一个最小实例、上线之前还要补哪些功课。1. RAG到底在解决什么大模型的三大硬伤与“开卷考试”思路1.1 大模型看着聪明实际上有三个硬伤第一个硬伤是幻觉。大模型本质上是概率预测机器它不是在“回忆知识库”而是在“预测下一个最合理的词”。一个问题只要在训练语料里见过相似模式它就能给出像模像样的回答一旦那个主题恰好很冷门或者问法比较偏它就开始“圆谎”——语气越笃定越容易把人带沟里。我自己亲历过一个场景给领导演示内部问答系统问到一个行业特有名词屏幕上赫然出现一段工整但完全错误的定义。当时冷汗就下来了。这不是模型笨而是训练数据里根本没有那个词的正确用法模型只能根据上下文拼凑一个看起来合理的答案。第二个硬伤是知识过时。大模型训练语料有一个截止日期之后发生的事它一概不知。今天刚发布的技术、公司刚刚调整的政策、某款产品的实时参数它通通没有概念。哪怕模型支持联网更新底层的静态知识依然是滞后的。第三个硬伤是私有知识缺失。企业内部文档、个人笔记、产品说明书、项目复盘这些内容不在互联网公开语料里通用模型压根没见过。你想让模型成为“懂你们业务”的助手光靠通用模型是不可能的。1.2 “开卷考试”思路不用重新训练就能让模型懂业务那怎么办重新训练一个大模型成本极高普通团队根本玩不起。RAG的思路非常朴素像开卷考试一样答题之前先翻书。常规系统流程是“问题 → 大模型 → 回答”。RAG在中间加了一步“问题 → 先去知识库里检索相关内容 → 把问题和相关内容一起交给大模型 → 回答”。模型本身没有变强但它被强行加载了参考资料至少不会空口胡说。这个设计有两个直观的好处知识可替换文档今天更新了明天回答就跟着更新不用重新训练模型回答可溯源我们知道它参考了哪几段资料出了问题能回来追责。这也解释了为什么RAG这两年迅速成为大模型应用落地的主力方案——它解决的不是“模型能力”问题而是“模型没看过你的资料”这个信息缺口问题。1.3 那为什么不直接微调有人会问微调不也能让模型懂业务吗可以但微调更适合改变模型的“能力和风格”比如让输出格式统一、学会特定领域的表达习惯。而微调很难承载每天都在变的动态知识——今天公司流程改了你还得重新训一版模型。生产环境里最实用的做法是把两者结合用RAG做主线知识外挂用微调来调整回答的格式和语气。刚入门的时候优先把RAG链路跑通性价比最高。2. RAG完整工作流拆解索引、检索、生成三阶段都在忙什么先给你一张全局表把三个阶段的关键信息理清楚。我当初就是靠这张表建立起整体认知的。阶段输入输出核心工作索引阶段各种原始格式的文档向量化后的切片和索引文档清洗、文本切分、向量化、入库检索阶段用户问题相关文档片段Top-K问题向量化、相似度计算、排序生成阶段问题 检索片段最终回答构造提示词、大模型生成、附引用来源2.1 为什么非要把文档切成一截一截大模型的上下文窗口再长也装不下整本操作手册。就算硬塞进去模型也不知道重点在哪回答质量反而不如精准投喂几段。所以索引阶段会把文档切成几百字的块这个动作叫Chunking。切块粒度直接影响后面的检索效果块切大了一块里掺杂好几个议题和用户问题的相关性被稀释块切小了单个块信息量不足大模型拿到手也拼不出完整上下文。这个权衡我在第4章细说这里先有概念就行。2.2 为什么非要把文本转成“向量”计算机直接比较两段文字的语义是非常困难的。传统关键词搜索能做到精确匹配但用户说“年假怎么算”文档里写的是“带薪休假制度”关键词完全不同传统方案就不灵了。Embedding模型解决的就是这件事。它会把一段文字映射成一串几百到几千维的数字也就是向量。语义相近的文本在向量空间里的位置也相近。于是“找相似内容”就变成了“找相邻向量”。这是整个RAG检索的地基后面的所有操作都是在这个地基上盖楼。2.3 检索阶段和SQL查询完全是两码事传统数据库查询是精确条件匹配而向量检索是“模糊语义匹配”。用户说“年假按什么标准休”文档里写的是“带薪年休假依照累计工作年限核定”两个句子没有一个字重叠但向量空间里它们离得很近照样可以召回。这个能力来自Embedding模型在海量语料里学到的词语关系和上下文模式。它不关心字面是否一致只关心“意思”是否接近。这也是RAG能比传统搜索更懂“人话”的根本原因。2.4 生成阶段的提示词决定模型是“引用资料”还是“自由发挥”很多初学者把检索结果往问题后面一拼就丢给大模型这是最常见的错误。模型没理解“你给我的参考资料是用来回答问题的”就可能无视内容直接放飞自我。一个合格的RAG提示词至少要包含三层要求请严格基于给定参考资料回答资料中没有的内容明确说“未找到相关信息”不要编造尽量还原原文里的细节和数据需要时可以引用原文片段。另外生成参数里的温度也要调低。温度太高模型会加戏做知识问答类RAG温度设在0到0.3之间比较稳。3. 手写一个几十行的极简RAG不依赖框架也能把链路跑通很多教程一上来就教你用各种编排框架反而把底层原理掩盖了。我建议先自己手写一个极简版本整个链路从头到尾调通一次再去看框架文档会有一种“不过如此”的顿悟感。3.1 准备工作和测试语料先把环境准备好Python 3.8以上即可需要一个能算余弦相似度的数值计算库。Embedding接口和对话模型接口我用两个占位函数表示你实际操作时把它们替换成自己手头可用的模型服务就行。我用的测试语料是一段模拟的内部员工手册内容大概包括考勤制度、年假规则、报销流程。这样的私有文档最适合演示RAG的价值——因为通用模型肯定没学过它。3.2 项目结构和完整代码核心逻辑放在一个文件里就够了拆分函数按“加载文档、切分、向量化、检索、生成”的顺序组织import numpy as np def load_document(path: str) - str: 读取本地文本文件内容。 with open(path, r, encodingutf-8) as f: return f.read() def split_chunks(text: str, chunk_size: int 200, overlap: int 50) - list: 滑动窗口切分文本。 chunk_size 控制每块长度overlap 让相邻块有部分重叠 避免关键信息刚好落在切分缝里被截断。 chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) start end - overlap # 去掉过短的无效块 return [c for c in chunks if len(c.strip()) 50] def call_embedding_api(text: str) - np.ndarray: 占位函数调用你正在使用的embedding模型服务 把输入文本变成向量返回numpy数组。 不同服务的向量维度不同无需关心具体数字。 # 示例return np.array(你的模型服务接口.encode(text)) raise NotImplementedError def call_llm_api(prompt: str) - str: 占位函数调用你正在使用的对话大模型服务 输入完整提示词返回最终回答文本。 # 示例return 你的对话模型服务.chat(prompt) raise NotImplementedError def cosine_search(query_vec: np.ndarray, doc_vecs: list, top_k: int 3) - list: 把查询向量和所有文档向量做余弦相似度计算 返回相似度最高的 top_k 个文档块下标和分数。 q query_vec / (np.linalg.norm(query_vec) 1e-10) scored [] for i, doc_vec in enumerate(doc_vecs): d doc_vec / (np.linalg.norm(doc_vec) 1e-10) scored.append((i, float(np.dot(q, d)))) scored.sort(keylambda x: x[1], reverseTrue) return scored[:top_k] def build_prompt(query: str, context: str) - str: 把用户问题和检索到的资料拼成最终提示词。 明确的指令是防止模型编造的关键。 return f请根据下面的参考资料回答问题。 要求 1. 只依据参考资料作答不要使用你自己的额外知识 2. 如果参考资料中没有答案请直接回答“资料库中未找到相关信息” 3. 涉及数据时请引用原文。 参考资料 {context} 问题{query} 回答 def main(): # 1. 加载并切分文档 doc load_document(employee_handbook.txt) chunks split_chunks(doc, chunk_size200, overlap50) # 2. 切分后的块全部向量化 doc_vecs [call_embedding_api(c) for c in chunks] # 3. 用户问题同样向量化 query 公司年假制度是怎么规定的 query_vec call_embedding_api(query) # 4. 检索最相关的3块内容 top_k cosine_search(query_vec, doc_vecs, top_k3) # 5. 组装提示词交给大模型 context \n\n\n\n.join([chunks[i] for i, _ in top_k]) prompt build_prompt(query, context) answer call_llm_api(prompt) print(f检索到的片段序号和相似度: {top_k}) print(f最终回答: {answer}) if __name__ __main__: main()3.3 实际运行能看出什么效果我把同样的问题分别用“纯大模型”和“极简RAG”跑了一遍。纯大模型版本回复得头头是道但仔细一核对年假天数和文件里的规定完全对不上典型的幻觉。加了RAG之后回复变成了类似这样的样子根据员工手册第三章入职满1年不满10年的员工当年度可享受5天带薪年假满10年不满20年的为10天。试用期内不单独计算年假额度转正后统一按入职时间折算。这就是RAG最直观的价值模型开始“引用原文”而不是“凭感觉输出”。哪怕你后面换更大更强的模型这个机制也依然有效——它改变了模型生成答案时的知识来源。3.4 这个极简版离生产级别还差什么这段代码能跑通但距离生产可用还有几条明显的差距没有上向量数据库文档多了全量遍历会越来越慢没有做查询改写用户口语化提问遇到文档书面语时召回率可能不够没有重排序Top-K里可能混着不相关的内容没有评估环节改一版配置好坏只能靠肉眼判断。这些短板我在第5章逐一展开。先把手写版的逻辑吃透后面优化时你会很清楚每一步是在解决什么问题。4. 切分与向量化最容易被忽视、却直接影响效果上限的两个动作4.1 文本切分没有标准参数但有几条经验很多人第一次跑RAG就是直接按固定字符数切效果时好时坏然后开始怀疑模型其实问题多半出在切分上。切分粒度要考虑你的下游任务。问题通常是针对某个具体事实的比如“报销发票需要哪些材料”这时候块太大里面还夹着出差制度、预算审批噪声就会干扰检索块太小比如只剩一句话模型看不到上下文很难判断这条信息到底在回答什么。我总结了一套比较稳妥的起步参数中文文本按标点符号做基础分段每块大约300到500字块之间重叠50到100字。等系统跑通之后再根据实际检索效果微调。如果文档本身有清晰的章节结构优先按章、节聚合后再切而不是直接从正文硬切。4.2 Embedding模型选型决定“语义相近”判断准不准Embedding模型是语义理解的核心选错了后面怎么优化都费劲。重点看四个维度语言支持语料以中文为主就选中文效果好的中英混合就选手双语模型输入长度有些模型单次只支持512个token你的chunk超过长度会被截断语义信息直接丢失向量维度维度高表达能力强但占用存储多、检索慢维度低省资源但细节辨别力弱一些部署和调用成本API调用最简单但对数据敏感的团队可能更愿意本地部署开源模型。这里想再强调一句Embedding模型更新换代不像对话大模型那么频繁选一个在你目标语言评测集上成绩稳定的模型基本可以稳定用很久。没必要追新。4.3 余弦相似度的直觉理解向量检索最常用的相似度指标是余弦相似度。它做的事很直观计算两个向量之间的夹角余弦值1代表方向完全一致0代表相互垂直-1代表方向完全相反。为什么不直接用欧氏距离因为向量长度本身会影响欧氏距离两个长向量哪怕方向一致欧氏距离也可能很大。而语义相似性更像“方向”问题——你的侧重点和我的侧重点是不是同一个方向而不是“你这段话比那段话长多少”。余弦相似度只看夹角正好贴合这种直觉。手算一遍就懂了把几段话分别embedding成向量随便选两对计算余弦相似度你会发现“语义接近的文本”分数明显高字面上完全不同但意思相近的句子也能拿到不错的分数。5. 上线前必须看的避坑清单检索不准、答非所问、越来越慢怎么办5.1 检不到或检不准按这个顺序排查我最怕收到“检索结果乱七八糟”这种描述因为问题可能出在任何环节。按顺序排查能快速定位切分参数是不是太大了或太小了先把chunk_size调到400左右overlap调到80看趋势Embedding模型跟语料语言匹配吗中文语料配英文模型效果一定差Top-K是不是太小了只取1条一旦那条不相关回答就废了有没有做查询改写用户说“休假咋算的”文档里写的是“年休假实施细则”这个问题交给大模型改成贴近文档用语的问法召回会明显变好。如果以上都调过还是不准再考虑上重排序。我用过的套路是先向量检索粗召回50条再用精排模型把真正相关的排到最前面最后取前5条给大模型。粗排负责不漏精排负责准确排序两者配合效果好很多。5.2 检索到了但模型回答还是错的这一步十有八九是提示词问题。模型拿到了相关资料但它不认为自己应该“照着念”。我常用的加固手段有三个明确要求“只依据参考资料回答”资料没有就直说没有要求回答中标注引用来源比如【资料3】把生成温度调到0.2以下减少发散。另外生成前可以加一道“相关性门槛”如果检索片段和问题的相似度低于某个阈值就不要带进提示词直接告诉用户“未检索到相关内容”。宁可承认不知道也不能硬答。5.3 多知识库场景下检索容易“串味”知识库一大不同主题混在一起向量检索很容易把不相关的内容也捞上来。比如员工手册和市场宣传手册放同一个库里问“报销流程”时可能捞出来一片市场文案。解法是给每个块打元数据标签比如来源、部门、文档类型、日期。检索前先按业务条件过滤再在过滤结果里做向量相似度计算。如果业务天然分域干脆把知识库拆成多个独立集合按路由规则选择检索哪一个这样互不干扰。5.4 怎么科学评估RAG好不好而不是靠感觉“效果感觉还不错”是RAG项目最常见的止步点。没有量化指标后续优化就无从谈起。我搭过一套轻量评估流程准备30到50道题每道题标注标准答案和应该命中的文档每次修改后统一跑一遍记录三个指标检索命中率标准答案对应的文档是否出现在Top-K里回答忠实度回答是否严格基于资料有没有自行编造回答有用性用户拿到回答能不能解决实际问题。现在的开源社区里也有自动评估框架核心逻辑本质上就是这三点。我自己的经验是没上评估之前觉得系统已经稳了一上评估发现一半的case不达标这才是RAG项目真正开始变靠谱的节点。5.5 响应越来越慢性能问题不能等到上线再处理数据量几百条时内存遍历没问题量一大全量相似度计算就会拖慢整个链路。上线前最好提前做三件事引入专门的向量检索方案比如近邻索引或开源向量数据库对热门问题做结果缓存相同或近似问题直接返回缓存答案把非核心环节异步化比如文档入索引这类离线任务不要占用在线请求路径。性能优化没有银弹但先把缓存和专用检索做上日常问答场景的响应时间基本能控制在可接受范围。6. 给小白的学习路线按这个顺序走少走半年弯路6.1 六个阶段从代码到工程第一步搞懂对话大模型API。能写一个“输入问题输出回答”的最小程序理解上下文参数和温度参数的含义。第二步理解Embedding。调用向量接口把几段话变成向量手动算一算余弦相似度感受“语义相近则向量相近”这个核心直觉。第三步手写极简RAG。就像本文第三章那样从加载文档到生成回答完整敲一遍不要直接套框架。第四步引入编排框架和向量数据库把链路工程化。有了手写版的底子框架文档会变得很好理解因为每个抽象概念你都亲手实现过了。第五步做效果优化。把第5章的重排序、查询改写、元数据过滤、缓存逐项加上去每加一项都记录指标变化。第六步搭评估体系和监控。持续追踪线上效果定期回归测试集。这步做完你才算真正从“能跑Demo”进化到“能上生产”。6.2 看完这篇文章下一步该做什么最优先的任务只有一件把第三章的代码敲一遍换成你自己的知识库跑通之后再谈优化。我见过太多人想法很满、代码一行没写结果几周过去还停在理论层面。跑通之后把失败的case收集起来分类汇总哪些是检索问题哪些是提示词问题哪些是知识库本身缺内容。带着这些真实问题去查资料效率远高于漫无目的地刷技术文章。6.3 三个容易陷进去的误区第一个误区是“为了用新技术而用新技术”。有些场景里传统的关键词搜索已经够用硬套RAG反而增加延迟和成本。先分析问题再选方案而不是拿锤子找钉子。第二个误区是“只会调框架说不清原理”。框架封装的层次越来越深总有一天你会遇到框架处理不了的边界case那时候能救你的只有对底层原理的理解。第三个误区是“光读不练”。大模型应用是高度工程化的领域手感是靠一行行代码堆出来的。快捷键背得再熟不敲代码照样不会写程序。我带过的组里新人也经常会犯这样的毛病一上来就想上最火的框架和向量数据库结果一晚上都在装环境。我每次都劝先把本文这个几十行的版本亲手敲一遍整个链路跑通了你会发现框架文档里那些抽象概念突然都变得特别具体。这大概就是RAG入门最笨、也最踏实的路。
延伸阅读

更多相关文章

2026/10/18 10:07:35

告别熬夜改稿:AI辅助PPT制作全流程效率提升实战指南

凌晨两点十七分,我对着同一页公司介绍已经改了第九遍,只因为上面那条业务模块的配图怎么摆都差那么点意思。这不是我第一次为PPT熬夜,也大概率不会是最后一次——如果我没在那个周末试过那款叫PaperXieAIPPT的工具的话。真正让我把标题写成“…

2026/10/18 10:07:35

视频深度理解三阶段方案:先浏览再定位后精读,成本降58.3%

这阵子我一直在折腾视频长内容的深度理解。这里说的深度理解,不是随便剪一段视频丢给模型让它出个总结,而是像Deep Research那样,针对一个具体的问题,从视频里挖出答案、收集证据、整理成结论。网页文本上的Deep Research已经跑得…

2026/10/18 13:52:50

UE4物理时间调度器:解决暂停时Timer失效问题

1. 项目概述:为什么“暂停时还能跑的Timer”是UE4里一个高频痛点在UE4项目开发中,我几乎每次带新人做第一个完整功能模块时,都会遇到同一个问题:明明逻辑写对了,但一按P键暂停游戏,那个本该3秒后弹出的提示…

2026/10/18 13:52:50

HOOPS AI:AI落地CAD的最务实路径,藏在数据管道里

最近总做CAD/PLM的朋友问我:AI到底该怎么进三维设计这条赛道。ChatGPT能写文档,扩散模型能生成图片,可放到CAD这种要精确到小数点后四位、还要对制造负责的领域,AI好像一下子就没那么神了。其实答案不在最上层,而在中间…

2026/10/18 13:52:50

Python+Vue学院兼职平台开发实战:Flask/Django后端与部署全解析

1. 项目概述与核心需求拆解站在一个做过类似项目的开发者角度,第一次看到“基于Python的某学院兼职平台”这个题目,我第一反应是:这是一个非常典型的Web全栈练手项目,也是很多计算机专业同学毕设或者课设最爱选的选题方向。它既有…

2026/10/18 13:52:50

Excel存货健康度看板:12指标+3类归因的业财监控方案

简介:本资源是一份面向企业财务、供应链及数据分析岗位从业者的「财经存货看板」专业文档,聚焦存货可视化管理与财务协同分析场景,解决实时库存监控、呆滞风险识别、周转率追踪及合规数据支撑等核心痛点。文档为单文件Word格式(.d…

2026/10/18 13:47:50

Windows下Chromium编译指南:GN/Ninja参数配置与避坑实践

在Windows上编译Chromium这件事,说难不难,说简单也真不简单。经常有同行跑来问我:为什么gn gen一跑就报错?为什么同一份源码,别人半天就能跑起来,我这边编了一整夜还在链接?最后排查下来&#x…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑