ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RAGless:从RAG到零LLM API运行时成本的知识库检索新思路

RAGless:从RAG到零LLM API运行时成本的知识库检索新思路 从 RAG 到 RAGless这个词最近在检索增强生成这个圈子里逐渐热了起来。我在浏览 Hacker News 时看到一个很有意思的项目标题Show HN: RAGless – similar to RAG, but $0 LLM API costs at runtime。第一反应是怀疑第二反应是觉得这个视角很值得展开聊聊。长期做知识库问答、客服机器人、内部文档检索的同学应该都有体会RAG 本身并不难搭难的是 runtime 阶段的 LLM API 费用像流水一样出去尤其当知识库复杂、用户问题多、单次上下文又很长的时候账单会迅速变得不可控。RAGless 这个名字想表达的并不是“不需要大模型”也不是“比 RAG 更聪明”而是把成本发生的位置做了一次关键迁移尽量把开销留在离线和索引阶段让 runtime 阶段不再依赖外部 LLM API。这篇文章我会围绕 RAGless 这个思路做一次系统的拆解。我们会先理清 RAG 到底贵在哪再讨论 RAGless 的实现原理与适用边界最后带大家用本地向量检索的方式落地一个最小可运行 Demo。通过这个 Demo哪怕你不调用任何商用 LLM API也可以让用户通过自然语言查到自己知识库里的准确答案。适合正在做知识库问答、刚接触 RAG、或者被 LLM API 账单困扰的开发者阅读。1. RAG 与 RAGless先搞清楚两个概念1.1 传统 RAG 的工作流程RAG 的全称是 Retrieval-Augmented Generation也就是检索增强生成。它的核心目的是让大语言模型在回答问题时能够参考外部知识库中检索出来的内容而不是只依赖模型自身的参数记忆。一个标准 RAG 流程通常包含四个阶段。第一阶段是文档预处理。我们需要把 PDF、Word、Markdown、数据库表等不同来源的文档读出来做清洗、段落切分去掉页眉页脚、表格噪声等无关内容。第二阶段是索引构建。将切分好的文本块通过 Embedding 模型转化为向量然后写入向量数据库例如 Milvus、Qdrant、Elasticsearch、pgvector或者内存中的 FAISS 索引。第三阶段是查询检索。当用户输入问题时我们需要先把问题也转成向量然后去向量库或索引中做相似度搜索召回与问题最相关的一批文档片段。第四阶段是增强生成。把召回的片段、用户问题、系统提示词一起拼进 Prompt送给 LLM由 LLM 总结生成一段自然语言回答。RAG 之所以受欢迎是因为它比直接使用大模型多了一个“外部记忆”通道回答更有依据相对不容易胡编乱造知识也能及时更新。所以无论是个人知识库、企业客服机器人还是辅助研发文档搜索大家都会优先考虑这套方案。1.2 RAG 的成本到底花在了哪里很多教程里只会告诉你 RAG 能够提高回答准确性却很少明确告诉你传统 RAG 的 runtime 阶段也就是用户每发起一次提问时都包含一次甚至多次 LLM API 调用。举个例子用户问了一句“我们公司请假流程是什么”系统先要调用向量检索拿到 5 个片段然后把问题、片段、系统提示词拼成一个很长的 Prompt发给 LLM。LLM 返回结果以后还需要把这 5 个片段一起计入输入 token。假设平均每个片段 300 token5 个片段就是 1500 token再加上指令、历史记录单次请求的输入可能接近 2200 token输出可能又是几百 token。这个成本并不仅仅体现在单价上它还会被以下因素放大。第一用户量和提问次数会持续增加成本线性甚至非线性增长。第二Context 越长输入 token 越多成本越高。第三一旦引入 Agent、ReAct、多轮规划等机制一次用户提问可能触发好几次 LLM API 调用成本成倍上涨。第四LLM API 的调用还有延迟、频控、网络抖动的问题高并发下体验不稳定。所以当你看到 RAGless 标题中$0 LLM API costs at runtime这几个字时应该已经明白它是在针对以上这些痛点做文章不否认离线构建有成本不否认场景有限制但希望在运行时把外部 API 调用请求降为 0。1.3 RAGless 的核心思想把“生成答案”改成“定位答案”如果你只是想从知识库里找到一个明确的事实比如“项目上线时间”“服务器地址”“报销标准”其实并不需要让大模型现场写一篇小作文。你真正需要的是把问题定位到知识库中最相关的某一段或某几段内容然后原样呈现给用户。这就是 RAGless 的核心思想查询时不调用 LLM 来重新表达和生成而是直接通过检索系统返回最匹配的原文片段让用户在原文中找到答案。用一句话说RAG 是“检索 阅读 组织 生成答案”RAGless 是“检索 定位 返回原文片段”。这是不是完全没有 LLM 参与呢也不一定。在离线阶段完全可以借助 LLM 对文档做摘要、提炼关键词、生成别名、构建知识图谱等。只不过这些成本一次性发生不会因为你每天多问了 1 万次而持续增加。这就把“查询成本”替换成“索引成本”把“按次付费”变成了“固定投入”。1.4 RAGless 的适用与不适用场景RAGless 并不适合所有场景但它适合很大一部分真实业务。先说适合的场景。企业制度文档查询“年假怎么休”“报销限额是多少”“客户归属如何判定”答案本来就在制度原文中。产品帮助文档 / FAQ用户问“如何重置密码”直接把操作步骤对应的原文片段返回效果非常直接。法律、医疗、金融等强合规领域只返回原文不额外生成衍生内容反而更可控。那不适合的场景有哪些呢需要跨文章、跨片段进行多跳推理的问题比如“A 部门规定和 B 部门规定冲突时怎么办”。需要把多个信息汇总后改写为一段综述的问题比如“分析一下近三个季度销售额变化的原因”。用户缺乏阅读耐心或者非技术人员需要口语化答案的场景。理解这一点非常重要因为它决定了这个思路能走多远也避免我们看到一个新概念后就盲目替换自己已经做好的整套系统。2. 为什么运行时 LLM API 成本值得被单独讨论2.1 一次问答的隐性成本如何计算很多刚开始做 RAG 的开发者在自测阶段不会觉得贵因为一天可能只测试几十次。可一旦系统上线或者做成内部工具服务上百个员工问题量会迅速上升。假设我们做一个非常粗略的计算模型用户每天提问 10000 次每次请求上下文包含若干文档片段折合输入约 2000 token输出长度约 300 token那么单日 token 消耗约为 2000 万输入 300 万输出。把这个量换算成收费 API 的计费不同模型价格差异很大你只需要把上面的 token 数乘以每千 token 单价就能算出可观的月账单。更麻烦的是多轮对话。为了保持上下文一致许多 RAG 应用会把用户历史聊天记录也一并发送到 API这让单次输入的 token 数越来越膨胀。加上检索到的片段本身通常没有做太多剪枝用户问第三轮、第四轮时Prompt 已经变得又长又臃肿而大部分历史内容与当前问题没有直接关系属于无效支出。相对而言如果把运行时方案换成“向量检索 原文片段返回”成本主要落在本地的 CPU/GPU 计算与向量相似度计算上。没有 API 调用自然也就没有 token 账单、没有并发超限、没有外网链路延迟。2.2 除了钱还有延迟、稳定性与隐私“runtime 零 LLM API 成本”带来的另一个好处是延迟可控。调用外部大模型 API 通常需要几百毫秒到几秒不等受网络状况和模型负载影响。向量检索则更快在小规模数据集中一次本地向量检索可能只需要几十毫秒。对于知识库条目比较固定、答案要求即时返回的场景这种体验差异很重要。稳定性也一样重要。商用 API 有频控、限流、服务故障、模型下线的风险。你在本地做向量检索只要索引没坏、服务能启动查询性能就是稳定可预期的。还有一个不可忽视的点是数据安全。很多企业知识库内容很敏感不适合发给外部 API。RAGless 如果只靠本地向量化与本地检索那么用户问题与文档内容都可以不出内网。它在隐私合规上显然更加友好。2.3 从 RAG、Agentic RAG 到 RAGless 的路线变化如果观察 RAG 的发展趋势你会发现今年的热点其实有两条路线。一条是 Agentic RAG也就是让 Agent 自主规划检索策略决定先查哪个库、拆解成几个子问题、调用哪类工具然后多轮迭代。这条路能处理更复杂的问题但 token 消耗和延迟也成倍增加。另一条就是 RAGless 这种“降本路线”。它把能离线做成的事情尽量离线完成把 runtime 阶段需要实时调用的智能能力降到最低。它不追求用一次昂贵的大模型调用解决所有问题而是承认大量用户真实问题其实都是“查找类问题”。所以RAGless 并不是 Agentic RAG 的反义词也不是要否定大模型生成的潜力。它是一种适合固定知识域、高频查询场景的工程权衡方案。3. RAGless 的核心原理拆解3.1 离线阶段把知识库处理成“可检索的答案片段”既然 runtime 不再有大模型帮你总结归纳那么离线阶段就更需要精心设计文档结构。核心目标之一是让知识库中的文本切块尽可能独立成“一个可作答的片段”。举一个反例如果文档以连续三段长文存储检索模块命中之后返回的是整整三千字用户根本看不清答案。切分策略要考虑句子边界、段落边界、标题层级还要尽可能避免把语义相关的上下文切开。所以在 RAGless 中文档切分不是可有可无的预处理而是决定检索质量的基础设施。更进阶的做法是在切分前用规则或模型给文档生成一个结构化摘要或者把每个片段附上来源、标题、上下文档信息。这样检索返回的不只是“一段文字”而是一个带来源的答案卡片。3.2 索引阶段本地 Embedding 与多维索引如果希望用户用自然语言检索那还是需要把文本转换成向量。向量模型可以完全运行在本地或内网例如使用开源的 Sentence-BERT 系列模型。这一步不需要调用外部 LLM API。在数据规模可控的情况下我们甚至可以直接把向量存入内存使用 NumPy 做矩阵内积。当数据量达到百万级时再考虑 FAISS、Milvus、Qdrant 等专业索引。除了向量索引还可以同时构建倒排索引用于关键词检索这样后续可以做向量与关键词的混合排序弥补向量模型对专业名词、缩略语可能不敏感的问题。3.3 查询阶段双路召回取代 LLM 生成用户输入问题之后系统会把问题向量化然后与索引中的文档向量做相似度比对得到 top-k 候选。这一步的关键在于 Query 的理解质量如果只做向量检索可能出现“语义相近但关键词不匹配”的召回缺失。所以很多工程实现会采用双路召回一路做 Dense Vector Search也就是向量检索另一路做稀疏检索如 BM25。最后在重排阶段合并两路结果。由于不需要把候选片段发给大模型让模型阅读后重新组织语言整个查询链路就是“向量化 Query 相似度计算 排序 结果格式化”。哪怕每天 10 万次查询主要开销也只是计算资源而不是 token。3.4 常见误区RAGless 不是“不用 LLM”理解 RAGless 的时候很容易走极端这是需要特别注意的。RAGless 不是反对 LLM也不是说所有 LLM 都完全没有参与。它真正反对的是“用户在运行时每次提问都必须调用一次 LLM API”这种按次付费的模式。在离线阶段LLM 依然很有价值。比如对文档做纠错和改写让文本更适合检索自动提取文档关键词和同义词增强召回将长文档拆成结构化问答对识别文档中的实体、时间、人物关系补充到索引中为文档片段生成摘要作为检索结果的卡标题。这些任务可以批量执行也可以定时增量执行成本是可控的。一次预计算多次使用这是把“智能能力”用在刀刃上的做法。4. 环境准备与原型方案设计4.1 方案整体架构在实际动手写代码前我们先明确一下这个最小 Demo 的整体链路。它由下面几部分组成文档源存放少量测试文档用来模拟企业知识库。文本加载器读取 JSON 格式的文档内容并切分为多个片段。本地向量化模型将片段文本映射成固定长度的向量。向量索引模块将向量保存到一个矩阵中并提供相似度查询。检索查询入口把用户自然语言问题转化为向量返回最相关的几个片段。整体查询流程如下用户输入问题系统调用同一个本地模型生成问题向量系统计算问题向量与所有文档片段的向量余弦相似度系统按相似度从高到低排序返回前 k 个片段附带标题与文档编号用户直接阅读原文片段不需要再经过大模型生成。你会发现整个流程没有任何外部 API 调用所以从理论上说运行时不会产生 LLM API 费用。这个 Demo 规模很小不追求做完整搜索引擎但足以展示 RAGless 与标准 RAG 在运行机制上的差异。4.2 技术选型与版本说明我选择 Python 3.9 以上版本作为示例环境主要依赖如下sentence-transformers用于加载本地 Embedding 模型numpy用于存储向量矩阵并做相似度计算json和rePython 标准库用于数据读取和文本切分。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你之前没有安装sentence-transformers可以执行下面的命令安装pip install sentence-transformers numpy这里要提醒一下sentence-transformers第一次加载模型时会从模型仓库下载模型文件因此需要网络能够正常访问模型下载地址。如果网络环境不允许可以提前下载好模型文件并加载本地路径或者更换成你内网可访问的模型源。具体模型名称也需要根据实际环境调整。4.3 示例项目目录为了后文讲解清晰我们先把项目结构定义好ragless-demo/ ├── data/ │ └── docs.json ├── src/ │ ├── loader.py │ ├── embedder.py │ ├── search.py │ └── app.py └── requirements.txtdocs.json存放原始业务文档一条记录代表一篇文档。src/loader.py负责加载文档并切分片段。src/embedder.py负责本地向量化。src/search.py负责向量索引和 Top-K 检索。src/app.py提供命令行问答入口。5. 从零实现一个 RAGless 最小可运行 Demo5.1 准备测试文档我们先创建数据文件data/docs.json内容模拟一个企业知识库中的几条规范说明。这里我加入了一些与 RAG、文档管理相关的内容方便观察检索结果是否合理。{ documents: [ { id: doc-001, title: RAG 的基本流程, content: RAG 是检索增强生成的缩写通常由文档预处理、向量索引构建、用户问题检索和大模型生成四个阶段组成。它能够结合外部知识库中的文档片段帮助大模型回答更准确的问题。 }, { id: doc-002, title: RAG 的运行时成本, content: 在传统 RAG 中用户每次提问都会调用大模型 API。输入 token 包括检索到的文档片段、问答历史与系统提示词因此当用户量和上下文长度增长时运行时会话成本会显著增加。 }, { id: doc-003, title: RAGless 的设计思路, content: RAGless 是一种让运行时尽量不调用大模型 API 的检索方案。它通过高质量的文档切分和本地向量检索直接向用户返回原文片段将成本从按次付费的生成请求转化为固定的离线索引成本。 }, { id: doc-004, title: 向量检索与混合检索, content: 向量检索能够理解语义找到与问题意思相近的片段。混合检索则同时使用关键词检索和向量检索再通过重排融合结果适合处理专业名词多、缩略语多的场景。 }, { id: doc-005, title: 企业知识库的常见问答场景, content: 员工关心的问题往往比较明确例如请假流程、报销标准、服务器地址、项目上线时间等。这类问题的答案已经存在于制度文档中通过检索返回原文即可解答。 } ] }在真实项目里这个文件可能会替换为数据库中的文档表或者对象存储中保存的 Markdown / PDF 数据。这里的核心是给后续处理提供“标题 正文”结构。5.2 文档切分模块下面编写src/loader.py。它的职责是读取上面的 JSON 文件然后对长文本做简单切分。实际业务文档往往很长不能把整篇文档作为一个片段返回所以我们实现了一个按句子边界切分的小函数。# 文件路径ragless-demo/src/loader.py import json import re def load_documents(data_path): 加载 JSON 文档返回文档列表。 with open(data_path, r, encodingutf-8) as f: data json.load(f) return data[documents] def split_text(text, max_len120, overlap20): 按句子边界切分文本。 如果单个句子太长再按 max_len 长度截断。 使用 overlap 保留相邻片段之间的上下文。 # 按中文句号、问号、感叹号、分号、换行符分割句子 sentences re.split(r(?[。\n]), text) sentences [s.strip() for s in sentences if s.strip()] chunks [] current for sent in sentences: # 如果加入当前句后会超过 max_len先保存当前片段 if len(current) len(sent) max_len and current: chunks.append(current) # 截断时保留末尾 overlap 个字符避免上下文断裂 current current[-overlap:] if overlap 0 else current sent if current: chunks.append(current) # 如果某个句子本身过长按 max_len 硬切 final_chunks [] for chunk in chunks: while len(chunk) max_len: final_chunks.append(chunk[:max_len]) chunk chunk[max_len - overlap:] if overlap 0 else chunk[max_len:] if chunk: final_chunks.append(chunk) return final_chunks def build_chunks(documents): 将文档列表转换为带元信息的片段列表。 chunk_list [] for doc in documents: text doc[content] title doc[title] doc_id doc[id] parts split_text(text) for idx, part in enumerate(parts): chunk_list.append({ doc_id: doc_id, title: title, chunk_index: idx, text: part, }) return chunk_list这个模块将文档正文按句子粒度拆分。为什么要这么做呢因为你最终返回给用户的是一段可直接阅读的内容而不是把整篇文档都塞给用户。切分后的每个片段需要尽量保持语义完整片段之间增加少量重叠是为了避免检索时正好落在两个片段的边界上。5.3 本地 Embedding 与向量索引接下来编写src/embedder.py与src/search.py。这一步是整个 RAGless 方案的“智能基础设施”。我们需要使用一个本地向量化模型将所有片段转换成向量然后存进一个矩阵。# 文件路径ragless-demo/src/embedder.py from sentence_transformers import SentenceTransformer class LocalEmbedder: 本地向量化器不依赖任何 LLM API。 def __init__(self, model_nameparaphrase-multilingual-MiniLM-L12-v2): # 选择一个多语言或中文模型按实际环境调整 self.model SentenceTransformer(model_name) def embed_texts(self, texts): 将多个文本编码为向量并做 L2 归一化。 vectors self.model.encode( texts, normalize_embeddingsTrue, show_progress_barFalse, ) return vectors def embed_query(self, query): 将单个查询文本编码为向量。 vector self.model.encode( [query], normalize_embeddingsTrue, show_progress_barFalse, ) return vector[0]这里使用的是 Sentence-BERT 方案它把一整段句子映射为向量。之所以选择它是因为我们希望用户问题与文档片段之间具备语义可比性。比如用户问“每次问知识库问题是不是都要花钱”如果只用关键词索引很难匹配到“运行时成本”这一篇文档。而向量模型可以找到语义相近的文本。然后是src/search.py负责把片段向量组装成一个矩阵并提供 Top-K 相似度查询。# 文件路径ragless-demo/src/search.py import numpy as np class VectorSearchEngine: 简单的内存向量检索器。 def __init__(self, chunks, embedder): self.chunks chunks self.embedder embedder self.vectors None self.build_index() def build_index(self): texts [c[text] for c in self.chunks] vectors self.embedder.embed_texts(texts) self.vectors np.array(vectors) def search(self, query, top_k3): query_vec self.embedder.embed_query(query) # 向量已经归一化点积等价于余弦相似度 scores self.vectors query_vec top_indices np.argsort(scores)[::-1][:top_k] results [] for idx in top_indices: results.append({ score: float(scores[idx]), chunk: self.chunks[idx], }) return results这里的代码非常短但它已经完成了一个传统 RAG 中“检索”步骤的大部分工作。我们使用点积代替余弦相似度是因为每一行向量在 Embedding 阶段已经做了归一化点积结果天然落在 -1 到 1 之间数值越高表示越相似。5.4 命令行问答入口最后写src/app.py把所有模块串起来进入交互式循环。当用户输入一个问题时系统会直接在本地库中检索最相关片段然后输出结果。为了便于阅读我会把排序、格式化输出都放在这个入口文件中。# 文件路径ragless-demo/src/app.py import sys import os # 添加项目根目录到 sys.path方便直接运行 src/app.py sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from src.loader import load_documents, build_chunks from src.embedder import LocalEmbedder from src.search import VectorSearchEngine def main(): base_dir os.path.dirname(os.path.dirname(os.path.abspath(__file__))) data_path os.path.join(base_dir, data, docs.json) # 1. 加载文档并切分 documents load_documents(data_path) chunks build_chunks(documents) print(f文档数: {len(documents)}, 片段数: {len(chunks)}) # 2. 构建本地向量索引 print(正在加载本地向量模型并构建索引...) embedder LocalEmbedder() engine VectorSearchEngine(chunks, embedder) print(索引构建完成。输入 exit 退出。\n) # 3. 进入问答循环 while True: query input(请输入问题).strip() if query.lower() in {exit, quit, q}: break if not query: continue results engine.search(query, top_k3) print(\n 检索结果 ) for rank, r in enumerate(results, start1): chunk r[chunk] print(f\n[Top{rank}] 相似度: {r[score]:.4f}) print(f来源: {chunk[doc_id]} - {chunk[title]}) print(f片段: {chunk[text]}) print(\n\n) if __name__ __main__: main()在这个入口文件里我们没有调用任何外部大模型 API。用户问什么系统就检索什么最终返回的永远是知识库中的原文片段。你可以把这句话理解成 RAGless 和传统 RAG 最直观的差异传统 RAG 返回的是一段“根据检索内容生成的回答”RAGless 返回的是一段“和问题最匹配的知识库原文”。5.5 运行与验证完成代码后在项目根目录执行cd ragless-demo python src/app.py首次运行时sentence-transformers会下载模型文件所以需要耐心等待。如果下载失败可以检查网络环境或者手动下载模型后把 LocalEmbedder 中的模型名改成模型文件所在目录。运行后的输出大致如下文档数: 5, 片段数: 8 正在加载本地向量模型并构建索引... 索引构建完成。输入 exit 退出。 请输入问题RAGless 为什么可以降低 API 成本随后系统会返回与问题最相关的 Top3 片段。注意相似度分数会因模型迭代版本、文本切分方式而略有变化这里不写死具体数值。你拿到结果后可以对照我们的预期关于 RAGless 设计思路的 doc-003 片段应该出现在较前位置其次可能是 doc-002 关于传统 RAG 运行时成本的说明。这个测试虽然简单但它演示了一个完整闭环自然语言问题 → 本地向量化 → 知识库检索 → 返回来源与原文片段。在这一过程中不需要 API Key不需要记录 token 消耗也不存在按次计费。如果你希望尝试中文效果更合适的本地模型可以更换 Embedding 模型名称例如常见的bge-small-zh-v1.5或text2vec-base-chinese等。由于不同模型对语言和领域适配度不同建议用你自己的知识库数据进行验证后再做决定。6. 常见问题与排查清单RAGless 实现起来难度不大但真正放到业务中还是会遇到各种细节问题。下面整理了几类高频问题与排查思路。问题现象常见原因解决思路检索返回片段与问题不相关切分粒度不合理或文档本身没有覆盖问题调整切分窗口增加同义词与关键词扩展所有问题都返回同一篇文档文档主题太接近向量空间无法区分增加文档元信息约束引入 BM25 关键词召回用户提问是长句返回片段太短切分窗口过小语义被切断调大max_len或按段落标题维度切分模型加载失败或下载卡住网络受限、模型文件不完整预下载模型并加载本地路径检查文件是否完整线上向量矩阵占用内存过高文档片段总量太大切换为 FAISS、Milvus 或 Qdrant 等专业向量索引用户觉得原文不友好还是需要一句话答案场景不适合纯原文返回增加离线生成的摘要字段检索时把摘要作为展示文案既然写到这里我们再把其中几个典型问题展开看看。6.1 文档切分边界导致答案不完整这是最常见的问题。比如文档中写了“请假 3 天以内由部门负责人审批超过 3 天由 HR 总监审批”如果切分时把这句话拆成两个片段用户问“请假 4 天找谁审批”时检索系统可能只返回了前半句。解决方案不是简单把切分窗口调大而是采用结构化切分策略优先按 Markdown 标题层级、序号列表节点、表格行来切分而不是机械地按字数切。如果原文档是按条目组织的切分时尽量把一个完整条目保留在同一片段内。6.2 用户问法和文档写法差异很大向量模型虽然理解语义但并不是万能的。用户可能用口语问“请年假按什么规矩走”而文档里写的是“员工带薪年休假管理规范”。两者在字面上相差极大向量检索也可能匹配不到。工程上有几个常见补救方法使用混合检索在向量检索之外叠加 BM25 关键词检索扩充索引别名离线为每个片段生成可能被问到的同义说法建立用户查询日志记录没有命中的问题定期补充同义表达。RAGless 把成本压力转移到索引侧之后这些“离线笨功夫”反而更容易做因为不需要考虑每次运行时调用大模型的花费。6.3 纯原文返回但业务方想要一句话总结如果产品经理明确要求“让用户直接看到一句结论”那么可以考虑一种折中方案离线阶段为每个片段提前生成一个短摘要检索时先展示摘要用户点击后再展开原文。更进一步还可以为高频问题预生成问答对提前把“一个问题、一句答案、一段原文依据”绑定好。用户在 runtime 提问时只做“问题到预生成问答对”的检索仍然不需要调用外部 LLM API。6.4 想要把 LLM 保留在 runtime 但控制成本如果你的业务确实离不开运行时生成那么同样可以参考 RAGless 的降本思路先进行一次纯检索只有检索结果的置信度低于阈值时才调用 LLM API 做一次总结。这样的策略可以大幅削减调用次数而不是彻底禁止调用。从成本角度来说它已经比每次提问都让大模型生成要划算得多。7. 进阶把 RAGless 做得更像一个产品7.1 引入混合检索与重排只做向量检索在真实场景中往往不够。一个更稳定的结构是向量检索负责语义召回BM25 负责关键词召回两路结果合并后再通过一个重排模型或规则排序。对于规模不大的场景甚至可以用一个简单的线性加权公式。# 示意代码向量分数与关键词分数的线性融合 def hybrid_score(semantic_score, keyword_score, alpha0.7): return alpha * semantic_score (1 - alpha) * keyword_score这里的核心是可解释性如果用户输入一个公司内部缩写词比如“PO”向量模型可能不知道它代表“产品负责人”但 BM25 可以在文档中直接命中“PO”。两路互补能够明显提升召回质量。不同行业、不同知识库权重alpha也不同需要通过测试集去定。7.2 增量更新与索引构建流水线知识的有效期决定了 RAGless 系统的保鲜程度。上线之后你不可能每次都重建全量索引。建议采用增量流水线新文档入库后将内容写入消息队列后台任务重新切分、向量化并请求更新索引旧文档被修改时需要同步删除旧片段每天凌晨执行一次全量一致性检查。这种设计有一个明显优势用户可以容忍后台任务跑几分钟但很难接受每次问答都要等大模型慢慢生成。RAGless 把时间压力从查询链路转移到后台链路恰恰符合这类业务诉求。7.3 缓存同一类查询结果在传统 RAG 中即使用户问同样的问题每次也会调用一次 API因为生成结果带有随机性。RAGless 的检索结果通常非常稳定同一问题得到的文档向量也很接近。因此我们可以为高频问题建立缓存将问题做归一化处理计算其向量指纹再通过简单缓存直接返回结果连向量检索都可以省掉。这会把运行时成本进一步压低响应时间也能降到更低。7.4 评估检索质量而不是只看生成答案好不好RAG 项目的常规评估习惯是让用户看最终生成答案是否顺眼、是否正确。但 RAGless 没有生成环节所以我们要把评估焦点放到检索命中率上。建议建立一个小型测试集每个测试问题标注出“正确答案应该来自哪个文档的哪个片段”。然后计算以下指标RecallK前 K 个结果中是否包含正确答案片段命中平均排序位置返回片段与问题的时间/主题分布是否合理。有了这个测试集调参、换模型、改切分方式时都能有量化的对比依据而不是靠拍脑袋。8. 总结与学习路线这篇文章从 RAGless 这个项目标题切入拆解了它的核心思想把知识库问答的成本从“用户每次提问时的按次付费 API 调用”转移到“一次性的离线索引建设”。在运行时我们可以通过“本地向量化 向量检索 返回原文片段”来完成很大一部分知识库问答任务不依赖外部 LLM API。围绕这个思路我们实际完成了一个最小 Demo。它的数据量很小代码也很简单但已经包含了加载文档、切分片段、本地向量化、相似度检索、结果输出这几个关键模块。你也可以把data/docs.json替换成自己的 FAQ 表格或者在build_chunks中接入自己业务里存放 Markdown 文档的数据库让这套方案直接跑在真实数据上。接下来如果想继续深入可以按下面的路线学习。第一步理解向量检索原理。学习余弦相似度、向量索引、ANN 近邻搜索等基础概念。只有理解了向量空间里的“近似”是什么你才清楚为什么有些问题会召回失败有些召回结果又为什么异常可靠。第二步补充关键词检索与重排知识。RAGless 不是堆一个 Embedding 模型就完成的工作它更接近一个完整的搜索引擎切分、索引、召回、重排、缓存每个环节都会影响最终体验。第三步尝试在合适场景中替换传统 RAG 的生成环节。你可以把一个已经在调用 LLM API 的问答场景保留 A/B 对照组把服务层改成先检索原文如果用户反馈原文足够直接再考虑彻底切断 runtime 生成请求。第四步关注成本建模与评估。这篇文章多次强调“把成本放到离线阶段”但离线建设也需要算力、需要人力维护。只有在问题场景足够固定、内容边界足够清晰时RAGless 的收益才会最大化。如果这个思路对你有启发建议先不要急着换掉你的整套 RAG 系统而是准备一小部分边界明确的知识库做一个 RAGless 原型再用真实用户问题去对比检索效果、响应体验和账单差异。自己亲手跑一遍应该比任何理论分析都能帮你判断它终究适不适合你的项目。希望这篇教程能给你提供一些不同角度的思考。
返回列表