
1. 项目概述与核心动机聊到RAG检索增强生成这几年几乎成了企业私有知识库和智能问答系统的标配方案。标题里这套组合——LangChain FAISS 通义千问我实际搭建过不止一次整体用下来的感受是轻量、够用、落地快。尤其适合那种“手头有一批文档想快速让大模型基于这些内容回答问题”的场景比如内部知识库问答、产品说明书智能客服、个人笔记检索助手之类。这一篇是系列的第二篇上一篇已经把RAG的基础原理和整体架构梳理过了这次直接进入实操环节讲清楚怎么用LangChain做流程编排、用FAISS做本地向量检索、用通义千问做大模型生成最终组装成一个完整的轻量级RAG系统。先说结论这套方案最大的优势是“不需要动用重武器”。FAISS是一个本地运行的向量检索库不需要额外部署专门的向量数据库服务数据量在百万级以内性能表现都很稳LangChain把文档加载、文本切分、向量化、检索、生成这些环节封装成了标准组件代码量能砍掉一大半通义千问作为国内可直接访问的大模型接口延迟低、费用可控对中小型项目非常友好。适合谁来参考主要两类人一类是刚接触RAG、想跑通第一个可用Demo的开发者另一个是已经跑通过基础流程、想把系统做得更规范更可控的进阶用户。无论哪一类这篇内容都会覆盖从环境准备到完整实现的每一个环节并且把我在实际调试过程中踩过的坑、试出来的经验直接写出来省去你自己反复折腾的时间。在开始之前有几个核心概念需要对齐一下。RAG做的事情可以简单理解为先根据用户问题去知识库中检索最相关的几个文本片段再把这些片段连同原始问题一起丢给大模型让模型基于“检索到的证据”生成回答。这样的好处很明显模型不再单纯依赖自己的内部知识而是有了实时可查的参考资料回答更准确、更可追溯还能有效降低幻觉。LangChain是一个开发框架负责把“检索”和“生成”这两步粘合起来并提供标准化的接口抽象。FAISS是Meta开源的向量相似度搜索库负责把文档片段变成向量并且支持快速检索。通义千问负责最后一步的文本生成把检索到的片段理解并组织成自然语言答案。2. 工具选型与整体架构解析2.1 为什么选 LangChain FAISS 通义千问这套组合不是唯一的RAG实现方式甚至也不是性能最强的方案但它是“投入产出比”很高的一套。我从几个维度逐一说明选型逻辑。LangChain的价值在于抽象层。不使用LangChain也能实现RAG直接用OpenAI的Embedding接口加FAISS再加千问的对话接口大概两百行代码也能跑通。但LangChain把这些环节都封装成了标准组件文档加载器Loader、文本分割器Splitter、向量存储VectorStore、检索器Retriever、对话链Chain。这意味着每一个环节都可以独立替换。今天用FAISS明天数据量大了想换成Milvus或者pgvector只需要改动几行配置今天用通义千问明天想换其他模型只需替换LLM的实例化代码。对于需要持续迭代的项目这种解耦带来的维护收益远超引入框架的学习成本。FAISS适合轻量级场景。很多人在选型时一上来就考虑分布式向量数据库但对于百万级以内的文档片段量这明显是过度设计。FAISS以库的形式嵌入应用进程零服务依赖部署简单检索速度快。它支持多种索引类型最基础的IndexFlatIP内积索引在数据量不大时就已经很快了而且是精确检索。数据量大一些可以切换到IVF系列索引用极小的精度损失换取几十倍的检索速度提升。对于文档问答这种场景TopK通常只取4到8个片段FAISS响应时间实测都在几十毫秒级别用户完全感知不到延迟。通义千问的接入体验很顺畅。国内可直接调用、文档清晰、SDK完善模型本身的中文理解能力也足够应付多数知识库问答场景。特别是DashScope兼容OpenAI的接口风格这意味着如果你之前写过OpenAI调用的代码迁移成本很低。从我实测的情况来看长文本理解和指令遵循能力在同类开源模型中属于第一梯队对RAG场景尤为关键——因为检索到的片段往往上下文不够完整需要模型具备较强的片段整合能力。2.2 系统整体架构与数据流向整个系统的数据流向可以用一条线串起来文档入库阶段和问答查询阶段。文档入库阶段做的是“一次写入、多次查询”的准备性工作。原始文档经过加载、切分、向量化处理后被写入FAISS索引持久化到本地磁盘。这个阶段的核心目标是建立“文本片段 → 向量 → 原始内容”的映射关系保证后续检索能快速定位到相关文档内容。问答查询阶段做的是“实时检索 生成”的协作。用户传入一个问题系统先把问题向量化再到FAISS中检索最相似的TopK个文本片段最后把这些片段和问题拼接成Prompt交给通义千问生成回答。这个阶段的性能瓶颈不在大模型调用通常需要1到3秒而在于检索是否精准——如果检索到的片段相关度不高模型再强也回答不好。补充一个重要的细节Embedding模型的选择直接影响检索质量。这一步是整个RAG系统中最容易被低估的环节。文本向量化质量差后续所有环节都会跟着遭殃。我在实践中优先使用通义千问的text-embedding-v3或者text-embedding-v2模型中文语义理解能力强维度适中1024维对于大多数业务文档的场景已经足够了。2.3 轻量级架构的边界与权衡这套架构毕竟叫“轻量级”它的边界在哪里我需要坦诚地说明。更适合数据量在几十万片段以下、并发量不高、对检索精度没有极致要求的场景。如果业务规模发展到百万级向量以上、需要分布式部署和高并发查询FAISS单机模式就会成为瓶颈届时需要迁移到专门的向量数据库。另外这套方案默认你的文档是结构化程度较高的文本或PDF如果涉及大量扫描图片、复杂表格需要额外引入OCR和表格解析能力这超出了本方案的覆盖范围。但反过来说正因为轻量它特别适合作为第一版RAG系统的起步方案。先把完整链路跑通验证业务效果积累数据等规模真正大了再迁移也不迟——因为在LangChain的抽象下替换向量存储的改动成本很低。3. 环境准备与核心代码实现3.1 环境配置与依赖安装我以Python 3.10作为基准版本安装核心依赖。为了不污染系统环境建议用虚拟环境。python -m venv rag_env source rag_env/bin/activate # Windows下是 rag_env\Scripts\activate依赖安装直接用pippip install langchain langchain-community langchain-openai faiss-cpu pymupdf几个关键点说明一下。langchain和langchain-community是LangChain框架的主体langchain-openai是用来对接通义千问的——因为DashScope兼容OpenAI接口这个包可以直接复用。faiss-cpu是FAISS的CPU版本够用且安装方便不需要GPU。pymupdf用于PDF解析如果你日常处理的是Word或Markdown可以换成对应的加载器。我建议把版本锁定在较新的稳定版本上避免因为LangChain频繁升级导致的API变动。如果安装时遇到问题不妨先检查Python版本和pip版本很多依赖冲突都是环境问题引起的。3.2 初始化大模型与Embedding在开始之前需要准备好通义千问的API Key。在阿里云百炼控制台申请创建API-KEY然后配置到环境变量里。import os os.environ[DASHSCOPE_API_KEY] 你的API-KEY大模型的初始化代码如下from langchain_openai import ChatOpenAI from langchain_openai import OpenAIEmbeddings # 通义千问大模型 llm ChatOpenAI( modelqwen-plus, api_keyos.environ[DASHSCOPE_API_KEY], base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, temperature0.3, ) # 文本向量化模型 embeddings OpenAIEmbeddings( modeltext-embedding-v3, api_keyos.environ[DASHSCOPE_API_KEY], base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, )这里的核心思路是DashScope提供了OpenAI兼容接口所以base_url指向它的兼容模式地址模型名采用通义千问的命名规范。qwen-plus在效果与成本之间比较平衡日常问答够用如果对复杂推理要求高可以考虑升级到qwen-max速度略慢但效果更好。temperature设置为0.3在回答稳定性与一定灵活性之间取一个平衡对于知识问答类场景不宜太高以免产生幻觉。3.3 文档加载与文本切分接下来是文档处理的环节。这里以PDF文档为例from langchain_community.document_loaders import PyMuPDFLoader # 加载PDF文档 loader PyMuPDFLoader(knowledge_base.pdf) documents loader.load()加载完成后需要做文本切分。为什么必须切分有两个原因一是大模型上下文窗口有限整个文档太长放不下二是检索需要细粒度的相关性——用户问一个具体问题时匹配到的是“某个与问题最相关的段落”而不是“整篇文档”。如果不切分一段5000字的文档被检索出来后塞进Prompt不仅浪费token也稀释了关键信息。from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每段最大字符数 chunk_overlap50, # 段间重叠字符数 separators[\n\n, \n, 。, , , , , ], ) texts text_splitter.split_documents(documents)参数选择上chunk_size取500字符是经过取舍的。过短则每段信息量太少大模型缺乏足够上下文过长则检索精度下降且容易在Prompt中引入无关内容。chunk_overlap取50是为了保留段落之间的上下文衔接特别是当某个核心观点恰好被切分到两个片段边界时重叠部分能避免信息丢失。切分器用RecursiveCharacterTextSplitter而不是简单的按长度切分是因为它按照优先顺序尝试不同的分隔符能最大程度保持语义完整性。我特别把中文标点句号、问号、感叹号、分号加进了分隔符列表这对中文文档切分质量非常重要——默认的separators对中文的支持并不理想不调整的话经常会把一个完整句子拦腰截断。3.4 向量化与FAISS索引构建文本切分完成后进入向量化环节。把每个文本片段传入Embedding模型得到向量表示然后写入FAISS索引from langchain_community.vectorstores import FAISS # 创建向量存储 vector_store FAISS.from_documents( documentstexts, embeddingembeddings, ) # 持久化到本地 vector_store.save_local(faiss_index)这两行代码的背后发生了几件事每一段文本都被调用了Embedding接口转换为1024维向量FAISS在当前进程内构建了索引结构索引文件和文档元数据被序列化保存到磁盘。整个入库阶段到这里就完成了。这里有一个容易被忽略的点Embedding是文本检索系统的“内容理解层”。无论是文档入库还是查询向量化用的都是同一个Embedding模型。如果入库和查询用了不同模型或不同版本向量空间不一致检索效果会大打折扣。所以通义千问的text-embedding-v3模型一旦选定内部项目里就要保持统一。3.5 检索器构建与RAG链路组装索引构建好之后下一步是把检索器从向量库中实例化出来并组装最终的问答链路# 加载已有索引重启服务不用重新构建 vector_store FAISS.load_local( folder_pathfaiss_index, embeddingsembeddings, allow_dangerous_deserializationTrue, ) # 构建检索器 retriever vector_store.as_retriever( search_typesimilarity, search_kwargs{k: 4} )search_type选择相似度检索取Top4。这个数字不是拍脑袋定的我交替测试过2、4、6、8四档结论是k2时信息太少模型经常找不到关键论据k6和8时虽然召回更全但无关片段也随之增加反而干扰了大模型的判断k4在这类文档场景中效果最稳。RAG的完整问答链路有两种实现方式我分别说明。第一种用LangChain的检索问答链简洁高效from langchain.chains import RetrievalQA qa_chain RetrievalQA.from_chain_type( llmllm, retrieverretriever, chain_typestuff, return_source_documentsTrue, ) # 执行查询 response qa_chain.invoke({query: 什么是RAG}) print(response[result])第二种手动控制Prompt和流程灵活性更高也方便调试from langchain.prompts import PromptTemplate from langchain_core.runnables import RunnablePassthrough prompt_template 你是一个基于知识库回答问题的助手。 请根据以下资料回答问题如果资料中没有相关信息请明确回答“资料中未提及”。 资料 {context} 问题{question} 请给出详细回答 prompt PromptTemplate.from_template(prompt_template) rag_chain ( {context: retriever, question: RunnablePassthrough()} | prompt | llm ) # 执行查询 result rag_chain.invoke(什么是RAG) print(result.content)第二种方式是我更推荐的。原因有两个一是Prompt是RAG效果的关键变量手动控制Prompt意味着你可以随时调整系统提示词的语气约束、回答策略、引用格式二是LangChain的RetrievalQA链内部逻辑比较多出了问题时排查链路长而手动组装的方式每一步都看得见摸得着对理解整个系统的工作原理也有帮助。3.6 完整代码整合把上面的代码串联起来形成一整个可运行的脚本import os from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.document_loaders import PyMuPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain.prompts import PromptTemplate from langchain_core.runnables import RunnablePassthrough # 配置API Key os.environ[DASHSCOPE_API_KEY] 你的API-KEY # 初始化大模型 llm ChatOpenAI( modelqwen-plus, api_keyos.environ[DASHSCOPE_API_KEY], base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, temperature0.3, ) # 初始化Embedding embeddings OpenAIEmbeddings( modeltext-embedding-v3, api_keyos.environ[DASHSCOPE_API_KEY], base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, ) # 加载文档 loader PyMuPDFLoader(knowledge_base.pdf) documents loader.load() # 切分文档 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , ], ) texts text_splitter.split_documents(documents) # 构建并保存向量索引 vector_store FAISS.from_documents(documentstexts, embeddingembeddings) vector_store.save_local(faiss_index) # 构建检索器 retriever vector_store.as_retriever(search_typesimilarity, search_kwargs{k: 4}) # 自定义Prompt prompt_template 你是一个基于知识库回答问题的助手。 请根据以下资料回答问题如果资料中没有相关信息请明确回答“资料中未提及”。 资料 {context} 问题{question} 请给出详细回答 prompt PromptTemplate.from_template(prompt_template) # 组装RAG链路 rag_chain ( {context: retriever, question: RunnablePassthrough()} | prompt | llm ) # 执行查询 result rag_chain.invoke(什么是RAG) print(result.content)这个脚本可以直接复制运行只要把Knowledge_base.pdf路径和API Key替换成自己的就行。如果文档比较多可以循环加载多个文件合并到documents列表中再统一切分和建索引。4. 检索效果优化与参数调优实践4.1 文本切分策略的精细调整很多人跑通了基础Demo之后紧接着就遇到一个问题回答质量不稳定有时候准得惊人有时候又明显答非所问。排除模型本身的因素大部分问题出在文本切分环节。我踩过最典型的一个坑是一份产品手册中每个章节的标题和正文之间用空行隔开默认的RecursiveCharacterTextSplitter会把标题和正文切到同一个片段里这还好但如果分隔符优先级设置不当容易把“5.3 参数配置说明”这类标题单独切出来成为一个独立片段。当用户问“怎么配置参数”时检索器可能召回的是那个孤立标题而不是包含详细步骤的正文内容大模型拿到的就是一段没有上下文的信息。解决办法是分段策略用“父子切分法”先用较大块比如1000字符做粗切再对每个粗块细切成小块比如300字符检索时用小块向量召回但喂给大模型时将所属粗块全文返回。这样既保证了检索精度又让大模型获得更完整的背景信息。LangChain的ParentDocumentRetriever就是专门干这个的代码会比基础版复杂一些但对文档类型多样、上下文强依赖的场景效果提升是立竿见影的。4.2 检索数量与阈值的选择逻辑TopK值直接影响回答质量这个前面提到了但还要补充一个维度——阈值设定。FAISS相似度检索返回的是向量之间的距离或相似度理论上可以设定一个最低相关度阈值低于阈值的片段直接丢弃。但在实践中我不推荐设置硬性阈值因为Embedding模型的相似度分数分布跟模型训练数据的分布强相关不同模型给出的绝对分数没有可比性。更好的做法是只在TopK范围内考虑分数相对值比如观察召回的4个片段中有没有明显的分数断层如果有说明真正相关的文档可能就只有那一个。更实用的是调试阶段的诊断方式每次回答后同时打印检索到的片段原文和相似度分数肉眼检查召回内容是否与问题相关。这个习惯能帮你快速定位问题是出在“检索不到”还是“检索到了但模型没用上”从而决定是调整切分策略还是优化Prompt。4.3 混合检索与重排序的引入时机纯向量检索有一个天然弱点它依赖语义相似度但语义相似不等于词汇命中。比如文档里写的是“安装部署”用户问的是“怎么装”语义上相关但向量空间中的距离可能并不近。这就是为什么很多生产级RAG系统会引入混合检索——在向量检索的同时叠加一个BM25关键词检索然后把两路结果合并。就这套轻量级方案而言是否要做混合检索取决于你的文档特性。如果文档是技术手册、法律条款、产品说明这类术语密集、表述固定的文本纯向量检索通常已经够用如果文档是对话记录、论坛帖子这类口语化表达多的文本混合检索的价值会明显放大。需要说明的是引入BM25需要在本地自建倒排索引代码量会多一些属于进阶改造项第一版先用纯向量检索跑通即可。重排序Rerank是另一个可选项。当检索回来的TopK结果其实只有1到2个真正相关时重排序模型可以对候选片段进行更精细的打分把最相关的排到最前面。但通义千问也提供了专门的Rerank模型gte-rerank接入LangChain的查询转换器需要额外写一部分自定义代码。小型项目建议暂缓先把基础链路调好。5. 常见问题与排查技巧实录5.1 接口报错与依赖问题速查我在搭建过程中遇到过不少报错这里挑出最有代表性的整理成一张排查表按现象、原因、解法三列呈现问题现象可能原因解决方案导入OpenAIEmbeddings报错langchain-openai包未安装或版本过旧升级langchain-openai到最新版同时确认langchain核心包版本兼容调用通义千问接口超时网络不稳定或接口地址错误确认base_url为完整兼容地址增加timeout参数检查代理设置FAISS索引load失败索引文件损坏或版本不兼容删除索引目录重新构建确认Embedding模型与构建时一致检索结果为空文档切分后为空或Embedding调用失败检查文本切分后片段数是否为0查看嵌入调用日志回答内容完全不在知识库检索器失效或Prompt提示词约束不足打印检索结果确认召回质量检查Prompt中是否要求“基于资料回答”内存占用过高文档过大导致全部向量载入内存分批处理文档或切换FAISS的IVF索引降低内存占用补充一个经验报错最快定位的方法是开调试日志。把LangChain的日志级别改为DEBUG可以看到完整的执行链路和每一步的输入输出。虽然日志量大但在排查问题时效率最高。5.2 从“答非所问”到“精准回答”的排查路径我遇到的最挫败的情况是检索结果明明是对的模型还是给出了一个跟知识库完全不搭边的回答。排查后发现是Prompt设计的问题——原版的Prompt没有加“如果资料中没有相关信息请明确回答”这个约束导致大模型在检索片段信息不足时自作聪明地“脑补”。这个教训很重要RAG系统的输出质量是Prompt和检索共同决定的。如果检索器没问题问题基本出在Prompt上。通用的排查路径是先打印检索到的片段原文确认召回内容相关再检查Prompt模板确认系统提示词是否明确了“只能基于给定资料回答”最后检查temperature参数语义模糊的情况下温度过高会加重模型的任意发挥。还有一个容易忽略的细节当知识库文档本身内容矛盾时模型会无所适从。比如文档V1和文档V2对同一个功能的描述不一致检索器可能同时召回两个矛盾片段。这时候需要引入时间戳或版本号作为元数据在Prompt中让模型优先引用较新版本的内容。5.3 性能优化与成本控制的实操经验通义千问的API调用是有成本的RAG系统在这方面天然有优势——它只需要把检索到的TopK片段通常不到2000字符发给模型而不是把整个知识库都塞进去。但成本仍然是可以进一步优化的空间。第一Embedding结果可以做本地缓存。文档库中不变的内容没必要每次启动都重新向量化。入库时用文档内容的哈希值作为缓存键只有内容变化才重新调用Embedding接口能为批量导入场景省下一大笔API费用。第二FAISS索引可以增量更新。有一种常见误解是新增文档必须重建整个索引。实际上FAISS支持add_documents方法新文档直接追加到现有索引即可# 新增文档时无需重建全部索引 new_documents loader.load() new_texts text_splitter.split_documents(new_documents) vector_store.add_documents(new_texts) vector_store.save_local(faiss_index)第三控制并发与重试策略。LangChain默认的请求频率是有限制的如果批量处理大量文档建议用RetryWithIncrementalBackoff组件包裹Embedding调用逻辑并在每次调用之间加上短暂延时避免触发接口限流。5.4 生产环境部署的几个注意点开发环境跑通之后如果要部署到生产环境有几个点需要提前考虑。一是索引的实时更新机制。知识库文档动态变化时需要一个定时任务或事件触发机制来增量更新FAISS索引。简单的做法是定时重跑入库脚本复杂的做法是把文档变更写入消息队列异步更新索引。对于轻量级场景前者已经足够。二是检索接口的超时与降级策略。大模型接口偶尔会超时或返回异常这属于常态。建议在API调用层做好重试、熔断和降级处理比如通义千问暂时不可用时返回检索到的TopK文本片段本身作为一种降级回答方式。三是日志与监控。RAG系统的监控点主要包括检索响应时间、模型调用成功率、生成答案的token消耗量、用户问题的语义分布。这些数据能帮助你持续优化切分参数和Prompt设计。6. 进阶扩展从轻量级到实用级的改造路径跑通基础版RAG系统只是第一步真正落地到业务场景通常还需要做一系列增强改造。这里分享几个我认为优先级最高的扩展方向。6.1 多格式文档支持与知识库管理实际业务中的文档远不止PDF一种Word、Excel、PPT、Markdown、HTML甚至扫描件都是常态。LangChain生态中每一种格式都有对应的DocLoader比如Docx2txtLoader处理Word、UnstructuredExcelLoader处理Excel、PyMuPDFLoader处理PDF。把这些Loader统一注册到一个函数里根据文件扩展名自动选择加载器就能实现多格式知识库的统一入库。扫描件的情况特殊需要先用OCR把图片转成文本。PaddleOCR和RapidOCR是两个中文识别效果较好的开源方案通常的流程是扫描件 → OCR提取文本 → 文本切分 → 向量化。这个流程会比纯文本处理慢很多但对真实业务来说是必不可少的一环。6.2 多轮对话状态管理我搭的第二版RAG系统增加了一个重要能力多轮对话。基础版RAG是无状态的——每来一个问题都是独立检索独立回答无法处理“它和上一个方法有什么区别”这类指代性问题。要支持多轮对话需要把历史对话信息纳入上下文。LangChain的ConversationalRetrievalChain组件专门解决这个问题它的做法是把历史对话转化为一个新的问题再基于新问题做检索。具体实现是初始化一个QuestionGenerator LLM负责把“它”这类指代词替换成实际指代的内容然后走标准RAG流程。不过这个方案的代价是增加了额外的一次LLM调用响应时间会翻倍。我的建议是如果确实需要多轮对话增加这一层是值得的如果产品本身是单轮问答就不要为了“支持多轮”而牺牲响应速度。6.3 结合通义百炼平台的工程化能力如果业务规模进一步提升完全可以在现有代码基础上接入阿里云百炼平台的RAG产品化能力。百炼提供的知识索引服务支持文档解析、切分、向量化等全套流程的托管代码层面只需要调用Retrieve接口获取检索结果再调用千问生成接口完成回答。这样做的好处是省去了自建向量索引的运维成本坏处是灵活性降低且单次调用的费用会上升。从我的实践来看建议的演进路径是先用本文这套轻量级方案快速验证产品价值积累一段时间的真实用户查询日志确认RAG确实解决了业务问题之后再根据规模和数据量决定是否迁移到托管服务或分布式向量数据库。千万不要一开始就上重型方案。7. 个人经验总结与避坑清单最后分享几条我在整个搭建过程中沉淀下来的核心经验算不上系统性的方法论但每一条都是在实际项目中付出了试错成本换来的。第一条经验跑通Demo只是完成了10%剩下90%的精力都花在调参和修数据上。第一次跑通RAG链路的时候感觉到效果惊艳是正常的但那往往是知识库文档质量高、问题恰好落在检索范围内。真实业务中的数据是脏的、乱的、格式千奇百怪的你会发现大量时间不是花在代码上而是花在清洗文档、调整切分规则、修正元数据上。尽早建立这个心理预期能节省不少心态成本。第二条经验Prompt工程在RAG系统中的重要性被严重低估。很多人以为RAG的核心是检索其实生成环节的Prompt设计同样关键。我试过同一个检索链路仅仅改了一句话——“请仅根据提供的资料回答问题当资料内容不足以回答时明确告知资料缺失”——回答的准确率和可接受度就能提升一个明显的档次。Prompt中明确模型的行为边界是RAG落地时绕不开的关键步骤。第三条经验用真实场景的数据来做效果评估不要用自己随手编的问题。一开始我测试RAG效果时用的都是自己头脑风暴出来的问题结果自然是看起来完美。后来引入真实用户的历史提问记录做测试集才发现检索系统在真实口语化问题上的表现远没有想象中好。建议从第一天就把测试集建好每次改动切分参数、Prompt模板、检索策略都用同一套测试集来回归验证避免凭感觉做判断。第四条经验向量化是整个系统的上限。如果检索效果不满意先怀疑Embedding模型再怀疑切分参数。“换一个更强大的Embedding模型”往往比调任何代码参数都有效。通义千问的text-embedding-v3已经很强了但如果你的知识库内容极度垂直专业可以考虑用领域微调后的Embedding模型或者更大参数的版本检索效果通常会有明显提升。这套LangChain FAISS 通义千问的组合我已经在多个模拟和实际项目中验证过稳定性和效果都达到了预期。它最值得称道的是模块间的解耦设计——每一个环节都可以独立调优、独立替换。当你把这套系统的每个细节都理解透彻之后再去迁移到更大规模的架构会从容得多。希望这篇文章能帮你少走弯路顺利跑通属于自己的第一个RAG系统。