掌握 RAG 理论只是基础,很多开发者学完范式、名词、原理,依然看不懂 RAG 代码、调不通完整流程,核心问题是没有理解 RAG 最本质的数据流转逻辑。绝大多数教程一上来就堆砌向量、嵌入、框架调用,反而本末倒置,让初学者忽略了 RAG 的核心精髓。
承接上篇理论基础,本篇采用降维学习法,剥离 LangChain、向量数据库、Embedding 等复杂模块,纯原生 Python 手写极简 RAG 系统。跳过复杂数学计算,聚焦核心流程,手把手实现文档切片、检索匹配、Prompt 增强、约束式问答,让你彻底搞懂 RAG 的运行内核。
一、剥离框架的纯手工 RAG 核心流(沙盒演练)
本章旨在理解数据在 RAG 系统中是如何流转的,屏蔽了底层数学复杂性。
代码部分仍旧基于 Stage 1 建立的最小 Agent:上,中,下。
1. 不用切分器的字符串拆解(Chunk)
不用 langchain 调包,而是手动切分字符。
选取一段包含背景知识的长字符串,并将其放在全局变量区,在程序启动时直接把它切成一个列表(知识库)。
- 放在哪里 :放在
client = OpenAI(...)下面,system_prompt的上面。
示例:
python
# 手工 RAG:构建知识库
RAW_TEXT = "小麦存储标准:温度 8~22℃,湿度≤65%,仓内 CO₂不超 1200ppm;超标满 2 小时触发三级告警,推送短信与企业微信消息。"
# 手动 Chunking:这里我们用分号";"作为切分点,将其切成两个短句(Chunk)
knowledge_base = RAW_TEXT.split(";")
print(f"【系统初始化】知识库已加载,共切分为 {len(knowledge_base)} 个 Chunk。")
2. 不用向量的暴力检索(Retrieval)
写一个最简单的函数,遍历刚才的 knowledge_base,看看哪个 Chunk 包含用户提问的关键词。
- 放在哪里: 和
get_weather函数做伴,可以放在get_weather的正下方。
示例:
python
# 手工 RAG:暴力检索函数
def retrieve(query: str, kb: list) -> str:
"""
极简检索:只要 Chunk 里包含了 query 中提到的核心关键词,就认为匹配成功。
"""
# 1. 我们先定义几个业务相关的核心关键词(充当极简版的词表)
keywords = ["温度", "湿度", "告警", "CO2", "小麦", "短信"]
# 2. 看看用户的提问里,命中了上面哪几个关键词
user_keywords = [kw for kw in keywords if kw in query]
# 如果用户没提到任何关键词,直接返回空,不去检索了
if not user_keywords:
return ""
# 3. 遍历知识库里的每一个 Chunk
for chunk in kb:
# 只要这个 chunk 里包含了用户提到的任意一个关键词,我们就把它当做参考资料返回
if any(kw in chunk for kw in user_keywords):
return chunk
# 4. 如果整个 for 循环都跑完了,还是没 return 出去,说明没匹配到,最后再返回空字符串
return ""
3. 组装 Grounded Answer Prompt 强制引用
这是 RAG 最核心的"灵魂欺骗"术。大模型本身不知道什么是"知识库",你需要通过系统提示词逼迫它。
修改 System Prompt:
现在的 system_prompt 是为了强迫模型无限循环查询天气。我们需要把它换成严谨的研究助手,替换现有的 system_prompt 变量。
示例:
python
system_prompt = {
"role": "system",
"content": "你是一个严谨的资料研究助手。请严格根据用户提供的【参考资料】回答问题。如果资料中没有相关信息,请直接回答'根据资料,我不知道'。"
}
在核心循环中组装 Prompt:
这是 RAG 发挥作用的核心地带。我们需要在用户的话发给大模型之前,先去检索资料,然后把资料塞进用户的话里。
- 放在哪里: 在
generate_response(prompt)函数内部的开头,替换掉chat_history.append({"role": "user", "content": prompt})。
示例:
python
def generate_response(prompt):
try:
# 1. 拦截用户的 prompt,先去知识库里检索
retrieved_chunk = retrieve(prompt, knowledge_base)
# 2. 组装 Grounded Answer Prompt
if retrieved_chunk:
print(f"🔍 检索到相关资料: {retrieved_chunk}")
augmented_prompt = f"【参考资料】{retrieved_chunk}\n用户问题:{prompt}"
else:
print("🔍 未检索到相关资料。")
augmented_prompt = prompt
# 3. 将组装后的超级 Prompt 存入对话历史
chat_history.append({"role": "user", "content": augmented_prompt})
# ... 保持你原有的 client.chat.completions.create 逻辑不变 ...
特别提示: 因为我们在做纯 RAG 测试,你可以暂时把 client.chat.completions.create 里的 tools=tools 和 tool_choice="auto" 注释掉,避免模型分心去调天气工具。
4. 运行示例
text
【系统初始化】知识库已加载,共切分为 2 个 Chunk。
AI对话程序,输入 quit 结束对话
你:你好,我叫 Alkaid,想向你咨询小麦存储标准。我想知道小麦需要在什么样的温度和湿度下储存。
🔍 检索到相关资料: 小麦存储标准:温度 8~22℃,湿度≤65%,仓内 CO₂不超 1200ppm
❌ 模型无需调用工具,直接文字回答
AI: 你好 Alkaid,根据提供的参考资料,小麦存储所需的条件如下:温度:8~22℃,湿度:≤65%
你:哦,那么如果超标会告警吗?会给我发短信吗?
🔍 检索到相关资料: 超标满 2 小时触发三级告警,推送短信与企业微信消息。
❌ 模型无需调用工具,直接文字回答
AI: 根据资料,若超标满 2 小时会触发三级告警,届时会推送短信与企业微信消息。
注:
在这个环节中,我们选择跳过 Embedding 和余弦相似度以避免它分散你对"信息流转(Pipeline)"的注意力:
Embedding 的本质是复杂的数学黑盒: 要真正在本地从零写一个现代语义 Embedding 模型,需要用 PyTorch 去写一个 Transformer 神经网络的前向传播,涉及极其复杂的矩阵乘法,偏离我们学习 Agent 和 RAG 组装的初衷。
RAG 的灵魂在于"欺骗"大模型,而不是搜索算法: 很多初学者误以为 RAG 最伟大的是"向量检索",但实际上,RAG 最核心的机制是"把搜索到的文本,悄悄塞进 Prompt 里,逼迫大模型基于此回答"。
降维打击学习法: 用 Python 最简单的
in关键字(关键词匹配)代替 Embedding 向量检索,你可以用 3 行代码完成"检索"这一步。这样你就能把 100% 的精力集中在:文本如何切分(Chunking)、如何拼接进 Prompt(Grounded Answer)以及大模型看到这个 Prompt 后的反应。
二、本篇总结 & 下期预告
通过本篇手写沙盒案例,我们成功实现了最简可用的 RAG 能力,彻底厘清了"切片-检索-增强-生成"的完整链路,摆脱了对框架的黑盒依赖。但当前极简版 RAG 仅能实现基础关键词匹配,无法支撑大规模文档、语义检索、持久化知识库等工业级需求。
下一篇 Stage 2 - RAG下篇,我们将完成从玩具demo到工业级落地的升级,基于 LangChain + 本地向量库搭建完整版 RAG 系统,实现文档智能分块、本地向量化、持久化存储、语义检索,并无缝对接我们 Stage 1 搭建的 Agent 主循环。