1. 为什么企业知识库需要 RAG,而不是直接微调大模型
很多团队一上来就问:我们想把公司几百份产品文档、售后工单、内部规范喂给大模型,是不是直接微调一个专属模型就行了?我踩过这个坑,也见过不少同行在这条路上浪费了两三周时间,最后发现效果还不如老老实实做检索增强生成(RAG)。这里先把结论摆出来:对于企业知识库这种"内容持续更新、要求答案可溯源、预算有限"的场景,RAG 是性价比最高、落地最快的方案,微调只适合解决"说话风格"和"固定格式输出"的问题,不适合承载知识本身。
1.1 微调和 RAG 到底在解决什么问题
要理解这个选择,得先搞清楚两者的本质区别。微调(Fine-tuning)是把知识"烧"进模型参数里,相当于让模型重新上一次学,把新知识变成它的"肌肉记忆"。而 RAG 是把知识放在外部数据库里,模型每次回答前先去"查资料",再基于查到的资料组织语言。
这两条路的差异,落到企业实际场景里就非常明显了:
| 对比维度 | 微调方案 | RAG 方案 |
|---|---|---|
| 知识更新 | 每次更新都要重新训练,成本高、周期长 | 改数据库即可,分钟级生效 |
| 答案溯源 | 无法给出出处,出了错很难查 | 可返回原文片段和来源文档 |
| 幻觉控制 | 知识记错时照样一本正经胡说 | 检索不到就明确说不知道 |
| 初始成本 | 需要标注数据、GPU 算力 | 主要是文档处理和向量化 |
| 适合场景 | 固定话术、风格迁移、格式约束 | 知识问答、文档检索、客服助手 |
我自己的经验是,企业知识库最大的痛点是"内容一直在变"——产品文档每周更新,售后政策每季度调整,如果走微调路线,你等于给自己挖了个无底洞。而 RAG 的核心优势就是知识库和模型解耦,文档变了只更新向量库,模型完全不用动。
1.2 RAG 的完整链路拆成四步就懂了
很多人觉得 RAG 很玄乎,其实拆开看就四个环节,我用一个生活化的类比帮你记住:把 RAG 想象成一个图书馆管理员。
- 文档切分(Chunking):把厚厚一本书拆成一页页卡片,方便快速翻阅。对应到技术里,就是把长文档切成一段段文本块。
- 向量化(Embedding):给每张卡片贴上"语义标签",让意思相近的卡片能被归到一起。这一步用嵌入模型把文本转成高维向量。
- 检索(Retrieval):用户提问时,管理员根据问题去卡片堆里找出最相关的几张。这一步在向量数据库里做相似度搜索。
- 生成(Generation):管理员把找到的卡片递给大模型,让它基于这些内容组织成一段通顺的回答。
整个链路里,向量数据库是承上启下的关键。它决定了检索快不快、准不准、能不能扛住企业级的数据量。这也是为什么我这次选腾讯云向量数据库来落地——它把部署、扩缩容、索引优化这些脏活累活都包了,我们只需要专注在文档处理和检索策略上。
1.3 什么样的企业场景适合这套方案
不是所有场景都值得上 RAG。根据我做过几个项目的经验,下面这几类需求最适合:
- 内部知识问答:员工问"年假怎么算""报销流程是什么",系统从制度文档里检索并回答。
- 智能客服:客户问产品参数、售后政策,系统从产品手册和工单库里找答案。
- 技术文档助手:开发者问 API 怎么调、报错怎么解,系统从技术文档里检索。
- 合同/法规检索:法务问某条款怎么规定,系统从合同库里定位原文。
反过来说,如果你的需求是"让模型学会某种特定的说话风格"或者"输出固定格式的 JSON",那微调更合适。判断标准很简单:知识是"查得到"还是"学得会"。查得到的用 RAG,学得会的用微调。
2. 动手前的环境准备与腾讯云向量数据库开通
环境准备这一步看着简单,但我见过太多人卡在依赖版本冲突、SDK 装不上、密钥配错这些低级问题上。这一章我把踩过的坑都摊开讲,你照着做能省下至少半天时间。
2.1 Python 环境与依赖清单
先说 Python 版本。腾讯云向量数据库的 Python SDK 对版本有要求,我实测下来Python 3.8 到 3.11 都能跑,但推荐 3.10,因为它在兼容性和性能之间平衡得最好。如果你还在用 3.7 或者更早的版本,建议先升级,否则某些依赖会装不上。
安装依赖的时候,我建议用虚拟环境隔离,别直接往全局环境里装。命令如下:
# 创建虚拟环境 python -m venv rag_env # 激活(Linux/Mac) source rag_env/bin/activate # 激活(Windows) rag_env\Scripts\activate # 安装核心依赖 pip install tcvectordb pip install langchain pip install langchain-community pip install sentence-transformers pip install pypdf pip install python-dotenv这里有几个坑要提醒:
- tcvectordb 是腾讯云向量数据库的官方 SDK,别装成别的同名包。
- sentence-transformers 会连带装 torch,如果你机器上没有 GPU,装的是 CPU 版本,下载量比较大,耐心等。
- 如果你用 Mac M 系列芯片,torch 的安装可能会慢,建议提前配好国内镜像源。
提示:依赖装完后,先跑一句
python -c "import tcvectordb; print(tcvectordb.__version__)"验证一下,能打印出版本号就说明装好了。
2.2 开通腾讯云向量数据库并拿到连接信息
登录腾讯云控制台,搜索"向量数据库",进入后创建一个实例。创建时几个关键参数我解释一下为什么这么选:
- 地域:选离你应用服务器最近的地域,能显著降低网络延迟。如果你的应用部署在广州,数据库就选广州。
- 规格:测试阶段选最小规格就够,1 核 2G 能撑住几万条向量。生产环境根据数据量估算,一般 10 万条文档块选 2 核 4G 起步。
- 副本数:测试选 1 副本,生产建议 2 副本以上保证高可用。
创建完成后,在实例详情页能拿到三个关键信息:访问地址(Endpoint)、用户名、API 密钥。这三个东西千万别硬编码在代码里,用.env文件管理:
# .env 文件 TCVDB_URL=http://your-instance-endpoint:port TCVDB_USERNAME=root TCVDB_API_KEY=your-api-key-here然后在代码里用python-dotenv读取:
import os from dotenv import load_dotenv load_dotenv() url = os.getenv("TCVDB_URL") username = os.getenv("TCVDB_USERNAME") api_key = os.getenv("TCVDB_API_KEY")注意:API 密钥泄露等于把数据库大门敞开,一定要走环境变量,并且把
.env加进.gitignore。我见过有人把密钥提交到公开仓库,结果被扫到后数据库被清空,这个教训太惨痛了。
2.3 嵌入模型的选择:本地跑还是调 API
向量化的质量直接决定检索效果,所以嵌入模型的选择很关键。有两条路:
路线一:本地部署嵌入模型。用sentence-transformers加载开源模型,比如BAAI/bge-large-zh-v1.5,中文效果很好,而且完全免费、数据不出内网。缺点是首次加载慢,且需要一定的内存。
路线二:调用云端嵌入 API。腾讯云、各家大模型厂商都提供嵌入接口,优点是省事、效果好,缺点是要花钱且数据要出网。
我的建议是:如果数据敏感,走本地模型;如果追求效果和省事,走云端 API。本文为了演示完整链路,用本地模型,代码里换成 API 调用也很简单。
from sentence_transformers import SentenceTransformer # 加载中文嵌入模型 model = SentenceTransformer('BAAI/bge-large-zh-v1.5') # 测试一下 texts = ["企业知识库怎么搭建", "RAG 检索增强生成"] embeddings = model.encode(texts) print(embeddings.shape) # 输出 (2, 1024)注意bge-large-zh-v1.5输出的是 1024 维向量,这个维度要和你后面建集合时指定的维度一致,否则会报错。这是新手最容易踩的坑之一。
3. 从零构建知识库:文档处理与向量入库全流程
这一章是整篇的核心,我会把文档切分、向量化、入库这条链路完整走一遍,每一步都解释清楚为什么这么做,以及我踩过的坑。
3.1 文档加载与清洗:脏数据是检索不准的元凶
企业文档的来源五花八门:PDF、Word、Markdown、网页、数据库导出。第一步是把它们统一加载成纯文本。用 LangChain 的文档加载器能省不少事:
from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain_community.document_loaders import UnstructuredMarkdownLoader def load_documents(file_path): if file_path.endswith('.pdf'): loader = PyPDFLoader(file_path) elif file_path.endswith('.md'): loader = UnstructuredMarkdownLoader(file_path) elif file_path.endswith('.txt'): loader = TextLoader(file_path, encoding='utf-8') else: raise ValueError(f"不支持的文件类型: {file_path}") return loader.load()加载完之后,千万别直接切分,先做清洗。我见过太多检索效果差的案例,根源就是文档里混着页眉页脚、乱码、重复的导航栏文字。清洗要做这几件事:
- 去掉连续的空行和多余空格
- 去掉 PDF 转换产生的乱码字符
- 去掉页眉页脚这类重复内容
- 统一全角半角标点
import re def clean_text(text): # 去掉多余空白 text = re.sub(r'\s+', ' ', text) # 去掉常见乱码 text = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]', '', text) # 去掉页码类内容 text = re.sub(r'第\s*\d+\s*页', '', text) return text.strip()提示:清洗规则要根据你的文档实际情况调整。建议先拿几份典型文档跑一遍,人工看看清洗后的效果,别一股脑全量处理完才发现问题。
3.2 文本切分策略:块大小和重叠度怎么定
切分是 RAG 里最讲究技巧的一步。切太大,检索出来的内容太杂,模型抓不住重点;切太小,语义被割裂,检索出来的片段不完整。我的经验参数是:
- 块大小(chunk_size):中文场景建议 300 到 500 字。英文可以到 500 到 800 词。
- 重叠度(chunk_overlap):设为块大小的 10% 到 20%,保证跨块的语义连贯。
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(documents) print(f"切分后共 {len(chunks)} 个文本块")这里separators的顺序很关键。它会优先按段落切,段落太长再按句子切,最后才按字符切。中文一定要把句号、问号这些标点加进去,否则会切出半句话。
我踩过的一个坑:表格和代码块被切碎。如果你的文档里有大量表格,建议先把表格单独提取出来,转成"字段:值"的文本形式再切分,否则检索出来的表格片段根本没法用。
3.3 向量化与批量入库:性能优化的关键点
切分完成后,就要把每个文本块转成向量并写入腾讯云向量数据库。先创建集合(Collection):
from tcvectordb import VectorDBClient from tcvectordb.model.collection import Collection from tcvectordb.model.index import Index, FilterIndex, VectorIndex from tcvectordb.model.enum import FieldType, IndexType, MetricType client = VectorDBClient(url=url, username=username, key=api_key) # 定义索引结构 index = Index( FilterIndex(name="id", field_type=FieldType.String, index_type=IndexType.PRIMARY_KEY), FilterIndex(name="text", field_type=FieldType.String, index_type=IndexType.FILTER), FilterIndex(name="source", field_type=FieldType.String, index_type=IndexType.FILTER), VectorIndex(name="vector", dimension=1024, index_type=IndexType.HNSW, metric_type=MetricType.COSINE) ) # 创建集合 db = client.create_database("rag_kb") collection = db.create_collection(name="docs", shard=1, replicas=1, index=index)几个参数解释一下:
- dimension=1024:必须和嵌入模型输出维度一致,
bge-large-zh-v1.5就是 1024。 - index_type=HNSW:这是目前主流的近似最近邻索引,检索快、召回率高。数据量小的时候也可以用 FLAT,精度更高但慢。
- metric_type=COSINE:余弦相似度,适合文本向量。也可以用内积(IP)或欧氏距离(L2)。
入库的时候,一定要批量写,别一条条写。我实测过,单条写入 1000 个块要几分钟,批量写入只要几秒。腾讯云 SDK 支持批量 upsert:
from tcvectordb.model.document import Document def batch_insert(collection, chunks, model, batch_size=100): for i in range(0, len(chunks), batch_size): batch = chunks[i:i+batch_size] texts = [c.page_content for c in batch] vectors = model.encode(texts).tolist() docs = [] for j, chunk in enumerate(batch): docs.append(Document( id=f"doc_{i+j}", text=chunk.page_content, source=chunk.metadata.get("source", "unknown"), vector=vectors[j] )) collection.upsert(documents=docs) print(f"已写入 {i+len(batch)} / {len(chunks)}")注意:批量大小别设太大,100 到 200 比较稳妥。设太大容易触发请求体超限,反而报错。
4. 检索与生成:让大模型答得准、答得稳
数据入库只是上半场,真正决定用户体验的是检索和生成这两个环节。这一章我讲讲怎么把检索做准,以及怎么让大模型基于检索结果稳定输出。
4.1 相似度检索的三种策略对比
最基础的检索就是拿用户问题去向量库里找最相似的 Top-K 个块。但实际用下来,纯向量检索有几个明显短板:
- 对专有名词不敏感:比如产品型号"X200-Pro",向量检索可能找不准。
- 对精确匹配无能为力:用户问某个具体编号,向量检索不如关键词匹配。
- Top-K 难定:K 太小漏信息,K 太大引入噪声。
所以我一般用混合检索:向量检索 + 关键词检索,两路结果融合。腾讯云向量数据库支持标量过滤,可以配合关键词做粗筛:
def search(collection, query, model, top_k=5): query_vector = model.encode([query])[0].tolist() results = collection.search( vectors=[query_vector], limit=top_k, params={"ef": 128} # HNSW 的搜索参数,越大越准但越慢 ) return resultsef这个参数值得说一下。它是 HNSW 索引的搜索广度,值越大召回率越高,但检索越慢。测试阶段可以设 128 到 256,生产环境根据延迟要求调。
还有一种进阶策略叫重排序(Rerank):先用向量检索召回 Top-20,再用一个专门的重排序模型精排出 Top-5。这样能显著提升相关性。开源的重排序模型有BAAI/bge-reranker-large,效果不错。
4.2 提示词模板:把检索结果喂给大模型的正确姿势
检索到相关片段后,要把它们和用户问题一起组装成提示词。这里有个关键原则:明确告诉模型"只基于给定资料回答,资料里没有就说不知道"。否则模型会自由发挥,产生幻觉。
PROMPT_TEMPLATE = """你是一个企业知识库助手。请严格基于下面提供的资料回答用户问题。 要求: 1. 只使用资料中的信息,不要编造。 2. 如果资料中没有相关信息,直接回答"根据现有资料无法回答该问题"。 3. 回答要简洁准确,必要时引用资料原文。 资料: {context} 用户问题:{question} 回答:""" def build_prompt(query, search_results): context = "\n\n".join([r['text'] for r in search_results]) return PROMPT_TEMPLATE.format(context=context, question=query)这个模板我调过很多版,最后发现把"不要编造"和"无法回答"这两条写死最有效。很多团队只写"请基于资料回答",结果模型还是会脑补,加上明确的兜底话术后,幻觉率明显下降。
4.3 调用大模型生成答案并附上引用来源
生成环节可以接各家大模型的 API。为了演示,我用一个通用的调用方式:
import requests def generate_answer(prompt, api_key, api_url): headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } payload = { "model": "your-model-name", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1 # 知识问答场景温度调低,减少随机性 } response = requests.post(api_url, headers=headers, json=payload) return response.json()["choices"][0]["message"]["content"]temperature设 0.1 是有讲究的。知识问答要的是稳定、准确,不是创意,温度越低输出越确定。如果你设成 0.8,同一个问题每次答案都不一样,用户会觉得系统不靠谱。
生成完答案后,一定要把引用来源一起返回。这是 RAG 相比微调最大的优势之一:
def answer_with_citation(query, collection, model, llm_api_key, llm_api_url): results = search(collection, query, model, top_k=5) prompt = build_prompt(query, results) answer = generate_answer(prompt, llm_api_key, llm_api_url) sources = list(set([r['source'] for r in results])) return { "answer": answer, "sources": sources }用户看到答案下面标着"来源:产品手册 v2.3.pdf",信任度会高很多,也方便他们自己去核对原文。
5. 上线后才发现的问题:检索质量调优与常见坑
系统跑通只是第一步,真正上线后你会发现一堆问题。这一章我把自己和同行踩过的坑整理出来,都是血泪教训。
5.1 检索不准的排查链路
当用户反馈"答非所问"时,别急着换模型,按这个顺序排查:
第一步:看检索结果本身对不对。把用户问题和检索出的 Top-5 块打印出来,人工判断相关性。如果检索结果就不对,那问题在检索环节,跟大模型无关。
第二步:检查切分是否合理。如果检索出的块是半句话或者跨了多个主题,说明切分有问题。调整 chunk_size 和 separators。
第三步:检查嵌入模型是否匹配。中文场景用英文模型,效果肯定差。确认你用的是中文优化的模型。
第四步:检查是否有脏数据干扰。如果知识库里混着大量无关文档,会稀释检索效果。考虑加元数据过滤,只检索相关来源。
我遇到过一个典型案例:用户问"退款政策",检索出来的全是"退货政策"。原因是这两个词向量很接近,但业务上是两回事。解决办法是在切分时把标题一起带上,让每个块都包含所属章节的标题信息,检索时就能区分开。
5.2 大模型答非所问的三种典型情况
检索对了,但模型还是答不好,通常是这三种情况:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 答案太笼统 | 检索块太大,信息不聚焦 | 减小 chunk_size,提高检索精度 |
| 答案不完整 | Top-K 太小,漏了关键信息 | 增大 Top-K,或加重排序 |
| 答案自相矛盾 | 检索到冲突信息 | 加时间过滤,只取最新版本 |
还有一种情况是模型不遵守提示词,明明资料里没有,它还是硬答。这时候可以加一个"置信度判断"步骤:先让模型判断资料是否足够回答问题,不够就直接返回兜底话术。
5.3 性能与成本的平衡技巧
企业知识库上线后,性能和成本是两个绕不开的话题。几个实用技巧:
- 缓存高频问题:把常见问题的答案缓存起来,命中缓存直接返回,省下检索和生成的开销。
- 异步处理:文档入库是 IO 密集型任务,用异步并发能大幅提速。
- 分级检索:先用便宜的向量检索粗筛,只对 Top 结果做重排序,避免全量重排。
- 控制上下文长度:喂给大模型的资料别太多,一般 3 到 5 个块就够,多了反而干扰且费钱。
提示:腾讯云向量数据库的计费跟存储量和计算规格相关,测试阶段用最小规格,上线前根据实际 QPS 和数据量做压测再决定规格,别一上来就买大的。
6. 关于 RAG 和知识图谱结合的一些实践思考
最近"RAG 瓶颈"和"KG 知识库"这两个词很热,很多人在讨论 RAG 和知识图谱(Knowledge Graph)怎么结合。我在项目里也做过一些尝试,分享几点真实体会。
纯 RAG 的瓶颈主要在两个地方:一是多跳推理能力弱,比如问"A 产品的负责人所在的部门今年的预算是多少",这种需要跨多个文档推理的问题,纯向量检索很难搞定;二是关系型问题处理差,问"哪些产品和 X 属于同一系列",向量检索只能找到语义相似的,找不到结构化关系。
知识图谱的优势正好补上这两块:它把实体和关系显式建模,支持多跳查询和关系推理。所以现在比较流行的做法是GraphRAG:用知识图谱做实体关系检索,用向量库做语义检索,两者融合。
不过我要泼盆冷水:知识图谱的构建成本很高,需要实体抽取、关系抽取、图谱维护,对数据质量要求也高。如果你的场景只是简单的文档问答,纯 RAG 完全够用,别为了追热点硬上知识图谱。判断标准是:你的问题需不需要"跨文档推理"和"关系查询",需要才上图谱,不需要就别折腾。
如果确实要上,一个务实的路径是:先用纯 RAG 跑起来,收集用户真实问题,分析哪些问题是纯 RAG 答不好的,再针对性地补知识图谱。这样投入产出比最高,也不会一上来就被复杂的图谱工程劝退。
7. 我在实际落地中总结的几条经验
最后分享几条踩坑踩出来的经验,都是文档里不会写、但实际项目里特别管用的。
第一,先小范围验证再全量铺开。别一上来就把公司所有文档都灌进去。先挑一个部门、一类文档做试点,跑通链路、调好参数,再逐步扩展。我见过团队一次性灌了几十万文档,结果检索效果一塌糊涂,排查起来无从下手。
第二,建立评估集。准备 50 到 100 个真实问题和标准答案,每次调整参数后跑一遍评估集,看准确率变化。没有评估集,你调参就是盲人摸象,改好改坏全凭感觉。
第三,日志要记全。用户问了什么、检索到什么、模型答了什么,全都要记下来。这些日志是你后续优化的金矿,能帮你发现检索盲区和模型幻觉的高发场景。
第四,别迷信参数,多信数据。chunk_size 到底设 300 还是 500,Top-K 设 3 还是 5,没有标准答案,取决于你的文档特点和用户问题分布。用评估集测出来的结果才是真的。
第五,给用户一个反馈入口。答案旁边放个"有用/没用"的按钮,收集用户反馈。这些真实反馈比任何离线评估都值钱,能帮你快速定位问题。
这套基于腾讯云向量数据库的 RAG 方案,我从环境搭建到上线调优完整走过一遍,整体下来最大的感受是:RAG 的门槛不在技术,而在数据治理和检索调优。把文档清洗干净、切分合理、检索策略调好,效果自然就上来了。工具和框架都是现成的,真正花时间的是那些琐碎但关键的细节。