
1. 为什么企业都在往 RAG 知识库上砸资源这两年做企业服务绕不开的一个词就是 RAG。全称 Retrieval-Augmented Generation检索增强生成。说白了就是给大模型外挂一个资料库让它回答问题的时候先去资料库里翻一翻找到相关内容再组织语言回答而不是全靠脑子里那点训练时记住的东西。我接触过的企业客户里十个有八个第一句话就是我们想把公司内部的文档用起来。这个需求太真实了。一家中型公司几年下来积累的产品手册、技术文档、客服话术、合同模板、会议纪要少说几千份多则几十万份。这些文档躺在共享盘里找起来全靠关键词搜索搜出来的东西还未必是想要的。新员工入职培训光熟悉这些文档就得花一两个月。老员工遇到问题也得在群里问一圈才知道答案在哪份文件里。RAG 知识库解决的正是这个痛点。你把文档喂进去它自动切分、向量化、存进向量数据库。用户用自然语言提问系统把问题也转成向量去数据库里找最相似的文档片段然后把这几段内容和问题一起交给大模型让它基于这些内容生成回答。整个过程用户感知到的就是问了一个问题得到了一个带出处的准确答案。但这里有个关键问题向量数据库怎么选、怎么用。很多人一上来就用本地文件或者内存里的简单索引文档少的时候没问题一旦上到几万、几十万条向量检索速度断崖式下跌内存也扛不住。这时候就需要专业的向量数据库。腾讯云的向量数据库Tencent Cloud VectorDB是我在几个项目里实际用过的方案它兼容 Milvus 接口支持 Python SDK对于已经用腾讯云生态的团队来说接入成本很低。这篇文章我打算把基于腾讯云向量数据库搭建 RAG 企业知识库的完整流程拆开讲。从环境准备、文档处理、向量化、入库、检索到最终和大模型对接每一步都会给出可运行的 Python 代码和参数说明。同时我会把实际踩过的坑、参数怎么调、成本怎么控这些经验一并写出来。适合正在做企业知识库的开发者、技术负责人也适合想了解 RAG 落地细节的产品经理。哪怕你 Python 刚入门跟着步骤走也能跑通一个最小可用版本。2. 整体方案设计与技术选型思路2.1 RAG 系统的核心链路拆解一个完整的 RAG 系统拆开来看就是两条链路写入链路和查询链路。写入链路负责把原始文档变成可检索的向量。流程是文档加载 → 文本清洗 → 分块chunking→ 向量化embedding→ 存入向量数据库。每一步都有讲究。文档加载要处理 PDF、Word、Markdown、HTML 等各种格式文本清洗要去掉页眉页脚、乱码、多余空行分块决定了检索的粒度块太大检索不精准块太小上下文不完整向量化模型的选择直接影响检索质量入库时要带上元数据方便后续过滤。查询链路负责把用户问题变成答案。流程是问题向量化 → 向量检索 → 重排序可选→ 组装 Prompt → 大模型生成。问题向量化必须用和入库时同一个 embedding 模型否则向量空间对不上检索结果就是乱的。向量检索返回 Top-K 个最相似的片段K 值一般取 3 到 10。重排序是用一个更精细的模型对初步检索结果再排一次序提升精度。最后把检索到的片段和用户问题拼成 Prompt交给大模型生成回答。这两条链路里向量数据库是承上启下的核心组件。它决定了检索的速度、规模和稳定性。我见过不少团队前期用 FAISS 或者 Chroma 做原型效果不错但一到生产环境数据量上来了单机内存撑不住检索延迟从几十毫秒涨到几秒用户体验直接崩掉。所以如果目标是企业级应用一开始就选一个支持分布式、能水平扩展的向量数据库能省掉后面迁移的麻烦。2.2 为什么选腾讯云向量数据库向量数据库这个赛道选择其实不少。Milvus、Qdrant、Weaviate、Pinecone、腾讯云 VectorDB、阿里云 DashVector 等等。我选腾讯云 VectorDB 主要基于几个实际考量。第一是运维成本。自建 Milvus 集群你得考虑机器、存储、网络、监控、备份、扩容这些隐性成本很高。腾讯云 VectorDB 是托管服务这些都不用管控制台点几下就能创建实例SDK 直接连。对于团队里没有专职运维的情况这一点很关键。第二是接口兼容性。腾讯云 VectorDB 兼容 Milvus 的 API这意味着你之前用 Milvus 写的代码改一下连接地址和认证信息就能迁移过来。反过来如果你以后想从腾讯云迁到自建 Milvus代码也不用大改。这种兼容性降低了锁定风险。第三是生态整合。如果公司已经在用腾讯云的 CVM、COS、TI 平台那么向量数据库和这些服务的网络互通、权限管理、计费都是打通的接入起来很顺。特别是和 COS 对象存储配合文档存在 COS 里向量数据库直接读省去了很多数据搬运的工作。第四是性能和规模。腾讯云 VectorDB 支持亿级向量规模单次查询延迟在毫秒级。它底层做了很多优化比如量化索引、GPU 加速、分布式检索。对于企业知识库这种场景几万到几百万条向量是常态这个规模它完全能扛住。当然也有取舍。托管服务意味着你没法直接控制底层参数某些极端定制化的需求可能满足不了。另外数据放在云上对于数据敏感度极高的行业需要评估合规性。但对于大多数企业知识库场景这些都不是问题。2.3 技术栈全景与版本选择把整个方案的技术栈列一下方便你对照准备环境。组件选型说明编程语言Python 3.103.8 也能跑但 3.10 对类型提示和异步支持更好向量数据库腾讯云 VectorDB兼容 Milvus 接口托管服务Embedding 模型BGE-M3 或 text-embedding-3中文场景 BGE-M3 表现好英文可用 OpenAI大模型DeepSeek、混元、Qwen 等根据预算和合规要求选文档解析PyMuPDF、python-docx、markdown按格式选对应库文本分块LangChain TextSplitter 或自研推荐按语义分块框架LangChain 或 LlamaIndex可选也可以纯手写部署CVM 或容器服务生产环境建议容器化Python 版本我建议 3.10 以上。3.8 虽然还能用但很多新库已经不支持了而且 3.10 的 match-case 语法、更完善的类型系统写起来舒服很多。安装 Python 直接去官网下载对应系统的安装包Windows 记得勾选Add Python to PATHMac 用 Homebrew 装最省事。Embedding 模型这块中文企业知识库我强烈推荐 BGE-M3。它是智源研究院出的支持多语言、多粒度在中文检索任务上表现很稳。而且可以本地部署数据不出内网对于有合规要求的场景很重要。如果预算充足且追求效果OpenAI 的 text-embedding-3-large 也是好选择但要注意数据出境问题。大模型的选择要看具体场景。DeepSeek 性价比高混元在腾讯云生态里调用方便Qwen 开源版本可以私有化部署。如果企业对数据安全要求极高那就选可以私有化部署的开源模型比如 Qwen2.5 系列。3. 环境准备与腾讯云向量数据库开通3.1 Python 环境搭建与依赖安装先把 Python 环境弄好。Windows 用户去 python.org 下载 3.10 或 3.11 的安装包安装时务必勾选Add Python to PATH否则后面命令行里敲 python 会提示找不到命令。Mac 用户用brew install python3.11最省心。Linux 用户可以用系统包管理器或者用 pyenv 管理多版本。装完验证一下python --version pip --version然后建一个虚拟环境这是好习惯避免不同项目的依赖打架python -m venv rag_env # Windows rag_env\Scripts\activate # Mac/Linux source rag_env/bin/activate虚拟环境激活后安装核心依赖pip install tcvectordb pip install pymupdf python-docx markdown pip install langchain langchain-community pip install sentence-transformers pip install openai pip install tqdm这里解释一下每个包的作用。tcvectordb是腾讯云向量数据库的官方 Python SDK必须装。pymupdf用来解析 PDF比 PyPDF2 快很多而且对中文支持好。python-docx处理 Word 文档。markdown处理 Markdown 文件。langchain提供文本分块、链式调用的工具能省不少事。sentence-transformers用来加载本地的 BGE-M3 模型。openai库虽然名字叫 openai但很多兼容 OpenAI 接口的大模型服务都能用它调用包括 DeepSeek。tqdm用来显示进度条处理大批量文档时很有用。如果安装sentence-transformers时遇到问题通常是 PyTorch 没装好。可以先去 PyTorch 官网根据你的 CUDA 版本选择安装命令。没有 GPU 的话装 CPU 版本也能跑就是慢一些。3.2 腾讯云向量数据库实例创建登录腾讯云控制台搜索向量数据库 VectorDB进入产品页面。点击新建实例。配置项有几个关键选择。地域选离你服务器最近的地域比如你的应用部署在广州就选广州这样网络延迟最低。实例规格根据数据量选初期测试选最小规格就行1 核 2G 的入门款足够跑几万条向量。等数据量上来了再扩容腾讯云支持在线扩容不用停机。网络配置这里要注意。如果你用 CVM 访问选私有网络 VPC把向量数据库和 CVM 放在同一个 VPC 里走内网通信速度快还免流量费。如果从本地开发机访问需要开通公网访问但生产环境不建议这么做有安全风险。创建完成后在实例详情页能看到访问地址和端口。还需要创建一个数据库和集合Collection。集合相当于关系型数据库里的表用来存向量数据。创建集合时要指定向量维度这个维度必须和你的 embedding 模型输出维度一致。BGE-M3 的输出维度是 1024OpenAI text-embedding-3-small 是 1536text-embedding-3-large 是 3072。维度填错了后面插入数据会直接报错。还需要获取 API Key。在访问管理里创建密钥会得到 SecretId 和 SecretKey。这两个东西相当于账号密码不要硬编码在代码里用环境变量或者配置文件管理。3.3 连接测试与常见报错处理环境准备好后先写个最小测试脚本验证连接import os from tcvectordb import VectorDBClient client VectorDBClient( urlos.environ[VDB_URL], usernameroot, keyos.environ[VDB_KEY], ) db client.database(rag_knowledge_base) print(连接成功数据库列表, client.list_databases())把 VDB_URL 和 VDB_KEY 设成环境变量。Linux/Mac 用export VDB_URLxxxWindows 用set VDB_URLxxx或者在代码里用 python-dotenv 加载 .env 文件。常见的报错有这么几个。Connection refused通常是网络不通检查 VPC 配置或者公网访问是否开通。Authentication failed是密钥错了检查 SecretId 和 SecretKey 有没有复制错注意前后空格。Collection not found是集合名写错了或者还没创建。Dimension mismatch是向量维度和集合定义不一致这个最常发生在换 embedding 模型的时候换了模型一定要同步改集合维度。提示腾讯云向量数据库的免费额度有限测试阶段注意控制数据量避免产生意外费用。可以在控制台设置消费预警。4. 文档处理与向量化实操4.1 多格式文档加载与清洗企业文档格式五花八门PDF、Word、Excel、PPT、Markdown、HTML、纯文本都有。我写了一个统一的加载器根据文件扩展名分派到不同的解析函数。import fitz # PyMuPDF from docx import Document import markdown from bs4 import BeautifulSoup import os def load_pdf(path): doc fitz.open(path) text for page in doc: text page.get_text() return text def load_docx(path): doc Document(path) return \n.join([p.text for p in doc.paragraphs if p.text.strip()]) def load_md(path): with open(path, r, encodingutf-8) as f: html markdown.markdown(f.read()) return BeautifulSoup(html, html.parser).get_text() def load_txt(path): with open(path, r, encodingutf-8) as f: return f.read() LOADERS { .pdf: load_pdf, .docx: load_docx, .md: load_md, .txt: load_txt, } def load_document(path): ext os.path.splitext(path)[1].lower() loader LOADERS.get(ext) if not loader: raise ValueError(f不支持的文件格式{ext}) return loader(path)PDF 解析是最容易出问题的。扫描件 PDF 用 PyMuPDF 提取出来是空的因为里面是图片不是文字。这种情况需要 OCR可以用 PaddleOCR 或者腾讯云的 OCR 服务。另外 PDF 里的表格提取出来会变成一堆乱序的文字如果表格内容很重要需要用专门的表格提取工具比如 Camelot 或者 pdfplumber。文本清洗这一步不能省。实际文档里经常有页眉页脚、页码、水印文字、多余的空格和换行。我一般用正则做一轮清洗import re def clean_text(text): # 去掉多余空白 text re.sub(r\s, , text) # 去掉页码形如 第 3 页 或 - 3 - text re.sub(r第\s*\d\s*页, , text) text re.sub(r-\s*\d\s*-, , text) # 去掉连续空行 text re.sub(r\n{3,}, \n\n, text) return text.strip()清洗规则要根据实际文档调整。我建议先拿几份典型文档跑一遍看看清洗后还剩什么噪音再针对性加规则。不要一上来就写一堆正则容易误伤正文内容。4.2 文本分块策略与参数调优分块是 RAG 里最容易被低估的环节。块分得好不好直接决定检索质量。分块的核心矛盾是块太大检索出来的内容包含太多无关信息大模型容易被干扰块太小上下文不完整答案可能缺胳膊少腿。我常用的策略是递归字符分块优先按段落分段落太长再按句子分句子还长就按字符硬切。LangChain 的 RecursiveCharacterTextSplitter 就是干这个的from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , , ], length_functionlen, ) chunks splitter.split_text(clean_text)参数怎么定chunk_size500是我在中文企业文档场景下比较常用的值。中文一个字符大约对应 1 到 2 个 token500 字符大概是 500 到 1000 token这个长度既能包含一个完整的知识点又不会太长导致检索不精准。chunk_overlap50是块之间的重叠部分防止一个完整的句子被切断后两边都读不懂。重叠比例一般取 chunk_size 的 10% 到 20%。但固定大小分块有个问题它不管语义边界。有时候一个段落正好在中间被切开前半段在块 A后半段在块 B检索到块 A 的时候答案不完整。解决办法是用语义分块根据句子之间的语义相似度来决定切分点。这需要额外的计算但效果确实好。LangChain 有 SemanticChunker 可以用不过对中文的支持一般我一般是自己写一个基于标点和段落结构的启发式分块。还有一个实践中的技巧给每个块加上它所属文档的标题和章节路径作为前缀。比如一个块来自《产品手册》第三章第二节就在块内容前面加上产品手册 第三章 第二节安装步骤。这样检索的时候即使块内容本身没提到安装但因为前缀里有也能被检索到。这个技巧对结构化文档特别有效。4.3 向量化模型选择与批量编码向量化就是把文本变成一串数字。这串数字捕捉了文本的语义信息语义相近的文本向量距离也近。BGE-M3 是我在中文场景的首选。它支持 8192 token 的输入长度输出 1024 维向量在多语言检索基准上表现很好。用 sentence-transformers 加载from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-m3) def embed_texts(texts, batch_size32): embeddings model.encode( texts, batch_sizebatch_size, normalize_embeddingsTrue, show_progress_barTrue, ) return embeddings.tolist()normalize_embeddingsTrue很重要它把向量归一化到单位长度这样余弦相似度计算就等价于内积检索时更快。batch_size根据你的显存调整GPU 显存 8G 的话 32 比较稳显存小就降到 8 或 16。如果文档量很大比如几十万条本地跑 embedding 会很慢。这时候可以考虑用腾讯云的 TI 平台或者批量调用 API。也可以多进程并行把文档分成几份每个进程处理一份。但要注意模型加载会占显存多进程的话每个进程都要加载一份模型显存不够就崩了。折中方案是用一个进程加载模型用多线程做推理因为推理本身是 GPU 密集型的多线程能提高 GPU 利用率。注意BGE-M3 首次运行会从 HuggingFace 下载模型文件大概 2G 多。国内网络可能下载慢或者失败可以设置镜像源或者手动下载后指定本地路径。5. 向量入库与检索实现5.1 集合创建与索引配置在腾讯云 VectorDB 里数据存在集合Collection中。创建集合时要定义字段和索引。from tcvectordb.model.collection import Collection from tcvectordb.model.index import Index, VectorIndex, FilterIndex from tcvectordb.model.enum import FieldType, IndexType, MetricType # 定义索引 index Index() index.add(VectorIndex( vector, dimension1024, metric_typeMetricType.COSINE, index_typeIndexType.HNSW, params{M: 16, efConstruction: 200}, )) index.add(FilterIndex(doc_id, FieldType.String, IndexType.FILTER)) index.add(FilterIndex(source, FieldType.String, IndexType.FILTER)) # 创建集合 db.create_collection( nameknowledge_chunks, dimension1024, indexindex, description企业知识库文档块, )这里几个参数解释一下。metric_type选 COSINE 余弦相似度适合文本向量。index_type选 HNSW这是目前最常用的近似最近邻索引查询快、召回率高。M是每个节点的连接数越大索引越精确但占内存越多16 是常用值。efConstruction是建索引时的搜索范围越大索引质量越高但建索引越慢200 是平衡值。FilterIndex 是标量字段索引用来做元数据过滤。比如你只想在某个部门的文档里检索就可以用 filter 条件过滤。这个功能在企业场景很实用能大幅缩小检索范围提升精度和速度。5.2 批量插入与元数据设计插入数据的时候除了向量本身还要带上原始文本和元数据。元数据的设计很关键它决定了你后面能做哪些过滤和溯源。def insert_chunks(collection, chunks, embeddings, metadata_list): data [] for i, (chunk, emb, meta) in enumerate(zip(chunks, embeddings, metadata_list)): data.append({ id: f{meta[doc_id]}_{i}, vector: emb, text: chunk, doc_id: meta[doc_id], source: meta[source], title: meta.get(title, ), chunk_index: i, }) # 批量插入每批 100 条 batch_size 100 for i in range(0, len(data), batch_size): batch data[i:ibatch_size] collection.insert(batch) print(f已插入 {min(ibatch_size, len(data))}/{len(data)})元数据我一般会存这几个字段doc_id是文档唯一标识source是文档来源路径或 URLtitle是文档标题chunk_index是块在文档中的位置。有了这些检索结果就能显示来自《XX 文档》第 N 段用户能点进去看原文可信度大大提升。批量插入的 batch_size 不要太大100 到 500 比较合适。太大容易超时太小效率低。插入过程中如果报错通常是某条数据的向量维度不对或者字段类型不匹配把出错的那条单独拿出来检查。5.3 相似度检索与结果重排检索是 RAG 的核心。用户提问系统把问题向量化去数据库里找最相似的 Top-K 个块。def search(collection, query, top_k5, filter_exprNone): query_vector embed_texts([query])[0] results collection.search( data[query_vector], limittop_k, output_fields[text, doc_id, source, title], filterfilter_expr, ) return results[0]top_k取多少我一般先取 10 到 20 个候选然后用重排序模型精排最后取前 3 到 5 个给大模型。为什么要重排序因为向量检索是基于语义相似度的它找的是意思相近的块但不一定是最能回答问题的块。重排序模型比如 BGE-Reranker会同时看问题和候选块给出一个更精准的相关性分数。from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-v2-m3) def rerank(query, candidates, top_n5): pairs [(query, c[text]) for c in candidates] scores reranker.predict(pairs) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) return [c for c, s in ranked[:top_n]]重排序会增加一点延迟大概几十到几百毫秒但精度提升很明显。如果对延迟极其敏感可以跳过重排序直接取向量检索的 Top-K。实测下来加了重排序之后答案的准确率能提升 15% 到 30%这个投入很值。实操心得检索时给结果加一个相似度阈值低于阈值的直接丢弃。比如余弦相似度低于 0.5 的块基本和问题无关留着反而干扰大模型。阈值多少合适拿一批测试问题跑一下看分数分布来定。6. 对接大模型与完整问答链路6.1 Prompt 模板设计与上下文组装检索到相关块之后要把它们和用户问题组装成 Prompt 交给大模型。Prompt 的设计直接影响回答质量。PROMPT_TEMPLATE 你是一个企业知识库助手。请根据下面提供的参考资料回答用户问题。 要求 1. 只根据参考资料回答不要编造资料中没有的信息。 2. 如果参考资料不足以回答问题直接说根据现有资料无法回答该问题。 3. 回答时在句末标注引用来源格式为 [来源: 文档名]。 参考资料 {context} 用户问题{question} 回答 def build_prompt(query, chunks): context_parts [] for i, chunk in enumerate(chunks): context_parts.append( f[资料{i1}] 来源{chunk[title]}\n{chunk[text]} ) context \n\n.join(context_parts) return PROMPT_TEMPLATE.format(contextcontext, questionquery)这个模板有几个关键点。只根据参考资料回答是防止大模型幻觉的核心指令。大模型有个毛病就是不知道的时候也爱编加上这句能大幅减少胡编乱造。标注引用来源让回答可追溯用户能验证。如果资料不足就直说这比给一个错误答案强得多。上下文长度也要控制。大模型的上下文窗口有限虽然现在很多模型支持 128K 甚至更长但塞太多内容进去一是贵二是大模型对中间部分的内容注意力会下降所谓的lost in the middle现象。我一般控制在 3000 到 5000 字符大概 3 到 5 个块。6.2 大模型调用与流式输出调用大模型我用 OpenAI 兼容的接口这样换模型只需要改 base_url 和 model 名字。from openai import OpenAI client OpenAI( api_keyos.environ[LLM_API_KEY], base_urlos.environ[LLM_BASE_URL], ) def generate_answer(prompt, streamTrue): response client.chat.completions.create( modelos.environ[LLM_MODEL], messages[{role: user, content: prompt}], temperature0.1, max_tokens1024, streamstream, ) if stream: for chunk in response: if chunk.choices[0].delta.content: yield chunk.choices[0].delta.content else: return response.choices[0].message.contenttemperature0.1是让输出更确定、更少发挥。知识库问答场景不需要创意需要准确。max_tokens1024限制回答长度避免大模型啰嗦。流式输出让用户能实时看到回答一个字一个字蹦出来体验好很多尤其是回答较长的时候。6.3 完整问答函数与端到端测试把上面的环节串起来def ask(question, top_k10, top_n5): # 1. 检索 candidates search(collection, question, top_ktop_k) # 2. 重排序 chunks rerank(question, candidates, top_ntop_n) # 3. 组装 Prompt prompt build_prompt(question, chunks) # 4. 生成回答 answer for token in generate_answer(prompt): answer token print(token, end, flushTrue) print() # 5. 返回答案和来源 sources list({c[title] for c in chunks}) return answer, sources测试一下answer, sources ask(公司的报销流程是什么) print(\n来源, sources)如果检索到的块和问题相关大模型的回答应该准确且带引用。如果检索结果不相关检查一下 embedding 模型是否一致、分块是否合理、相似度阈值是否合适。7. 常见问题排查与性能优化实录7.1 检索不准的排查思路检索不准是 RAG 最常见的问题。表现是用户问 A检索出来的却是 B 相关的内容。排查按这个顺序来。先看 embedding 模型是否一致。入库用的模型和查询用的模型必须是同一个否则向量空间对不上。我见过有人入库用 BGE-M3查询用 OpenAI结果检索全是乱的。再看分块是否合理。如果块太大一个块里混了好几个主题检索时匹配到的是其中一个主题但返回的块包含大量无关内容。如果块太小一个完整答案被切成好几块检索只返回其中一块答案不完整。调整 chunk_size 试试一般 300 到 800 之间。然后看相似度阈值。如果阈值设得太高相关块被过滤掉了太低无关块混进来。拿一批测试问题打印出检索结果的相似度分数看看分布再定阈值。最后考虑加查询改写。用户的问题往往口语化、有指代直接拿去检索效果不好。可以用大模型先把问题改写成更适合检索的形式。比如用户问那个报销的事怎么办改写成公司报销流程和所需材料。这一步能明显提升召回率。7.2 性能瓶颈与优化手段性能问题主要出在三个地方embedding 计算、向量检索、大模型生成。Embedding 计算是写入链路的主要耗时。几万条文档本地 CPU 跑可能要几个小时。优化手段是用 GPU或者用批量 API。如果文档更新频繁可以做增量处理只处理新增和修改的文档不用每次全量重跑。向量检索的延迟通常在几十毫秒但如果 top_k 设得很大或者数据量到了千万级延迟会上升。优化手段是加标量过滤缩小范围或者用更快的索引类型。腾讯云 VectorDB 支持调整索引参数可以按需优化。大模型生成是查询链路的主要耗时尤其是生成长回答的时候。优化手段是流式输出让用户先看到部分内容。另外可以限制 max_tokens避免生成过长。如果并发量大考虑用多个模型实例做负载均衡。7.3 成本控制与增量更新成本主要来自三块向量数据库实例费用、embedding 计算费用、大模型调用费用。向量数据库按规格和存储量计费。控制成本的手段是选合适的规格不要一上来就买大的。数据量小的时候用入门款等真的上量了再扩容。另外定期清理无用数据比如过期的文档、重复的块。Embedding 如果用 API按 token 计费。本地部署的话主要是机器成本。如果文档量大且更新不频繁本地部署更划算。如果文档更新频繁用 API 更灵活。大模型调用按 token 计费。控制成本的手段是优化 Prompt减少不必要的上下文。另外可以缓存常见问题的答案相同问题直接返回缓存结果不用每次都调大模型。增量更新是个实用功能。文档库不是一成不变的每天都有新文档、修改的文档。全量重建索引成本高、耗时长。我一般给每个文档算一个哈希值存在元数据里。更新时先算新文档的哈希和数据库里的对比只处理变化的文档。删除文档时根据 doc_id 把对应的所有块删掉。def incremental_update(collection, doc_path): doc_id get_doc_id(doc_path) new_hash compute_hash(doc_path) # 查已有哈希 existing collection.query( filterfdoc_id {doc_id}, output_fields[doc_hash], limit1, ) if existing and existing[0][doc_hash] new_hash: print(f{doc_path} 未变化跳过) return # 删除旧数据 collection.delete(filterfdoc_id {doc_id}) # 处理并插入新数据 text clean_text(load_document(doc_path)) chunks splitter.split_text(text) embeddings embed_texts(chunks) insert_chunks(collection, chunks, embeddings, [{doc_id: doc_id, ...}])这套机制跑起来之后每天定时扫描文档目录自动同步变化维护成本很低。7.4 常见问题速查表问题现象可能原因排查方向解决方案连接超时网络不通检查 VPC、安全组、公网配置调整网络配置或换内网访问认证失败密钥错误核对 SecretId/SecretKey重新生成密钥维度不匹配模型换了没改集合检查 embedding 维度和集合定义重建集合或换回原模型检索结果不相关模型不一致/分块不合理检查 embedding 模型、chunk_size统一模型、调整分块参数答案编造Prompt 没约束检查 Prompt 模板加强只根据资料回答指令插入慢batch 太小/网络差看插入日志增大 batch、走内网检索慢top_k 太大/数据量大看查询耗时减小 top_k、加过滤条件内存溢出模型太大/数据太多看资源监控换小模型、分批处理避坑技巧每次换 embedding 模型一定要重建集合。我见过有人换了模型没重建检索结果全是乱的排查了半天才发现是维度对不上但没报错因为新模型维度恰好也是 1024但向量空间完全不同检索自然不准。8. 几个实际项目中的经验补充8.1 知识库和结构化数据的边界RAG 知识库擅长处理非结构化文本但企业里还有很多结构化数据比如数据库里的订单、Excel 里的报表。这些数据用 RAG 处理效果不好因为向量检索不擅长精确匹配和聚合计算。用户问上个月销售额是多少向量检索找不到答案因为答案不在任何一段文本里而是需要查数据库算出来。我的做法是把结构化查询交给专门的工具。用户问题先经过一个路由判断如果是结构化查询就走 SQL 或者 API如果是文档问答才走 RAG。这个路由可以用大模型来做给它几个示例让它判断问题类型。这样两套系统各司其职效果比硬塞进 RAG 好得多。8.2 多轮对话与上下文管理企业知识库往往需要多轮对话。用户问报销流程是什么得到回答后接着问那需要哪些材料这时候那指代的是报销流程如果直接把那需要哪些材料拿去检索肯定找不到相关内容。解决办法是在检索前做查询改写把多轮对话的上下文考虑进去。把最近几轮对话和当前问题一起交给大模型让它改写出一个独立的、完整的查询。比如把那需要哪些材料改写成公司报销流程需要哪些材料。然后再用改写后的查询去检索。def rewrite_query(history, current_question): prompt f根据对话历史将用户的最新问题改写成一个独立的、完整的查询语句。 对话历史 {history} 最新问题{current_question} 改写后的查询 return call_llm(prompt)这个改写步骤会增加一次大模型调用但能显著提升多轮对话的检索质量。如果对延迟敏感可以只在检测到指代词那、它、这个的时候才触发改写。8.3 效果评估与持续迭代RAG 系统上线不是终点而是起点。你需要一套评估机制来持续监控和优化效果。我一般会建一个测试集包含几十到几百个问题和对应的标准答案。每次调整参数或者换模型都跑一遍测试集看准确率、召回率、回答质量的变化。评估指标包括检索命中率相关块是否在 Top-K 里、答案准确率回答是否正确、引用准确率引用的来源是否真的支持答案。人工评估也很重要。定期抽样看一些真实用户的问答记录看看哪些问题回答得好哪些不好。不好的案例分析原因是检索没找到、还是找到了但大模型没用好、还是问题本身超出知识库范围。针对性地优化。这套流程跑起来之后知识库的效果会随着时间越来越好。我做过的一个项目上线三个月经过几轮迭代答案准确率从最初的 60% 多提升到了 85% 以上。8.4 安全与权限控制企业知识库往往涉及敏感信息不同部门、不同职级的员工能访问的文档范围不同。这个权限控制要在检索层面做不能只在前端做。我的做法是在元数据里加一个access_level字段检索时根据当前用户的权限加过滤条件。比如普通员工只能检索access_level 2的文档经理能检索access_level 3的。这样即使前端被绕过后端检索也不会返回越权内容。def search_with_permission(collection, query, user_level): filter_expr faccess_level {user_level} return search(collection, query, filter_exprfilter_expr)另外用户的问题和检索结果里可能包含敏感信息日志记录时要注意脱敏。大模型调用如果走外部 API要评估数据出境合规性。对于数据敏感度高的场景建议用私有化部署的模型数据不出内网。这套基于腾讯云向量数据库的 RAG 知识库方案我在几个项目里实际跑过从几千条到几十万条向量都验证过。核心链路不复杂难的是细节调优和持续运营。把文档处理、分块、检索、Prompt 这几块的参数调好效果就能达到可用水平。后面再根据实际使用中的反馈慢慢迭代逐步提升。最后分享一个我踩过的坑不要一上来就追求大而全。我见过团队花几个月搭了一套复杂的系统结果因为文档质量差、分块不合理效果还不如人工搜索。正确的做法是先拿一个部门、一类文档做小范围试点跑通链路验证效果再逐步扩大范围。小步快跑比一步到位靠谱得多。