Appearance
RAG 检索增强生成与本地/企业知识库架构
回到总览:AI 时代的工程师能力
相关模块:Prompt 工程、Tool Calling 与 Agent 记忆机制 (Memory Workflow) · Agent Tool Calling (Function Calling) 架构与安全防护
一句话定义
RAG(Retrieval-Augmented Generation,检索增强生成)是指通过将外部私有知识库文档切片(Chunking)、文本向量化(Embedding)并存入向量数据库,在用户提问时检索最相关的 Context 注入到 Prompt 中,从而消除 LLM 幻觉并赋予大模型精准解答私有/实时知识的能力。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| 最小 RAG 检索闭环 | tool-calling-rag-streaming-demo | server.js · client.js |
为什么需要
- 为什么不能直接将包含几十万字的企业文档全部塞给 LLM 的 Context Window 进行提问?
- 一句话答:盲目塞入全部文档不仅会瞬间超出上下文窗口上限并产生巨大的 Token 费用,而且会导致 LLM 遭遇“Needle In A Haystack(大海捞针)”难题,忽略中间的关键细节;RAG 只检索最相关的 Top-K 切片,精准且高效。
- RAG 如何解决 LLM 的“幻觉(Hallucination)”与“知识时效性”问题?
- 一句话答:RAG 将问题锚定在检索到的真实文档切片上,要求大模型“严格基于提供的 Context 回答”,并在答案中附带出处引用;当私有知识更新时只需更新向量库索引,无需重新训练模型。
- 移动端 / 端侧应用如何构建轻量级本地 RAG 架构?
- 一句话答:在移动端通过 SQLite 向量扩展(或 ObjectBox Vector)存储向量,配合端侧轻量 Embedding 模型(如 ONNX/BGE-small)做本地检索,彻底不依赖网络即可实现 100% 隐私安全的本地知识助手。
底层机制
1. RAG 核心三步管线 (Indexing → Retrieval → Generation)
2. 向量余弦相似度 (Cosine Similarity) 匹配原理
将文本转化为高维向量 A 与 B,通过计算向量夹角余弦值判断语义相关度:
Similarity = cos(θ) = (A·B) / (‖A‖ × ‖B‖)
余弦值越接近 1.0,说明两段文本的语义相关度越高。
数值手算示例(降维到 3 维便于理解)
假设把两句文本编码成 3 维向量:
- 查询 Q:“如何开启深色模式” →
Q = (0.9, 0.2, 0.1) - 候选 A:“设置里切换深色主题” →
A = (0.8, 0.3, 0.2) - 候选 B:“餐厅今日营业时间” →
B = (0.1, 0.2, 0.9)
计算 Q 与 A 的相似度:
text
Q·A = 0.9×0.8 + 0.2×0.3 + 0.1×0.2 = 0.72 + 0.06 + 0.02 = 0.80
|Q| = sqrt(0.81 + 0.04 + 0.01) = sqrt(0.86) ≈ 0.927
|A| = sqrt(0.64 + 0.09 + 0.04) = sqrt(0.77) ≈ 0.877
cos = 0.80 / (0.927 × 0.877) ≈ 0.80 / 0.813 ≈ 0.984 ← 语义接近计算 Q 与 B 的相似度:
text
Q·B = 0.9×0.1 + 0.2×0.2 + 0.1×0.9 = 0.09 + 0.04 + 0.09 = 0.22
|B| = sqrt(0.01 + 0.04 + 0.81) = sqrt(0.86) ≈ 0.927
cos = 0.22 / (0.927 × 0.927) ≈ 0.22 / 0.859 ≈ 0.256 ← 语义很远- 观察重点:检索 Top-K 时按余弦值降序取前 K 条——上面例子中 A 的
0.984远高于 B 的0.256,A 会被选中作为上下文;真实场景向量有几百到几千维,但计算思想完全相同。归一化向量后,内积即可代替余弦(长度恒为 1),这也是很多向量库的实际做法。
3. 仓库内最小 RAG 样板是怎么收口的
当前仓库里没有单独接入真实向量库,也没有为了这个主题额外起一套 embedding 服务。最合理的最小落地,是在 Node demo 中保留 RAG 的工程骨架:
- 遍历
docs/读取 Markdown。 - 以标题块为单位做
chunkMarkdown()。 - 把 query 和 chunk 都转成关键词向量。
- 用
cosineSimilarity()排序取 Top-K。 - 把检索结果带着来源路径回灌给最终回答。
js
function searchDocs(query, limit) {
const files = walkMarkdownFiles(DOCS_DIR);
const queryVector = buildKeywordVector(query);
const matches = [];
for (const filePath of files) {
const content = fs.readFileSync(filePath, 'utf8');
const chunks = chunkMarkdown(content);
chunks.forEach((chunk, index) => {
const score = cosineSimilarity(queryVector, buildKeywordVector(chunk));
if (score > 0) {
matches.push({ file: ..., chunkIndex: index, score, excerpt: ... });
}
});
}
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
}- 可能执行顺序
- 用户问题进入检索层。
- 问题与文档 chunk 生成简化向量。
- 余弦相似度排序取 Top-K。
- 把命中片段拼进最终回答。
- 可能输出text
[tool_result] { "matches": [ { "file": "docs/08-ai-engineering/04-rag-and-knowledge-retrieval.md", "chunkIndex": 6, "score": 0.44, "excerpt": "...Chunk Overlap..." } ] } - 预期现象:问仓库已有知识点时,会命中对应文档片段并带出处;问完全无关的问题时,应诚实返回“基于当前知识库无法回答”。
- 观察重点:这套样板没有真实 embedding 模型,因此它验证的是RAG 工程分层,不是语义检索质量上限。
代码示例:文本切片与 Context 组装
kotlin
fun buildRagPrompt(userQuery: String, retrievedChunks: List<String>): String {
val contextText = retrievedChunks.joinToString("
---
")
return """
你是一个严谨的技术专家。请严格仅依据以下给出的参考上下文回答问题。
如果参考上下文中没有包含问题的答案,请坦诚回答“基于现有资料无法回答”。
【参考上下文】:
$contextText
【用户问题】:
$userQuery
""".trimIndent()
}Android / Flutter / Web / Backend 对照
| 维 | 移动端本地 RAG (端侧) | 企业级服务端 RAG |
|---|---|---|
| 向量数据库 | SQLite sqlite-vss / ObjectBox Vector | Milvus / Qdrant / Pinecone |
| Embedding 模型 | ONNX Runtime (MobileBert / BGE-small) | OpenAI text-embedding-3-small / Cohere |
| LLM 引擎 | On-Device LLM (ExecuTorch / MLC-LLM) | Claude / GPT / Gemini 等服务端模型 |
常见场景
1. 企业私有技术文档与 API 规范智能问答
员工提问“接口鉴权失败返回 40102 怎么处理”,RAG 检索 API 规范文档中的 40102 错误码定义与排错步骤,注入 Prompt 后大模型精准生成排错指导。
常见误配、事故后果与排障
1. 事故:Chunk 切片过大或没有重叠 (Overlap) 导致语义断裂
- 误配原因:按固定 2000 字简单切片,且未设置重叠区域(Overlap = 0)。
- 后果:关键的句子或代码块恰好在切片边界被强行砍成两半,检索到的上下文缺失了上文或下文,导致 LLM 生成的回答发生严重的逻辑混乱。
- 排障与修法:使用按 Markdown 标题语义切片(RecursiveCharacterTextSplitter),并设置
chunkSize = 500,chunkOverlap = 100。
2. 误配:把“接上向量库”当成 RAG 完成,忽略来源引用与未命中收口
- 后果:系统虽然“看起来在检索”,但回答不附出处,未命中时还继续硬答,用户无法判断可信度。
- 排障与修法:返回 Top-K 时带
file/chunkIndex等来源信息;无命中时显式返回“基于现有资料无法回答”,不要让生成层裸奔。
与相近概念对比
| 技术方案 | 成本 | 实时性 | 适合场景 |
|---|---|---|---|
| RAG 检索增强 | 低 (仅向量计算与少量 Token) | 高 (数据修改后索引立刻生效) | 企业私有文档、API 手册、频繁更新的知识 |
| Fine-tuning 微调 | 极高 (需要 GPU 训练) | 极低 (需重新训练模型) | 改变模型的输出风格、特定领域语法结构 |
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| tool-calling-rag-streaming-demo | 最小 RAG:chunk、Top-K、来源引用与 SSE 流式输出(与文首「代码索引」一致) | server.js · client.js |
复习检查题
在 RAG 系统中,为什么在进行文本切片 (Chunking) 时需要设置“重叠区域 (Chunk Overlap)”?
答:设置重叠区域(如切片大小 500 字符,重叠 100 字符)是为了防止关键的语义、句子或代名词引用在切片边界处被强行切断。重叠区域能保证前一个切片的末尾上下文保留在后一个切片的开头,从而维持了切片之间语义的连贯性,避免检索时丢失上下文导致 LLM 误读。
为什么说对于企业私有文档问答,RAG 比直接对大模型进行微调 (Fine-tuning) 更加高效?
答:原因有两个:第一,成本与实时性,企业文档经常更新,RAG 只需要重新计算变更文档的向量并存入数据库,秒级生效;而微调需要昂贵的 GPU 重新训练模型,耗时极长;第二,防幻觉与可追溯性,RAG 可以在回答中明确给出引用的具体文档切片与页码(支持 Source Attribution),而微调把知识变成了模型的黑盒参数,依然容易产生幻觉且无法验证出处。
速记
- RAG 三步走:文档切片向量化 (Indexing) → 相似度检索 Top-K (Retrieval) → 注入 Prompt 生成 (Generation)。
- 切片留重叠:使用语义切片并保留 Overlap,防止边界语义断裂。
- RAG vs 微调:RAG 适合频繁更新的私有知识,微调适合改变模型语言风格。