
简介这份PDF面向大模型与人工智能方向的学习者及面试准备者聚焦基于langchain框架的RAG问答应用实战帮助读者理解检索增强生成从数据准备到问答落地的完整链路。资源包内仅含1个PDF文件约390KB内容以图文与代码示例为主便于在面试前快速梳理RAG核心流程与关键实现细节。目前已有281人学习。文档以百度百科藜麦数据模拟私域语料依次覆盖环境搭建、本地数据加载、文档分割、向量化入库等环节并给出CUDA、Python、pytorch等版本要求与依赖安装命令。读者可从中掌握TextLoader加载、CharacterTextSplitter分割、m3e-base嵌入及Chroma入库等实操要点同时了解OCR识别误差处理与藜麦背景知识适合作为大模型八股文面试中RAG方向的实战参考。1. 从一份 PDF 说起LangChain RAG 问答应用到底在面试里考什么面试官甩过来一句「讲讲你怎么用 LangChain 搭一个 RAG 问答应用」很多人第一反应是背概念文档加载、切分、向量化、检索、生成。背完面试官接着问「切分 chunk_size 设多少」「召回率上不去怎么办」「多轮对话里历史怎么塞」就卡住了。这份《基于 LangChain RAG 问答应用实战》的标题考的从来不是你会不会调 API而是你有没有真正把一个 PDF 知识库问答从零跑通、调过参、踩过坑。RAGRetrieval-Augmented Generation检索增强生成解决的是一个很朴素的问题大模型不知道你私有的东西硬问它就编。把 PDF、文档切块存进向量库用户提问时先检索出相关片段再连同问题一起喂给大模型让它「看着材料回答」。LangChain 在这里的角色是胶水层把加载器、切分器、Embedding、向量库、检索器、LLM 串成一条链。面试里真正拉开差距的是你能不能讲清楚每一环的参数为什么这么设、哪一环最容易翻车。这篇就按「搭起来 → 调得动 → 排得掉」的顺序把这条链拆开讲透新手能照着复现熟手能对照自己的参数找边界。2. 把 PDF 灌进向量库LangChain RAG 索引链路的最小可跑通实现RAG 分两大阶段索引离线把文档变成可检索的向量和检索生成在线用户提问时召回并回答。这一章先把索引链路跑通这是所有后续调优的地基。索引链路的标准动作是加载 → 切分 → 向量化 → 入库LangChain 对每一步都有对应抽象。2.1 文档加载与切分chunk_size 和 overlap 怎么定PDF 加载最常见的坑是版面解析。PyPDFLoader 按页抽文本遇到双栏排版、表格、扫描件会乱序或抽空。常见做法是先用 PyPDFLoader 快速验证正式项目换 pdfplumber 或 unstructured 做版面还原。加载完得到的是 Document 对象列表每个带 page_content 和 metadatametadata 里的 source、page 后面做引用溯源要用。切分是索引链路里最影响召回质量的一步。RecursiveCharacterTextSplitter 会按分隔符优先级段落 → 换行 → 句号 → 空格递归切尽量保持语义完整。chunk_size 和 chunk_overlap 是必调的两个参数。from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 1. 加载 PDF每页一个 Document loader PyPDFLoader(./handbook.pdf) pages loader.load() print(f共加载 {len(pages)} 页) # 2. 递归切分中文场景分隔符要补全角标点 splitter RecursiveCharacterTextSplitter( chunk_size500, # 每块目标字符数 chunk_overlap80, # 相邻块重叠防止语义被切断 separators[\n\n, \n, 。, , , , , , ], length_functionlen, ) chunks splitter.split_documents(pages) print(f切出 {len(chunks)} 个 chunk)逻辑说明chunk_size500 是中文技术文档的经验起点对应大约 300400 个 token能塞进大多数 Embedding 模型的上下文窗口又不至于太碎。chunk_overlap80 保证跨块的句子不被腰斩代价是存储和检索时会有冗余。separators 里补了中文全角标点默认分隔符只认英文标点中文长句会被硬切。参数怎么改chunk_size 太小如 200单块信息不完整检索出来答不全太大如 1500一块里混了多个主题向量被平均掉检索精度下降。判断标准是看你的文档粒度——FAQ 类短问答用 200300技术手册用 500800法律合同这种长条款用 8001200。overlap 一般取 chunk_size 的 10%20%超过 30% 就是浪费。2.2 Embedding 与向量库选型本地跑还是调 API切完块要转成向量。Embedding 模型的选择直接决定检索质量面试里常被追问「你用的什么 Embedding为什么」。两条路线调云端 APIOpenAI text-embedding-3、通义、智谱等省事但要走网络、按量计费本地跑开源模型bge-large-zh、m3e、gte数据不出内网、零调用成本代价是要有 GPU 或忍受 CPU 慢。中文场景我一般推荐 bge-large-zh-v1.5 或 bge-m3后者支持多语言和长文本检索效果在中文榜单上稳定靠前。向量库选型看规模几万块以内用 FAISS 本地索引足够纯内存、零依赖要持久化、要元数据过滤、要多用户用 Chroma 或 Milvus。LangChain 对这几家都有统一接口切换成本低。from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS # 本地 Embedding首次运行会下载模型权重 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-large-zh-v1.5, model_kwargs{device: cuda}, # 没 GPU 改 cpu encode_kwargs{normalize_embeddings: True}, # bge 系列必须归一化 ) # 建库并持久化到本地目录 vectorstore FAISS.from_documents(chunks, embeddings) vectorstore.save_local(./faiss_index) print(索引已保存)逻辑说明normalize_embeddingsTrue 对 bge 系列是硬要求它用余弦相似度训练不归一化内积和余弦会不一致检索结果会飘。device 按机器实际情况选CPU 上建几万块的库大概几分钟能接受。参数说明model_kwargs 里还能传 trust_remote_code、max_length 等encode_kwargs 里 batch_size 影响建库速度GPU 上可以调到 64 或 128。向量库这边 FAISS 的 index 类型默认是扁平索引精确检索数据量上百万再考虑 IVF、HNSW 这类近似索引否则精度损失不划算。2.3 检索器配置similarity 还是 MMR建完库检索器决定「怎么把相关块捞出来」。最基础的是相似度检索similarity返回和 query 向量最接近的 k 个块。但纯相似度有个问题如果文档里有大量重复表述返回的 k 个块可能高度雷同信息冗余。MMR最大边际相关在相似度和多样性之间做权衡先选最相关的再选和已选块差异大的适合「一个问题需要多个角度材料」的场景。# 相似度检索直接取 top-k retriever_sim vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4}, ) # MMR 检索兼顾相关性与多样性 retriever_mmr vectorstore.as_retriever( search_typemmr, search_kwargs{k: 4, fetch_k: 20, lambda_mult: 0.5}, )逻辑说明fetch_k20 表示先从库里取 20 个候选再从中挑 4 个多样性最好的lambda_mult 控制相关性和多样性的权重1 等于纯相似度0 等于纯多样性0.5 是常用折中。k 设多少取决于 LLM 的上下文窗口和你的 chunk 大小一般 36 个块塞太多反而引入噪声、拉高成本。到这里索引链路就跑通了。面试里如果只让你讲「怎么搭」讲到这一步已经及格但真正区分水平的是下一章的调优和排错。3. 从检索到回答LangChain RAG 问答链的组装与多轮对话处理索引建好只是把材料备齐用户提问到拿到答案这条在线链路才是 RAG 应用的门面。这一章讲怎么把检索器、Prompt、LLM 串成一条能用的链以及多轮对话这个高频面试点怎么处理。3.1 用 LCEL 组装检索问答链LangChain 现在主推 LCELLangChain Expression Language用管道符把组件串起来写法简洁、支持流式、天然异步。一条标准的 RAG 链是问题 → 检索器拿上下文 → 拼 Prompt → 喂 LLM → 输出。from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_community.chat_models import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ChatPromptTemplate.from_template( 你是文档问答助手。只根据下面的上下文回答上下文没有的信息就说不知道不要编造。\n\n 上下文\n{context}\n\n问题{question} ) def format_docs(docs): return \n\n.join(d.page_content for d in docs) rag_chain ( {context: retriever_sim | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) answer rag_chain.invoke(这份手册里报销流程是怎样的) print(answer)逻辑说明字典那一步并行处理两个输入context 走检索器再格式化question 原样透传。Prompt 里明确「只根据上下文回答、没有就说不知道」是抑制幻觉的关键不写这句模型会拿自己的先验知识补。temperature0 让输出稳定问答场景不需要创造性。参数说明ChatOpenAI 的 model 换成你实际用的模型如果走本地 Ollama换成 ChatOllama(modelqwen2.5:7b) 即可接口一致。StrOutputParser 把消息对象转成纯字符串方便后续处理。3.2 多轮对话历史怎么塞、检索 query 怎么改写单轮问答跑通后面试官大概率会问「多轮对话怎么办」。直接的做法是把历史消息拼进 Prompt但有两个坑一是历史越长 token 越贵二是用户第二句「那它的截止日期呢」这种指代直接拿去检索会召回一堆无关内容。第一个坑用窗口或摘要解决只保留最近 N 轮。第二个坑是重点检索前要先做 query 改写condense question把「那它的截止日期呢」结合历史改写成「报销流程的截止日期是什么」再拿去检索。from langchain_core.prompts import MessagesPlaceholder from langchain.chains import create_history_aware_retriever, create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain # 1. 历史感知检索器先改写 query 再检索 condense_prompt ChatPromptTemplate.from_messages([ MessagesPlaceholder(chat_history), (human, {input}), (human, 结合上面的对话把最新问题改写成独立完整的检索问题只输出改写后的问题。), ]) history_aware_retriever create_history_aware_retriever(llm, retriever_sim, condense_prompt) # 2. 问答链带历史 qa_prompt ChatPromptTemplate.from_messages([ (system, 只根据上下文回答\n\n{context}), MessagesPlaceholder(chat_history), (human, {input}), ]) qa_chain create_stuff_documents_chain(llm, qa_prompt) rag_chain create_retrieval_chain(history_aware_retriever, qa_chain) # 3. 调用时传入历史 history [] resp rag_chain.invoke({input: 报销流程是怎样的, chat_history: history}) history.extend([(human, 报销流程是怎样的), (ai, resp[answer])]) resp2 rag_chain.invoke({input: 那它的截止日期呢, chat_history: history})逻辑说明create_history_aware_retriever 内部先调一次 LLM 做 query 改写再走检索多花一次调用但换来检索准确率。create_stuff_documents_chain 负责把检索到的文档「塞」进 Promptcreate_retrieval_chain 把两者串起来。chat_history 用 (role, content) 元组列表维护生产环境一般存 Redis 或数据库。参数说明历史窗口建议保留最近 510 轮再长就做摘要压缩。改写那步的 Prompt 要强调「只输出改写后的问题」否则模型会啰嗦地解释一通污染检索 query。3.3 引用溯源让答案能指回原文企业场景里光给答案不够用户要能点回原文核对。做法是在 Prompt 里要求模型标注来源或者干脆在返回结构里带上检索到的文档。create_retrieval_chain 的返回里就有 context 字段包含所有召回的 Document前端拿 metadata 里的 source 和 page 渲染引用即可。resp rag_chain.invoke({input: 报销流程是怎样的, chat_history: []}) for doc in resp[context]: print(doc.metadata.get(source), doc.metadata.get(page))逻辑说明metadata 是加载阶段带进来的PyPDFLoader 会自动填 source文件路径和 page页码。如果切分后 metadata 丢了检查 split_documents 是否保留了原 Document 的 metadata——它默认是保留的。引用溯源是 RAG 相对纯生成的核心优势之一面试里主动提这点是加分项。4. RAG 效果上不去的排查清单召回、切分、Prompt 三个高发区RAG 应用最让人头疼的不是搭不起来而是「搭起来了但答不准」。这一章按现象 → 原因 → 解决的结构列几个我踩过的坑基本覆盖了 RAG 效果问题的高发区。4.1 现象答案答非所问检索回来的块根本不相关原因通常有三个层次。第一层是 Embedding 模型和语料语言不匹配用英文模型跑中文语料向量空间对不上。第二层是切分粒度不对块太大导致一个向量混了多个主题相似度被稀释。第三层是 query 和文档表述差异大用户问「怎么报销」文档写「费用核销流程」字面不重合纯向量检索召不回。解决先换中文 Embeddingbge-large-zh 或 bge-m3验证第一层把 chunk_size 调小到 300500 试第二层第三层引入混合检索BM25 关键词 向量LangChain 的 EnsembleRetriever 可以把两路结果加权融合关键词路能兜住字面匹配向量路能兜住语义匹配。from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25 BM25Retriever.from_documents(chunks) bm25.k 4 ensemble EnsembleRetriever( retrievers[bm25, retriever_sim], weights[0.4, 0.6], # 关键词 0.4向量 0.6 )逻辑说明weights 按语料特点调术语多、缩写多的场景给 BM25 更高权重口语化提问多的场景给向量更高权重。混合检索是 RAG 提召回最立竿见影的手段之一。4.2 现象检索到了正确块但模型还是答错或答不全原因多半在 Prompt 和上下文组织。一是检索到的块顺序乱最相关的块被埋在中间模型注意力被稀释——有研究说 LLM 对上下文首尾更敏感把最相关的块放前面或后面。二是 Prompt 没约束「只用上下文」模型拿先验知识覆盖了材料。三是 k 太小答案分散在多个块里没凑齐。解决对召回块按相似度重排最相关的放首尾Prompt 里加「如果上下文不足以回答明确说不知道」k 从 4 提到 68 试但要观察是否引入噪声。更进阶的做法是加一个 rerank 模型如 bge-reranker先粗召回 20 个再用 rerank 精排出 top 4精度提升明显。4.3 现象PDF 里的表格和图片内容检索不到原因PyPDFLoader 只抽文本层表格会被拍平成乱序文本图片里的文字直接丢失。这是 PDF 类 RAG 的经典翻车点。解决表格用 pdfplumber 或 camelot 单独抽成结构化文本再入库扫描件和图片走 OCRPaddleOCR、Tesseract转文字复杂版面用 unstructured 的 hi_res 策略做版面分析。多模态场景可以把图片单独存检索时返回图片链接让用户自己看。这块没有银弹按文档类型选工具。4.4 现象多轮对话里检索 query 被历史污染原因把带指代的原始问题直接拿去检索或者改写 Prompt 写得太松模型把历史里的无关内容也揉进 query。解决改写 Prompt 明确「只输出独立完整的检索问题不要解释」历史窗口别开太大超过 10 轮就做摘要如果指代特别复杂可以在改写前先做一次指代消解。这个坑在多轮场景里出现频率极高面试里能主动讲出来是明显加分。4.5 现象本地 Embedding 建库慢到怀疑人生原因CPU 上跑 bge-large几万块要跑很久或者 batch_size 没调默认 32 没吃满 GPU。解决有 GPU 一定用 GPUdevice 设 cudaencode_kwargs 里 batch_size 调到 64128模型换 bge-small 或 bge-base精度损失有限但速度快几倍。建库是一次性成本但开发阶段反复重建很折磨建议把向量库持久化改代码时直接 load_local 别重建。5. 把 RAG 问答做成能交付的东西评估、缓存与一个提效技巧跑通和调优之后最后一个问题是「怎么证明它好用、怎么让它扛住真实流量」。这一章讲评估方法和两个工程化技巧都是面试里能体现工程素养的点。5.1 用 RAGAS 量化检索和生成质量凭感觉说「效果还行」在面试里站不住脚。RAG 评估有两个核心指标检索的命中率context recall / precision和生成的忠实度faithfulness答案是否只基于上下文。RAGAS 是常用的评估框架喂进问题、答案、上下文、标准答案它自动算分。from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_precision from datasets import Dataset data Dataset.from_dict({ question: [报销流程是怎样的], answer: [先提交申请主管审批后财务打款。], contexts: [[报销需先提交申请..., 审批通过后财务打款...]], ground_truth: [提交申请主管审批财务打款。], }) result evaluate(data, metrics[faithfulness, answer_relevancy, context_precision]) print(result)逻辑说明faithfulness 低说明模型在编要收紧 Promptcontext_precision 低说明检索噪声大要调切分或加 rerankanswer_relevancy 低说明答非所问要查 query 改写。评估集不用大几十条覆盖典型场景就能看出问题。参数上评估本身要调 LLM成本要算进去一般离线跑。5.2 缓存与流式两个让体验和成本都变好的技巧缓存分两层。Embedding 缓存相同文本不重复算向量LangChain 的 CacheBackedEmbeddings 可以接本地或 Redis。答案缓存相同问题直接返回历史答案用 GPTCache 或自己拿 query 向量做近似匹配。高频重复问题多的场景缓存能省一大半调用成本。流式输出是体验层面的刚需。LCEL 链天然支持 stream前端逐字渲染用户感知的响应时间从「等 5 秒」变成「立刻开始出字」。for chunk in rag_chain.stream({input: 报销流程是怎样的, chat_history: []}): print(chunk, end, flushTrue)逻辑说明stream 返回的是增量片段注意 create_retrieval_chain 的流式输出结构里 answer 字段是逐步拼出来的前端要按字段解析。流式不影响答案质量纯粹是体验优化但用户满意度提升很明显。5.3 一个我常用的提效习惯先建小评估集再调参调 RAG 参数最容易陷入「改一个参数、手动问几个问题、感觉好像好了」的玄学循环。我的习惯是动手调参前先攒一个 2030 条的小评估集覆盖典型问题、边界问题、指代问题每次改完参数跑一遍 RAGAS看指标是涨是跌。这样调参有依据面试里也能讲出「我用数据驱动调优」而不是「我凭经验试」。血泪经验是不要一上来就上复杂方案GraphRAG、Agentic RAG、多路召回先把最朴素的「切分 向量检索 Prompt 约束」调到及格线再按评估结果决定要不要加组件。很多团队一上来堆架构结果基础检索都没调好加了 rerank 也救不回来。RAG 的瓶颈往往不在架构复杂度而在切分粒度和 Prompt 这两件最基础的事上。希望这些能帮你在面试里把 RAG 讲得比背八股的人扎实一点。本文还有配套的精品资源点击获取