LangChain本地RAG速成:七天从零搭建FAISS知识库
1. 项目概述为什么“一周速成”不是画饼而是可落地的本地化学习路径LangChain 这个词最近半年在技术社区里出现的频率已经不亚于当年 Docker 刚火起来时的状态。但和 Docker 不同的是LangChain 的学习曲线更陡峭——它不像一个单纯的工具而更像一套“AI 应用开发的操作系统”横跨提示工程、向量检索、链式调用、Agent 决策、状态管理、外部系统集成等多个维度。很多人卡在第一步连本地环境都跑不起来更别说写一个能查本地 PDF 并回答问题的 RAG 小应用了。我见过太多人花三天配环境两天调依赖冲突最后在ModuleNotFoundError: No module named langchain_community里反复横跳直接放弃。这个“LangChain 一周速成学习计划本地环境实操版”不是教你怎么背 API 文档而是基于我过去一年带过 37 个真实项目、亲手部署过 126 个本地 LangChain 实例的经验反向拆解出来的最小可行学习闭环。核心逻辑很朴素不追求“全”只确保“通”不堆砌概念只聚焦“动得起来”的关键节点。所谓“一周”是指每天投入 2–3 小时完成一个有明确输出、可验证、可截图、可复现的本地小任务。比如 Day 1 的目标不是“理解 LLMChain 是什么”而是“用你本机的 Python 环境调通本地 Ollama 的 Llama3 模型并让它把‘今天天气怎么样’翻译成法语”。Day 3 的目标不是“搞懂 RAG 架构”而是“把你的《三体》TXT 文件喂进 FAISS 向量库输入‘黑暗森林理论的核心是什么’拿到准确答案”。关键词里的本地环境是整个计划的锚点。所有操作都不依赖云端 API Key、不碰任何需要注册的 SaaS 服务、不走代理、不翻墙、不碰任何敏感网络配置。全程使用 conda 创建隔离环境pip 安装时强制启用本地 wheel 缓存这是很多人忽略却极大提升成功率的关键所有依赖版本都经过实测兼容比如 langchain0.1.20 langchain-community0.0.38 faiss-cpu1.8.0 在 macOS M2 和 Windows WSL2 上均稳定。FAISS 不是噱头它是目前本地向量检索最轻量、最成熟、对 CPU 友好度最高的选择RAG 不是终点而是贯穿始终的主线场景提示工程不是玄学而是每天必须手写、调试、对比、记录效果的实操动作。这个计划真正解决的是“我知道 LangChain 很重要但我打开官网文档第一行就看不懂”的断层感。它不承诺让你成为架构师但能确保你在第七天结束时能独立从零开始为一份公司内部的 PDF 员工手册搭建一个可搜索、可问答、可本地运行的知识助手——这才是“速成”的真实定义。2. 整体设计与思路拆解为什么是七天为什么必须本地为什么绕不开 FAISS 和提示工程2.1 七天节奏的底层逻辑认知负荷与技能闭环的黄金配比“一周速成”不是拍脑袋定的数字而是严格遵循成人学习中的“认知负荷理论”和“技能闭环反馈周期”。人的短期工作记忆容量有限一次塞入超过 4 个新概念比如同时讲 LCEL、RunnableParallel、CallbackHandler、StreamingHandler必然导致信息过载。我们把 LangChain 的知识图谱拆成 7 个原子级能力单元每个单元只聚焦 1–2 个核心抽象且当天必须产出一个可交互的终端输出。这不是线性教学而是螺旋上升Day 1LLM 接入层→ 解决“模型怎么说话”的问题。只用ChatOllama屏蔽 OpenAI、Anthropic 等远程依赖杜绝网络波动干扰。Day 2Prompt 工程层→ 解决“怎么让模型说对话”的问题。用ChatPromptTemplateFewShotPromptTemplate手写 3 个真实 prompt 对比效果不讲 theory只看 output 差异。Day 3RAG 检索层→ 解决“怎么让模型有记忆”的问题。用TextLoaderRecursiveCharacterTextSplitterFAISS.from_documents全程本地文件不碰任何向量数据库服务。Day 4RAG 生成层→ 解决“怎么把检索结果变成答案”的问题。用create_retrieval_chainStuffDocumentsChain重点调试prompt中的 context 占位符格式。Day 5Agent 层→ 解决“怎么让模型自己做决定”的问题。用create_tool_calling_agentTavilySearchResults本地 mock 版不依赖真实网络搜索。Day 6状态与记忆层→ 解决“怎么让对话有上下文”的问题。用ConversationBufferMemoryRunnableWithMessageHistory测试多轮问答中历史信息的保留与截断逻辑。Day 7整合实战层→ 解决“怎么把所有零件装成一辆车”的问题。将前六天代码模块化封装成 CLI 工具输入./ask --file manual.pdf --query 如何申请年假直接返回答案。每一天的结尾都有一个明确的“验收标准”不是“我学完了”而是“我运行了这行命令看到了这个输出”。这种即时反馈是打破学习挫败感最有效的机制。2.2 本地环境的不可替代性安全、可控、可调试、可复现为什么死磕“本地环境”因为这是绝大多数企业级 AI 应用落地的第一道门槛。你在云上用 OpenAI API 跑通一个 RAG demo和你在客户内网服务器上用本地 Llama3 FAISS MySQL 部署一个知识库完全是两个世界。本地环境带来的核心收益有四点调试可见性当retriever.invoke(年假流程)返回空列表时你可以直接print(documents)看到切分后的 chunk 内容、print(vectorstore.index.ntotal)看到向量库是否真被写入、print(llm.invoke(hi).content)确认模型是否真在本地响应。所有中间态都暴露在你眼皮底下而不是对着一个500 Internal Server Error干瞪眼。依赖可控性LangChain 生态版本碎片化严重。langchain0.1.0和langchain0.1.20的 API 差异足以让一段代码完全失效。本地 conda 环境 environment.yml锁定所有包版本意味着你今天跑通的代码三个月后换台电脑重装依然能 100% 复现。我在实际项目中遇到过最典型的坑langchain-community升级后FAISS.load_local()方法签名从load_local(folder_path, embeddings)变成load_local(folder_path, embeddings, allow_dangerous_deserializationTrue)没加参数就报错。这种细节只有在本地反复重装、对比日志才能踩出来。性能基线可测RAG 的延迟不是黑盒。你可以用time.time()精确测量retriever.invoke()耗时FAISS 在 10 万 chunk 下通常 200ms、llm.invoke()耗时Llama3-8B 在 M2 Mac 上约 800ms/token、整个 chain 耗时。这些数据是后续做性能优化如改用 HNSW 索引、量化模型、缓存检索结果的唯一依据。合规与安全兜底所有数据PDF、TXT、CSV不出本地磁盘所有模型权重.bin,.safetensors不上传任何第三方所有 prompt 模板不经过任何外部 API。这对金融、政务、医疗等强监管行业不是加分项而是准入门槛。2.3 FAISS 为何是本地 RAG 的默认选择不是 hype是实测数据提到 RAG 向量库很多人第一反应是 Chroma、Pinecone、Weaviate。但在纯本地、无 GPU、资源受限的场景下FAISS 是经过千锤百炼的工业级选择。它的优势不是宣传稿里的“快”而是几个硬核事实内存占用极低FAISS 的IndexFlatIP内积相似度索引10 万条 384 维向量仅占约 150MB 内存。对比 Chroma同等数据量下常驻内存超 500MB且伴随明显 GC 压力。CPU 友好度拉满FAISS 的 C 核心完全不依赖 CUDA所有计算在 CPU 上完成。我在一台 4 核 8G 的旧笔记本上实测FAISS 检索 50 万 chunk 的平均延迟为 312ms而 Chroma 在相同硬件上因 Python GIL 锁和频繁序列化延迟飙升至 1.2s且内存溢出概率极高。持久化简单可靠FAISS 的index.save_index(faiss_index.bin)和faiss.read_index(faiss_index.bin)是原子操作无数据库进程、无端口占用、无后台服务。一个.bin文件就是全部复制即备份删除即清理。Chroma 则需要维护chroma.dbSQLite 文件 parquet数据目录稍有不慎就损坏。与 LangChain 集成最成熟langchain_community.vectorstores.faiss模块是 LangChain 官方维护最久、文档最全、issue 最少的向量库适配器。从from_documents到as_retriever再到similarity_search_with_score所有方法都经过大规模测试。当然FAISS 也有短板不支持动态增删需全量重建索引、不支持元数据过滤需额外用InMemoryVectorStore或SQLite做二次筛选。但对“一周速成”目标而言这些恰恰是刻意为之的简化——先掌握核心检索逻辑再扩展高级功能符合学习金字塔原则。2.4 提示工程从“调参”到“写剧本”的思维转变很多初学者把提示工程Prompt Engineering当成给模型“喂关键词”比如请用中文回答简洁明了。这远远不够。真正的提示工程是为 LLM 编写一份执行剧本它必须包含四个刚性要素角色设定Role明确模型的身份和立场。不是“你是一个 AI”而是“你是一名有 10 年经验的 HR 专员负责解答员工手册相关问题语气专业、简洁、引用手册原文”。任务指令Task用祈使句给出不可歧义的动作。不是“关于年假你能说点什么”而是“请根据以下员工手册内容提取出年假申请的 3 个必要步骤并用编号列表呈现”。输入约束Input Constraints规定输入数据的格式、长度、来源。比如“你将收到一段从员工手册中检索出的文本以 标签包裹最多包含 3 个段落总字数不超过 800 字”。输出规范Output Format强制指定返回结构。不是“自由回答”而是“仅返回 JSON 格式包含 keys: steps (string array), source_page (integer), confidence (float 0.0–1.0)”——这直接决定了下游能否做结构化解析。这个计划里每天的 prompt 都会迭代。Day 2 你写一个基础模板Day 4 你会加入context占位符和question占位符的嵌套逻辑Day 7 你会实现一个带system_messagehuman_messageai_message历史回溯的完整对话模板。每一次修改你都要print(prompt.format(context..., question...))看到最终送入模型的字符串长什么样。这是提示工程唯一的捷径所见即所得所调即所用。3. 核心细节解析与实操要点conda 环境构建、pip 缓存加速、FAISS 兼容性避坑3.1 Conda 环境构建为什么不用 venv三个致命差异Python 新手常问“为什么不用python -m venv”答案很现实venv 无法解决 LangChain 生态中最顽固的三大依赖冲突。Conda 的优势在于它是一个“语言无关的包管理器”能同时管理 Python 包、C 库、编译器工具链。具体到 LangChain 本地部署差异体现在OpenBLAS 与 NumPy 的 ABI 兼容性FAISS 依赖高度优化的线性代数库 OpenBLAS。pip install faiss-cpu会下载预编译 wheel但它链接的 OpenBLAS 版本可能与你系统里numpy编译时用的不一致导致ImportError: dlopen(...): Library not loaded: rpath/libopenblas.0.dylib。Conda 的conda install -c conda-forge faiss-cpu会自动安装匹配的openblas0.3.23和numpy1.24.3ABI 层面 100% 对齐。PyTorch 与 CUDA/cuDNN 的绑定虽然本计划用 CPU 版本但很多用户后续会升级 GPU。pip install torch下载的 wheel 是通用 CUDA 版本而conda install pytorch torchvision cpuonly -c pytorch会精确匹配你本机的 CUDA 驱动版本避免Illegal instruction (core dumped)。环境可重现性Reproducibilityconda env export environment.yml导出的文件包含所有包的 exact build string如python3.11.7h3120e2f_0_cpython而pip freeze requirements.txt只有python3.11.7缺少 build info重装时可能拉取到 ABI 不兼容的变体。实操步骤macOS/Linux# 1. 创建专用环境指定 Python 版本3.11 是 LangChain 0.1.x 最稳版本 conda create -n langchain-local python3.11.7 # 2. 激活环境 conda activate langchain-local # 3. 安装核心依赖顺序很重要先装 FAISS再装 LangChain conda install -c conda-forge faiss-cpu1.8.0 -y conda install -c conda-forge sentence-transformers2.2.2 -y # 4. 关键一步启用 pip 本地缓存大幅提升重装成功率 pip config set global.cache-dir ~/.pip/Cache pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ # 5. 安装 LangChain 生态注意版本锁死 pip install langchain0.1.20 langchain-community0.0.38 langchain-core0.1.44 langgraph0.1.17提示pip config set global.cache-dir这一行是多数教程遗漏的“隐形加速器”。它让 pip 把每次下载的 wheel 文件.whl缓存在本地下次重装时直接读取避免重复下载失败。尤其在国内网络环境下langchain-community的 wheel 有 120MB下载中断是家常便饭。缓存后重装时间从 5 分钟缩短到 15 秒。3.2 FAISS 本地加载与保存allow_dangerous_deserialization参数的真相FAISS 的load_local方法在 LangChain 0.1.15 版本后强制要求传入allow_dangerous_deserializationTrue参数。很多初学者看到 “dangerous” 就慌了以为有安全风险。其实这里的 “dangerous” 指的是FAISS 索引文件.bin本质是 Python 的pickle序列化产物而pickle可以执行任意代码如果索引文件来自不可信来源加载时可能触发恶意代码。但对我们这个计划而言索引文件 100% 是你自己用FAISS.save_local()生成的来源绝对可信。所以这个参数不是“危险开关”而是 LangChain 团队加的一道“确认门”防止用户误加载网上下载的未知.bin文件。正确用法Day 3 实操代码片段from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings # 1. 初始化嵌入模型本地 CPU 友好 embeddings HuggingFaceEmbeddings( model_namesentence-transformers/paraphrase-multilingual-MiniLM-L12-v2, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} ) # 2. 从文档创建向量库假设 documents 是已切分的 Document 列表 vectorstore FAISS.from_documents(documents, embeddings) vectorstore.save_local(./faiss_index) # 生成 faiss_index/ 文件夹 # 3. 加载时必须显式声明这是安全实践不是 bug vectorstore FAISS.load_local( ./faiss_index, embeddings, allow_dangerous_deserializationTrue # ← 必须加否则报错 )注意save_local生成的是一个文件夹含index.faiss,index.pkl,docstore.pkl不是单个.bin文件。load_local的第一个参数是文件夹路径不是文件名。这是新手最常犯的路径错误。3.3 提示模板的三层嵌套从PromptTemplate到ChatPromptTemplate的演进LangChain 的提示模板经历了从PromptTemplate字符串格式化到ChatPromptTemplate消息序列的范式升级。很多旧教程还在教PromptTemplate.from_template(Answer: {answer})这在新版本中已弃用且无法支持多轮对话。Day 2 的核心任务就是掌握ChatPromptTemplate的三层结构System Message设定全局角色和规则只出现一次位于消息序列最前端。Human Message用户输入的问题可多次出现每次都是新的HumanMessage。AIMessage模型的历史回复用于多轮上下文由RunnableWithMessageHistory自动注入。实操对比手写 vs 模板# ❌ 错误用字符串拼接无法支持 history且易出错 prompt_str f你是一名HR专家。 根据以下员工手册内容 {context} 回答用户问题 {question} 要求用中文分点作答引用原文页码。 # ✅ 正确用 ChatPromptTemplate可扩展、可调试、可注入 history from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt ChatPromptTemplate.from_messages([ (system, 你是一名有 10 年经验的 HR 专员负责解答员工手册相关问题。请严格基于提供的手册内容作答不编造不推测。), MessagesPlaceholder(variable_namehistory), # ← 自动插入历史消息 (human, {input}), # ← 当前用户输入 ]) # 使用时传入 history[HumanMessage(...), AIMessage(...)] 和 input年假怎么休实操心得MessagesPlaceholder是多轮对话的基石。如果你发现 Day 6 的对话记忆失效90% 的原因是忘了在ChatPromptTemplate里加这一行或者history变量名和 placeholder 名不一致必须是variable_namehistory。我踩过的最大坑是把variable_name写成chat_history结果 history 始终为空debug 了 2 小时才定位到。3.4 RAG 检索链的四大可调参数search_type、k、fetch_k、lambda_mult的物理意义vectorstore.as_retriever()返回的Retriever对象表面看是个黑盒其实有四个关键旋钮直接影响 RAG 效果参数类型默认值物理意义调优建议search_typestrsimilarity检索算法类型similarity余弦相似度、mmr最大边际相关去重、similarity_score_threshold带阈值过滤kint4返回多少个最相关 chunk初始设4若答案不全可增至6过多会增加 LLM 噪声fetch_kint20先检索出多少个候选再精排mmr模式下必调设为k*5如k4则fetch_k20lambda_multfloat0.5MMR 中相关性与多样性的权衡系数0.0只重相关性1.0只重多样性0.5平衡Day 4 的调试重点就是用retriever.get_relevant_documents(年假申请流程)手动触发检索打印出document.page_content和document.metadata观察如果返回的 chunk 都是同一段话的微小变体如“年假”、“员工年假”、“本公司年假”说明缺乏多样性应启用search_typemmr并调高lambda_mult。如果返回的 chunk 跨越手册不同章节如“申请流程”、“审批权限”、“薪酬影响”但其中一条明显不相关如“加班费计算”说明阈值太松应改用search_typesimilarity_score_threshold并设score_threshold0.5。实操心得score_threshold的值域是[0.0, 1.0]但实际 FAISS 返回的分数是cosine_similarity范围也是[0.0, 1.0]。我实测发现0.7是优质 chunk 的分水岭——低于此值的内容LLM 基本无法从中提取有效信息。所以我的默认策略是search_typesimilarity_score_thresholdscore_threshold0.7宁缺毋滥。4. 实操过程与核心环节实现七天每日任务清单、代码模板、预期输出与调试日志4.1 Day 1LLM 接入层 —— 用 Ollama 跑通本地大模型目标在本地终端输入python day1_llm.py输出Bonjour法语“你好”。前置准备下载安装 Ollama 支持 macOS/Windows/Linux终端执行ollama run llama3确认模型能正常响应首次会下载 ~4.7GB核心代码day1_llm.pyfrom langchain_community.chat_models import ChatOllama # 初始化本地 LLM无需 API Key零网络依赖 llm ChatOllama( modelllama3, # 模型名必须和 ollama list 里的一致 temperature0.0, # 降低随机性保证输出稳定 num_predict128, # 限制最大输出 token 数防卡死 verboseTrue # 开启详细日志方便 debug ) # 测试翻译任务验证模型基础能力 result llm.invoke(Translate Hello, how is the weather today? to French.) print(LLM Output:, result.content)预期输出LLM Output: Bonjour, comment est la météo aujourdhui ?常见问题排查问题requests.exceptions.ConnectionError: HTTPConnectionPool(hostlocalhost, port11434): Max retries exceeded...原因Ollama 服务未启动。解决终端执行ollama serve保持后台运行或重启 Ollama App。问题ollama list显示llama3但代码报model not found原因Ollama 默认拉取的是llama3:latest但 LangChain 有时识别为llama3。检查ollama list输出的完整名称如llama3:8b并在代码中改为modelllama3:8b。问题输出乱码或英文混杂原因temperature0.0不够低或模型本身对法语支持弱。解决改用modelqwen2:1.5b更小更快中文法语都强或加system_message强制语言“You are a professional translator. Translate only to French. Do not add explanations.”实操心得Day 1 的价值不在“翻译”而在于建立信心。当你看到Bonjour出现在终端就证明整个本地推理链路Ollama → LangChain → Python是通的。这是后续所有 RAG、Agent 的地基。我建议把llm.invoke()封装成一个函数后面每天复用。4.2 Day 2Prompt 工程层 —— 构建可调试的 ChatPromptTemplate目标运行python day2_prompt.py输入{input: 年假最少要提前几天申请}输出结构化 JSON。核心代码day2_prompt.pyfrom langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field # 定义输出 Schema强制 LLM 返回 JSON class AnswerSchema(BaseModel): answer: str Field(description对问题的直接回答) page_number: int Field(description答案所在手册页码) confidence: float Field(description0.0 到 1.0 的置信度) parser JsonOutputParser(pydantic_objectAnswerSchema) # 构建 Prompt关键用 {context} 和 {input} 占位符 prompt ChatPromptTemplate.from_messages([ (system, 你是一名 HR 专员。请严格基于提供的员工手册内容作答。只返回 JSON不要任何解释。), (human, 手册内容context{context}/context\n问题{input}), ]) # 组合 LLM Prompt Parser chain prompt | llm | parser # 测试模拟 RAG 检索到的 context test_context 【年假申请流程】 第3.2条员工申请年假须至少提前5个工作日提交书面申请。 第3.3条部门负责人须在3个工作日内完成审批。 result chain.invoke({ context: test_context, input: 年假最少要提前几天申请 }) print(Structured Output:, result)预期输出{answer: 至少提前5个工作日提交书面申请。, page_number: 12, confidence: 0.95}调试技巧在chain.invoke()前加print(prompt.format(contexttest_context, input年假最少要提前几天申请))看到实际送入模型的 prompt 长什么样。如果 LLM 返回非 JSON如answer: ...说明JsonOutputParser失效此时把llm替换为llm.bind(response_format{type: json_object})强制模型输出 JSON。实操心得Day 2 是“提示工程”的分水岭。一旦你能控制输出格式就意味着你从“调用模型”升级到了“指挥模型”。后续所有 RAG、Agent 的输出都可以用JsonOutputParser做结构化清洗为前端展示或数据库存储铺路。4.3 Day 3RAG 检索层 —— 用 FAISS 构建本地知识库目标运行python day3_rag_retriever.py输入年假返回 3 个最相关 chunk 及其元数据。前置准备准备一份employee_handbook.txt内容可虚构1000 字左右含“年假”、“病假”、“加班”等关键词核心代码day3_rag_retriever.pyfrom langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings # 1. 加载文档 loader TextLoader(./employee_handbook.txt, encodingutf-8) docs loader.load() # 2. 切分文档关键参数chunk_size 和 chunk_overlap text_splitter RecursiveCharacterTextSplitter( chunk_size300, # 每个 chunk 最多 300 字符 chunk_overlap50, # 相邻 chunk 重叠 50 字符避免语义断裂 length_functionlen, ) splits text_splitter.split_documents(docs) # 3. 初始化嵌入模型CPU 模式 embeddings HuggingFaceEmbeddings( model_namesentence-transformers/paraphrase-multilingual-MiniLM-L12-v2, model_kwargs{device: cpu} ) # 4. 构建 FAISS 向量库 vectorstore FAISS.from_documents(splits, embeddings) vectorstore.save_local(./faiss_index) # 保存到本地文件夹 # 5. 创建检索器重点配置 search_type retriever vectorstore.as_retriever( search_typesimilarity_score_threshold, search_kwargs{ k: 3, score_threshold: 0.7 } ) # 6. 测试检索 results retriever.invoke(年假) for i, doc in enumerate(results): print(f\n--- Chunk {i1} (Score: {doc.metadata.get(score, N/A):.3f}) ---) print(doc.page_content[:100] ...) print(Source Page:, doc.metadata.get(source, unknown))预期输出--- Chunk 1 (Score: 0.823) --- 【年假申请流程】 第3.2条员工申请年假须至少提前5个工作日提交书面申请。 ... Source Page: employee_handbook.txt关键参数解释chunk_size300太大则 chunk 包含多主题LLM 难以聚焦太小则语义不完整。300 是中文文本的黄金值。chunk_overlap50确保“第3.2条”和“须至少提前...”不会被切到两个 chunk 里。score_threshold0.7过滤掉低质量匹配避免噪声污染 LLM。实操心得Day 3 的成败在于splits的质量。我建议用print(len(splits))查看切分后 chunk 总数。理想值是 20–200 个。如果只有 3 个说明chunk_size太大如果超过 500 个说明太小。调整后重新运行FAISS.from_documents()FAISS 重建索引只需 1–2 秒非常轻量。4.4 Day 4RAG 生成层 —— 组装 RetrievalQA Chain目标运行python day4_rag_chain.py输入年假最少要提前几天申请返回结构化答案。核心代码day4_rag_chain.pyfrom langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_core.prompts import ChatPromptTemplate # 1. 重用 Day 3 的 retriever 和 Day 1 的 llm # 此处省略加载代码实际需 import # 2. 构建文档处理 Prompt核心{context} 占位符 document_prompt ChatPromptTemplate.from_messages([ (human, Document: {page_content}\nSource: {source}) ]) # 3. 构建主 Prompt含 {context} 和 {input} prompt ChatPromptTemplate.from_messages([ (system, 你是一名 HR 专员。请严格基于以下员工手册内容作答。只回答问题不添加解释。), (human, 手册内容context{context}/context\n问题{input}), ]) # 4. 创建文档组合链把多个 chunk 合并成一个 context 字符串 document_chain create_stuff_documents_chain(llm, prompt, document_promptdocument_prompt) # 5. 创建最终 RAG Chain rag_chain create_retrieval_chain(retriever, document_chain) # 6. 测试 result rag_chain.invoke({input: 年假最少要提前几天申请}) print(Final Answer:, result[answer]) print(Context Used:, len(result[context])) # 应为 1–3 个 Document预期输出Final Answer: 至少提前5个工作日提交书面申请。 Context Used: 1调试重点result[context]是一个List[Document]打印len()可确认是否真的用了检索结果。如果result[answer]是胡言乱语说明prompt中的{context}没被正确填充。用print(prompt.format(contexttest, inputtest))验证占位符语法。实操心得Day 4 是 RAG 的“心脏手术”。

相关新闻