实战演练基础代码

2025年9月3日 · 2179

学习文档地址:第八章

在学习完了前面章节之后,对RAG技术已经有了比较深入的了解,现在可以通过一个项目将这些知识结合起来。 程序员在家做饭方法指南是一个由程序员发起的菜谱项目,项目收集了大大小小各类菜谱共400余道,每道菜谱都是一个MD文档,且有明确的格式要求。 我们可以通过这个项目的菜谱数据,来构建一个今天吃点啥RAG项目。

项目架构

code/C8/
├── config.py                   # 配置管理
├── main.py                     # 主程序入口
├── requirements.txt            # 依赖列表
├── rag_modules/               # 核心模块
│   ├── __init__.py
│   ├── data_preparation.py    # 数据准备模块
│   ├── index_construction.py  # 索引构建模块
│   ├── retrieval_optimization.py # 检索优化模块
│   └── generation_integration.py # 生成集成模块
└── vector_index/              # 向量索引缓存(自动生成)

数据模块

分块

因为文档都是标准的MarkDown格式,所以可以采用MarkdownHeaderTextSplitter来进行切分,按照标题能够很好的分块。

元数据

在文档中,有明确的文件夹区分,通过这个可以获取菜谱的分类,属于早餐还是主食等等;以及每篇文档都会有难度标记,这些都可以纳入元数据,为后续元数据检索做准备。

智能去重

在第一部分块之后,同一文档会被切分多块,在检索时可能会检索到同一文章的不同语句,这时候需要进行去重,只返回一篇文章。 在切分时,为每篇文章都生成一个随机ID作为parent_id,也就是每篇文章的唯一索引。 在检索去重时,通过比较parent_id是否相同进行去重,同时根据数量进行排序,优先返回数量最多的。

检索与优化

文档加载完成后,需要进行索引构建,并存入向量数据库。

索引构建

实战项目使用FAISS作为向量数据库,并将索引存到本地,后续启用时直接加载。

索引优化

实战项目采用两种检索方式的混合检索,通过RFF来融合并计算最终结果。 使用向量检索获取基础意图,使用BM25作为关键词匹配,精确查找。

元数据过滤

在文档加载时,我们像文档注入了元数据,包括难易程度、菜名、分类等,可以在此处先进行元数据筛选,然后再从筛选后的文档中进行检索,提高检索效率。

生成集成 模块

在通过检索获取到相关文档后,我们需要将用户的输入查询(用户意图)与文档内容结合,生成符合意图的高质量回答。

根据上一章提到查询路由,这里也对查询进行分发,形成三个类型:listdetailgeneral

  • 列表模式:适用于推荐类查询,返回简洁的菜品列表
  • 详细模式:适用于制作类查询,提供分步骤的详细指导
  • 基础模式:适用于一般性问题,提供常规回答

查询路由实现

核心还是让LLM基于用户的输入进行判断,通过提示词,让LLM进行分发。

def query_router(self, query: str) -> str:
    """查询路由 - 根据查询类型选择不同的处理方式"""
    prompt = ChatPromptTemplate.from_template("""
根据用户的问题,将其分类为以下三种类型之一:
1. 'list' - 用户想要获取菜品列表或推荐,只需要菜名
   例如:推荐几个素菜、有什么川菜、给我3个简单的菜
2. 'detail' - 用户想要具体的制作方法或详细信息
   例如:宫保鸡丁怎么做、制作步骤、需要什么食材
3. 'general' - 其他一般性问题
   例如:什么是川菜、制作技巧、营养价值

请只返回分类结果:list、detail 或 general
用户问题: {query}
分类结果:""")
    
    # ... (LCEL链式调用)
    return result

查询重写

与路由相似,也是让LLM基于用户输入,判断查询语句是否完整准确,否则让LLM根据提示词进行重写。

template="""
你是一个智能查询分析助手。请分析用户的查询,判断是否需要重写以提高食谱搜索效果。

原始查询: {query}

分析规则:
1. **具体明确的查询**(直接返回原查询):
   - 包含具体菜品名称:如"宫保鸡丁怎么做"、"红烧肉的制作方法"
   - 明确的制作询问:如"蛋炒饭需要什么食材"、"糖醋排骨的步骤"
   - 具体的烹饪技巧:如"如何炒菜不粘锅"、"怎样调制糖醋汁"

2. **模糊不清的查询**(需要重写):
   - 过于宽泛:如"做菜"、"有什么好吃的"、"推荐个菜"
   - 缺乏具体信息:如"川菜"、"素菜"、"简单的"
   - 口语化表达:如"想吃点什么"、"有饮品推荐吗"

重写原则:
- 保持原意不变
- 增加相关烹饪术语
- 优先推荐简单易做的
- 保持简洁性

示例:
- "做菜" → "简单易做的家常菜谱"
- "有饮品推荐吗" → "简单饮品制作方法"
- "推荐个菜" → "简单家常菜推荐"
- "川菜" → "经典川菜菜谱"
- "宫保鸡丁怎么做" → "宫保鸡丁怎么做"(保持原查询)
- "红烧肉需要什么食材" → "红烧肉需要什么食材"(保持原查询)

请输出最终查询(如果不需要重写就返回原查询):

实际效果

实战项目实际输出效果

讨论