基本流程:
- 文档加载,按条件切割成片段
- 将切割的文本片段灌入检索引擎
- 封装检索接口
- 构造调用流程:Query -> 检索 ->Prompt->LLM->回复
检索引擎:Elasticsearch
Elasticsearch 是一个分布式搜索和分析引擎,专门用于处理大规模的数据搜索、日志分析和数据存储。它基于 Apache Lucene 库构建,提供了实时的全文搜索功能,可以在大规模数据集上进行高效的搜索、索引和分析。
下载链接:Download Elasticsearch | Elastic
下载完成后 bin内运行elasticsearch
mac系统提醒打不开,在系统设置里,找到隐私与安全性,点仍要打开
我下载的是8.15.1版本的,默认用了自带的jdk,但是jdk打不开,只能用我本地自己的jdk
解决方法:进入终端,cd到bin目录
vim elasticsearch-env
找到以下代码块并修改为:
if [ ! -z "$JAVA_HOME" ]; then
JAVA="$JAVA_HOME/bin/java"
JAVA_TYPE="JAVA_HOME"
if [ ! -x "$JAVA" ]; then
echo "could not find java in $JAVA_TYPE at $JAVA" >&2
exit 1
fi
# 检查用户指定的 JDK 版本
"$JAVA" -cp "$ES_HOME/lib/java-version-checker/*" org.elasticsearch.tools.java_version_checker.JavaVersionChecker
else
# 默认使用捆绑的 JDK
if [ "$(uname -s)" = "Darwin" ]; then
# macOS 有不同的结构
JAVA="$ES_HOME/jdk.app/Contents/Home/bin/java"
else
JAVA="$ES_HOME/jdk/bin/java"
fi
JAVA_TYPE="bundled JDK"
fi
配置好后重启elasticsearch
终端显示了 用户名和密码,访问https://localhost:9200,如果出现了这个错误,就改为 https 不是 http(未加密)
[WARN ][o.e.h.n.Netty4HttpServerTransport] [MacBook-Pro.local] received plaintext http traffic on an https channel, closing connection Netty4HttpChannel{localAddress=/[0:0:0:0:0:0:0:1]:9200, remoteAddress=/[0:0:0:0:0:0:0:1]:51677}
显示以下内容则说明ok了
接下来我们在项目中新建一个.env文件
ELASTICSEARCH_BASE_URL='https://localhost:9200'
ELASTICSEARCH_PASSWORD='xxx' # 替换成自己的
ELASTICSEARCH_NAME='xxx' # 替换
使用load_dotenv引入配置环境
from dotenv import load_dotenv
load_dotenv()
# 引入配置文件
ELASTICSEARCH_BASE_URL = os.getenv('ELASTICSEARCH_BASE_URL')
ELASTICSEARCH_PASSWORD = os.getenv('ELASTICSEARCH_PASSWORD')
ELASTICSEARCH_NAME= os.getenv('ELASTICSEARCH_NAME')
ok 接下来我们就可以进行rag的项目流程了
pdf预处理函数
# pdf预处理
def extract_text_from_pdf(filename, page_numbers=None, min_line_length=1):
'''从pdf文件中 按指定页码提取文字'''
paragraphs = []
buffer = ""
full_text = ""
# 提取全部文本
for i, page_layput in enumerate(extract_pages(filename)):
# 如果指定了页码范围,跳过范围外的页
if page_numbers is not None and i not in page_numbers:
continue
for element in page_layput:
# 检查当前元素是否是LTTextContainer对象,LTTextContainer对象是pdfminer中的的一个类,用于表示PDF中的文本容器
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
字符预处理函数
# 预处理函数
def to_keywords(input_string):
# 使用正则表达式替换所有非字母数字的字符为空格
no_symbols = re.sub(r'[^a-zA-Z0-9\s]', ' ', input_string)
tokenize = word_tokenize(no_symbols) # 分词
# 加载停用词表
stop_words = set(stopwords.words('english'))
ps = PorterStemmer() # 创建porterStemmer对象
# 去停用词,取词根
filter_sentence = [ps.stem(w) for w in tokenize if not w.lower() in stop_words]
return ' '.join(filter_sentence) # 用空格分隔
关键字检索函数
# 关键字检索
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"]]
灌库流程
# 1 创建 Elasticsearch 连接
es = Elasticsearch(
hosts=[ELASTICSEARCH_BASE_URL], # 服务器地址与端口
http_auth=('ELASTICSEARCH_NAME', 'ELASTICSEARCH_PASSWORD'), # 账号密码
verify_certs=False, # 忽略自签名证书验证
ssl_show_warn=False # 禁用 SSL 警告
)
# 2 定义索引名称
index_name = 'demo01'
# 3. 如果索引已存在,删除它(仅供演示,实际应用时不需要这步)
if es.indices.exists(index=index_name):
es.indices.delete(index=index_name)
# 3 创建索引
es.indices.create(index=index_name)
# 4 灌库指令
actions = [
{
"_index": index_name,
"_source":{
"keywords":to_keywords(para),
"text":para
}
}
for para in paragraphs
]
# 文本灌库
helpers.bulk(es, actions) # 批量导入
# 异步灌库
time.sleep(2)
注意:第一次运行在代码中添加以下内容
nltk.download('punkt')
nltk.download('stopwords')
查询结果
results = search("how many parameters does llama 2 have", top_n=3)
for res in results:
print(res+"\n")
成功查询