
前阵子帮一个客户做企业级智能客服遇到一个特别典型的选型问题资料库小两万份PDF累计几亿字问答场景还要求必须给出出处。团队里争论得很凶一派人说直接买长上下文模型把资料全塞进输入里简单粗暴另一派人坚持上RAG说不然成本收不住。我把两条路都完整实测了一遍结论是这事没有银弹但有一套清晰的判断方法。这篇文章就把我的实测过程和最终选型逻辑完整写出来。无论你手上是几万份文档的知识库还是几十页的产品手册只要涉及“大模型 外部知识”的组合这篇都适合你从头读完。1. 两种方案并非“新老之争”本质是知识进模型的方式不同先把概念对齐。RAGRetrieval-Augmented Generation检索增强生成和长上下文模型Long-Context LLM经常被放在一起比较但把它们理解成“新方案vs老方案”就错了。它们解决的是同一个问题——如何让模型知道原本不知道的知识但走的是完全相反的两条路径。1.1 长上下文的做法让大模型直接“读全文”长上下文方案的核心思路是把所有资料作为上下文一次性预填充给模型。比如一份合同50万字那就在调用接口时把这50万字全放进system或user消息里模型在生成答案前会把整份材料“读完”。这个方案的优势很明显信息无损耗模型能看到材料内部的完整逻辑脉络人的阅读顺序、章节关联性、前后文指代都保留着。过去的瓶颈是上下文窗口太小装不下长文档所以各家模型厂商拼命卷上下文长度。如今几十万、上百万token的窗口已经不是新闻这确实是长上下文方案能成立的技术前提。1.2 RAG的做法先检索再阅读RAG走的是另一条路把资料预先切成片段、做成向量索引用户提问时系统先从向量库中检索出最相关的几个片段再把这些片段作为上下文交给大模型生成答案。模型不必“读全文”——它只需要读被检索出来的那几段。这个过程可以拆成四个环节文档加载与解析把PDF、Word、Markdown等格式转成纯文本。文本分块按语义或固定长度把长文切成小块。向量化与索引用Embedding模型把每个文本块转成向量存入向量数据库。查询召回与生成用户提问时把问题向量化在向量库中做相似度检索召回Top-K个片段拼进Prompt让模型作答。1.3 它们其实是在回答同一个问题两个方案真正想回答的问题是怎么让模型拥有它训练时没学过、或学得不准的知识区别在于知识驻留在哪里——长上下文把知识放在Prompt里RAG把知识放在外部存储里。对比维度长上下文方案RAG方案知识驻留位置模型输入的上下文窗口外部向量数据库读取范围全部资料一次性读取仅召回Top-K相关片段更新知识的方式修改Prompt或重新发起调用更新向量库索引成本结构随输入长度线性增长与检索量和片段数相关失败模式中间段落被忽略、长文遗忘检索不到对应片段可解释性依赖模型自述难以追溯可返回检索到的源文档这个表格看下来你会发现两者根本不是“哪个先进哪个落后”的关系而是适用场景高度互补的关系。2. 长上下文模型的隐性短板中间遗忘、成本曲线与更新困境很多人选长上下文方案是被“百万token窗口”这个营销词打动的。但真实工程环境里这个方案有三个隐性短板每一项都能让项目在关键时刻掉链子。2.1 长上下文远未达到“无损记忆”我在自己的测试集上做过实验用的是当时某主流长上下文模型文档是一份约200页的招股说明书。问题涉及文档中间偏后的一个关键财务指标结果模型的回答引用了文档开头的信息完全遗漏了中段更精确的数据。这不是个例。学术界对这种现象有个专门的说法叫Lost in the Middle即模型对长上下文中间部分的信息利用效果显著差于开头和结尾。原因跟注意力机制有关模型在处理超长序列时注意力权重会被分散处于“中间地带”的内容容易被边缘化。这意味着什么即使某模型宣称支持1M token的上下文它对长文中每一段内容的理解精度并不是均匀的。你放进去的资料越多中段内容的“有效记忆率”就越低这是长上下文方案的底层天花板不是简单加大窗口能解决的。2.2 成本不只看长度还要看调用频次长上下文的输入成本随token数线性上涨很多人忽略了这一点。假设某模型输入价格是每百万token 30元一份10万token的资料单次调用光输入成本就是3元——注意这是“一次调用”的成本。如果这是个客服场景一天有1000次咨询单日成本就是3000元一个月9万元。而RAG方案的每次调用只发送检索到的4-6个片段假设总共3000 token一次调用输入成本不足0.1元同样是1000次咨询单日成本不到100元。你可能会说“我们有上下文缓存”这当然能省一部分钱但缓存命中率在随机问答场景并不高而且缓存的是一整份静态资料只要资料有更新整条缓存就失效了。实际算下来高频交互场景长上下文的成本优势非常有限。2.3 动态知识注入与更新困境企业知识库没有“静态”的。产品参数今天改一个明天加一个合同模板定期更新法规要求随时变动。如果走长上下文方案知识一旦变化就必须更新Prompt里的完整内容并重新发起调用。我见过一个团队把公司全部规章制度塞进Prompt每次制度更新要技术团队手动改配置、重新测试、重新上线。后来制度更新频繁了技术团队一周能接到三四次“改Prompt”的需求苦不堪言。而RAG方案只需要在向量库里增量更新或删除对应片段业务人员自己就能操作。2.4 长上下文真正适合的场合说了一堆短板但也必须承认长上下文有它的用武之地。我自己常用的场景是单次深度分析比如分析一份上百页的行业报告要求模型给出完整逻辑链条长上下文表现明显更好。超长单文档QA一份合同、一本小说、一套遗留系统的完整源码这种内容不需要跨文档检索全文提供给模型是最自然的方式。快速原型验证在产品没有正式定方案时直接塞全文试效果比先搭一套完整RAG管道快得多。3. RAG最适合的三种典型场景私有库、动态库、可审计库说完长上下文的边界再看RAG真正发力的地方。我总结了三个最典型的场景几乎覆盖了我经手过的所有RAG项目。3.1 需要私有知识与大模型能力结合的场景企业内部的知识库通常高度私有比如SOP文档、客户历史工单、项目复盘、老板讲话全文。这些内容模型训练时肯定没见过而且出于数据安全考虑也不应该发给外部模型的训练系统。RAG天然适合这类场景——私有知识存本地向量库只有用户提问触发的Top-K片段才会作为上下文发给模型整体数据暴露面被控制在最小范围。这里有个容易被忽略的细节如果你用公共大模型的API做RAG文档解析后分块片段仍然会经过模型服务商的接口。所谓“私有”只能保证不进入对方的训练集但传输过程本身还是会接触外部厂商的服务器。真要做到严格私有要么部署私有化模型要么在网关层做脱敏和权限过滤这段后面实操部分我会展开。3.2 知识更新的时效性场景做客服系统的人应该深有体会产品刚上线一个新功能客服团队手里的资料还没同步用户咨询就已经涌进来了。RAG的索引更新粒度是“片段级”新文档加入后只需要做一次向量化然后把结果追加到向量库表中整个链路分钟级完成。更新不涉及模型本身不需要重新微调也不需要改Prompt结构。我合作过一家做智能菜谱推荐的创业公司他们的知识库每周都会新增大量用户提交的菜谱。上线了基于RAG的智能推荐系统后新菜谱录入到生效只需要不到十分钟。换成纯长上下文方案的话每周更新一次Prompt还算可以接受但一天可能更新十几条数据时长上下文方案的维护成本就完全失控了。3.3 需要可追溯与可解释的业务场景金融、医疗、法律、客服这四类场景对“这个答案凭什么这么说”有极高要求。RAG天然支持溯源——它能把生成答案依据的原文片段直接展示给用户。模型每回答一个问题系统都可以携带source字段返回引用的文档ID和页码。出了问题定位到具体片段比跟黑盒模型反复对话要高效得多。我在智能客服项目里把这个能力做成了一个标准功能所有回答末尾附上“参考文档”链接点进去能看到命中原文。客户那边合规团队特别满意因为审计时可以直接拿到“问题-答案-依据”的三元组记录。3.4 RAG做不好的场景也必须说清楚RAG不是没有缺点。首当其冲的是“检索失败”带来的回答质量下降——如果向量检索没有召回正确片段模型再厉害也只能答非所问其次混合了不相关文档的上下文甚至比不提供任何上下文更容易产生幻觉。此外分块策略设置不当会导致语义被切断比如把一个表格从中间劈开检索结果就会很糟糕。这些坑我在第五章实操部分会给出具体解法。4. 决策框架从知识规模、更新频率、可追溯性和成本四个维度打分聊完原理和场景下面进入最实用的部分你手上正巧有一个项目需要做选型到底怎么判断我建议你不要拍脑袋按四个维度逐个评分最后加权选择。4.1 四个核心评估维度知识规模维度。把全部资料转成文本统计总token数。如果总token数小于模型上下文窗口的30%长上下文方案在规模上可行如果总token数远大于可用窗口RAG基本是唯一选择。假设10万token窗口的模型资料整体超过3万token时长上下文方案的表现就开始不可控了。知识更新频率维度。统计资料每月的新增和变更条目数。如果每月更新不超过十次长上下文方案的维护成本可控如果每天都有新文档入库强烈建议上RAG。更新频率越高RAG优势越明显。回答要求维度。看你的业务是否需要严格的出处、引用、可审计记录。需要的话RAG几乎是唯一选择因为长上下文方案无法稳定输出准确的出处。如果只需要“给个答案我自己判断”长上下文也可以接受。成本与延迟维度。估算单次调用的平均输入token数、日调用量、每token单价算出月度成本。再把RAG方案的“检索耗时片段输入成本生成成本”算出来对比。延迟方面长上下文方案由于预填充大量token首字延迟通常明显高于RAG。我实测过10万token上下文的预填充时间可能在10-20秒而RAG方案的检索耗时通常几百毫秒。4.2 一张决策表为了让你能直接对照我列一个通用决策表业务画像推荐方案理由单份大文档深度分析合同、报告、源码长上下文需要整体逻辑脉络无需跨文档检索海量文档垂直问答客服、百科、知识库RAG知识规模大、需要溯源、更新频繁中量文档低频问答几十份资料每周问答次数极少两者皆可优先长上下文实现简单成本可控动态且频繁更新的政策问答RAG增量更新业务自助维护索引专业领域高频问答法律、医疗、金融RAG可追溯、可验证、可合规审计快速原型验证/一次性任务长上下文最快验证业务逻辑不用搭管道4.3 一个实际打分案例我以开头提到的智能客服项目为例演示下完整的判断过程知识规模两万份PDF总token数约3亿远超任何主流模型的上下文窗口 → 判RAG。更新频率产品每周小迭代每月大迭代知识库每周新增约100篇文档 → 判RAG。回答要求客户要求所有回答附出处且需要审计记录 → 判RAG。成本与延迟日调用量约2000次长上下文方案月成本估算超18万元RAG方案估算不到2万元首字延迟要求3秒内 → 判RAG。四个维度全部指向RAG选型没有任何悬念。如果你评估完发现四个维度各有所指那就进入最后一种解法——混合架构这个我会在第六章细讲。5. 一套可以直接复用的实现FastAPI LangChain pgvector选型定了RAG之后第一步就是搭建一套可用的RAG管道。这里我给出一个生产级的最小实现技术栈采用FastAPI LangChain pgvector这也是目前中小团队落地最快、运维成本最低的方案组合。5.1 为什么选这三件套FastAPI的优势在于异步原生、Pydantic数据校验、自动生成OpenAPI文档部署到容器里也非常顺滑。LangChain的价值不是它有多智能而是它把“文档加载→切分→向量化→检索→生成”这套流程抽象成了标准组件大量适配器帮你省掉很多造轮子的时间。pgvector则是在PostgreSQL上加了向量类型和索引支持好处是你不需要额外维护一套独立的向量数据库业务数据和向量数据放在同一个库里事务、备份、权限体系全部复用PostgreSQL的成熟能力。如果你的数据规模实在大比如千万级向量以上再把存储层替换成Milvus也不迟。pgvector适合起步和中等规模Milvus适合最终大规模检索。我经手的项目里从pgvector迁移到Milvus的唯一原因就是单表向量量级到了千万以上查询延迟明显变长。5.2 整体架构流程整个系统分成两个阶段索引阶段文档加载 → 文本切分 → Embedding向量化 → 写入pgvector表。查询阶段用户问题 → Embedding向量化 → pgvector相似度检索 → Top-K片段 → 拼Prompt → 大模型生成 → 返回结果来源。5.3 核心代码实现先安装依赖pip install fastapi langchain langchain-openai langchain-community langchain-text-splitters pgvector psycopg2-binary pypdf第一步文档加载与切分from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 加载目录下的所有PDF loader DirectoryLoader( ./docs, glob**/*.pdf, loader_clsPyPDFLoader, show_progressTrue ) documents loader.load() # 文本切分按中文语义分隔符 text_splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap100, separators[\n\n, \n, 。, , , , , ], length_functionlen, ) chunks text_splitter.split_documents(documents)这里我特别强调下分块参数中文文本的separators一定要把中文标点加进去否则切出来的块很可能在句子中间断开语义残缺。chunk_size我习惯设为800字符左右chunk_overlap100字符既保证每块有足够上下文又避免检索时关键信息恰好被切到两块的分界处而丢失。这个参数不是固定的我见过有人用512效果很好也有人用1000效果不错要基于你的实际语料测试。但无论如何overlap必须有。第二步向量化入库from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import PGVector CONNECTION_STRING postgresqlpsycopg://user:passwordlocalhost:5432/ragdb COLLECTION_NAME enterprise_kb embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore PGVector.from_documents( documentschunks, embeddingembeddings, connection_stringCONNECTION_STRING, collection_nameCOLLECTION_NAME, )这段代码执行完pgvector会自动创建一张存储文本块、向量和元信息的表。这里有个点容易忽略collection_name对应一张独立表如果你的知识库有多个业务域建议按业务域拆分collection查询时可以按条件过滤避免全局检索把不相关业务的内容也带出来。第三步混合检索增强第5.3节这里再做一个增强把向量检索和关键词检索做合并也就是热搜词里频繁出现的hybrid RAG。向量检索擅长语义相似但精确匹配的专有名词或编号反而容易翻车。比如用户问“A3-2024合同”这种编号向量相似度不一定比得上BM25关键词精确命中。混合检索能同时拿到语义相关和字面相关的候选。from langchain_community.retrievers import BM25Retriever from langchain.retrievers.ensemble import EnsembleRetriever # 基于同一批chunks构建关键词检索器 bm25_retriever BM25Retriever.from_documents( chunks, k4, ) # 向量检索器 vector_retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4} ) # 权重混合向量检索0.7关键词0.3 hybrid_retriever EnsembleRetriever( retrievers[vector_retriever, bm25_retriever], weights[0.7, 0.3] )第四步拼接Prompt并生成from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI prompt ChatPromptTemplate.from_messages([ (system, 你是知识库助手。只能基于【资料片段】回答问题。 如果资料中没有答案明确回答“我暂时没有找到相关信息”。 每一条结论后面必须用【[来源文档名]】标注出处。\n\n 【资料片段】\n{context}), (human, {question}), ]) def format_docs(docs): return \n\n---\n\n.join( f[来源{d.metadata.get(source, 未知)}]\n{d.page_content} for d in docs ) chain ( {context: hybrid_retriever | format_docs, question: RunnablePassthrough()} | prompt | ChatOpenAI(modelgpt-4o-mini, temperature0.1) | StrOutputParser() ) result chain.invoke(我们产品的退款政策是什么)这部分Prompt设计有两个关键点强制模型基于资料片段回答无信息时主动承认——这能显著降低幻觉发生率。要求每条结论标注来源——组件链路已经拿到了原文元数据让模型把来源带出来即可满足审计要求。第五步封装FastAPI服务from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titleRAG Knowledge Service) class Query(BaseModel): question: str class Response(BaseModel): answer: str sources: list[str] app.post(/ask, response_modelResponse) async def ask(query: Query): docs hybrid_retriever.invoke(query.question) answer chain.invoke(query.question) sources list({d.metadata.get(source, unknown) for d in docs}) return Response(answeranswer, sourcessources)5.4 分块与Embedding选型的实操经验分块策略决定了RAG系统75%以上的质量上限。实践中请务必做这几件事表格和代码块要单独处理PyPDFLoader会把表格拍平成文本行容易导致后续切分把表头和数据切开。我在生产环境会先用unstructured或pdfplumber提前识别表格区域整块保留不参与普通文本切分。不要用固定size切Markdown或HTML文档优先用RecursiveCharacterTextSplitter按标题层级来切保证每个chunk内部尽量是自包含的语义单元。Embedding模型优先选择中文效果好的模型或者用多语言模型。我在跨国项目里用过text-embedding-3-large中文语义匹配效果可接受如果数据特别垂直比如医疗术语很多建议在同一领域语料上做一次Embedding模型的对比测试别只看公开benchmark。另外pgvector索引必须设置为IVFFlat或HNSW否则数据量上去后检索会全表扫描百毫秒级的服务直接变成秒级。建索引代码如下CREATE INDEX ON enterprise_kb USING hnsw (embedding vector_cosine_ops);用HNSW索引后百万级向量的查询也能稳定在几十到几百毫秒。6. 工程上的出路不是二选一而是分层融合第五部分实现的是纯RAG。但正如我在决策框架里说的很多真实业务场景并不会刚好落在“全RAG”或“全长上下文”的极端区间。工程上更理智的做法是把两者看成可以组合的组件。这也是现在热门的“Agentic RAG”和混合架构背后真正的工程动机。6.1 路由层先判断问题类型再决定走哪条管道在我的客服项目里知识库既有开放式的产品咨询又有“帮我查下订单号PG2301的状态”这类事务性查询。前者适合走RAG检索文档后者应当直接查询数据库没必要再过一遍文档检索。我设计了一个简单的路由模块先用轻量级意图识别模型对大模型调用做分类不同意图指向不同处理管道。管道包括直连长上下文管道适合“分析这份合同的风险条款”这种单文档深度阅读任务。RAG管道适合“产品参数是什么”这类多文档问答。工具调用管道适合“查询订单状态”这种结构化数据操作。路由模块的核心价值在于它把两种方案放进了同一个系统里各司其职而不是逼用户二选一。6.2 用长上下文模型做Rerank把检索精度提升一个台阶RAG的检索阶段用向量相似度召回的Top-K并不总是“语义相关度排序的最优解”。有些问题里排名第三的片段可能才是正确答案而排名第一的片段只是用词相近。这时候可以引入一个Rerank环节把召回的8-10个片段拼进一个“长上下文”窗口让大模型自动挑出最相关的3-4个片段并压缩成最终上下文。这其实就是“用小窗口长上下文模型去做RAG管道里的精排”。我用这个方案解决了不少检索质量不稳定的问题成本增加不多但回答准确率有明显提升。实现时可以先用CrossEncoder或Cohere Rerank这类专用模型做精排效果不够再让大模型做深度阅读分析。6.3 Agentic RAG多步检索与自主决策有些复杂问题没法通过“一次检索、一次生成”解决。比如用户问“我们的智能客服系统本月的退款率怎么样这个月政策变化对它有什么影响吗”这个问题需要先检索退款率数据再检索政策变更文档然后将两者关联分析。单次RAG把两步信息割裂了。这就是Agentic RAG的价值用Agent控制检索流程可以有条件地多次调用检索工具并根据中间结果决定下一步动作。我在项目里用LangGraph做过一个版本定义三个节点第一步检索退款相关记录第二步检索政策变更文档第三步汇总生成。每个节点都有独立的检索工具Agent根据状态自行跳转。这种设计大幅提高了复杂问题的处理能力但代价是延迟和token消耗会上升不适合所有场景无脑用。LangGraph的流程核心大致如下from langgraph.graph import StateGraph, END class AgentState(TypedDict): question: str documents: list answer: str def retrieve_refund(state: AgentState): # 调用向量库检索退款相关内容 ... def retrieve_policy(state: AgentState): # 调用向量库检索政策变更内容 ... def generate(state: AgentState): # 汇总多路检索结果生成答案 ... graph StateGraph(AgentState) graph.add_node(retrieve_refund, retrieve_refund) graph.add_node(retrieve_policy, retrieve_policy) graph.add_node(generate, generate) graph.set_entry_point(retrieve_refund) graph.add_edge(retrieve_refund, retrieve_policy) graph.add_edge(retrieve_policy, generate) graph.add_edge(generate, END) app graph.compile()6.4 冷静看待“融合”不是越复杂越好虽然融合架构听起来很厉害但我必须泼盆冷水大多数业务场景根本用不上Agentic RAG。多节点检索会把单次问答的延迟从1秒拉到5秒以上token成本也同步上涨。我建议所有团队先跑通单轮RAG确认检索质量稳定、评估指标达标后再考虑要不要引入路由和Agent。把简单场景做复杂是技术债务最好的温床。7. 踩过的坑与给同样在做决定的你的一些建议最后分享几个我在RAG和长上下文选型项目中踩过的实操坑以及一些省时间的方法。第一不要盲目迷信“检索准确率”指标。我做客服项目初期把RAG的召回准确率优化到95%结果上线后发现用户满意度并没有显著提升。原因在于“检索准确率”只衡量Top-K相关性但客服问题的难点常常在于跨文档聚合信息单一文档召回指标体现不了这个维度。真正有用的指标是你的业务指标——首次解决率、用户对答案的点赞率、转人工率。做技术评估前先把业务指标定义清楚。第二做选型对比时必须用同一套评估数据和评估标准。我在一个项目里测试长上下文和RAG各自的准确率最初用的测试集只有20条人工构造的问题完全覆盖不了真实分布的复杂性。后来我把近三个月的真实用户问题抽样了500条人工标注答案后组成了评估集对比才真正有参考价值。没有足够大的评估集所谓的“哪个方案更好”都是刻舟求剑。第三RAG上线后只是开始不是结束。我在生产环境里遇到过几个高频问题新入库文档没触发索引更新、部分PDF解析后出现乱码、用户问法太口语化导致召回不准。后来我加了一个后台队列任务文档上传后自动解析、切分、向量化并定时同步向量库同时给检索模块增加“同义词扩展”和关键词提示词改写把口语问题改写成更适合向量检索的表述。这套补丁做完系统才算是真正步入稳定期。最后再分享一个小技巧。不管选了哪个方案一定在项目第一天就搭好“问题 标准答案 来源文档”的评估集并且每周新增真实案例进去。这两件事坚持三个月你就有了一个比任何公开benchmark都靠谱的专属评估体系。有了这个体系长上下文模型新版本上线时你会立刻知道该不该升级RAG的切分参数调整后是否有效也一目了然。选型和优化的决策最终拼的不是谁的架构新而是谁手里的评估工具更扎实。