10分钟搭建第一个RAG问答系统前两篇我们把概念和技术栈聊透了。今天不废话直接撸代码——10分钟从零跑通一个RAG问答系统。你会得到一个能上传PDF、然后对着它问问题的AI助手。麻雀虽小五脏俱全。大家好我是黒漂技术佬。一、先明确我们这10分钟要干什么完整流程上传 PDF 文档 → 解析文本 → 分块 → Embedding → 存入 Chroma ↓ 用户提问 → Embedding → Chroma检索 → 拼接Prompt → LLM回答技术选型最小可行版组件选型理由文档解析PyMuPDFfitz轻量处理中文PDF效果好分块LangChain TextSplitter不用重复造轮子EmbeddingSiliconFlow APIBGE-large免部署注册即用免费额度够玩向量库Chroma零配置Python 一行代码起LLMDeepSeek API性价比最高中文一流为什么用 API 而不是本地部署先跑通再优化。本地部署 Embedding 模型要下好几个G的模型文件调试起来一小时起步。先用 API 把坑踩一遍后面再考虑私有化。二、环境准备Step 1安装依赖pipinstalllangchain langchain-community langchain-chroma\pymupdf chromadb openai python-dotenv这里解释一下langchainRAG 框架帮你把各种组件串起来langchain-chromaLangChain 的 Chroma 集成pymupdfPDF 解析利器chromadb向量数据库轻量版openai虽然我们用 DeepSeek但 DeepSeek 兼容 OpenAI 的接口规范所以可以用 openai 库调用Step 2申请 API KeyDeepSeek去 platform.deepseek.com 注册点「API Keys」创建一个。新用户一般送 10 元额度够你玩几天的。SiliconFlowEmbedding 用去 siliconflow.cn 注册同样创建 API Key。他们的 BGE-large-zh 模型调用免费额度相当大方。Step 3配置环境变量在项目根目录创建.env文件DEEPSEEK_API_KEYsk-xxxxxxxxxxxxxxxxDEEPSEEK_BASE_URLhttps://api.deepseek.com/v1SILICONFLOW_API_KEYsk-xxxxxxxxxxxxxxxxSILICONFLOW_BASE_URLhttps://api.siliconflow.cn/v1三、核心代码60行逐行注释新建rag_mini.py贴入以下代码importosfromdotenvimportload_dotenvfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_community.document_loadersimportPyMuPDFLoaderfromlangchain_chromaimportChromafromlangchain_openaiimportOpenAIEmbeddings,ChatOpenAIfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.runnablesimportRunnablePassthrough load_dotenv()# 1. 加载并解析 PDF defload_pdf(file_path:str):加载 PDF 文件返回 LangChain Document 对象列表loaderPyMuPDFLoader(file_path)documentsloader.load()print(floaded{len(documents)}pages from{file_path})returndocuments# 2. 文本分块 defsplit_documents(documents):把长文档切成小块每块约 500 字相邻块重叠 100 字text_splitterRecursiveCharacterTextSplitter(chunk_size500,# 每块最多 500 个字符chunk_overlap100,# 相邻块重叠 100 个字符separators[\n\n,\n,。,.,,, ,]# 分隔符优先级先按段落切再按句子切)chunkstext_splitter.split_documents(documents)print(fsplit into{len(chunks)}chunks)returnchunks# 3. 向量化并存入 Chroma defbuild_vectorstore(chunks):把文本块 Embedding 后存入 Chroma 向量数据库# SiliconFlow 兼容 OpenAI 接口格式embeddingsOpenAIEmbeddings(modelBAAI/bge-large-zh-v1.5,# 中文最好的开源Embedding之一base_urlos.getenv(SILICONFLOW_BASE_URL),api_keyos.getenv(SILICONFLOW_API_KEY),)vectorstoreChroma.from_documents(documentschunks,embeddingembeddings,persist_directory./chroma_db# 向量数据持久化到本地# 这样下次启动不用重新 Embedding)print(fvectorstore built with{vectorstore._collection.count()}vectors)returnvectorstore# 4. 构建完整的 RAG 链 defbuild_rag_chain(vectorstore):组装 RAG 流水线检索 → 拼 Prompt → LLM 生成# LLM用 DeepSeek兼容 OpenAI 接口llmChatOpenAI(modeldeepseek-chat,# DeepSeek-V3base_urlos.getenv(DEEPSEEK_BASE_URL),api_keyos.getenv(DEEPSEEK_API_KEY),temperature0.3,# 低温度 回答更确定性不易编造)# Prompt 模板告诉 LLM 怎么回答promptChatPromptTemplate.from_template( 你是一个专业的企业知识库助手。请根据下面的文档内容回答用户的问题。 规则 1. 只能基于提供的文档内容回答不要使用你自己的知识 2. 如果文档中没有相关信息请直接说文档中没有找到相关信息 3. 回答要简洁、准确用中文 【文档内容】 {context} 【用户问题】 {question} 【回答】 )# 构建链检索器 → 格式化 → LLM → 解析输出retrievervectorstore.as_retriever(search_kwargs{k:5}# 每次检索返回最相关的 5 个文本块)defformat_docs(docs):把检索到的文档拼接成一段文本return\n\n.join(doc.page_contentfordocindocs)chain({context:retriever|format_docs,question:RunnablePassthrough()}|prompt|llm|StrOutputParser())returnchain# 5. 主流程 defmain():pdf_pathtest.pdf# ← 改成你自己的 PDF 路径# 检查是否有已保存的向量库避免重复 Embeddingifos.path.exists(./chroma_db):print(found existing vectorstore, loading...)embeddingsOpenAIEmbeddings(modelBAAI/bge-large-zh-v1.5,base_urlos.getenv(SILICONFLOW_BASE_URL),api_keyos.getenv(SILICONFLOW_API_KEY),)vectorstoreChroma(persist_directory./chroma_db,embedding_functionembeddings)else:print(building new vectorstore...)docsload_pdf(pdf_path)chunkssplit_documents(docs)vectorstorebuild_vectorstore(chunks)# 构建 RAG 链chainbuild_rag_chain(vectorstore)# 交互式问答print(\n RAG 问答系统就绪输入 quit 退出 \n)whileTrue:questioninput(你)ifquestion.lower()quit:break# 流式输出打字机效果print(AI,end,flushTrue)forchunkinchain.stream(question):print(chunk,end,flushTrue)print(\n)if__name____main__:main()四、跑起来python rag_mini.py第一次运行会下载 Chroma 的依赖然后依次执行解析PDF → 分块 → Embedding → 入库。之后在同目录下生成chroma_db/文件夹下次启动直接加载秒进问答状态。找一份你自己的 PDF 扔进去试试。比如公司的《员工手册》、产品的《技术白皮书》、或者你在网上随便下载的某份技术文档。问几个问题感受一下你这份文档的核心内容是什么 AI基于文档内容回答…… 你第三章提到了哪些安全要求 AI从第三章的文本块中检索并回答…… 你作者最喜欢的编程语言是什么 AI文档中没有找到相关信息注意最后一个问题——如果 PDF 里确实没写这个AI 应该老实说不知道而不是编一个 Python 出来。这就是 Prompt 模板里那个规则的作用。五、这60行代码里藏着哪些关键细节1.RecursiveCharacterTextSplitter的分隔符很重要separators[\n\n,\n,。,.,,, ,]这个列表的顺序决定了切分优先级先找双换行段落边界找不到再找单换行、句号、感叹号……最后才在空格甚至任意字符处切。这样能最大限度保证每个 chunk 是一个完整的语义单元而不是在句子中间一刀砍断。2.chunk_overlap100是安全网相邻 chunk 有 100 字的重叠。假设原文是「……因此系统设计遵循了以下原则第一高内聚低耦合……」——以下原则刚好在 chunk A 末尾第一……“在 chunk B 开头。如果没有 overlap检索时可能只命中 chunk A 或只命中 chunk B导致 LLM 看不到原则的具体内容”回答质量就差一截。3.temperature0.3抑制幻觉Temperature 控制 LLM 输出的随机性。知识库问答场景我们希望 AI 忠实于文档而不是自由发挥。所以把 temperature 调到 0.3范围 0~2越低越确定性。但这个值也不能是 0太低会让回答像机器人复读机。4. Chroma 的 persist_directory 让你不重新花钱Embedding API 虽然便宜但每调一次都是钱。把向量库持久化到本地后只要 PDF 不变重启多少次都不需要重新调 Embedding API——这就是persist_directory的价值。六、跑通之后你会发现的第一个问题95% 的人第一次跑通 RAG Demo 后都会说同一句话“这东西有时候准有时候不准啊。”没错。Demo 和「好用」之间还隔着好几座大山文档里有表格解析后变成一坨无意义的数字用户问法太口语化向量检索完全跑偏一个 PDF 上百页但用户的问题高度浓缩Top-5 的检索结果全是废话这些就是我们后面几篇要逐一解决的。今天这篇的目的只是让你10 分钟内把整个链路跑通让你对大框架有个感性认识。知道哪里会出问题才知道后面该往哪优化。 你跑通了吗用的是什么文档有什么奇怪的问题评论区发出来一起看看怎么调