第3周 Day 2:RAG 入门——给 Agent 一个"图书馆"
上次用 Function Calling 让 Agent 能"自己动手",但有个问题:AI 只能回答"通用问题",不知道咱们用户的数据。
比如我问 Agent:"我上周学了哪些单词?"
Agent 说:"我不知道你的学习记录。"
因为它没有看过我的数据。
这就要用到 RAG(检索增强生成)。
今天要干啥:
- ✅ 理解 RAG 是什么(让 AI 先翻书再回答)
- ✅ 理解为什么需要 RAG(AI 不知道用户的私数据)
- ✅ 动手实现简单 RAG(关键词检索 + Prompt 增强)
- ✅ 理解 Embedding(把文字变数字,语义相似)
一、RAG 是什么?
场景对比
| 没有 RAG | 有 RAG |
|---|---|
| 我问:"我学了哪些单词?" → AI:"我不知道" | AI 先读取你的学习记录 → 回答:"你学了 cat、dog、bird" |
| 我问:"公司今年的销售额是多少?" → AI:"我没有数据" | AI 先读取公司报表 → 回答:"今年销售额 500 万" |
RAG = Retrieval-Augmented Generation(检索增强生成)
简单理解:让 AI 先"翻书",再回答问题。
二、为什么需要 RAG?
大模型有个天生的局限:它只知道训练时看过的内容,不知道用户的私数据。
| 大模型知道什么 | 大模型不知道什么 |
|---|---|
| 公开的互联网知识(百科、新闻) | 你公司的内部文档 |
| 通用的语言知识(语法、词汇) | 你的个人学习记录 |
| 代码、数学、科学知识 | 你用户的订单数据 |
RAG 的作用:把用户的数据给 AI 看,让它能回答用户的问题。
三、RAG 实战:让 Agent 能查询学习记录
我们先准备好"图书馆"(数据),然后教 Agent 如何"翻书"。
第一步:准备数据
// documents/learning_records.json
[
{"id": 1, "content": "2026-04-15 学习了 cat,意思是猫,造句:I have a cat."},
{"id": 2, "content": "2026-04-16 学习了 dog,意思是狗,造句:The dog is running."},
{"id": 3, "content": "2026-04-17 学习了 bird,意思是鸟,造句:A bird is flying."},
{"id": 4, "content": "2026-04-17 测验答错了 fish,正确答案是 fish(鱼)"},
{"id": 5, "content": "2026-04-18 学习了 apple,意思是苹果"}
]
这是咱们的"图书馆"——存放着用户的学习记录。
第二步:编写检索函数
def retrieve(query, documents, top_k=3):
"""从文档中找到相关内容"""
results = []
# 提取关键词(简单版)
keywords = extract_keywords(query)
# 遍历所有文档,找匹配的
for doc in documents:
content = doc["content"]
match_count = sum(1 for kw in keywords if kw in content)
if match_count > 0:
results.append({
"content": content,
"score": match_count
})
# 按匹配度排序,取前3条
results.sort(key=lambda x: x["score"], reverse=True)
return results[:top_k]
运行效果:
用户问:"我学了哪些单词?"
检索结果:
- "2026-04-15 学习了 cat..." (匹配度: 2)
- "2026-04-16 学习了 dog..." (匹配度: 2)
- "2026-04-17 学习了 bird..." (匹配度: 2)
第三步:增强 Prompt
def augment_query(query, retrieved_docs):
"""把检索结果加到问题里"""
if not retrieved_docs:
return f"用户问题:{query}"
# 把检索结果拼成上下文
context = "\n".join([doc["content"] for doc in retrieved_docs])
# 构建增强后的 prompt
augmented_prompt = f"""
根据以下学习记录回答用户的问题:
学习记录:
{context}
用户问题:{query}
要求:根据学习记录回答,不要编造内容。
"""
return augmented_prompt
增强后的 Prompt:
根据以下学习记录回答用户的问题:
学习记录:
2026-04-15 学习了 cat,意思是猫...
2026-04-16 学习了 dog,意思是狗...
2026-04-17 学习了 bird,意思是鸟...
用户问题:我学了哪些单词?
要求:根据学习记录回答,不要编造内容。
第四步:调用 AI 生成回答
def generate(prompt):
"""调用 AI API 生成回答"""
data = {
"model": "glm-5",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3
}
response = requests.post(API_URL, headers=API_HEADERS, json=data)
result = response.json()
return result["choices"][0]["message"]["content"]
# 完整流程
def rag_chat(query):
# 1. 检索
docs = retrieve(query, documents)
# 2. 增强
augmented_prompt = augment_query(query, docs)
# 3. 生成
answer = generate(augmented_prompt)
return answer
最终效果:
用户:我学了哪些单词?
AI:根据你的学习记录,你已经学习了以下单词:
- cat(猫)- 2026-04-15
- dog(狗)- 2026-04-16
- bird(鸟)- 2026-04-17
- apple(苹果)- 2026-04-18
四、完整代码结构
week3/02_rag/
├── app.py # 主程序
└── documents/
└── learning_records.json # 学习记录
app.py 核心逻辑:
# 完整 RAG 流程
def rag_chat(query):
print(f"\n用户问题:{query}")
# 1. 检索(Retrieval)
print("【第一步:检索】")
retrieved_docs = retrieve(query, documents)
print(f"找到 {len(retrieved_docs)} 条相关记录")
# 2. 增强(Augmented)
print("【第二步:增强】")
augmented_prompt = augment_query(query, retrieved_docs)
# 3. 生成(Generation)
print("【第三步:生成】")
answer = generate(augmented_prompt)
print(f"AI 回答:{answer}")
return answer
完整代码
# -*- coding: utf-8 -*-
"""
Week 3 Day 2: RAG 入门示例
让 Agent 能回答"我学了哪些单词"这类需要查询用户数据的问题。
RAG 流程:
1. 检索(Retrieval):从文档中找到相关内容
2. 增强(Augmented):把检索结果加到问题里
3. 生成(Generation):调用 AI 回答
运行:
python app.py
测试问题:
- 我学了哪些单词?
- 我答错了什么?
- 我什么时候学了 cat?
"""
import requests
import json
import os
# ===== API 配置 =====
API_URL = "https://coding.dashscope.aliyuncs.com/v1/chat/completions"
API_HEADERS = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-sp-xxx"
}
API_MODEL = "glm-5"
# ===== 加载文档数据 =====
# 使用脚本所在目录的相对路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DOCUMENTS_FILE = os.path.join(SCRIPT_DIR, "documents", "learning_records.json")
def load_documents():
"""从文件加载学习记录"""
if os.path.exists(DOCUMENTS_FILE):
with open(DOCUMENTS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
print(f"警告:文档文件不存在:{DOCUMENTS_FILE}")
return []
# 全局文档数据
documents = load_documents()
# ===== 第一步:检索(Retrieval) =====
def retrieve(query, documents, top_k=3):
"""
从文档中检索相关内容。
入门版:使用关键词匹配。
进阶版:会用 Embedding 进行语义搜索。
"""
results = []
# 简单关键词匹配
keywords = extract_keywords(query)
for doc in documents:
content = doc["content"]
# 计算匹配度:有多少关键词出现在文档中
match_count = sum(1 for kw in keywords if kw in content)
if match_count > 0:
results.append({
"content": content,
"score": match_count
})
# 按匹配度排序,取前 top_k 个
results.sort(key=lambda x: x["score"], reverse=True)
return results[:top_k]
def extract_keywords(query):
"""从用户问题中提取关键词"""
# 简单版:提取常见关键词
keywords = []
# 学习相关关键词
if "学" in query or "学习" in query:
keywords.append("学习")
if "错" in query or "答错" in query:
keywords.append("答错")
if "测验" in query or "考试" in query:
keywords.append("测验")
if "正确" in query:
keywords.append("正确")
# 单词关键词(提取英文单词)
for word in ["cat", "dog", "bird", "fish", "apple", "banana", "happy", "rabbit"]:
if word in query.lower():
keywords.append(word)
# 如果没有提取到关键词,就用原始问题
if not keywords:
keywords = [query]
return keywords
# ===== 第二步:增强(Augmented) =====
def augment_query(query, retrieved_docs):
"""
把检索结果加到问题里,形成增强后的 prompt。
"""
if not retrieved_docs:
# 没有找到相关文档,直接返回原问题
return f"用户问题:{query}\n\n注意:没有找到相关的学习记录,请根据已有知识回答。"
# 把检索结果格式化
context = "\n".join([doc["content"] for doc in retrieved_docs])
# 构建增强后的 prompt
augmented_prompt = f"""
根据以下学习记录回答用户的问题:
学习记录:
{context}
用户问题:{query}
要求:
1. 根据学习记录回答,不要编造内容
2. 如果学习记录中没有相关信息,就说"记录中没有找到"
3. 回答要简洁清晰
"""
return augmented_prompt
# ===== 第三步:生成(Generation) =====
def generate(prompt):
"""
调用 AI API 生成回答。
"""
data = {
"model": API_MODEL,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3 # 低温度,确保回答准确
}
try:
response = requests.post(API_URL, headers=API_HEADERS, json=data, timeout=60)
if response.status_code != 200:
return f"API 错误:{response.status_code}"
result = response.json()
return result["choices"][0]["message"]["content"]
except requests.exceptions.Timeout:
return "API 请求超时,请稍后重试"
except requests.exceptions.RequestException as e:
return f"网络错误:{str(e)}"
# ===== 完整 RAG 流程 =====
def rag_chat(query):
"""
完整的 RAG 流程:检索 → 增强 → 生成
"""
print(f"\n用户问题:{query}")
print("-" * 30)
# 1. 检索
print("【第一步:检索】")
retrieved_docs = retrieve(query, documents)
print(f"找到 {len(retrieved_docs)} 条相关记录:")
for doc in retrieved_docs:
print(f" - {doc['content']}")
print()
# 2. 增强
print("【第二步:增强】")
augmented_prompt = augment_query(query, retrieved_docs)
print("增强后的 prompt(前100字):")
print(augmented_prompt[:100] + "...")
print()
# 3. 生成
print("【第三步:生成】")
answer = generate(augmented_prompt)
print("AI 回答:")
print(answer)
return answer
# ===== 测试用例 =====
def test_rag():
"""测试 RAG 功能"""
print("=" * 50)
print("RAG 入门示例测试")
print("=" * 50)
test_questions = [
"我学了哪些单词?",
"我答错了什么?",
"我什么时候学了 cat?",
"我测验成绩怎么样?",
]
for question in test_questions:
rag_chat(question)
print("\n" + "=" * 50 + "\n")
# ===== 命令行交互 =====
def main():
print("=" * 50)
print("RAG 入门示例 - 英语学习记录查询")
print("=" * 50)
print(f"已加载 {len(documents)} 条学习记录")
print()
print("试试这些问题:")
print(" - 我学了哪些单词?")
print(" - 我答错了什么?")
print(" - 我什么时候学了 cat?")
print(" - 我测验成绩怎么样?")
print("=" * 50)
print()
# 先跑一遍测试
print(">>> 运行测试用例 <<<")
test_rag()
print("\n>>> 进入交互模式 <<<")
print("输入问题查询学习记录,输入 'exit' 退出\n")
while True:
query = input("你的问题:").strip()
if query.lower() in ["exit", "quit", "退出"]:
print("再见!")
break
if not query:
continue
rag_chat(query)
print()
if __name__ == "__main__":
main()
五、RAG 的核心技术:Embedding
上面的例子用的是关键词匹配,实际生产环境用 Embedding(向量搜索)。
1. 什么是 Embedding?
"猫" → [0.23, 0.45, 0.12, ...]
"狗" → [0.25, 0.44, 0.11, ...] (和"猫"的数字很像)
"汽车" → [0.01, 0.02, 0.88, ...] (和"猫""狗"差别很大)
原理:把文字变成数字向量,意思相近的向量距离也近。
2. Embedding 流程
1. 准备阶段:
文档 → Embedding API → 向量 → 存到向量数据库
2. 查询阶段:
用户问题 → Embedding API → 向量 → 在数据库找相似 → 返回相关文档
3. 为什么用 Embedding?
| 关键词匹配 | Embedding 匹配 |
|---|---|
| "猫" ≠ "猫咪" | "猫" ≈ "猫咪" ≈ "小猫" |
| "狗" ≠ "犬" | "狗" ≈ "犬" ≈ "狗狗" |
| 完全匹配 | 语义相似 |
4. 为什么 RAG 比 Elasticsearch 快?
你可能好奇:ES 也是用来做搜索的,为什么数据量大时 ES 很慢,RAG 却很快?
核心区别:检索方式不同
| Elasticsearch | RAG 向量检索 | |
|---|---|---|
| 原理 | 倒排索引 + 文本匹配 | 向量 Embedding + 近似最近邻(ANN) |
| 查询时 | 遍历索引,逐个匹配关键词 | 计算向量距离,找最近邻 |
| 时间复杂度 | O(N),数据量越大越慢 | O(logN) 或 O(1),数据量影响小 |
| 相似度 | 只能字面匹配 | 语义相似即可匹配 |
5. ES 为什么慢?
用户搜:"猫咪怎么养"
↓
ES 去倒排索引找包含"猫咪"的文档
↓
数据量 100万条 → 扫描 10万条包含"猫咪"的
↓
再排序、过滤、聚合...
↓
耗时:几百毫秒~几秒
ES 需要遍历文档,数据量越大,要扫描的文档越多。
6. RAG 为什么快?
用户搜:"猫咪怎么养" → 转成向量 [0.23, 0.45, ...]
↓
在向量空间里找"距离最近"的向量(用 ANN 算法)
↓
数据量 100万条 → 只需查几十次距离计算
↓
返回 Top-K 结果
↓
耗时:几毫秒~几十毫秒
关键:ANN 算法(如 HNSW)把向量空间组织成图结构,查找时像"跳台阶"一样快速定位,不需要遍历所有数据。
类比理解:
| ES 像 | RAG 向量检索像 |
|---|---|
| 图书馆的目录卡片 | 图书馆的位置地图 |
| 找"猫" → 翻所有带"猫"的卡片 | 找"猫" → 直接在地图上标出位置 |
| 卡片越多翻得越久 | 地图大小不影响查找速度 |
实际应用场景:
# ES 适合:精确匹配
"订单号 = 12345"
"用户名 = 张三"
# RAG 适合:语义搜索
"我想学动物单词" → 找到 "cat"、"dog"、"bird"
"猫怎么养" → 找到 "猫咪护理指南"(虽然没有"猫"字)
这段代码用的是关键词匹配(类似 ES),后面用 Embedding 后就变成真正的 RAG 向量检索了。
六、RAG vs 传统数据库(MySQL)
你可能还想问:RAG 和 MySQL 有什么区别?我什么时候用 MySQL,什么时候用 RAG?
核心区别:数据类型和查询方式
| MySQL | RAG | |
|---|---|---|
| 存储什么 | 结构化数据(表、行、列) | 非结构化文本(文档、知识库) |
| 查询方式 | SQL 精确匹配 | 语义相似匹配 |
| 查询语言 | SELECT * WHERE id = 123 | "帮我找关于猫的资料" |
| 结果特点 | 精确、确定 | 相似、相关 |
| 适合场景 | 订单、用户、库存 | 文档、FAQ、知识库 |
举个例子
场景:电商系统
-- MySQL:查询订单信息(精确匹配)
SELECT * FROM orders
WHERE user_id = 12345 AND status = 'paid';
-- 结果:订单号、金额、时间、状态
# RAG:查询商品使用说明(语义搜索)
用户问:"这个充电宝能带上飞机吗?"
↓
RAG 检索:找到说明书里的 "航空携带规定" 段落
↓
回答:根据产品说明书,该充电宝容量为 10000mAh,
符合民航规定,可以随身携带上飞机...
数据对比
MySQL 存储的结构化数据:
+----+----------+--------+-----------+
| id | user_name| order | amount |
+----+----------+--------+-----------+
| 1 | 张三 | A001 | 199.00 |
| 2 | 李四 | A002 | 299.00 |
+----+----------+--------+-----------+
RAG 处理的非结构化数据:
产品说明书.txt:
"本品采用锂离子电池,容量 10000mAh。
符合民航局规定,可随身携带上飞机。
请勿放入托运行李..."
用户常见问题.doc:
"Q: 充电需要多久?
A: 约 3-4 小时可充满..."
简单对比
| 用 MySQL 当你需要 | 用 RAG 当你需要 |
|---|---|
| 查订单、查用户、查库存 | 查文档、查知识、回答问题 |
| 精确匹配(id = 123) | 语义匹配(相关问题) |
| 结构化数据(表格) | 非结构化数据(文本) |
实际项目中两者经常一起用:
用户:"我上个月买的充电宝怎么充电?"
系统处理:
1. MySQL:查用户上个月订单 → 找到充电宝商品
2. RAG:检索该商品的说明书 → 找到充电说明
3. AI:整合信息,生成回答
七、RAG 和 Function Calling 的区别
| Function Calling | RAG |
|---|---|
| AI 调用函数,执行动作 | AI 检索数据,回答问题 |
| "动手"能力 | "翻书"能力 |
| 适合:查天气、发消息、存记录 | 适合:查文档、问数据、知识问答 |
结合使用:
用户:"帮我查一下我上周学了多少单词,然后发个总结到我的邮箱"
AI 处理流程:
1. 用 RAG 检索学习记录(查数据)
2. 用 Function Calling 调用邮件接口(发邮件)
3. 生成总结内容
八、明天要干的
- 用 Embedding API 实现真正的语义搜索
- 让英语 Agent 能回答"我学了什么"
写于 2026-04-28 RAG入门
最近有点懈怠!!!!