【手搓 Agent 第2.1关】搭建 Agent 进阶能力:RAG(中)

掌握 RAG 理论只是基础,很多开发者学完范式、名词、原理,依然看不懂 RAG 代码、调不通完整流程,核心问题是没有理解 RAG 最本质的数据流转逻辑。绝大多数教程一上来就堆砌向量、嵌入、框架调用,反而本末倒置,让初学者忽略了 RAG 的核心精髓。

承接上篇理论基础,本篇采用降维学习法,剥离 LangChain、向量数据库、Embedding 等复杂模块,纯原生 Python 手写极简 RAG 系统。跳过复杂数学计算,聚焦核心流程,手把手实现文档切片、检索匹配、Prompt 增强、约束式问答,让你彻底搞懂 RAG 的运行内核。

一、剥离框架的纯手工 RAG 核心流(沙盒演练)

本章旨在理解数据在 RAG 系统中是如何流转的,屏蔽了底层数学复杂性。

代码部分仍旧基于 Stage 1 建立的最小 Agent:

1. 不用切分器的字符串拆解(Chunk)

不用 langchain 调包,而是手动切分字符。

选取一段包含背景知识的长字符串,并将其放在全局变量区,在程序启动时直接把它切成一个列表(知识库)。

  • 放在哪里:放在 client = OpenAI(...) 下面,system_prompt 的上面。

示例:

# 手工 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 的正下方。

示例:

# 手工 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 变量。

示例:

system_prompt = {
    "role": "system",
    "content": "你是一个严谨的资料研究助手。请严格根据用户提供的【参考资料】回答问题。如果资料中没有相关信息,请直接回答‘根据资料,我不知道’。"
}

在核心循环中组装 Prompt

这是 RAG 发挥作用的核心地带。我们需要在用户的话发给大模型之前,先去检索资料,然后把资料塞进用户的话里。

  • 放在哪里:generate_response(prompt) 函数内部的开头,替换掉 chat_history.append({"role": "user", "content": prompt})

示例:

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=toolstool_choice="auto" 注释掉,避免模型分心去调天气工具。

4. 运行示例

【系统初始化】知识库已加载,共切分为 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 主循环。

posted @ 2026-08-11 20:34  Alkaid2077  阅读(16)  评论(0)    收藏  举报