当前位置: 首页 > news >正文

【RAG】RAG 系统的基本搭建流程(ES关键词检索示例)

RAG 系统的基本搭建流程

搭建过程:

  1. 文档加载,并按一定条件切割成片段
  2. 将切割的文本片段灌入检索引擎
  3. 封装检索接口
  4. 构建调用流程:Query -> 检索 -> Prompt -> LLM -> 回复

1. 文档的加载与切割

# !pip install --upgrade openai
# 安装 pdf 解析库
# !pip install pdfminer.six
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer


def extract_text_from_pdf(filename, page_numbers=None, min_line_length=1):
    '''从 PDF 文件中(按指定页码)提取文字'''
    paragraphs = []
    buffer = ''
    full_text = ''
    # 提取全部文本
    for i, page_layout in enumerate(extract_pages(filename)):
        # 如果指定了页码范围,跳过范围外的页
        if page_numbers is not None and i not in page_numbers:
            continue
        for element in page_layout:
            if isinstance(element, LTTextContainer):
                full_text += element.get_text() + '\n'
    # 按空行分隔,将文本重新组织成段落
    lines = full_text.split('\n')
    for text in lines:
        if len(text) >= min_line_length:
            buffer += (' '+text) if not text.endswith('-') else text.strip('-')
        elif buffer:
            paragraphs.append(buffer)
            buffer = ''
    if buffer:
        paragraphs.append(buffer)
    return paragraphs


paragraphs = extract_text_from_pdf("llama2.pdf", min_line_length=10)

for para in paragraphs[:4]:
    print(para+"\n")
######
##输出:
Llama 2: Open Foundation and Fine-Tuned Chat Models

 Hugo Touvron∗ Louis Martin† Kevin Stone† Peter Albert Amjad Almahairi Yasmine Babaei Nikolay Bashlykov Soumya Batra Prajjwal Bhargava Shruti Bhosale Dan Bikel Lukas Blecher Cristian Canton Ferrer Moya Chen Guillem Cucurull David Esiobu Jude Fernandes Jeremy Fu Wenyin Fu Brian Fuller Cynthia Gao Vedanuj Goswami Naman Goyal Anthony Hartshorn Saghar Hosseini Rui Hou Hakan Inan Marcin Kardas Viktor Kerkez Madian Khabsa Isabel Kloumann Artem Korenev Punit Singh Koura Marie-Anne Lachaux Thibaut Lavril Jenya Lee Diana Liskovich Yinghai Lu Yuning Mao Xavier Martinet Todor Mihaylov Pushkar Mishra Igor Molybog Yixin Nie Andrew Poulton Jeremy Reizenstein Rashi Rungta Kalyan Saladi Alan Schelten Ruan Silva Eric Michael Smith Ranjan Subramanian Xiaoqing Ellen Tan Binh Tang Ross Taylor Adina Williams Jian Xiang Kuan Puxin Xu Zheng Yan Iliyan Zarov Yuchen Zhang Angela Fan Melanie Kambadur Sharan Narang Aurelien Rodriguez Robert Stojnic Sergey Edunov Thomas Scialom∗

 GenAI, Meta

这段代码实现了从PDF文档中提取文本并按段落进行切割的功能,是构建RAG(检索增强生成)系统中文档处理的关键步骤。下面详细解析其工作原理和实现逻辑:


1. 环境准备

# !pip install --upgrade openai
# !pip install pdfminer.six
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer
  • pdfminer.six:PDF解析库,用于提取PDF中的文本和布局信息。
  • extract_pages:逐页解析PDF文档。
  • LTTextContainer:识别PDF中的文本块(段落或文字区域)。

2. 核心函数 extract_text_from_pdf

输入参数
  • filename:PDF文件路径。
  • page_numbers:指定提取的页码范围(可选)。
  • min_line_length:最小行长度阈值,用于过滤无意义的短行(如页码或页眉)。

步骤解析
步骤1:逐页提取文本
for i, page_layout in enumerate(extract_pages(filename)):
    if page_numbers is not None and i not in page_numbers:
        continue
    for element in page_layout:
        if isinstance(element, LTTextContainer):
            full_text += element.get_text() + '\n'
  • 遍历PDF每一页,通过LTTextContainer识别文本块。
  • 将文本块内容拼接为full_text,并用换行符分隔不同文本块。
步骤2:处理换行与断词
lines = full_text.split('\n')
for text in lines:
    if len(text) >= min_line_length:
        buffer += (' '+text) if not text.endswith('-') else text.strip('-')
    elif buffer:
        paragraphs.append(buffer)
        buffer = ''
  • 断词处理:若行以连字符-结尾,表示单词跨行,需拼接(如"inter- esting"合并为"interesting")。
  • 空格拼接:普通行首添加空格,避免直接拼接导致单词粘连。
步骤3:段落切割
  • 当遇到短行(如空行或页眉),将buffer中的内容作为一个段落存入paragraphs
  • 遍历结束后,检查buffer是否剩余内容并存入。

3. 代码调用示例

paragraphs = extract_text_from_pdf("llama2.pdf", min_line_length=10)
for para in paragraphs[:4]:
    print(para+"\n")
  • 提取llama2.pdf中所有页的文本,过滤长度小于10的短行。
  • 打印前4个段落,验证输出效果。

4. 设计优缺点

设计优点
  • 跨页处理:自动合并跨页的段落。
  • 断词修复:处理因换行导致的单词拆分。
  • 灵活过滤:通过min_line_length过滤无意义短行。
局限性
  • 依赖PDF结构:若PDF使用非标准布局(如分栏、图片内文字),提取可能不准确。
  • 段落切割逻辑:依赖空行或短行分割段落,对无空行的长文本可能不够鲁棒。

5. 拓展建议

  • 布局分析:结合LTFigureLTImage处理图片中的文本。
  • 高级分段:使用NLP工具(如spacy)基于语义分割段落。
  • 并行处理:对大文档采用多线程加速解析。

通过这段代码,可以实现基础的PDF文本提取与段落切割,为后续的向量化存储和检索增强生成(RAG)奠定基础。实际应用中需结合具体文档结构调整参数和逻辑。

2. 检索引擎

先看一个最基础的ES实现

pip install elasticsearch7

# 安装 NLTK(文本处理方法库)

pip install nltk
from elasticsearch7 import Elasticsearch, helpers
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
import nltk
import re

import warnings
warnings.simplefilter("ignore")  # 屏蔽 ES 的一些Warnings

nltk.download('punkt')  # 英文切词、词根、切句等方法
nltk.download('stopwords')  # 英文停用词库
nltk.download('punkt_tab')



def to_keywords(input_string):
    '''(英文)文本只保留关键字'''
    # 使用正则表达式替换所有非字母数字的字符为空格
    no_symbols = re.sub(r'[^a-zA-Z0-9\s]', ' ', input_string)
    word_tokens = word_tokenize(no_symbols)
    # 加载停用词表
    stop_words = set(stopwords.words('english'))
    ps = PorterStemmer()
    # 去停用词,取词根
    filtered_sentence = [ps.stem(w)
                         for w in word_tokens if not w.lower() in stop_words]
    return ' '.join(filtered_sentence)
# 此处 to_keywords 为针对英文的实现,针对中文的实现请参考 chinese_utils.py

将文本灌入检索引擎

import os, time

# 引入配置文件
ELASTICSEARCH_BASE_URL = os.getenv('ELASTICSEARCH_BASE_URL')
ELASTICSEARCH_PASSWORD = os.getenv('ELASTICSEARCH_PASSWORD')
ELASTICSEARCH_NAME= os.getenv('ELASTICSEARCH_NAME')

# 1. 创建Elasticsearch连接
es = Elasticsearch(
    hosts=[ELASTICSEARCH_BASE_URL],  # 服务地址与端口
    http_auth=(ELASTICSEARCH_NAME, ELASTICSEARCH_PASSWORD),  # 用户名,密码
)

# 2. 定义索引名称
index_name = "teacher_demo_index111"

# 3. 如果索引已存在,删除它(仅供演示,实际应用时不需要这步)
if es.indices.exists(index=index_name):
    es.indices.delete(index=index_name)

# 4. 创建索引
es.indices.create(index=index_name)

# 5. 灌库指令
actions = [
    {
        "_index": index_name,
        "_source": {
            "keywords": to_keywords(para),
            "text": para
        }
    }
    for para in paragraphs
]

# 6. 文本灌库
helpers.bulk(es, actions)

# 灌库是异步的
time.sleep(2)

实现关键字检索

def search(query_string, top_n=3):
    # ES 的查询语言
    search_query = {
        "match": {
            "keywords": to_keywords(query_string)
        }
    }
    res = es.search(index=index_name, query=search_query, size=top_n)
    return [hit["_source"]["text"] for hit in res["hits"]["hits"]]
    
results = search("how many parameters does llama 2 have?", 2)
for r in results:
    print(r+"\n")
    
### 输出
 1. Llama 2, an updated version of Llama 1, trained on a new mix of publicly available data. We also increased the size of the pretraining corpus by 40%, doubled the context length of the model, and adopted grouped-query attention (Ainslie et al., 2023). We are releasing variants of Llama 2 with 7B, 13B, and 70B parameters. We have also trained 34B variants, which we report on in this paper but are not releasing.§

3. LLM接口封装

from openai import OpenAI
import os
# 加载环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEY

client = OpenAI()
#%%
def get_completion(prompt, model="gpt-3.5-turbo"):
    '''封装 openai 接口'''
    messages = [{"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0,  # 模型输出的随机性,0 表示随机性最小
    )
    return response.choices[0].message.content

4. Prompt模版

def build_prompt(prompt_template, **kwargs):
    '''将 Prompt 模板赋值'''
    inputs = {}
    for k, v in kwargs.items():
        if isinstance(v, list) and all(isinstance(elem, str) for elem in v):
            val = '\n\n'.join(v)
        else:
            val = v
        inputs[k] = val
    return prompt_template.format(**inputs)
prompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。

已知信息:
{context}

用户问:
{query}

如果已知信息不包含用户问题的答案,或者已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。
请不要输出已知信息中不包含的信息或答案。
请用中文回答用户问题。
"""

5. RAG Pipeline

user_query = "how many parameters does llama 2 have?"

# 1. 检索
search_results = search(user_query, 2)

# 2. 构建 Prompt
prompt = build_prompt(prompt_template, context=search_results, query=user_query)
print("===Prompt===")
print(prompt)

# 3. 调用 LLM
response = get_completion(prompt)

print("===回复===")
print(response)
===Prompt===

你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。

已知信息:
 1. Llama 2, an updated version of Llama 1, trained on a new mix of publicly available data. We also increased the size of the pretraining corpus by 40%, doubled the context length of the model, and adopted grouped-query attention (Ainslie et al., 2023). We are releasing variants of Llama 2 with 7B, 13B, and 70B parameters. We have also trained 34B variants, which we report on in this paper but are not releasing.§

 In this work, we develop and release Llama 2, a collection of pretrained and fine-tuned large language models (LLMs) ranging in scale from 7 billion to 70 billion parameters. Our fine-tuned LLMs, called Llama 2-Chat, are optimized for dialogue use cases. Our models outperform open-source chat models on most benchmarks we tested, and based onour human evaluations for helpfulness and safety, may be a suitable substitute for closed source models. We provide a detailed description of our approach to fine-tuning and safety improvements of Llama 2-Chat in order to enable the community to build on our work and contribute to the responsible development of LLMs.

用户问:
how many parameters does llama 2 have?

如果已知信息不包含用户问题的答案,或者已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。
请不要输出已知信息中不包含的信息或答案。
请用中文回答用户问题。

===回复===
Llama 2有7B, 13B和70B参数。

扩展:

Elasticsearch(简称ES)是一个广泛应用的开源搜索引擎: https://www.elastic.co/

关于ES的安装、部署等知识,网上可以找到大量资料,例如: https://juejin.cn/post/7104875268166123528

关于经典信息检索技术的更多细节,可以参考: https://nlp.stanford.edu/IR-book/information-retrieval-book.html

6. 关键字检索的局限性

同一个语义,用词不同,可能导致检索不到有效的结果

# user_query="Does llama 2 have a chat version?"
user_query = "Does llama 2 have a conversational variant?"

search_results = search(user_query, 2)

for res in search_results:
    print(res+"\n")

1. Llama 2, an updated version of Llama 1, trained on a new mix of publicly available data. We also increased the size of the pretraining corpus by 40%, doubled the context length of the model, and adopted grouped-query attention (Ainslie et al., 2023). We are releasing variants of Llama 2 with 7B, 13B, and 70B parameters. We have also trained 34B variants, which we report on in this paper but are not releasing.§

 variants of this model with 7B, 13B, and 70B parameters as well.

相关文章:

  • Linux应用:文件属性
  • vtkAppendPolyData vtkMultiBlockDataGroupFilter 区别 合并数据
  • 代码贴--二叉树(链式结构)
  • 如何利用DeepSeek+OCR技术打造一款AI投标工具:自动解析招标文件并生成投标标书的工具?
  • Ubuntu无风扇工控机:解决精密仪器散热难题的利器
  • Python笔记(简单版)
  • SmolVLM2 - 将视频理解带到每个设备
  • 【从零开始学习计算机科学】计算机组成原理(四)指令系统
  • 核函数及其常见类型
  • 视觉图像处理
  • Day05 实例:正向反向连接内外网环境防火墙出入站
  • 【HarmonyOS Next】鸿蒙应用故障处理思路详解
  • Java常用集合与映射的线程安全问题深度解析
  • 【CSS3】筑基篇
  • Google参数逆向 谷歌搜索
  • C++蓝桥杯基础篇(十)
  • tcc编译器教程2 编译lua解释器
  • 如何在内网私有化离线部署AI大模型?有哪些AI模型可以在内网环境私有化离线部署?
  • 雪花算法
  • 1.python基础知识点1
  • 民间打拐志愿者上官正义遭人身安全威胁,杭州公安:已立案
  • 马上评|文玩字画竞拍轻松赚差价?严防这类新型传销
  • 张广智︱“编年事辑”:打开学人心路历程的窗户
  • 沙青青评《通勤梦魇》︱“人机组合”的通勤之路
  • AI含量非常高,2025上海教育博览会将于本周五开幕
  • 王毅谈中拉命运共同体建设“五大工程”及落实举措