行业资讯
📅 2026/8/26 9:26:18
RAG+向量数据库+Embedding:从零搭建AI知识库实战指南
最近半年经常有读者问同一类问题我用 PostgreSQL 存了很多文档也想做一个“AI 知识库”让大模型回答我私有文档里的问题为什么直接丢给 ChatGPT 不行为什么网上都说要上向量数据库这东西到底解决什么问题其实这个问题的核心不在大模型本身而在“检索”这一步。大模型的知识是有截止时间的它不知道你公司内部的制度、你项目里的接口文档、你硬盘里的几百篇研究报告。想让大模型基于你自己的资料回答问题标准做法不是重新训练模型而是把资料切碎、变成向量、存进向量数据库再在提问时把最相关的内容捞出来拼进 Prompt 里交给模型。这条链路就是最近两年被反复提到的 RAGRetrieval-Augmented Generation检索增强生成。这篇文章会从零开始讲清楚整套链路什么是 Embedding什么是向量数据库语义搜索和关键词搜索到底差在哪以及怎么用几十行代码跑通一个最小可用的 RAG 系统。读完你会发现所谓 AI 知识库并没有那么神秘它的骨架就是“Embedding 向量数据库 大模型”三件套。1. 这篇文章真正要解决的问题先说结论向量数据库不是数据库领域的一次“炫技”而是搜索范式转变后的基础设施。过去十年互联网搜索、站内搜索、日志检索基本都被两类技术在统治关系型数据库的LIKE %关键词%以及全文搜索引擎的倒排索引。这类方案的本质是字符串匹配你搜“苹果手机”它找的是包含“苹果手机”这四个字的文档。但真实世界中同一个意思有无数种表达。你搜“怎么申请年假”文档里可能写的是“员工休假管理办法”“带薪休假规定”“请假流程”。“苹果手机”和“iPhone”在语义上完全等价但在字符串层面没有任何匹配关系。传统全文搜索搞不定这类问题。于是有了 Embedding 和向量检索把文本映射成高维空间里的坐标语义相近的句子在空间里距离也近。搜索时不再比较字符是否一样而是比较“意思”是否接近。向量数据库就是为这种检索方式设计的存储和计算引擎。它要解决三个问题存海量高维向量怎么高效存储。算几百上千万条向量怎么在毫秒级找到最相似的 Top K 条。管向量数据怎么跟业务元数据一起管理支持过滤、更新、删除、权限控制。如果你正在做 AI 知识库、智能客服、语义搜索、文档问答、代码检索推荐或者打算把大模型接入公司内部知识体系那么这套技术栈你绕不开。2. 核心概念向量、Embedding 与向量数据库2.1 什么是向量向量就是一串数字。在几何里它是一个有方向有长度的箭头在数据科学里它就是一个[0.1, 0.5, -0.3, ...]这样的数组。整个 AI 搜索体系的核心思想是把人类语言变成数字数组然后用数学运算代替语义比较。一段文本变成向量后两个文本“语义相似”在数学上就表现为两个向量“距离近”。常用距离包括余弦相似度关注方向是否一致对向量长度不敏感是文本检索最常用的度量。欧氏距离关注空间中的直线距离。内积在归一化处理后与余弦相似度等价。2.2 什么是 EmbeddingEmbedding 是把文本、图片、音频等非结构化数据转换成数值向量的过程。生成这段向量的模型称为 Embedding 模型。用一个类比来理解假设你把世界上所有物品按“颜色、大小、用途、材质”四个维度打分那“苹果”和“梨子”的分数非常接近而“苹果”和“石头”差异很大。Embedding 模型做的事情类似只不过维度不是 4 维而是几百到几千维。常见的开源和商用 Embedding 模型包括开源BGE 系列例如 bge-m3、bge-large-zh、BCE、M3E、GTE 系列。商用OpenAI 的 text-embedding-3-small / text-embedding-3-large阿里云百炼的 text-embedding-v 系列等。不同模型的能力差异主要体现在对中文的支持程度、对长文本的编码能力、语义区分度、以及 embedding 维度大小。维度越大通常表示能力越强但存储和计算成本也越高。这里有个容易踩的坑不同模型生成的向量不能混用。你用模型 A 生成的向量建立的索引用模型 B 生成的向量去查询得到的结果基本不可用。原因很简单不同模型的向量空间完全不同坐标系都不一样谈何距离。2.3 什么是向量数据库向量数据库是一种专门处理向量数据的数据库。它除了像传统数据库一样支持增删改查还提供了高效的相似度检索能力。它跟传统数据库最核心的区别在索引结构上。传统数据库用 B 树、倒排索引向量数据库通常用 HNSWHierarchical Navigable Small World、IVFInverted File Index、PQProduct Quantization等近似最近邻算法。这些算法的目的只有一个在十亿级数据量下用可接受的精度损失把检索耗时压到几十毫秒。常用的向量数据库有Milvus / Zilliz Cloud开源生态活跃支持分布式适合大规模生产环境。QdrantRust 实现性能好API 现代。Weaviate自带模块化能力GraphQL 接口适合知识图谱结合。Chroma轻量级开发体验友好适合原型项目和个人知识库。pgvectorPostgreSQL 的扩展如果项目已经用了 PostgreSQL可以低成本起步。选型的核心不是“哪个最强”而是“你处在哪个阶段”。做原型验证Chroma 足够做生产系统Milvus 或 Qdrant 更靠谱团队已经有 PostgreSQL 运维能力且数据量可控pgvector 是性价比之选。3. 语义搜索和傳統搜索到底差在哪上一节说了向量数据库是“为语义搜索设计的”但语义搜索不是简单的“用向量数据库换个搜索后端”。它改变的是一整套检索流程。3.1 三种搜索方式的对比搜索方式匹配逻辑典型工具优点缺点关键词搜索字面匹配MySQL LIKE、Elasticsearch精准、快、实现简单无法处理同义词、语义变体全文检索分词 倒排索引Elasticsearch、Solr支持分词、模糊匹配对语义理解有限语义搜索向量相似度Milvus、Qdrant、Chroma理解语义、支持跨语言依赖模型质量、有误召回风险举一个更具体的例子。假设你的知识库里有这样一条文档“员工累计工作满一年后次月起享有 5 天带薪年假。”用传统关键词搜索搜“年假多少天”分词后的“年假”和“多少天”未必能和文档中的“满一年”“带薪年假”建立关联如果搜索词是“休假几天”传统搜索基本无能为力。语义搜索则不同它会把“休假几天”与“年假多少天”视为同一语义空间中的邻近点从而找到这条文档。3.2 为什么说语义搜索的核心是语义解析而非字符串匹配很多人误以为语义搜索是“更智能的搜索”其实它的底层机制并不神秘。它靠的是 Embedding 模型把文本编码成向量让语义相近的句子在向量空间中聚集。搜索引擎本身没有“理解”任何东西它只是做了一次向量距离计算。所谓“核心机制是语义解析而非字符串匹配”说的是这层意思搜索不再依赖字符的重合而是依赖模型对文本意义的编码能力。模型编码得好不好直接决定了检索效果。这也是为什么实际项目中除了 embed 原始文本还会配合**文本切块策略chunking和重排rerank**机制来提升检索质量。切块决定了检索的最小单元重排在召回之后再做一次精细排序。4. RAG为什么“知识库 向量数据库”能增强大模型4.1 大模型的局限大模型本身有两个问题知识截断模型训练数据有截止时间更新的知识它不知道。无法获取私有数据公司内部文档、个人笔记、未公开资料模型不可能提前学到。如果强行让模型回答私有域问题它只能靠训练数据里的相似文本“编”这就产生了幻觉。根本原因不是模型不够聪明而是它没有答案的上下文。4.2 RAG 的解决思路RAG 的思路是在模型回答问题之前先从一个外部知识库中检索出相关的片段把这些片段作为“参考资料”拼进 Prompt再让模型根据参考资料作答。一个标准的 RAG 流程分两段离线索引阶段写加载文档。将文档切分成小块chunk。每个 chunk 调用 Embedding 模型生成向量。将向量和原始文本、元数据一起存入向量数据库。在线查询阶段读用户输入问题。把问题用同一个 Embedding 模型转成向量。在向量数据库中检索最相似的 Top K 个 chunk。把问题和这 K 个 chunk 一起组装成 Prompt。大模型根据 Prompt 生成答案。这个流程中向量数据库承担的是“外部记忆”的角色。大模型只负责“理解问题、组织语言、基于资料作答”。所以 RAG 也被称为“给大模型外挂一个可实时更新的知识库”。4.3 RAG 的架构图式文字描述可能还不够直观。可以在本地目录中按下面结构组织 RAG 项目rag-demo/ ├── data/ │ └── source_docs/ # 原始文档 ├── scripts/ │ ├── build_index.py # 离线索引脚本 │ └── query.py # 查询脚本 ├── utils/ │ ├── chunker.py # 切块工具 │ └── embedding_client.py # embedding 封装 └── requirements.txt这个结构没有引入任何重框架适合理解和起步。后面第 6 节的代码示例也会遵循这个思路。5. 环境准备与前置条件在写代码之前先把运行环境说明白。本节示例用 Python 完成选择 Python 是因为生态最全对大模型和向量处理的封装最多。5.1 运行环境操作系统Windows / macOS / Linux 均可本文代码不依赖特定系统。Python建议 3.10 及以上版本。至少要有一个 Embedding 模型可用。关于 Embedding 模型有两种接入方式调用云端服务如 OpenAI、阿里云百炼的 Embedding API。这种方式简单不需要本地 GPU但需要 API Key 和网络。本地运行开源模型如 BGE、M3E 系列通过sentence-transformers或FlagEmbedding加载。需要一定内存和 CPU 推理能力但对小规模项目完全够用。为了保证流程完整本文以本地开源模型为例用BAAI/bge-small-zh-v1.5。这个模型体积小、中文效果好适合做示例。如果你用的是 OpenAI 或阿里云 API可以把 Embedding 实现换成对应 SDK。5.2 安装依赖创建虚拟环境并安装依赖python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install sentence-transformers chromadb依赖说明sentence-transformers加载本地 Embedding 模型并生成向量。chromadb轻量级向量数据库开箱即用无需单独启动服务。如果你希望用 Milvus 做生产级演示可以在后面单独安装pymilvus。本文先用 Chroma 把链路跑通。如果你的网络环境无法下载 HuggingFace 模型可以用 ModelScope魔搭下载 BGE 模型本地保存后通过/path/to/model加载本文重点演示通用思路。5.3 验证环境写一个最小脚本验证 embedding 和 chroma 能否正常工作# 文件路径scripts/check_env.py from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) vec model.encode(你好世界) print(vec.shape) print(vec[:5])如果输出类似(512,) [ 0.01581024 -0.01984724 0.02122799 -0.02541005 0.03373944]说明环境就绪向量维度是 512。如果下载模型缓慢检查网络节点或考虑使用 HuggingFace 镜像站。也可以预先用huggingface-cli将模型下载到本地目录然后传绝对路径给SentenceTransformer。6. 完整示例用 Chroma 实现一个最小 RAG 系统这一节实现一个完整的知识库问答链路。知识源用一个小型百科文本集合代替最后完成“提问 - 检索 - 回答”的完整闭环。如果你已经准备好自己的文档PDF、Word、TXT可以跳到 6.3 节参考如何扩展为文档加载。6.1 准备知识库数据先在data/source_docs/下创建几个 TXT 文件模拟企业内部知识文档。文件一员工休假制度.txt员工累计工作满一年后次月起享有5天带薪年假。工作满十年后年假天数增加为10天。 每年1月1日为本年度年假重置日未休完的年假原则上不跨年结转。 员工请假需提前3个工作日通过OA系统提交申请由直属上级审批。 病假需提供医院开具的证明产假、陪产假按国家及地方法规执行。文件二研发环境部署手册.txt新项目统一使用 Docker Compose 管理本地开发环境。 生产环境通过 CI 流水线构建镜像推送到私有镜像仓库再由运维平台滚动发布。 所有服务必须配置健康检查接口 /healthz否则无法接入负载均衡。 配置信息统一放在配置中心禁止将密钥提交到代码仓库。文件三客户反馈处理流程.txt客户反馈优先通过工单系统登记工单分为咨询、故障、投诉三类。 咨询类工单要求在4小时内响应故障类工单按紧急程度分为P0/P1/P2三级。 P0故障需在10分钟内启动应急响应并同步通知研发负责人。 客户满意度评分低于4分的工单需在T1日进行回访。这些内容覆盖面广便于后续演示语义检索的效果。6.2 编写 Embedding 封装把所有 embedding 逻辑放到一个文件里方便其他脚本引用。# 文件路径utils/embedding_client.py from sentence_transformers import SentenceTransformer class EmbeddingClient: def __init__(self, model_name: str BAAI/bge-small-zh-v1.5): self.model SentenceTransformer(model_name) def encode(self, texts: list[str]): if isinstance(texts, str): texts [texts] # normalize_embeddings 让余弦相似度计算更稳定 return self.model.encode( texts, normalize_embeddingsTrue, show_progress_barFalse ).tolist()这里有一个值得注意的细节normalize_embeddingsTrue会把向量归一化为单位向量。归一化后内积、余弦相似度、欧氏距离在排序结果上是等价的很多向量数据库默认按内积计算能减少很多精度问题。6.3 切块与索引脚本RAG 效果好不好切块策略占了很大权重。切块太大会混入无关内容太小会丢失上下文。这里用最朴素的固定长度切块块大小为 200 字符、重叠 20 字符先把链路跑通。# 文件路径scripts/build_index.py import os from pathlib import Path import chromadb from utils.embedding_client import EmbeddingClient DATA_DIR Path(__file__).resolve().parents[1] / data / source_docs CHUNK_SIZE 200 CHUNK_OVERLAP 20 def read_text_files(directory: Path): docs [] for file_path in directory.glob(*.txt): with open(file_path, r, encodingutf-8) as f: content f.read() docs.append({ content: content, source: file_path.name, }) return docs def split_text(text: str, chunk_size: int CHUNK_SIZE, overlap: int CHUNK_OVERLAP): chunks [] start 0 while start len(text): end start chunk_size chunk text[start:end] if chunk.strip(): chunks.append(chunk) if end len(text): break start end - overlap return chunks def main(): client chromadb.PersistentClient(path./chroma_data) collection client.get_or_create_collection(internal_docs) embedder EmbeddingClient() docs read_text_files(DATA_DIR) for doc in docs: chunks split_text(doc[content]) ids [] embeddings [] metadatas [] documents [] for i, chunk in enumerate(chunks): ids.append(f{doc[source]}_{i}) embeddings.append(embedder.encode(chunk)[0]) metadatas.append({source: doc[source], chunk_index: i}) documents.append(chunk) collection.upsert( idsids, embeddingsembeddings, documentsdocuments, metadatasmetadatas, ) print(finserted {len(chunks)} chunks from {doc[source]}) print(index built.) if __name__ __main__: main()脚本关键逻辑读取data/source_docs下的所有 TXT 文件。split_text按固定字符数切块并保留重叠区域避免一个语义完整的句子恰好被切成两半。每个 chunk 生成 embedding 后连同原文、来源文件名、块序号一起写入 Chroma。运行方式python scripts/build_index.py看到类似输出inserted 4 chunks from 员工休假制度.txt inserted 3 chunks from 研发环境部署手册.txt inserted 3 chunks from 客户反馈处理流程.txt index built.说明索引建立成功。6.4 查询与检索验证索引建好后先不急着接大模型直接测试语义检索看召回结果是否符合预期。# 文件路径scripts/search_demo.py import chromadb from utils.embedding_client import EmbeddingClient def main(): client chromadb.PersistentClient(path./chroma_data) collection client.get_collection(internal_docs) embedder EmbeddingClient() query 员工每年能休几天假 query_vector embedder.encode(query)[0] results collection.query( query_embeddings[query_vector], n_results3, ) print(检索结果) for i, doc in enumerate(results[documents][0]): source results[metadatas][0][i][source] print(f\n--- Top {i 1} (source: {source}) ---) print(doc) if __name__ __main__: main()运行python scripts/search_demo.py预期输出中Top 1 应该来自员工休假制度.txt并且内容包含“带薪年假”。如果检索结果正确说明 Embedding 和向量存储链路没问题。这里可以多试几个查询比如“部署环境有没有健康检查要求” - 应该召回研发环境部署手册。“客户投诉多久要响应” - 应该召回客户反馈处理流程。如果一个问题能命中不同来源的文档说明语义检索确实没有停留在字符匹配层面。6.5 接入大模型形成 RAG 问答闭环检索只是前半段。完整的 RAG 还要让大模型基于检索出的片段作答。这一段以大模型 API 为例用一个可替换的LLMClient封装。你本地如果部署了 Ollama也可以把generator实现换成对应的本地模型。# 文件路径utils/llm_client.py import os import requests class OpenAICompatibleLLM: 兼容 OpenAI Chat Completions 接口的服务。 可通过环境变量配置 base_url / api_key / model。 如果使用 Ollama 本地模型base_url 改为 http://localhost:11434/v1 def __init__(self): self.base_url os.getenv(LLM_BASE_URL, https://api.openai.com/v1) self.api_key os.getenv(LLM_API_KEY, ) self.model os.getenv(LLM_MODEL, gpt-4o-mini) def chat(self, system_prompt: str, user_prompt: str) - str: url f{self.base_url}/chat/completions headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } payload { model: self.model, messages: [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature: 0.2, } resp requests.post(url, headersheaders, jsonpayload, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content]查询脚本整合检索与生成# 文件路径scripts/rag_query.py import os import chromadb from utils.embedding_client import EmbeddingClient from utils.llm_client import OpenAICompatibleLLM def build_context(results) - str: lines [] for i, doc in enumerate(results[documents][0]): lines.append(f[片段{i 1}] {doc}) return \n\n.join(lines) def main(): client chromadb.PersistentClient(path./chroma_data) collection client.get_collection(internal_docs) embedder EmbeddingClient() llm OpenAICompatibleLLM() query 客户投诉的处理时限是多少 query_vector embedder.encode(query)[0] results collection.query( query_embeddings[query_vector], n_results3, ) context build_context(results) system_prompt ( 你是一个企业内部知识库问答助手。请根据提供的知识片段回答问题。\n 如果片段中没有足够信息请如实回答“知识库中没有找到相关信息”不要编造。 ) user_prompt f知识片段\n{context}\n\n问题{query} answer llm.chat(system_prompt, user_prompt) print(f问题{query}\n) print(f答案{answer}\n) print(参考片段) print(context) if __name__ __main__: main()运行前需要设置环境变量export LLM_BASE_URLhttps://api.openai.com/v1 export LLM_API_KEYsk-xxxx export LLM_MODELgpt-4o-mini如果你使用本地 Ollama可这样配置export LLM_BASE_URLhttp://localhost:11434/v1 export LLM_API_KEYollama export LLM_MODELqwen2.5:7b运行python scripts/rag_query.py如果一切正常你会看到检索到的知识片段来自客户反馈处理流程.txt包含“咨询类工单要求在 4 小时内响应故障类工单按紧急程度分为 P0/P1/P2 三级”等内容。大模型根据片段生成答案比如“客户投诉工单需要在规定时限内响应咨询类 4 小时P0 故障需 10 分钟内启动应急响应”。答案中不会出现知识库里没有的信息。到这里一个最小可用的 RAG 知识库问答系统就完整跑通了。7. 常见问题与排查思路RAG 系统的坑很多绝大多数问题不是出在向量数据库本身而是前面的文本处理和后面的提示词设计。把最常见的几类问题整理成表供排查时对照。问题现象可能原因排查方式解决方案检索结果完全不相关Embedding 模型与查询向量不一致确认索引和查询是否使用同一模型统一使用同一个 embedding 模型不要混用检索结果太碎、上下文不完整切块过小打印实际 chunk 内容检查语义完整性增大 chunk_size调整 overlap或改用语义切块检索到相关内容但模型答非所问Prompt 设计没有约束检查 system prompt 是否要求只依据片段回答在 prompt 中强调“只能根据参考片段回答不要联想”检索结果命中但相似度普遍偏低文本类型与模型训练数据差异大计算 query 与命中文档的相似度分数更换更合适的 embedding 模型或做领域微调召回相关内容在 Top 5 之外数据量或切块策略导致语义分散调整 n_results 并人工评估引入 rerank 模型做二次排序或优化切块向量数据库检索快但入库很慢embedding 模型推理太慢或并发不足查看 embedding 阶段耗时使用批量编码、GPU 推理或接入云端 embedding API生产环境数据更新后查不到新数据索引未更新或 collection 隔离确认写入后是否已 commit/refresh使用 upsert 更新向量检查写入后的查询一致性再多说一点标题里提到的 rerank。向量检索的 Top K 只是“初步召回”它的目标是不漏掉相关内容而不是精确排序。如果想要更高质量可以在检索之后增加一个 rerank 模型比如 BGE-reranker把 Top 50 的结果重新精排选出 Top 5 喂给大模型。这在信息密度大、相似文档多的企业知识库场景中提升非常明显。8. 最佳实践与工程建议前几节解决了“怎么跑通”这一节讲“怎么跑到生产环境不出事”。8.1 切块策略要结合文档类型固定长度切块是最简单的方案但不是最优方案。对于 PDF 报告、网页文章可以考虑按标题、段落、列表项切分。先识别文档结构再在结构边界处切块每个块自带一个语义完整的主题。推荐一个通用策略先按文档结构分块比如一级标题、二级标题作为切块边界。如果块仍然过长再按句子或固定长度二次切分。保留块与块之间的少量重叠防止关键信息被边界截断。8.2 元数据过滤是向量数据库的隐藏价值向量数据库不只是“向量比对”它还能存元数据。例如来源文件名、作者、部门、发布日期、文档类型。检索时可以先用元数据过滤缩小搜索范围results collection.query( query_embeddings[query_vector], n_results5, where{source: 员工休假制度.txt} )这样能显著提升准确性也便于实现“只搜索 XX 部门文档”“只搜索今年发布的文章”等业务需求。8.3 混合检索关键词 语义互相兜底语义搜索有长处也有短板。如果用户查询的是精确编号、产品型号、人名、报错码比如“ERR_1024”向量模型可能把它当成普通语义编码效果反而不如关键词搜索。生产系统更稳妥的方案是混合检索向量数据库做语义召回。全文检索Elasticsearch 或 PostgreSQL 全文索引做关键词召回。两路结果合并后用 rerank 模型统一排序。这样既保留了语义理解的泛化能力又保留了精确匹配的确定性。8.4 评估是 RAG 项目的一等公民RAG 项目不能靠“看起来回答不错”来验收。建议建一个小规模的评测集每个问题包含标准问题。对应文档片段。期望答案关键词。每次调整切块、embedding 模型、检索 Top K、Prompt 后跑一遍评测集计算检索命中率和答案准确率。数据规模不需要大50 到 100 条高质量样本就够用。没有评测集你根本不知道一次升级到底变好了还是变差了。8.5 安全与合规边界使用云端 Embedding API 或大模型 API 时要特别注意数据合规。企业内部文档、客户隐私数据发送到第三方 API 前必须确认是否有数据使用协议约束。更稳妥的做法是私有化部署开源模型例如 BGE 系列 Ollama 本地模型数据不出内网。同时要考虑越权问题。知识库里的文档可能面向不同角色检索接口必须做权限过滤不能因为向量数据库检索快就把权限校验省了。权限过滤至少要在元数据层做例如where{ allowed_roles: employee }。8.6 选型建议不要一上来就上重型分布式结合你现在的阶段做选型阶段推荐方案个人知识库、原型验证Chroma零运维API 简单已有 PostgreSQL数据量小于千万级pgvector不引入新组件独立生产系统数据量大、并发高Milvus 或 Qdrant需要专业运维云上快速起步云厂商托管的向量数据库服务按量付费很多人一上来就搭 Milvus 集群结果数据只有几万条纯属增加运维负担。先用 Chroma 跑通业务逻辑再根据真实数据量做迁移是更务实的路径。9. 总结与后续学习方向这篇文章把 AI 知识库背后最核心的链路拆开了Embedding 把文本变成向量向量数据库把海量向量变成可毫秒级检索的索引RAG 把检索结果作为上下文送给大模型最终让模型基于私有知识回答问题。从实际开发角度看有四个关键点值得记住向量数据库不是万能的它替换的是“检索层”不是“存储层”的全部。语义搜索的核心是 embedding 模型的选择和切块策略向量数据库只是执行者。生产系统建议做混合检索和 rerank纯向量召回在精确匹配场景会吃亏。没有评测集RAG 项目永远处于“好像能用但不知道改完好不好”的状态。学完这篇下一步可以沿着三个方向深入把固定切块换成语义切块或按标题结构切块对比检索效果。引入 bge-reranker 做精排观察 Top K 质量变化。使用 LlamaIndex 或 LangChain 替代手写流程理解框架封装的边界。也可以把 Chroma 换成 Milvus 或 pgvector数据量爬到百万级后再对比性能差异。不管走哪条路先把 Embedding 向量检索 生成这段最小闭环跑熟后面的架构演进就有了稳定的地基。建议先按文中的三份文档建一个自己的实验知识库把检索和问答跑通再逐步加上切块优化、rerank 和评估集这套技术栈会越用越顺手。