做企业级大模型应用,最让人头疼的不是模型不够聪明,而是它太会“一本正经地胡说八道”。客户问售后政策,它给你编一条不存在的条款;员工问报销流程,它把两个版本的规定混在一起讲;甚至一个简单的产品参数,它也能自信地给出错误数字。解决这个问题,目前最主流的方案就是RAG(检索增强生成)。RAG的核心思路其实不复杂:让大模型在回答问题之前,先去外部知识库里把相关文档和片段捞出来,再基于这些检索结果组织答案。它相当于把大模型的“闭卷考试”换成了“开卷考试”,考的是查找资料和归纳信息的能力,而不是凭空回忆的能力。
这篇文章我会结合自己做过的几个知识库问答项目,把RAG从原理到实战、从入门到优化讲透,重点说清楚怎么让大模型“只说有依据的话”,也就是行业里最在意的“减少幻觉”问题。无论你是刚开始接触RAG的开发者,还是已经在做智能客服、企业内部知识库、产品手册问答的同学,这篇都适合你。文章不会只贴概念,会把每一层的关键参数、踩坑点、调优思路都摊开来讲,争取让你读完就能上手搭一套能用的系统。
1. 先搞懂:为什么大模型会“撒谎”,以及RAG怎么让它说真话
1.1 大模型胡说八道的根源:参数化记忆的局限
大模型的“知识”并不是我们理解的那种查字典式的知识。它在训练阶段做的事情,本质上是把海量文本压缩成神经网络里的参数,回答问题时靠的是这套参数形成的隐式记忆。这种参数化记忆有几个天然的短板:知识有截止时间、低频信息容易被压缩丢失、私有数据根本没进过训练语料。问题是,生成模型天生有“必须给出回答”的倾向,它不会因为你没教过它某件事就拱手说不知道,而是会顺着语言规律编一个听起来合理的答案。
举个我实际遇到的例子:某企业想用大模型回答员工关于差旅报销制度的问题,制度文档里明确写了“住宿费上限是每晚600元”,但模型在回答时结合了网上五花八门的差旅标准,给出“每晚可以报销800到1000元”的建议。从语言流畅度来说,这个回答毫无破绽,但从事实层面来说,它完全错误。这正是RAG要解决的核心问题:模型的内部记忆不可控,那就不要让它凭记忆答题,改为让它先在外部知识库里找到确切依据,再照着依据回答。
1.2 RAG的原理:从“闭卷考试”到“开卷考试”
所谓RAG,英文全称是Retrieval-Augmented Generation,拆开看就是三个动作:Retrieval(检索)、Augmentation(增强)、Generation(生成)。流程上,用户提出一个问题后,系统先到这个业务的知识库中去检索相关文档片段,然后把问题和这些片段一起交给大模型,模型在限定的上下文中生成答案。
这个机制最重要的转变,是从“让模型回忆”变成“让模型阅读”。就好比考试从闭卷变成了开卷,学生不需要把整本书背下来,但必须会翻书、会定位重点、会照着材料作答。对应到系统上,知识更新不再需要重新训练模型,只需要更新文档库;答案正确性不再完全依赖模型参数,而是依赖检索质量;关键结论还能追溯到原始文档,方便人工核验。
这里要强调一点,RAG不是某个单独的开源项目,也不是一个具体的模型,而是一套系统架构方案。它可以只用一个向量数据库加一个通用大模型就搭起来,也可以在检索环节加入关键词搜索、重排序、意图改写等复杂模块。所以“入门到精通”的过程,本质上是不断往这套架构里补模块、提精度的过程。
1.3 哪些场景适合用RAG
不是所有问题都适合用RAG来解决。判断标准很简单:这个问题是不是依赖于一份“内容相对固定、且可被检索”的知识源?如果是,RAG通常是性价比最高的方案;如果不是,可能需要结合Agent、工具调用甚至模型微调。
| 适合RAG的场景 | 不适合单靠RAG的场景 |
|---|---|
| 企业内部制度、流程、报销准则问答 | 需要复杂多步数学推导的问题 |
| 产品使用手册、设备维修指南问答 | 需要连续自主探索才能回答的问题 |
| 客服知识库、售后政策问答 | 模型本身已经掌握得很好的常识问题 |
| 私有文档、合同条款的定向查询 | 需要实时访问业务系统数据的操作 |
| 高校课程资料、教材附带内容问答 | 大规模实时数据汇总分析 |
我见过不少团队一上来就想用Agent或者微调去解决知识问答问题,结果工程复杂度翻了几倍,效果反而不如先老老实实把RAG做好。只要是“知识型问答”,优先做RAG,这是投入产出比最高的一条路。
2. 一套可落地的RAG系统长什么样
2.1 全流程链路:从原始文档到最终答案
一套可落地的RAG系统,内部环节比很多人想象中多得多。完整链路大致是:文档加载、格式解析、内容清洗、文本切分、向量化、索引存储、检索召回、重排序、提示词组装、大模型生成。每个环节都会影响最终答案的“真话比例”。
我在项目中见过不少团队,精力全放在挑选大模型和Embedding模型上,结果上线后检索出来的文档片段乱七八糟,答案自然也是错的。实际上,文档解析和切分对最终效果的影响往往远超模型本身。举个例子,某设备维修手册里有一张故障码对照表,表格被解析成一堆分行的零散文本后,检索时很容易把“故障码ERR-203”和“解决步骤”这两段内容切到不同的知识块里,导致模型只拿到故障码解释,拿不到解决方法,最终答非所问。
所以搭建RAG系统之前,心里一定要有一个完整的地图:先做什么、后做什么、哪些环节最容易出问题。不要急着写代码,更不要急着调模型,先看看你的原始文档长什么样,这决定了链条上80%的问题。
2.2 文档切分:决定“能不能直接被回答”的关键
文本切分是RAG里最容易被低估的环节。切得太粗,一个知识块里塞了多个主题,模型检索到的内容过于杂乱;切得太细,一个完整结论被拆成两半,模型拿不到完整的上下文。切分的目标只有一个:每个知识块尽量是一个“语义相对完整、能独立支撑一个回答”的片段。
常用的切分策略有两种:固定字符切分和结构感知切分。固定字符切分实现简单,设定一个chunk_size和chunk_overlap即可,但很容易把段落从中间切断;结构感知切分会优先按照标题、段落、章节边界来切,再辅助字符长度限制,效果通常更好。实际操作中,我一般让chunk_size在300到800字之间,chunk_overlap设置为50到100字,中文场景按句号和分号作为次级分隔符。
提示:切分时一定要保留来源信息。每个片段至少要能追溯到原始文件、章节名或页码,否则后面做引用溯源、评测bad case时都会寸步难行。
很多入门教程喜欢把切分写得非常简单,但真实文档千奇百怪。表格、代码块、条款编号、多级列表,每一种都有坑。做切分方案时,先抽查20个片段,肉眼判断每个片段是否“自成一体”,这个动作比调任何参数都有用。
2.3 Embedding与向量检索选型
切分之后,每个文本片段要转换成向量。Embedding模型的选择直接影响检索效果,但也不是越贵越好。中文场景下,优先看模型对中文的支持度、最终向量维度、推理速度和是否支持长文本。我第一版项目里用的是开源中文向量模型,维度512,虽然不算豪华,但配合后续的混合检索和重排,效果已经完全够用。
向量数据库的选型又是一个常见纠结点。FAISS轻量、适合本地原型,Chroma内嵌、写起来最方便,Qdrant支持过滤和混合检索,Milvus适合大规模分布式生产环境。如果是公司内部做第一个MVP,直接用Chroma或者FAISS都行;如果一开始就能预见到数据量会到千万级、需要权限过滤,那可以直接上Qdrant或Milvus。
| 存储方案 | 特点 | 适合阶段 |
|---|---|---|
| FAISS | 轻量级本地向量索引 | 原型验证、单机 |
| Chroma | Python内嵌,持久化简单 | MVP阶段 |
| Qdrant | 支持过滤、负载均衡、混合检索 | 中小规模生产 |
| Milvus | 分布式、大规模、生态完善 | 企业级大规模数据 |
我想说的是,向量库选型不要被性能参数绑架,大多数知识库项目的问题根本不在向量库本身,而在前面的切分和后面的召回策略。选一个自己最熟悉、能快速跑起来的方案,先把链路打通,后面再迁移也不迟。
3. 从0到1:搭一套“能说真话”的RAG MVP
3.1 环境与依赖准备
搭MVP的时候,我建议直接用Python生态里的成熟库组合。下面这组依赖可以覆盖文档加载、切分、向量化和检索生成,是我实测比较稳定的一套:
pip install langchain langchain-community langchain-text-splitters chromadb sentence-transformers pypdfLangChain版本更新非常快,接口变动也频繁,如果后面代码报错,先检查是不是版本问题。如果你不想被封装库的版本折腾,也可以完全不用LangChain,自己写切分逻辑、自己调向量库API,代码量大概多几十行,但每个环节都透明可控。两种方式没有绝对好坏,我建议新手先跟着成熟封装走一遍,理解了链路后再考虑原生实现。
3.2 文档切分与向量入库
下面这段代码展示了从本地文档读取到写入向量库的完整过程。我把Embedding函数单独包装了一下,方便后面复用到不同的库:
from langchain_community.document_loaders import DirectoryLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from sentence_transformers import SentenceTransformer from chromadb.config import Settings import chromadb # 1. 加载当前目录下的markdown文档 loader = DirectoryLoader("./docs/", glob="**/*.md") docs = loader.load() # 2. 按段落优先切分,保留重叠区 splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", ";", ",", " ", ""] ) chunks = splitter.split_documents(docs) print(f"共切分为 {len(chunks)} 个片段") # 3. 初始化Embedding模型 model = SentenceTransformer("BAAI/bge-small-zh-v1.5") def embedding_fn(texts): return model.encode(texts, normalize_embeddings=True).tolist() # 4. 写入Chroma向量库 client = chromadb.PersistentClient(path="./vecdb") collection = client.get_or_create_collection( name="enterprise_kb", metadata={"hnsw:space": "cosine"} ) ids = [f"chunk_{i}" for i in range(len(chunks))] texts = [c.page_content for c in chunks] vectors = embedding_fn(texts) metadatas = [{"source": c.metadata.get("source", "")} for c in chunks] collection.upsert(ids=ids, documents=texts, embeddings=vectors, metadatas=metadatas)这里有一个重要细节:写入向量库时一定要把原始文本一起存进去,而不仅仅存向量和ID。因为最终生成阶段需要把原始片段拼进提示词,如果只存向量,还要回头查原始文档,多一次IO不说,还可能因为文本被修改导致片段对不上。
3.3 检索与生成的一次完整问答
向量入库之后,问答链路就简单了:先根据用户问题检索TopK片段,再拼进Prompt调大模型。下面这段代码把生成部分设计成“只依据提供的资料回答”,这是让模型“说真话”的第一道关口。
def search(query, top_k=5): results = collection.query( query_embeddings=embedding_fn([query]), n_results=top_k, ) return results def build_prompt(query, chunks): context = "\n\n".join( f"[{i+1}] {chunk}" for i, chunk in enumerate(chunks) ) prompt = f"""你是一个严谨的知识库问答助手。 请只依据下面的参考资料回答用户问题,不要使用模型自身的知识补充事实。 参考资料: {context} 用户问题: {query} 回答要求: 1. 如果参考资料中找不到答案,请直接回答“资料中未找到相关信息”,不要编造。 2. 关键结论标注来自哪个参考片段,如[1]。 3. 保证答案简洁、准确。 """ return prompt query = "设备开机后提示ERR-203,应该怎么处理?" results = search(query) # 把命中的片段按次序取出 hit_texts = [doc for doc in results["documents"][0]] final_prompt = build_prompt(query, hit_texts) print(final_prompt) # 将final_prompt交给大模型API或本地模型接口,temperature设置为0.1这里TopK我习惯设置为5。太少会漏答案,太多会引入噪声。如果你发现回答里经常混入无关信息,第一件事不是改Prompt,而是把TopK降下来,或者加入重排只取前3个高质量片段。
3.4 MVP跑通之后,先问自己三个问题
第一版能跑起来并不代表系统靠谱。我每次做MVP复盘,都会先问三个问题:第一,检索结果里有没有包含能直接回答问题的那段原文?第二,切分出来的片段是“刚好够用”,还是“缺头少尾”?第三,模型生成答案时是否真的在依据检索内容,而不是凭惯性自由发挥?
这三个问题的答案,基本能决定后续优化的优先级。如果第一第二个问题不达标,优先处理切分和检索;如果第三个问题不达标,优先调整Prompt和生成参数。不要一上来就加重排、加混合检索,那是在盖高楼但地基还没打牢。
4. 让检索“搜得准”:混合检索 + 重排这套组合拳
4.1 为什么单靠向量检索不够
向量检索的本质是语义匹配,它对“说人话”的提问很友好,比如“设备老是卡顿怎么办”这种不包含关键型号的模糊问题,它也能凭借语义找到相关内容。但它也有明显的盲区。现场提问里经常出现精确编号、型号、工单号,比如“ERR-203”“SN: A100-42”,这类字符串在向量空间里很容易被泛化,导致语义相近但完全不是同一个东西的片段被召回,而精确匹配的片段反而被漏掉。
还有个常见问题:向量模型对生僻词、缩略词、中英文混排的表达并不敏感,容易把“苹果手机保修政策”和“苹果公司介绍”这类内容搞混。单纯靠向量检索,召回率可能还行,但精确率和排序质量会非常不稳定。
4.2 混合检索:让精确匹配参与进来
混合检索的思路很简单:同时跑一路基于词法和词频的检索(如BM25),再跑一路向量语义检索,最后把两路结果融合排序。BM25能保证“模型编号、精确名称”这种硬条件匹配,向量检索能保证“口语化、同义改写”这种软语义匹配,谁也别想漏。
两路结果怎么融合?最常用的方法是RRF(Reciprocal Rank Fusion),它对每一路检索结果的排名倒数求和,公式如下:
score(doc) = Σ 1 / (K + rank_i(doc))其中K通常取60,rank是文档在第i路召回中的排名。比如某个片段在向量检索中排第2,在BM25中排第5,那么它的融合得分就是1/(60+2) + 1/(60+5),然后按融合分重新排序。RRF的好处是不需要调权重,简单且稳定。实际项目中,我把两份Top20结果做RRF融合后,再取前10做重排。
生产环境里,建议直接用支持混合检索的搜索引擎或向量数据库,比如Qdrant、Elasticsearch这类自带BM25方案的系统,避免自己用两套存储再写一堆胶水代码。手写实现大多数时候只适合MVP验证。
4.3 重排:粗召回之后的关键一步
即使加了混合检索,召回结果依然可能“相关但不精准”。原因在于向量检索和BM25都属于“粗粒度召回”,它们判断相关性的方式要么是双塔编码相似度,要么是词频统计,都缺乏对查询和文档之间细粒度交互的理解。重排就是用Cross-Encoder模型,把问题和文档拼接成一对输入,做深度交互打分,精度比双塔高一个档次。
流程通常是这样的:先用混合检索从全库召回50到100条候选文档,然后用重排模型对这100条逐一打分,最后只取Top3或Top5送给大模型。这样既控制了成本,又保证了最终进入Prompt的内容质量。重排模型的开源选择很丰富,比如基于中文训练的bge-reranker系列,base级别就能在CPU上跑,单条文档打分耗时几十毫秒,属于性价比极高的优化。
我做过一个对比实验:某设备维修知识库,纯向量检索时正确答案出现在前5条的比例大概是62%,加了BM25混合检索后提升到78%,再加重排后直接到了92%。最直观的感受是,回答不再“东拉西扯”,因为送给模型的Top1片段,真的就是对着问题写的那一段。
4.4 一个调优实验记录,给你参考
下面这张表是我在实际项目中记录下来的三组对比,用的是同一批50条测试问题,考察指标是“正确答案是否出现在最终送进模型的前3个片段中”。
| 方案 | 正确答案进Top3的比例 | 回答整体可用度 |
|---|---|---|
| 纯向量检索 | 61% | 有明显答非所问 |
| 向量 + BM25混合 | 79% | 多数问题能定位到相关章节 |
| 混合 + 重排 | 93% | 回答基本稳定,可直接用 |
这个实验让我形成一个很深的印象:单点模型的选择对RAG效果的影响,远小于“召回 → 融合 → 重排”这套组合链路带来的提升。所以在能力有限的时候,先把检索链路做完整,比换一个更贵的Embedding模型管用得多。
5. 提示词设计:把“说真话”写进模型的工作流程
5.1 三个必须写进Prompt的原则
检索做得再好,最后生成答案的还是大模型。如果Prompt不约束,模型依然会犯“活在自己的世界”的毛病。我总结了三个必须写进Prompt的原则,缺一不可。
第一,只依据给定的参考资料回答。这句话要从任务描述就开始强调,不要在Prompt末尾才轻描淡写加一句“请参考以上内容”。第二,允许说不知道。很多模型在大量指令微调中被训练成“回答一切提问”,你必须在Prompt里明确解除这个限制,告诉它找不到答案就直接拒绝回答。第三,要求标注来源。让模型在答案中对关键结论标注出处编号,这不仅方便人工核验,还能从机制上压制模型“自由发挥”的冲动。
5.2 一套可以直接复制的Prompt模板
下面这个模板是我在多个项目里反复调整后固定下来的底稿,你可以直接复制,再根据业务场景微调:
任务:你是一名严谨的知识库问答助手。 你只能依据下方“参考资料”回答用户问题,不得使用模型内部记忆补充事实,不得编造数据、条款、步骤。 参考资料: {context} 用户问题: {query} 输出要求: 1. 如果参考资料无法支撑回答,直接输出:暂无相关资料,请补充问题或联系人工。 2. 回答用中文,保持简洁,结论需给出参考来源编号,如[1]。 3. 如果参考资料内部存在矛盾,指出矛盾点,不要强行融合。这个模板有一个容易被忽视的细节:参考资料要放在用户问题之前,而且要用编号分隔。编号的目的是让模型在回答时能稳定引用特定片段,“[1]”比“根据上面的内容”要可验证得多。用户问题放在参考资料之后,模型在生成时对问题的注意力会更靠近输出位置,回答时更容易围绕“问题+刚才的材料”一起思考。
5.3 参数设置与引用机制
生成参数里,temperature是影响“说真话”的重要旋钮。知识库问答场景,我通常把temperature设置在0到0.1之间,最大不得超过0.3。温度越高,模型越敢“发散”,一旦发散,就可能偏离检索内容。如果你发现答案本身词句特别流畅但事实经常对不上,先检查温度是不是开太高了。
引用机制除了在Prompt里要求之外,还可以在系统层面配合。具体做法是:在组装Prompt时,给每个片段打上原始文件名或页码标签,模型回答时引用的是这些标签,系统后端再把标签映射回真实文档链接。这样用户看到的回答不仅文字可信,还能直接点击查看原文,整个系统的可信度会大幅提升。
5.4 防幻觉的兜底策略
Prompt约束不是万能的,尤其是面对一些“看似相关实则无关”的检索结果时,模型仍可能强行作答。一个有效的兜底策略是:在后处理阶段检查答案中的关键实体和数字是否都能在上下文中找到对应。比如答案里出现了某个金额、某个日期、某个零件编号,而这些字符串在提供的参考资料中完全不存在,系统就自动标记为“风险回答”,推送人工复核。
这条策略实现起来并不复杂,用小规模的命名实体识别规则就能覆盖大部分场景。对制度条款、财务政策、医疗健康这类高风险场景,我建议宁可在回复后面加一句“以上信息仅供参考,具体请以最新制度文件为准”,也不要把话说死。
6. 怎么量化“说真话”:评测集与迭代优化
6.1 先建一套“小而硬”的评测集
很多团队做RAG全靠人工体验,觉得“好像还行”就上线了。这种做法最大的问题是,你无法判断一个改动到底是变好了还是变坏了。正确做法是先建评测集,哪怕一开始只有30条问题。
评测集怎么建?收集真实用户问过的问题,覆盖三种类型:高频典型问题、语义相似但表达方式多变的问题、以及知识库里根本没有答案的边界问题。第三类尤其重要,因为“说真话”不只包括答得对,还包括在不该答的时候明确拒绝。每种问题再配上标准答案和答案所在的参考文档片段,这就组成了最基础的“标准答案集”。不需要一口气做几百条,从30到50条开始,随着系统迭代不断补充。
6.2 用哪些指标衡量“说真话”
有了评测集,我们需要把“说真话”拆解成可量化的指标。我常用下面几个维度:
| 指标 | 含义 | 实测中重点关注 |
|---|---|---|
| 召回命中率 Recall@k | 正确答案片段是否出现在前k条检索结果中 | 决定模型“看没看到”正确答案 |
| 引用准确率 | 模型引用的片段是否真的支撑最终结论 | 防“挂着引用却乱说” |
| 忠实度 | 答案中信息是否能全部对上参考片段 | 衡量有没有跳出资料发挥 |
| 拒答准确率 | 无答案问题时模型是否正确拒答 | 衡量“知之为知之,不知为不知” |
| 幻觉率 | 答案中出现资料中不存在事实的比例 | 核心红线,越低越好 |
人工逐条评测太费时,可以用一个大模型当“裁判”来初筛,比如把问题、参考片段、模型答案一起交给裁判模型,让它判断答案是否忠实于参考内容。但注意,裁判模型本身也有幻觉风险,所以关键case必须人工抽样复核,不能完全放手。
6.3 Bad Case分析:把优化变成日常
评测集建好之后,优化就变成一件可循环的事:跑评测、看分数、抽bad case、定位环节、修复、回归。我见过很多同学成绩上不去是因为定位不准,比如检索已经命中但答案还是错的,这是Prompt问题;答案泛泛但引用却沾边,这是重排或TopK没调好;检索结果压根没有正确片段,这是切分或索引问题。
典型的bad case可以分成几类:切片太碎导致上下文不完整、切片太大导致主题混杂、向量召回错位、模型忽略了检索内容直接凭记忆回答。每类问题对应不同的优化动作,不能眉毛胡子一把抓。我在项目里会把bad case按环节打标签,一段时间统计下来,就能清楚地看到系统最薄弱的环节在哪。
6.4 从埋点到优化的闭环
要让评测集保持生命力,必须依靠线上数据回流。我建议系统上线第一天就做日志埋点:用户问了什么、系统检索了哪些片段、模型引用了哪些来源、用户有没有继续追问,这些都要记录下来。每天抽一点时间看一遍低质量回答,把问题补充进评测集。
两周一次的迭代节奏是我觉得比较合适的。每次都把新发现的边界问题加进评测集,然后回放历史数据,看看改动有没有引入新的问题。这套流程跑起来之后,系统会进入一个稳定上升的轨道,而不是靠某一次“灵感大调参”碰运气。
7. 常见问题排查与我的避坑经验
7.1 常见问题速查表
下面这张表沉淀了我多次排查的经验,遇到问题可以照着快速定位:
| 现象 | 可能原因 | 排查动作 | 解决方案 |
|---|---|---|---|
| 明明有答案但检索不到 | 切分太碎、关键词不匹配、Embedding没覆盖专有词 | 打印检索Top10片段看内容 | 调整切分,增加混合检索 |
| 回答内容不在资料里 | 模型没遵守Prompt约束 | 检查Prompt和temperature设置 | 强化约束,temperature调到0 |
| 引用了错误片段 | 重排不足、相似片段干扰 | 人工看Top3检索结果 | 加重排,降低TopK |
| 答案过时没更新 | 向量库缺乏增量更新机制 | 查文档来源和入库时间 | 建增量更新与定期重建任务 |
| 回答过长太模板化 | 上下文塞了过多无关片段 | 检查送入模型的片段数量 | 减小TopK,或加重排精选片段 |
| 表格内容回答错乱 | 解析阶段表格结构被破坏 | 检查原始解析后的文本 | 针对表格单独做结构保留或行列转写 |
排查的时候有一个原则:先看检索结果,再看Prompt,最后才怀疑模型。因为RAG链路里,大部分“答案不对”的问题根源都在检索阶段,模型只是把检索结果里的“坑”忠实复述了出来。
7.2 我踩过的几个坑,以及现在的固定套路
有几个坑,我几乎是每个项目都会遇到一遍。第一个是扫描版PDF没有OCR就直接喂给RAG,结果检索出来的片段全是乱码,用户问“设备怎么保养”,系统答非所问。后来我养成了习惯:凡是PDF,先检查能不能复制文字,不能的话先过OCR。第二个是表格类文档被切成零散的碎片,一个完整的报销标准表被按行切成了五六块,检索时只命中其中一行,答案自然残缺。现在遇到表格,我会在切分前做“表格整块处理”,要么整表作为一个大片段,要么把表格转成键值对描述文字再切。
还有一个容易忽略的问题:提示词里如果上下文很长,模型对排在后面的片段关注度会下降。所以我在拼接上下文时,会把重排后得分最高的片段放在最前面,并且控制总长度不超过模型上下文窗口的一半,留足模型“思考”的空间。
我的固定套路大致是这样:先用很小规模的文档把基础链路跑通,确认检索能命中;再上混合检索和重排;接着建30到50条评测集,跑基线;然后每天都抽bad case,带着日志去改切分策略和Prompt。这套流程走下来,系统的稳定性不会太差。
最后再分享一个经验:不要迷信更贵的模型、更大的向量库、更复杂的知识图谱。RAG项目的成败,往往藏在那些不起眼的细节里——有没有OCR、表格切得对不对、Prompt有没有允许模型说“不知道”、有没有持续更新的评测集。把这些基本功打扎实,你的大模型知识库系统才能真正做到“说真话”。