一、基础RAG流程
1、首先加载需要的外部知识文本文件,利用合适的分割方式将文本划分为多个文本块,建立索引并构建外部文本知识库;
2、基于合适的检索算法来构建检索器组件,用于获取与用户查询最相关的文本块;
3、结合用户查询和相关的文本块生成大模型的Prompt提示,最终输入到大模型并返回答案结果。
二、LangChain框架简介
LangChain是一个集成的开发框架,用于驱动LLM进行应用程序的开发。具体详情自行阅读官方文档。
三、利用langchain框架实现基础的RAG知识库问答
本案例是基于上市公司的年度报告数据,构建知识库文档,利用BM25算法从文档中检索查询相关的文本片段,嵌入到LLM提示中,引导LLM生成相应的回答。
1、数据获取和处理
数据的获取和预处理请查看本人之前发布的一篇文章,点击此链接即可跳转至相关页面。
2、导入相关的第三方库
import pandas as pd
import numpy as np
import re
import jieba
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.retrievers import BM25Retriever, TFIDFRetriever
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import RetrievalQA
3、加载报告文档
text_path = './test_contents.txt'
loader = TextLoader(text_path, encoding='utf-8')
docs = loader.load()
4、自定义文档分割组件,划分文档块
class ReportTextSplitter(CharacterTextSplitter):
"""自定义报告文本分割器"""
def __init__(self, sentence_size: int = 250, **kwargs):
super().__init__(**kwargs)
self.sentence_size = sentence_size
def split_text(self, text: str):
split_rules = '\n第[一二三四五六七八九十]|\n[一二三四五六七八九十]|\n[123456789]、'
ls = re.split(split_rules, text)
for ele in ls:
if len(ele) > self.sentence_size:
ele1_ls = ele.split("\n([一二三四五六七八九十])|\n\([一二三四五六七八九十]\)|\n([123456789])")
for ele_ele1 in ele1_ls:
if len(ele_ele1) > self.sentence_size:
l = list(range(0, len(ele_ele1), self.sentence_size))
r = list(range(self.sentence_size, len(ele_ele1)+self.sentence_size, self.sentence_size))
ele2_ls = [ele_ele1[l:r] for l, r in zip(l, r)]
ele_id = ele1_ls.index(ele_ele1)
ele1_ls = ele1_ls[:ele_id] + [i for i in ele2_ls if i] + ele1_ls[ele_id + 1:]
idx = ls.index(ele)
ls = ls[:idx] + [i for i in ele1_ls if i] + ls[idx + 1:]
return ls
chunk_size = 500 # 设置文本块最大长度
splitter = ReportTextSplitter(sentence_size=chunk_size)
split_docs = splitter.split_documents(docs)
5、构建检索器组件
本方案利用BM25检索算法来查找相似的文本块内容,我们利用langchain框架提供的BM25Retriever检索组件来构建我们的检索器,代码如下:
def cut_words(text):
"""
利用jieba库进行文本分词
"""
return jieba.lcut(text)
# k是检索器返回的相关文本块数量,preprocess_func是文本的预处理函数
retriever = BM25Retriever.from_documents(split_docs, preprocess_func=cut_words, k=3)
6、定义ChatGLM大模型接口调用
如果你有OpenAI的API key,可以使用OpenAI的大模型;若没有,可以去ChatGLM大模型官网申请相应的大模型接口应用API key,新用户注册可以免费领取文本tokens数量使用。具体的申请操作流程自行查询相关文章介绍,此处不再赘述。
申请完成后,根据如下代码构建大模型接口调用:
import time
import jwt
def generate_token(apikey: str, exp_seconds: int):
"""
动态生成token令牌,设置令牌有效时间
"""
try:
id, secret = apikey.split(".")
except Exception as e:
raise Exception("invalid apikey", e)
payload = {
"api_key": id,
"exp": int(round(time.time() * 1000)) + exp_seconds * 1000,
"timestamp": int(round(time.time() * 1000)),
}
return jwt.encode(
payload,
secret,
algorithm="HS256",
headers={"alg": "HS256", "sign_type": "SIGN"},
)
token_key = "****************************" # 此处设置自己申请的token key
llm = ChatOpenAI(
model_name="glm-4",
openai_api_base="https://open.bigmodel.cn/api/paas/v4",
openai_api_key=generate_token(token_key, exp_seconds=3000),
streaming=False,
temperature=0.01 # 此参数设置大模型的幻觉程度大小,一般知识库问答设置小一些,回答要准确
)
7、生成Prompt提示模板(可自行根据需求定义)
qa_sys_prompt = """你是文档问答系统高级专家。根据以下原则、步骤及示例,你可以通过在文档中查找相关内容并仿造示例进行输出。
原则:
1、输出一定在提供的文档内容中,且只能根据提供的文档内容来问答问题;
2、直接回答问题,不需要列出中间的思考过程,不需要拓展,不需要输出"根据提供的文档内容"等前缀语句;
3、同一个问题,文本中有不同的答案,需要综合起来回答;
4、问题答案在文档中是分点/条、系列措施等列举时,需要全部列出不要遗漏;
5、若无法在文档中找到相关内容,请直接回答文档中未提及相关的问题内容即可,不需要做任何其他补充,也不要出现"因此"等字符做解释。
步骤:
1、判断下面问题中是否有多个问题,若存在多个问题,将问题拆分出多个问题;
2、根据拆分出来的问题,精确定位文档中的内容,可能存在一个或多个位置,存在多个时需要都找出来;
3、根据定位出来的位置前后内容,回答每个问题;
4、优先根据文档原文来回答每个问题;若没有,再概况总结回答。
示例:
下面将提供示例文档内容、问题及输出,仿照样例输出
文档内容:(四)研发机构设置
本公司设科学委员会和研发中心负责产品研发。科学委员会由本公司首席科
学家及核心技术人员组成,主要负责研发战略的制定、研发项目立项评审、考核
与项目重大变更等。
问题:云南沃森生物技术股份有限公司负责产品研发的是什么部门?
输出:云南沃森生物技术股份有限公司负责产品研发的是科学委员会和研发中心部门。
现在开始,根据下面的文档内容,回答问题:
文档内容:{context}
问题:{question}
"""
qa_prompt = ChatPromptTemplate.from_template(qa_sys_prompt)
8、构建问答链,生成答案
基于langchain框架的RetrievalQA组件建立知识库文档问答链,最后调用生成答案。代码如下:
# return_source_documents参数表示是否需要输出检索到的文本块内容
qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=retriever, return_source_documents=True,
chain_type_kwargs={"prompt": qa_prompt})
question = '公司2019年营业外收入是多少元?'
qa_chain.invoke(question)
生成的结果如下图所示:
其中,query为用户输入的查询文本,result是模型最终的回答结果,source_documents则是检索到的文本块内容信息。
以上就是基础的RAG知识库问答实现代码,希望能够帮助到大家,后续会继续更新大模型RAG系列的相关技术文章。