☰
RAG、KAG与CAG对比:从原理到Mac知识库搭建实战
2026/10/8 9:55:38 网站建设 项目流程

RAG已经火了好几年了,但最近圈子里又开始密集出现KAG、CAG这几个新缩写。我最初看到KAG和CAG的时候也愣了一下,毕竟RAG(Retrieval-Augmented Generation,检索增强生成)的坑还没完全填平,怎么又冒出来两个变体?后来花时间把三者的前世今生、技术思路、适用场景全部捋了一遍,又在Mac上实际搭了一套完整的知识库问答系统,才算是真正摸清了它们之间的关系。

这篇文章不打算写成学术综述,就按我自己的理解路径来讲:RAG到底卡在哪、KAG和CAG分别想解决什么问题、最后再附上我在Mac上从零搭建RAG知识库的完整操作,以及一路踩过来的坑。看完你至少能分清什么时候该用RAG、什么时候更适合上结构化知识库、什么时候干脆别折腾,直接用CAG的"笨办法"反而更香。

1. RAG到底在解决什么问题,又卡在什么地方

1.1 RAG的基本链路和我们最初对它的期待

RAG的初衷其实特别朴素:大模型的参数知识是训练时固化下来的,你没法指望它记住你公司内部那些散落在几十个Wiki、几百份技术文档里的具体细节。RAG的做法是在模型回答之前,先从一个外部知识库中检索出和问题最相关的片段,把检索结果塞进上下文,让模型"看着资料回答"。

这一套链路拆开来看就是四个环节:文档解析(把PDF、Word、Markdown变成纯文本)→ 文本切分(chunking,把长文切成带重叠的小段)→ 向量化(用embedding模型把每个chunk变成向量)→ 检索排序(把用户问题也向量化,在向量库里做相似度检索)。我早期在项目里搭的第一版RAG,走的正是这条标准流程,核心代码也就几十行:

from langchain_community.vectorstores import Chroma from langchain_community.embeddings import OllamaEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter # 1. 切分文档 splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) docs = splitter.split_text(original_text) # 2. 向量化并入库 embeddings = OllamaEmbeddings(model="bge-m3") vectorstore = Chroma.from_texts(docs, embeddings, persist_directory="./kb_store")

当时我对这套流程的期待是:只要把文档喂进去,模型就能精准回答任何相关问题。但用下来的真实感受是,RAG在"广撒网"式的开放问答上确实能打——你问什么它都能给你捞出来一段相关文本,但离"靠谱"还有相当距离。

1.2 容易被低估的三个瓶颈

第一个瓶颈是召回质量不稳定。向量检索本质上是在做语义相似度匹配,它理解不了"逻辑上的相关性"。比如你问"这个接口的超时时间默认值是多少",如果你的文档里写的是"连接建立后若在30秒内未收到响应则触发重试",这个chunk在语义上确实相关,但如果你的embedding模型不够强,或者chunk切分把"超时时间默认30秒"截断了,召回结果就可能丢掉最关键的句子。

第二个瓶颈是上下文碎片化。RAG为了控制上下文长度,通常会限制召回chunk数量(比如top 5)。问题是这些chunk可能是从文档不同位置捞出来的,彼此之间的逻辑关系没有串联起来。模型看到的是"几块断裂的拼图",如果问题需要跨段落推理,比如"A模块调用B模块,B又依赖C,问整体链路的超时上限是多少",经典RAG基本就歇菜了。

第三个瓶颈是评估和调优闭环难。我见过太多项目,RAG流程跑通了,Pipeline搭起来了,但问几个刁钻问题就露馅。原因很简单:chunk_size的大小、overlap的比例、top_k的取值、embedding模型的选择,每一个参数都会显著影响效果,而这些参数之间又是耦合的。没有一套好的评测集,你根本不知道改动是变好了还是变坏了。

注意:如果你做RAG只是为了让它"能回答问题",那它及格很容易;但如果你要的是"稳定、可信、可解释地回答复杂问题",那传统向量RAG单独上阵是不够的,KAG和CAG的出现本质上就是冲着补齐这些短板来的。

2. KAG:用知识图谱给RAG装上"逻辑骨架"

2.1 KAG的核心思想和知识图谱的引入

KAG(Knowledge-Augmented Generation,知识增强生成)的出发点很直接:既然传统RAG把知识切成了互不关联的碎片,那我能不能把知识之间显式的关联关系也存进去?答案就是知识图谱(Knowledge Graph, KG)。知识图谱里节点是实体(比如"接口A"、"模块B"、"超时时间"),边是关系(比如"A调用B"、"B的超时时间是30秒"),这样当问题涉及多跳关系时,可以通过图谱路径直接推理出答案,而不是靠向量相似度去"猜"。

我在实际项目里测试过用KG替换纯向量的做法。你问纯向量RAG,"订单服务调用支付服务,支付服务又调用风控服务,如果风控超时会影响订单吗",它很可能召回两三段相关文本,然后靠模型的推理硬凑答案。但换成KG,上面的链路是天然存在的图谱路径:订单服务 →调用→ 支付服务 →调用→ 风控服务,模型只需要沿着图做路径推理,回答的准确率和可解释性都上了一个台阶。

这套东西在圈子里有很多近亲,比如GraphRAG就是把知识图谱和RAG结合,而KAG更强调同时利用**结构化知识(图谱)和非结构化知识(原始文本)**做联合增强。具体落地时,KAG的发展路径基本是:

  1. 从原始文档中用LLM抽取实体、关系、属性,构建知识图谱;
  2. 把图谱三元组和原始文本chunk同时入库;
  3. 回答问题时先走图谱检索(得到精确的路径证据),再辅以文本检索(补充上下文背景)。

2.2 向量知识库和结构知识库的区分,以及各自的应用场景

很多人会把"知识库"笼统地理解为"一个能查的东西",但在RAG语境下,向量知识库和**结构知识库(KG)**是两种完全不同的物种,选错了架构,后面的调优全是白费功夫。

维度向量知识库结构知识库(知识图谱/本体)
存储方式文本chunk的向量表示实体、关系、属性的三元组网络
检索原理语义相似度近似匹配图结构上的精确匹配和多跳遍历
擅长任务宽泛语义搜索、开放域问答精确事实查询、多跳逻辑推理、可解释问答
不擅长任务精确匹配、关系推理、聚合统计语义模糊表述、无明确实体的开放问题
构建成本低,几乎全自动高,需要实体抽取、关系建设、质量校验
典型场景文档问答、客服FAQ、知识库搜索企业级业务知识图谱、风控链路、合规审查

拿一个常见场景做对比:你想查"某个销售订单的审批流程涉及哪些角色"。向量知识库会把所有提到审批、订单、角色的文本段落捞出来,然后让大模型自己总结;结构知识库则直接查图谱:订单 →属于→ 销售流程 →经过→ 审批节点A(角色:销售经理)→经过→ 审批节点B(角色:财务总监)。后者不仅答案准确,还能在回答时给出完整的路径依据。

所以我现在的选型经验是:如果知识形态是"大量非结构化文档,问题偏总结、归纳、开放搜索",老老实实用向量RAG;如果知识形态是"实体关系明确、业务流程固定、问题偏精确查询和多跳推理",花力气构建KG是值得的;如果两者都有,那就做混合架构——这也是KAG最典型的落地方案。

2.3 Ontology RAG:让知识图谱更有"规矩"

搜"ontology rag"这个热词的人,多半已经在往KAG的深层探索了。Ontology(本体)可以理解为知识图谱的"Schema层",它定义了实体有哪些类型、每种类型有哪些属性、类型之间允许存在哪些关系。没有Ontology的KG,LLM抽取出来的关系可能是混乱不可控的;有了Ontology,实体抽取和关系映射就有了明确的约束框架。

比如你在做一个技术知识库,本体里可以规定:接口有属性"超时时间"、"请求方式"、"限流阈值";接口和模块之间存在"被调用"关系;模块和模块之间存在"依赖"关系。LLM在抽取实体关系时就被约束在这些框架内,抽出来的三元组质量更高,图的一致性也更强。后续做多跳推理时,本体还能提供推理规则,比如"如果A依赖B,B依赖C,那么A间接依赖C"。这种能力纯向量RAG完全没有,也是KAG相对RAG最核心的增量之一。

实操提醒:KG/本体RAG看起来很美,但建设和维护成本真的不低。实体抽取的准确率、图谱的更新策略、关系的完整度,每一项都直接影响问答质量。如果你只是做个人知识库或者小组文档问答,我建议先别急着上KG,把2.1和2.2的选型逻辑想清楚,再决定要不要投入。

3. CAG:把检索替换成"预加载"的另一个思路

3.1 CAG是什么,它凭什么能避开RAG的检索瓶颈

CAG(Cache-Augmented Generation,缓存增强生成)这个方向我关注得比较晚,但一上手就发现它简直是"暴力美学"的典范。CAG的核心思路极其简单粗暴:既然大模型的上下文窗口越来越大(128K、200K甚至更多),那我干脆不做检索了,直接把相关文档全部塞进上下文里,让模型一次性"读完"再回答。

这听起来像是个笨办法,但它精准地绕开了RAG最大的三个痛点:检索召回不准、chunk切碎导致上下文断裂、多条文档之间的逻辑关系无法串联。CAG在上下文窗口足够大的前提下,把"检索"这一步整个砍掉,变成了"全量加载,一次读完"。

具体落地时CAG有两个关键优化点。第一个是缓存KV Cache。同一个知识集如果反复被用户问询,每次都全量拼接上下文来预填充是不划算的,所以CAG会选择把所有知识文本先做一次前向计算,把KV Cache缓存下来,后面每次新问题只需要复用这个缓存的KV状态,只对新增提问部分做预填充。第二个是知识文本的有序组织。虽然不做检索,但文档的排版顺序仍然影响模型的理解效果,我会按照从总览到细节、从抽象到具体的顺序拼接知识文档,实测这个顺序对回答质量有明显影响。

3.2 CAG、RAG、KAG三者的适用边界

这三者放在一起比较,真正有价值的不是"谁替代谁",而是各自的适用边界。

RAG适合的场景是:知识库非常庞大(几千份文档以上),切分可以容忍部分召回噪音,要求的回答不需要严格多跳推理;CAG适合的场景是:知识集固定且规模可控(比如几份核心产品文档、几十页操作手册),上下文窗口能装下,且你有相对确定的问答场景;KAG适合的场景是:知识的实体关系结构非常明确,回答需要精确、可解释、多跳推理,且你愿意投入图谱构建和维护的成本。

我用一张表来总结一下决策逻辑:

知识规模问题类型推荐方案理由
大(海量文档)开放搜索、归纳总结RAG检索可以快速缩小范围,摊薄成本
中(可完整装载)针对固定文档的精确问答CAG免去检索环节,无召回损失
中/大多跳推理、关系查询KAG/GraphRAG图谱路径提供明确推理证据
混合型复杂业务问答RAG + KG混合同时兼顾宽泛检索和精确推理

我自己在Mac上做过一个对比实验:同一套产品FAQ文档,分别用RAG(Chroma + bge-m3)和CAG(直接把整本FAQ拼进prompt)跑了一组问题。结论是,在知识规模不大(约40页文档)的前提下,CAG的回答准确率和一致性明显优于RAG,而且实现代码简单得惊人——没有向量库、没有检索逻辑、没有chunk调参,就是一段把文档读进来拼进Prompt的代码。当然,一旦文档量涨到几百份,CAG就力不从心了,上下文塞不下,成本也扛不住。

关于CAG的实用提示:如果你决定用CAG,建议抓主要矛盾——先确认你的上下文窗口充足率是否达到了知识的2倍以上(留出模型回答的空间),再用KV Cache做预加载优化,否则每问一个问题都全量计算一遍,延迟和成本都会很尴尬。

4. Mac上自己搭一套RAG知识库的完整过程

4.1 环境准备与工具选型(Mac友好版)

听说很多人是冲着"怎么在mac上搭建rag知识库"这个关键词来的。我自己的开发环境是Apple Silicon(M2芯片),整体跑RAG的体验相当顺畅,核心工具链如下:

  • LLM主模型:Ollama + qwen2.5:7b(也能直接跑llama3.1:8b),本地推理,免费且隐私安全;
  • Embedding模型:Ollama + bge-m3(中文效果好,兼容好),也可以换成nomic-embed-text;
  • 向量数据库:Chroma(轻量级,嵌入式运行,适合个人项目和原型验证);
  • 编排框架:LangChain或者LlamaIndex,新手建议先用LangChain的LCEL表达式,代码直观;
  • 文档加载:LangChain社区的PyPDFLoader、MarkdownTextSplitter等。

安装环节其实没什么神秘的,先装Ollama,再拉模型:

# 安装Ollama(也可以用官网下载器) brew install ollama # 启动服务并拉取所需模型 ollama serve ollama pull qwen2.5:7b ollama pull bge-m3

注意:如果你第一次跑Ollama拉取模型,尽量确保网络稳定,模型文件比较大,中断后可以用ollama pull重新执行续传。

4.2 文档加载、切分和入库的详细操作

这一步是整个RAG知识库的"地基"。我在实际项目中踩过一个非常深的坑:不预先清洗文档,解析出来的文本全是页眉页脚、代码残留、表格错位。所以我现在都会先做一轮文本清洗再入库,效果提升非常显著。

下面是一段可以直接复用的完整入库代码,核心步骤是加载文档→按结构切分→清洗过滤→向量化→写入Chroma:

from langchain_community.document_loaders import PyPDFLoader from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import Chroma from langchain.text_splitter import RecursiveCharacterTextSplitter import re # 1. 加载PDF文档 loader = PyPDFLoader("./product_manual.pdf") raw_docs = loader.load() # 2. 合并全文并清洗噪音文本 full_text = "\n".join([doc.page_content for doc in raw_docs]) full_text = re.sub(r"\n{3,}", "\n\n", full_text) # 合并多余空行 full_text = re.sub(r"[ \t]{2,}", " ", full_text) # 合并多余空格 # 3. 按语义结构切分:chunk_size=600, overlap=100 splitter = RecursiveCharacterTextSplitter( chunk_size=600, chunk_overlap=100, separators=["\n\n", "\n", "。", ";", ",", " ", ""], ) chunks = splitter.split_text(full_text) # 4. 向量化并持久化到本地 embeddings = OllamaEmbeddings(model="bge-m3") vectorstore = Chroma.from_texts( chunks, embeddings, persist_directory="./kb_product", collection_name="product_manual", ) vectorstore.persist() print(f"入库完成,共 {len(chunks)} 个chunk")

这段代码里有几个细节参数值得展开说说。

chunk_size为什么设600?这个值不是随手写的。chunk太小(比如200),单段信息量不足,检索时经常只命中答案的半个片段;chunk太大(比如1200),向量表示的语义容易被稀释,而且会占掉大量上下文空间。600对于大多数技术文档是一个比较稳妥的中间值。chunk_overlap设100又是为什么?因为切分点容易把关键句从中间截断,overlap的作用是把前后文的衔接信息多保留一点,给检索兜底。

separators的排列顺序也很关键。RecursiveCharacterTextSplitter会按照我给的优先级依次尝试切分,先按段落双换行,再按单换行,然后按句号、分号、逗号,最后实在不行才按空格或硬切。这样切出来的chunk基本能保持语义完整性,而不是硬生生按字数剁碎。

入库完成后,我强烈建议先做一次检索验证,不要急着写问答链路。用下面这几行代码确认向量库能正确返回相关内容:

query = "登录超时时间默认值是多少" results = vectorstore.similarity_search_with_score(query, k=3) for doc, score in results: print(f"score: {score:.4f} | content: {doc.page_content[:120]}")

如果返回的前3条都跟问题强相关,说明知识库的索引质量是达标的;如果全是无关内容,不用怀疑,多半是切分参数或embedding模型出了问题。

4.3 问答链路的实现

知识库准备好了,剩下的就是搭问答链路。我用的LangChain LCEL方式,代码非常直观:检索器把相关chunk拿回来,组装成上下文,交给LLM生成回答。

from langchain_community.llms import Ollama from langchain.prompts import PromptTemplate from langchain_core.runnables import RunnablePassthrough # 1. 加载已有向量库 from langchain_community.vectorstores import Chroma embeddings = OllamaEmbeddings(model="bge-m3") vectorstore = Chroma( persist_directory="./kb_product", embedding_function=embeddings, ) # 2. 构造检索器,取top 4 retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 3. 定义提示词模板,约束模型基于上下文回答 template = """你是产品文档问答助手。请仅依据以下知识上下文回答用户问题。 如果上下文中没有相关信息,请直接回答“资料中未找到相关信息”,不要编造。 【知识上下文】 {context} 【用户问题】 {question} """ prompt = PromptTemplate.from_template(template) llm = Ollama(model="qwen2.5:7b", temperature=0.2) # 4. 组装LCEL链路 def format_docs(docs): return "\n\n---\n\n".join([d.page_content for d in docs]) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm ) # 5. 测试问答 print(rag_chain.invoke("登录超时时间默认值是多少?"))

这里我故意把temperature调到0.2,而不是0。有些教程会让你设成0,但实测下来,LLM完全确定性的输出反而容易让表述变得生硬,0.2的随机性可以让回答更连贯,同时不会明显牺牲准确性。如果是对事实准确性要求极高的场景,可以再往下压到0.05。

整个链路跑通后,我一般会用一个10到20条问题的测试集去验证效果。注意,测试集要覆盖不同难度:简单问题(直接可从单chunk找到)、中等难度(需跨chunk拼接)、困难问题(需多跳推理)。这样才能真实评估知识库到底能不能打。

5. 我在实际项目里踩过的坑(问题排查实录)

5.1 chunk参数和检索结果的坑

问题1:检索结果经常命中无关内容。后来排查发现两个主因:一是文档清洗不够干净,很多无效文本(版权声明、页眉信息)被当成有效知识入库;二是embedding模型对中文长文本的区分度不足。解决办法是把清洗逻辑前置,同时切换chunk策略,不按固定字数切,而是尽量按段落切,保证每个chunk内部主题一致。

问题2:top_k到底取多少合适?我是经历了大起大落的。最开始取top 2,回答经常信息量不足;调到top 8,上下文一下子被无关内容塞满,模型开始"被带偏",甚至引用不相关段落。最终稳定在top 4到top 5之间,上下文质量最高。你需要理解的是,top_k并不是越大越好,它决定了上下文信噪比,而大模型的注意力很容易被无关信息干扰。

问题3:不同文档之间主题相近,怎么防止检索串味?比如一个库里既有技术文档又有销售FAQ,用户问"这个产品多少钱",技术文档里提到的"成本优化"就可能被误召回。我的处理方式是给每个chunk加metadata元数据标签(来源、文档类型),检索时用filter参数限定文档范围,精度明显提升。

5.2 硬件与embedding模型的现实问题

问题4:Mac内存不够怎么跑本地大模型?我的M2 MacBook Pro建议把Ollama的模型量化版本控制在4bit或6bit。qwen2.5:7b在量化后大概占用5GB左右内存,配合32GB内存的机器,跑起来不会有明显卡顿。如果你的Mac只有16GB内存,建议换qwen2.5:3b或更小的模型,否则Ollama会疯狂占用swap,速度和体验都会崩。

问题5:中文语义搜得不准,可能是模型没选对。我之前用all-MiniLM-L6-v2跑中文知识库,结果检索质量惨不忍睹。换bge-m3之后召回质量提升了一个档次。embedding模型和主流LLM是两套生态,不能图省事随便挑,特别是中文场景,领域专属embedding模型的差距会直接体现在问答准确率上。

问题6:Chroma本地持久化偶尔数据不完整。我遇到过重启后向量库内容丢失一部分的情况,原因多半是持久化目录没有正确落盘。稳妥做法是在每次写入后显式调用persist(),或者干脆用Chroma(persist_directory=...)时确认存储路径存在。如果知识库体量再大一点,可以考虑上Docker版PostgreSQL+pgvector,但对Mac个人项目来说Chroma完全够用。

5.3 KAG和CAG方向的扩展避坑

问题7:KAG类项目上手的最大阻力是什么?不是LLM抽取实体的效果,而是图谱的schema设计。我最初做KG抽取时,没有定义清楚本体约束,结果实体类型五花八门,关系混乱不堪,查询时根本没法用。建议是先花一天时间把本体的类别层级、关系类型、属性约束全部定好,再用LLM做抽取,而不是让LLM自由发挥。这一步做得越细,后面的推理效果越好。

问题8:CAG项目最容易犯什么错?把"不检索"理解成"无脑拼接"。我在测试CAG时发现,如果知识文本的顺序混乱、首尾割裂,模型仍然会漏掉关键信息。CAG里的文本组织本身就是一种"检索"——只不过由用户/开发者手动完成。另外,KV Cache在商业API中并不总是可用的,需要确认你的模型服务是否支持缓存复用,否则全量预填充的算力开销会让你怀疑人生。

结尾

我最初对RAG、KAG和CAG也是一知半解,但把这三条路线全部实际跑过一轮之后,最大的体会是:没有任何一种方案是银弹。RAG擅长广撒网但怕碎片化,KAG擅长精确推理但建造成本高,CAG简单直接但只适合中小规模固定知识集。不要一上来就追求最"高级"的架构,先把你的知识形态和问题类型梳理清楚,再回头看选型,通常都能找到最匹配的答案。

最后分享一个小技巧。无论你选哪种方案,建议维护一个人工的评测问答集,每次改动之后都跑一遍,把回答结果逐条检查。RAG类项目翻车大多不是因为模型不够强,而是因为改了参数或数据之后,不知道它到底是变好了还是变坏了。一套固定的评测集就是你的安全带,越早建立,后面越稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询