
我给AI喂了50份文档,它变成了我的私人专家
前言:上一期我们用100行代码搭了个AI Agent。但Agent再聪明,脑子里没有你的数据也是白搭。这次,我给它装了个"外置大脑"——RAG,让AI拥有你的专属知识库,问什么都能答。
为什么你的AI总在"一本正经地胡说八道"?
我之前帮朋友做了个公司内部问答机器人,用的GPT-4,效果惨不忍睹。
员工问"我们公司年假政策是什么",AI答"根据劳动法,年假一般是5-15天"——听起来没毛病,但他们公司年假是20天起。AI不知道,因为它训练数据里根本没有这家公司的内部文档。
这就是大模型的知识盲区:
RAG(检索增强生成)就是解决这个问题的。
一句话理解:RAG = 考试时允许翻书。 大模型是大脑,RAG是精准的书架——大脑再聪明,也需要书架上的书当依据,才能给出靠谱答案。
RAG到底怎么工作的?
核心流程就3步:存→找→答
第1步:存——把知识"切碎"放进向量数据库
你不能把整本100页的PDF直接扔给AI,一是塞不下,二是检索不准。得先"切碎"。
原始文档(100页PDF) ↓ 文本分块(每块500字,重叠50字) 知识片段1(第1-2页内容摘要) 知识片段2(第2-3页内容摘要) 知识片段3(第3-4页内容摘要) ... 知识片段N ↓ 向量化(用Embedding模型转成数字向量) [0.12, -0.34, 0.56, ...] → 存入向量数据库 [0.08, -0.21, 0.43, ...] → 存入向量数据库 为什么要"切碎"? 因为整篇文档太长,检索时大部分内容跟你的问题无关。切成小块后,只把相关的那几块找出来,精准又高效。
为什么要重叠50字? 防止刚好在切分点把一句话截成两半,上下文断了。
第2步:找——从知识库里"捞"出相关内容
用户问问题时,把问题也转成向量,然后去数据库里找最相似的片段。
用户问:"公司年假政策是什么?" ↓ 向量化 问题向量 [0.15, -0.30, 0.52, ...] ↓ 与数据库中所有片段向量计算相似度 ↓ 返回相似度最高的Top 4个片段 片段3: "员工年假政策:入职满1年享20天年假..." 片段7: "年假申请流程:提前3天在OA系统提交..." 片段12: "年假可结转至次年Q1,过期作废..." 片段45: "试用期员工按比例享受年假..." 找的不是关键词匹配,而是语义相似。所以你问"休假规定"也能找到"年假政策",因为语义上它们是接近的。
第3步:答——让AI基于找到的内容回答
把找到的片段和用户问题一起发给大模型:
"根据以下参考资料回答用户问题。 如果资料中没有答案,就说不知道。 参考资料: [片段3] 员工年假政策:入职满1年享20天年假... [片段7] 年假申请流程:提前3天在OA系统提交... [片段12] 年假可结转至次年Q1,过期作废... [片段45] 试用期员工按比例享受年假... 用户问题:公司年假政策是什么?" AI回答:"根据公司政策,入职满1年享20天年假,需提前3天在OA系统申请。年假可结转至次年Q1,过期作废。试用期员工按比例享受。"
看,AI不再瞎编了,每个答案都有出处。
开干:从零搭一个RAG系统
装环境
pip install openai chromadb python-dotenv pypdf 完整代码(不到80行)
import os import json from openai import OpenAI from dotenv import load_dotenv import chromadb load_dotenv() client = OpenAI() # ===== 第1步:文档加载与分块 ===== def load_and_chunk(file_path, chunk_size=500, overlap=50): """读取文本文件并分块""" with open(file_path, "r", encoding="utf-8") as f: text = f.read() chunks = [] start = 0 while start < len(text): end = start + chunk_size chunk = text[start:end] chunks.append(chunk) start = end - overlap # 重叠部分 return chunks # ===== 第2步:向量化与存储 ===== def get_embedding(text, model="text-embedding-3-small"): """调用OpenAI Embedding API获取向量""" resp = client.embeddings.create(input=text, model=model) return resp.data[0].embedding def build_vector_db(chunks, collection_name="my_knowledge"): """把分块向量化并存入ChromaDB""" db = chromadb.PersistentClient(path="./chroma_db") collection = db.get_or_create_collection(collection_name) for i, chunk in enumerate(chunks): embedding = get_embedding(chunk) collection.add( ids=[f"chunk_{i}"], embeddings=[embedding], documents=[chunk], metadatas=[{"source": "local_doc", "chunk_index": i}] ) print(f"已存入 {len(chunks)} 个知识片段") return collection # ===== 第3步:检索 ===== def retrieve(collection, query, top_k=4): """检索与问题最相关的知识片段""" query_embedding = get_embedding(query) results = collection.query( query_embeddings=[query_embedding], n_results=top_k ) return results["documents"][0] # ===== 第4步:生成回答 ===== def generate_answer(query, retrieved_chunks): """让大模型基于检索到的内容回答""" context = "\n\n---\n\n".join(retrieved_chunks) prompt = f"""你是一个知识库问答助手。请根据以下参考资料回答问题。 如果参考资料中没有答案,请明确说"根据现有资料无法回答"。 回答时请引用资料来源。 参考资料: {context} 问题:{query} 回答:""" resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.3, ) return resp.choices[0].message.content # ===== 完整RAG流程 ===== def rag_query(collection, question): """一键RAG问答""" print(f"\n问:{question}") # 检索 chunks = retrieve(collection, question) print(f"检索到 {len(chunks)} 个相关片段") # 生成 answer = generate_answer(question, chunks) print(f"答:{answer}") return answer # ===== 测试 ===== if __name__ == "__main__": # 1. 加载文档并分块 chunks = load_and_chunk("公司制度.txt") # 2. 建向量库 collection = build_vector_db(chunks) # 3. 开始问答 rag_query(collection, "公司年假政策是什么?") rag_query(collection, "出差报销流程是怎样的?") rag_query(collection, "入职需要准备什么材料?") 运行效果:
问:公司年假政策是什么? 检索到 4 个相关片段 答:根据公司制度文档,入职满1年享20天年假,满3年享25天。需提前3天在OA系统提交申请,年假可结转至次年Q1,过期作废。 问:出差报销流程是怎样的? 检索到 4 个相关片段 答:根据行政制度,出差归来后5个工作日内提交报销单,附发票原件。交通费按高铁二等座标准,住宿按城市级别限额报销。超过5000元需部门总监审批。 问:公司年会什么时候办? 检索到 4 个相关片段 答:根据现有资料无法回答。检索到的片段中未提及年会相关信息。 注意最后一个问题——AI没有瞎编,而是诚实说"不知道"。这就是RAG的价值:减少幻觉,有据可查。
我实测踩了4个坑,你大概率也会遇到
坑1:分块太大,检索全是"废话"
一开始我把chunk_size设成2000,结果检索出来的片段又臭又长,AI在冗长文本里找不到重点,回答质量很差。
解法:chunk_size设500,overlap设50。这是我个人测下来最甜的参数。太小块语义不完整,太大块噪音多。
坑2:Embedding模型选错,中文检索效果差
用了OpenAI默认的ada-002模型,中文语义理解很弱。问"休假"找不到"年假",问"报销"找不到"费用"。
解法:中文文档优先用 text-embedding-3-small(效果比ada-002好很多),或者用国产模型如BGE-M3、M3E-Base,中文检索准确率能提升30%以上。
坑3:检索Top K太少,关键信息被漏掉
一开始设k=2,有些问题答案在第3或第4个相关片段里,导致回答不上来。
解法:k=4是性价比较高的设置。但超过6个片段后,上下文太长反而让AI分心,回答质量下降。如果需要更多内容,考虑用重排序(Reranker)先筛一轮再喂给AI。
坑4:PDF提取乱码,分块全乱了
直接用pypdf提取中文PDF,有时候段落顺序错乱、表格变成乱码,分块结果惨不忍睹。
解法:复杂PDF用PyMuPDF(fitz)提取,效果比pypdf好。如果文档有表格,先用OCR工具转成结构化文本再分块。记住:垃圾进,垃圾出。数据预处理决定了RAG的上限。
从Demo到生产级,还差什么?
| 数据源 | ||
| 分块策略 | ||
| 检索方式 | ||
| 重排序 | ||
| 评估 | ||
| 更新 | ||
| 多轮对话 |
最值得优先做的两件事:一是混合检索(向量+关键词),二是重排序。这两个加上去,效果直接上一个台阶。
向量数据库怎么选?
实测对比了3个主流选项:
| ChromaDB | |||
| FAISS | |||
| Milvus |
我的建议:先用ChromaDB跑通流程,数据量大了再换Milvus。别一上来就纠结选型,先让东西跑起来。
我的真心话
RAG不是什么高深技术,核心逻辑就3步:切碎→找回→拼好。但它解决的是AI落地中最实际的问题——让AI用你的数据说话。
如果你正在做AI应用,不管是什么场景,客服机器人、文档问答、知识管理……RAG几乎都是必经之路。先花1小时把这个80行代码跑通,你会有种"原来不过如此"的感觉。然后你就会发现,真正难的不是RAG本身,而是数据清洗和检索优化——这才是拉开差距的地方。
完整代码+测试数据集,公众号回复「RAG80」获取。
你有没有尝试过搭建RAG?用的什么方案?评论区聊聊 👇
下期预告:AI Agent + RAG合体——让Agent既能动手干活,又能查资料回答问题,这才是完整的AI助手
💡 关注【AI搞事星球】
每周三篇AI实战干货,从Agent到多模态,陪你一起用AI搞大事 💪
点击上方蓝字关注,不错过下期实战案例 →
🔍 相关搜索:AI Agent · MCP协议 · Function Calling · AI工具实战 · 大模型
更多内容请在公众号对话框回复关键词获取资料
夜雨聆风