
作者:飞哥(一位喜欢讲故事的全栈开发者,擅长把复杂的代码翻译成“人话”)
阶段:GenAI 与大模型应用
关键词:RAG, Embedding, 向量数据库, 知识库问答
大家好,我是飞哥。
前两周我们学会了让 AI “好好说话”(Prompt)和“动手干活”(Function Calling)。
但你在实际开发中,一定遇到过这样的尴尬场景:
- 你问 ChatGPT:“评价一下我们公司最新的 V3.0 架构。”
- ChatGPT 一脸懵逼:“亲,我只知道 2023 年以前的互联网公开信息,我不认识你们公司啊。”
或者更惨的:
- ChatGPT 为了迎合你,开始一本正经地胡说八道(这叫 幻觉 Hallucination)。
怎么解决?重新训练一个模型?太贵太慢了!
最聪明的办法是:考试时允许 AI “开卷”查资料。
这就是 RAG (Retrieval-Augmented Generation,检索增强生成)。
1. 什么是 RAG?(三步理解法)
第一步:锚定已知 ⚓️
回想一下你做 “开卷考试” 的过程:
RAG 的流程一模一样:先查库,再回答。
第二步:生动类比 📖
- 纯 LLM (ChatGPT):像一个 博学但与世隔绝的老教授。他懂历史天文,但不知道今天的新闻,也不知道你公司的机密。
- RAG:就是给这位老教授配了一个 超级图书管理员。
- 当你问问题时,图书管理员先去档案室(你的私有数据库)把相关文件找出来,递给教授。
- 教授看着文件,结合自己的学识,给你一个完美的答案。
第三步:提炼骨架 🦴
RAG = 检索 (Retrieval) + 增强 (Augmented) + 生成 (Generation)
2. 核心技术:AI 怎么知道“哪段话”是相关的?
这里涉及到一个魔法概念:Embedding (嵌入/向量化)。
计算机不认识字,只认识数字。Embedding 就是把一段文字变成 一串数字坐标。
2.1 什么是 Embedding?📍
- 类比:图书馆的 索书号。
- “苹果”和“香蕉”的索书号会离得很近(都是水果)。
- “苹果”和“iPhone”的索书号也会离得很近(科技语境)。
- “苹果”和“卡车”的索书号就会离得很远。
Embedding 模型会把每一句话变成一个高维向量(比如 OpenAI 的 text-embedding-3-small 模型生成的向量长度是 1536)。
意思越相近的话,它们在数学空间里的距离(余弦相似度 Cosine Similarity)就越近。
2.2 关键步骤:切片 (Chunking) 🔪
你可能会问:“为什么不把整本书直接塞给 AI?”
- 大模型一次只能处理一定长度的文字(称为 Token,比如 4k、32k 或 128k)。
- 如果你把整本《红楼梦》一次性发给它,超出的部分它根本“看不见”,或者会直接报错。
所以,我们需要把大文档切成小块(Chunk)。
- 不仅是切断:好的切片策略会保留上下文(比如句子不能切一半,段落之间要有重叠 Overlap)。
- 常用策略:固定字符数切分、按段落切分、递归字符切分(Recursive)。
3. 实战作业:构建你的第一个“个人知识库” 📚
任务背景:你有一份本地的 Android开发规范.md,你希望做一个问答工具,能回答关于这份文档的问题。
3.1 架构流程图
#mermaid-svg-Otf6mJSDonbcSEj5{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Otf6mJSDonbcSEj5 .error-icon{fill:#552222;}#mermaid-svg-Otf6mJSDonbcSEj5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Otf6mJSDonbcSEj5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Otf6mJSDonbcSEj5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Otf6mJSDonbcSEj5 .marker.cross{stroke:#333333;}#mermaid-svg-Otf6mJSDonbcSEj5 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Otf6mJSDonbcSEj5 p{margin:0;}#mermaid-svg-Otf6mJSDonbcSEj5 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster-label text{fill:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster-label span{color:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster-label span p{background-color:transparent;}#mermaid-svg-Otf6mJSDonbcSEj5 .label text,#mermaid-svg-Otf6mJSDonbcSEj5 span{fill:#333;color:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 .node rect,#mermaid-svg-Otf6mJSDonbcSEj5 .node circle,#mermaid-svg-Otf6mJSDonbcSEj5 .node ellipse,#mermaid-svg-Otf6mJSDonbcSEj5 .node polygon,#mermaid-svg-Otf6mJSDonbcSEj5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Otf6mJSDonbcSEj5 .rough-node .label text,#mermaid-svg-Otf6mJSDonbcSEj5 .node .label text,#mermaid-svg-Otf6mJSDonbcSEj5 .image-shape .label,#mermaid-svg-Otf6mJSDonbcSEj5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Otf6mJSDonbcSEj5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Otf6mJSDonbcSEj5 .rough-node .label,#mermaid-svg-Otf6mJSDonbcSEj5 .node .label,#mermaid-svg-Otf6mJSDonbcSEj5 .image-shape .label,#mermaid-svg-Otf6mJSDonbcSEj5 .icon-shape .label{text-align:center;}#mermaid-svg-Otf6mJSDonbcSEj5 .node.clickable{cursor:pointer;}#mermaid-svg-Otf6mJSDonbcSEj5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Otf6mJSDonbcSEj5 .arrowheadPath{fill:#333333;}#mermaid-svg-Otf6mJSDonbcSEj5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Otf6mJSDonbcSEj5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Otf6mJSDonbcSEj5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Otf6mJSDonbcSEj5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Otf6mJSDonbcSEj5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Otf6mJSDonbcSEj5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster text{fill:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 .cluster span{color:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Otf6mJSDonbcSEj5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Otf6mJSDonbcSEj5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Otf6mJSDonbcSEj5 .icon-shape,#mermaid-svg-Otf6mJSDonbcSEj5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Otf6mJSDonbcSEj5 .icon-shape p,#mermaid-svg-Otf6mJSDonbcSEj5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Otf6mJSDonbcSEj5 .icon-shape rect,#mermaid-svg-Otf6mJSDonbcSEj5 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Otf6mJSDonbcSEj5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Otf6mJSDonbcSEj5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Otf6mJSDonbcSEj5 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
1.切片 Chunking
2.Embedding
3.Embedding
4.检索 Search
5.Top K 相关资料
6.生成回答
文档 Document
文本块 Chunks
向量数据库 Vector DB
用户提问 Query
问题向量
LLM 大模型
最终答案
3.2 核心组件选择
- Embedding 模型:推荐 OpenAI text-embedding-3-small(便宜、效果好)或 HuggingFace 开源模型(如 BGE, M3E)。
- 向量数据库:
- 入门/本地:ChromaDB, FAISS(轻量级,Python 直接调)。
- 生产/云端:Pinecone, Weaviate, Milvus(高性能,支持亿级数据)。
3.3 极简代码实现 (Python)
这是一个可以运行的最小化 RAG Demo(不依赖复杂的向量数据库,直接用内存模拟,方便理解原理)。
前置准备:pip install openai numpy
import os
import numpy as np
from openai import OpenAI
# 1. 初始化客户端
# 注意:这里我们需要两个客户端,因为 DeepSeek 目前只负责对话,向量化还得找 OpenAI (或其他)
# 客户端 A:负责 Embedding (使用 OpenAI)
# client_emb = OpenAI(api_key="YOUR_OPENAI_API_KEY")
# 客户端 B:负责对话生成 (使用 DeepSeek)
client_llm = OpenAI(
api_key="YOUR_DEEPSEEK_API_KEY",
base_url="https://api.deepseek.com"
)
# 2. 模拟知识库:公司内部报销政策 (这是大模型绝对不知道的私有知识)
knowledge_base = [
"出差补贴政策:一线城市(北上广深)每天补贴 300 元,其他城市每天补贴 150 元。",
"交通报销规定:高铁二等座全额报销,飞机经济舱需提前 3 天申请,打车仅限晚 9 点后。",
"餐饮报销额度:单人单餐上限 50 元,超过部分自理,禁止报销酒水。",
"住宿标准:一线城市上限 600 元/晚,非一线城市上限 400 元/晚,必须提供增值税专用发票。"
]
# 辅助函数:获取文本的 Embedding 向量 (本地模拟版,方便教学)
def get_embedding(text):
# === 教学模式:本地模拟 Embedding (无需 API Key) ===
# 原理:关键词匹配模拟向量相似度
# 定义向量的维度意义:[补贴相关, 交通相关, 餐饮相关, 住宿相关]
vector = [0.0, 0.0, 0.0, 0.0]
if "补贴" in text or "一线城市" in text:
vector[0] = 1.0
if "交通" in text or "高铁" in text or "飞机" in text or "打车" in text:
vector[1] = 1.0
if "餐饮" in text or "吃" in text or "酒水" in text:
vector[2] = 1.0
if "住宿" in text or "酒店" in text or "发票" in text:
vector[3] = 1.0
if sum(vector) == 0:
return np.random.rand(4)
return vector
# === 如果您有 OpenAI Key,可以使用下面的真实代码 ===
# response = client_emb.embeddings.create(
# input=text,
# model="text-embedding-3-small"
# )
# return response.data[0].embedding
#辅助函数:计算余弦相似度
def cosine_similarity(v1, v2):
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
def simple_chat(user_query):
print(f"\\n[无 RAG] 正在直接问大模型:{user_query} …")
response = client_llm.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": user_query},
],
temperature=0.7, # 闲聊模式可以稍微高一点,更有创造性
)
return response.choices[0].message.content
def rag_chat(user_query):
print(f"\\n[有 RAG] 正在思考问题:{user_query} …")
# === 步骤 1:检索 (Retrieval) ===
# 1.1 把用户问题变成向量
query_vector = get_embedding(user_query)
# 1.2 遍历知识库,计算相似度
similarities = []
for doc in knowledge_base:
doc_vector = get_embedding(doc)
score = cosine_similarity(query_vector, doc_vector)
similarities.append((score, doc))
# 1.3 找出最相似的 Top 1
similarities.sort(key=lambda x: x[0], reverse=True)
best_doc = similarities[0][1]
print(f"✅ 找到最相关资料 (相似度 {similarities[0][0]:.4f}):\\n -> {best_doc}")
# === 步骤 2:生成 (Generation) ===
# 2.1 组装 Prompt
prompt = f"""
你是一个公司的行政助手。请基于以下【公司内部政策】回答员工问题。
⚠️ 严格基于参考资料回答,不要编造。
参考资料:
{best_doc}
员工问题:
{user_query}
"""
# 2.2 调用 LLM
response = client_llm.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": prompt}
],
temperature=0.1 # 关键设置:RAG 任务建议设低温度,让回答更严谨,减少幻觉
)
return response.choices[0].message.content
# Main 运行
if __name__ == "__main__":
test_query = "我去上海出差,每天补贴多少钱?"
# 1. 对比:无 RAG (直接问)
answer_no_rag = simple_chat(test_query)
print(f"\\n❌ [无 RAG] AI 回答:\\n{answer_no_rag}\\n")
print("-" * 50)
# 2. 对比:有 RAG (查资料后再问)
answer_rag = rag_chat(test_query)
print(f"\\n✅ [有 RAG] AI 回答:\\n{answer_rag}")
4. 总结
下一篇预告:
基本的 RAG 做出来了,但你会发现效果有时候并不好:
- 切片切断了上下文怎么办?
- 搜出来的资料不准怎么办?
下一篇,我们将学习 RAG 进阶与评估,引入 Rerank (重排序) 和 混合检索,打造生产级别的 RAG 系统!
📢 关注“飞哥”
看得懂、学得会、用得上,我是飞哥,一个拥有 10 年经验的全栈 AI 开发者。
- 👍 点赞/收藏:方便日后复习,也防止走丢。
- 🗣️ 交流讨论:在评论区留下你的问题,飞哥看到必回!
网硕互联帮助中心






评论前必须登录!
注册