GitHub周趋势2026W25 | Headroom 压缩 95% Token、NVIDIA 开源 AI Agent 安全扫描器、…
2026-07-28
2026-07-28 0
核心主题:用免费、开源的技术栈搭建一套完整的、生产级的 RAG(检索增强生成)架构,以便把大语言模型(LLM)"接地"到你自己的私有知识库上。文中以完整的 Kubernetes 官方文档(PDF 版)作为示例知识库,逐步走过你会遇到的关键设计决策。
大语言模型很强大,但在被迫"猜测"时也出了名地不可靠:它们会以极高的自信给出回答,并以惊人的流畅度编造事实(幻觉)。RAG 的存在,正是为了用从你私有知识库中检索到的、可验证的数据来"接地"LLM 的输出,从而消除这种猜测。过去几年里,RAG 已经成熟为任何严肃 AI 系统的核心构件——从智能体框架到开发者副驾。
RAG 由两个独立的流程组成:
负责从知识库中抽取文本,并把它存成可检索的格式。这是 RAG 系统中最重要也最复杂的部分——这一步做不好,整个系统就废了。难点在于:可用的技术非常多,而你必须根据数据类型在大量配置之间做出选择。
数据变成可检索格式后,你需要:
两个流程就绪后,用户通过基于 LLM 的应用提问时,回答前会先被知识库中的相关信息所"增强"。
RAG 有大量技巧,但并不意味着你需要或应该全用上。也不存在对所有数据都可靠的"完美设计"。架构决策最重要的驱动因素永远是你的数据本身,以及你希望它如何被消费。要先问清楚:
这是第一个也是最重要的决策,其余一切都由它衍生。
搜索引擎多年来一直使用关键词检索。查询中的词/词元被抽取出来,与知识库中每篇文档的词进行匹配。匹配通过稀疏向量完成——基于词表为查询或文档中的每个词建立索引。
匹配方法可以从简单词频,到更复杂的 BM25(Best Match 25)。BM25 用在整个语料上计算的**逆文档频率(IDF)**给词元赋权,降低那些在大量文档中频繁出现的词带来的噪声,从而给出更可靠的匹配。大多数全文数据库(如 Lucene、ElasticSearch)都使用 BM25 的某种变体。
如果查询中的确切词不在知识库里,关键词检索就会失效。嵌入(Embedding)通过把文本表示成数值向量来解决这个问题。嵌入由专门的机器学习模型生成,能够捕捉词的语义含义。
之所以叫稠密向量,是因为模型对每段文本生成的向量都具有相同的维度数。语义相近的词在向量空间中彼此更接近。检索时使用余弦相似度(Cosine Similarity)来查找语义相近的文本,因此这类检索常被称为语义检索(Semantic Search)。向量数据库专为存储数值向量并原生支持余弦相似度检索;关系型数据库(如 PostgreSQL、SQL Server)也开始原生支持向量。
稠密向量与稀疏向量检索可以合并成混合检索,往往能得到最好的效果。
但开箱即用支持混合检索的数据库不多,通常需要复杂的自定义工作来融合不同的检索结果。
本案例采用混合检索,因为它在 Kubernetes 文档上最可能表现最佳。权重设置为:
这是 RAG 中常用的比例。
选数据库时要考虑:
支持混合检索的数据库有几个。BM25 比较麻烦,因为它需要维护跨所有文本的词频索引,所以建议选一个能替你做这件事的向量库。
Qdrant 能同时存储稠密向量和稀疏向量,并对两者执行混合查询,结果用 倒数排名融合(Reciprocal Rank Fusion, RRF) 合并——这是 RAG 的标准做法。Qdrant 开源、可在 Docker 中运行、托管极其简单,且对 .NET 支持出色(原作者使用 .NET)。
其他支持混合检索的优秀选项:Weaviate、Pinecone、Milvus。
确定要做混合检索后,摄取管线就需要同时生成并存储稠密向量嵌入和稀疏向量。
Kubernetes 源文档相当长(RAG 知识库很常见),这对检索策略尤其是基于嵌入的策略不友好。嵌入试图提炼文本的语义含义,但长文本会覆盖很多不同主题。嵌入在语义内聚良好的较小文本块上效果最好。而且我们也很难把整篇长文档塞进 LLM 上下文——我们想根据用户查询取最相关的部分。RAG 系统通过在生成嵌入和存储前把文档**切块(Chunking)**来解决这个问题。
标准摄取管线的关键步骤:
第一步是从源文档中抽取文本。任何可用于检索或丰富回答的元数据也应一并抽取,例如文档名、页码、摘要。
如果文档有明确结构并被拆成章节,最好按章节抽取文本。这有助于后续切块——可以保证一个块不跨越多个章节。
章节按页存储,便于之后拼接还原并理解块属于哪些页。作者通常把子章节扁平化成一个"章节路径(Section Path)",并指定结构层级应嵌套多深。
由于文档布局/结构各异,通常需要多种抽取策略。示例仓库提供了:
Kubernetes 文档的标题格式非常一致,因此 FormatBasedExtractor 完美适用。下图展示了一个基于标题格式抽取出的章节与子章节的示例。
把文档抽取成章节/子章节后,你已经完成了高效切块的一半。下一步是把过长的章节切成更小的块,以生成高质量嵌入。
块长度(Chunk Length)
块切分点(Chunk Breaks)
块重叠(Chunk Overlap)
针对 Kubernetes 文档中一些较大的章节,作者发现以下配置效果好:
给块附加额外元数据有两个目的:
存哪些元数据取决于你的数据,示例:
嵌入由所选嵌入模型基于块文本生成。如果有助于语义相关性,也可以把源文档名和/或章节路径加入嵌入文本。
模型选择很多,不同模型对不同数据类型表现各异。更强的模型通常产出维度更高的向量,但需要更多算力和 GPU。
作者选用 BAAI/bge-small-en-v1.5,因为它足够小、能在 CPU 上较快运行,产出 384 维 向量。通过一个 Python FastAPI 应用托管 Hugging Face 模型。
稀疏向量只包含块中出现词项的词频。不存词项文本,而是给每个词项分配整数 ID。两种分配方式:
作者一般偏好简单哈希,避免维护词表。像 xxHash 这样的算法极快,且几乎不会发生哈希冲突。
如何运行、多久运行摄取管线取决于数据和场景:
大部分决策和复杂度都在摄取阶段解决了。接下来只需一个根据用户提示检索块的流程。
选 Qdrant 是因为它开箱即用地同时执行 BM25 关键词检索和嵌入余弦相似度检索。建议给混合检索的不同策略加权,RAG 常用:
嵌入捕捉语义、通常表现最好,所以一般最依赖它。但理想权重取决于你的数据以及关键词查询是否非常重要。
检索前,先把用户查询走一遍摄取管线中的部分步骤:
检索时应返回多个块,因为可能有多个块匹配查询——这通常叫 "Top k" 结果。Top k 取 5–10 通常够用,取决于数据和切块策略。块越小,通常需要越大的 Top k。
这套做法不错但并非万无一失。混合检索不完美,尤其当用户查询很短时。可以在检索之后叠加一些技术来提升召回率和精度。
我们是按章节抽取并在章节内切块的,这意味着检索结果的相邻块极可能也相关(如果它们没被检索直接返回的话)。
推荐的第一个后续步骤是用相邻块丰富检索结果。一般取检索块前后 1–2 个索引 内的块效果好。这正是为什么要把文档名、块索引、块总数、章节路径存为元数据——以便高效查询相邻块。
如果向量库支持索引(Qdrant 支持),应在这些字段上建索引,以高效查询相邻块。
检索常会取到完全不相关的块,关键词检索尤其严重。重排序可按相关性对结果排序并过滤掉不相关的块。一个强力技巧:检索时返回 Top k 的 2–3 倍 结果,再用重排序器筛到最相关的 Top k。
示例(Top k = 5):
这种方式能显著提升检索精度。
重排序器是接受一对文本作为输入、输出相关性分数的专用模型。把每个检索结果连同用户查询喂给重排序器即可。还可以用相关性分数设定一个最低相关度阈值,而不是只取 Top k。
交叉编码器(Cross Encoders):专为相关性打分训练的机器学习模型,处理文本比 LLM 快得多。更高质量的重排序器在生产系统中通常仍需 GPU。本例用免费开源的 BAAI/bge-reranker-base,表现不错且在 CPU 上够快,同样通过 Python FastAPI 托管 Hugging Face 模型。
LLM 重排序(LLM Reranking):目前云托管的重排序模型不多,另一种方式是用 LLM 配合定制的系统消息(System message)来重排序。建议用轻量 LLM(如 GPT-4.1 Mini),以免显著拖慢 RAG 系统的延迟。原文为此提供了一个可用于 LLM 重排序的系统消息模板(见原作者 GitHub 仓库)。
检索搞定后,剩下的就是把块送进 LLM 做总结。
上下文格式(Context Format):如果 LLM 支持,注入块的最佳方式是作为工具消息(Tool message)。在系统消息中加入 LLM 应如何解释检索结果、以及找不到匹配时如何反应的规则与护栏。
清晰的角色划分:
工具消息中要包含你希望 LLM 引用的任何元数据,连同块内容一起。用户消息示例:"Pod 是如何被调度的?"(原文给出了 LLM 重排序系统消息、RAG 系统消息、RAG 工具上下文 JSON 三个模板片段,完整内容见原作者 GitHub 仓库。)
更易管理的方式是用编排器(Orchestrator)来生成工具调用和输出,例如 Semantic Kernel 或 LangChain。用编排器还有两个额外好处:
用编排器时,你只需关心系统消息和用户消息。编排器负责在需要时调用 RAG 检索,并把数据作为工具消息(通常是 JSON)加入上下文。
示例应用中,作者用 Semantic Kernel 插件集成了 OpenAI 风格的工具调用,以支持基于提示的索引和 RAG 查询。
作者把 Semantic Kernel RAG 助手打包成了 CLI 控制台应用,简单场景够用。若要面向更广泛的用户,最好在上面写个小的 Web UI(如 React 或 Angular)。
每个服务(重排序器、嵌入器等)作为独立进程运行。独立托管每个服务可以独立扩缩,并为各服务使用不同的基础设施或资源配置。例如重排序器可能很吃资源,需要扩容到大量副本;而 LLM 很可能需要在 GPU 基础设施上运行。
容器非常适合这类进程——把各服务隔离打包成容器镜像,在不同托管环境中获得可靠运行。推荐用容器编排器以便快速扩缩,Kubernetes 就很理想。
示例方案全用开源技术拼成,因此你可以全部自托管,或在 CPU 上本地运行。但作者一般不推荐走纯自托管路线,除非你公司有重大数据风险且不接受云服务。要注意,大多数云服务(如 Azure OpenAI)面向企业用途且完全无状态——即不存储你提示或回答中的任何数据。
混合方式也可行:把较轻量的模型自托管。嵌入和重排序模型通常不像 LLM 那么吃算力。理想情况下,生成用的 LLM 需要 GPU 才能可靠运行,而在这方面云服务通常比自建基础设施便宜得多。
市面上有很多通用的开箱即用 RAG 方案,但自建架构能让一切都贴合你特定的数据需求,往往带来更优的召回率和精度。即便你决定用外部 RAG 服务,这里讨论的很多技术(不同切块策略、重排序等)也能与之协同使用。
完整代码见原作者 GitHub:matt-bentley/LLM-RAG-Architecture。
| 决策点 | 推荐值 / 选择 |
|---|---|
| 检索方式 | 混合检索(Hybrid Search) |
| 混合权重 | 稠密 70% / 稀疏 30% |
| 结果融合 | 倒数排名融合(RRF) |
| 向量数据库 | Qdrant(备选:Weaviate / Pinecone / Milvus) |
| 块长度 | 一般 200–300 词元;复杂文档可到 600;K8s 案例用 400 |
| 块重叠 | 10–20%;K8s 案例用 50 词元 |
| 嵌入模型 | BAAI/bge-small-en-v1.5(384 维,CPU 友好) |
| 稀疏向量词项 ID | 偏好哈希(如 xxHash),免维护词表 |
| Top k | 5–10(块越小,k 越大) |
| 重排序召回 | 检索 Top k 的 2–3 倍,再筛到 Top k |
| 重排序模型 | BAAI/bge-reranker-base(交叉编码器)或轻量 LLM(如 GPT-4.1 Mini) |
| 相邻块扩展 | 前后 1–2 个索引 |
| 编排器 | Semantic Kernel / LangChain |
| 生成 LLM | 纯 RAG 用便宜模型;需工具调用用 Llama 3.2 3B 等 |
| 部署 | 各服务独立容器化,用 Kubernetes 编排 |