现在各行各业纷纷选择接入大模型,其中最火且可行性最高的形式无异于智能文档问答助手,而LangChain是其中主流技术实现工具,能够轻松让大语言模型与外部数据相结合,从而构建智能问答系统。ERNIE Bot SDK已接入文心大模型4.0能力,同时支持对话补全、函数调用、语义向量等功能。
本教程是基于文心一言ERNIE Bot SDK与LangChain构建基于Embedding Vector方式的文本问答系统, 整体可以解构为三部分。
1、基于ERNIE Bot与LangChain结合的Embedding,获取向量矩阵并保存
2、基于用户问题,在向量矩阵库中搜寻相近的原文句子
3、基于检索到的原文与Prompt结合,从LLM获取答案
背景介绍
问答系统处理流程
加载文件 -> 读取文本 -> 文本分割 -> 文本向量化 -> 问句向量化 -> 在文本向量中匹配出与问句向量最相似的top_k个 -> 匹配出的文本作为上下文和问题一起添加到Prompt中 -> 提交给LLM生成回答
技术工具
ERNIE Bot SDK
ERNIE Bot SDK 提供便捷易用的接口,可以调用文心大模型的能力,包含文本创作、通用对话、语义向量、AI作图等。
LangChain
LangChain 是一个强大的框架,旨在帮助开发人员使用语言模型构建端到端的应用程序。它提供了一套工具、组件和接口,可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互,将多个组件链接在一起,并集成额外的资源,例如API和数据库。
项目代码
环境准备
安装相关库
!pip install -qr requirements.txt
读取 access_token
在星河社区的控制台访问令牌中找到自己的access_token,替换access_token.txt或下面代码中的access_token。
fileName='access_token.txt'
access_token=''
if len(access_token)==0:with open(fileName,'r') as f:#逐行读取文件内容lines = f.readlines()for line in lines:if line[:13]=='access_token=':access_token=line[13:]
assert len(access_token)>10
print('access_token:',access_token)
LangChain及Embedding部分
获取文档载入器
使用GetLoader(source)获取LangChain中的Loader,GetLoader会根据source类型,调用对应的LangChain文本载入器。
创建或载入向量库
引入Embeddings函数并切分文本,chunk_size按ERNIE Bot SDK要求设为384
text_splitter = RecursiveCharacterTextSplitter(chunk_size=ernieChunkSize, chunk_overlap=0)
splits = text_splitter.split_documents(documents)
获取整个文档或网页的Embedding向量并保存。
embeddings=ErnieEmbeddings(access_token=access_token)# vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())vectorstore = Chroma.from_documents(persist_directory=persist_directory,documents=splits, embedding=embeddings)
根据用户问题获取文档中最相近的原文片段
使用了LangChain中的similarity_search_with_score就可以获取所需的top_k个文案片段,并且返回其score。结果显示score差别不是很大。
def searchSimDocs(query,vectorstore,top_k=3,scoreThershold=5):packs=vectorstore.similarity_search_with_score(query,k=top_k)contentList=[]for pack in packs:doc,score=packif score<scoreThershold:##好像设置5,基本都会返回contentList.append(doc.page_content)# print('content',contentList)return contentList
具体Embedding代码见下方:
import osos.environ['access_token']=access_token
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_embedding_ErnieBotSDK import ErnieEmbeddings# Load documentsfrom langchain.document_loaders import WebBaseLoader
from langchain.document_loaders.text import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
import erniebot## https://python.langchain.com/docs/integrations/chat/ernie# loader = WebBaseLoader("https://cloud.tencent.com/developer/article/2329879")## 创建新的chroma向量库
def createDB(loader,persist_directory='./chromaDB'):#loader = TextLoader(file_path=file_path,encoding='utf8')documents=loader.load()# Split documentsernieChunkSize=384text_splitter = RecursiveCharacterTextSplitter(chunk_size=ernieChunkSize, chunk_overlap=0)splits = text_splitter.split_documents(documents)print('splits len:',len(splits))#,splits[:5])# Embed and store splitsembeddings=ErnieEmbeddings(access_token=access_token)# vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())vectorstore = Chroma.from_documents(persist_directory=persist_directory,documents=splits, embedding=embeddings)#https://juejin.cn/post/7236028062873550908# 持久化 会结合之前保存下来的 vectorstore#vectorstore.persist()return vectorstore
## 读取已保存的chroma向量库
def readDB(persist_directory="./chromaDB"):assert os.path.isdir(persist_directory)# # Embed and store splitsembeddings=ErnieEmbeddings(access_token=access_token)vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)return vectorstore
## 基于用户的query去搜索库中相近的文档
def searchSimDocs(query,vectorstore,top_k=3,scoreThershold=5):packs=vectorstore.similarity_search_with_score(query,k=top_k)contentList=[]for pack in packs:doc,score=packif score<scoreThershold:##好像设置5,基本都会返回contentList.append(doc.page_content)# print('content',contentList)return contentListdef getLoader(source):if source[-4:]=='.txt':#file_path='doupo.txt'assert os.path.isfile(file_path)loader = TextLoader(file_path=file_path,encoding='utf8')elif source[:4]=='http':#url='https://zhuanlan.zhihu.com/p/657210829'loader= WebBaseLoader(source)return loader#######################################
new=True ##新建向量库,注意如果拿几百万字的小说embedding
source='https://zhuanlan.zhihu.com/p/657210829'
loader=getLoader(source)
if new:vectorstore=createDB(loader)
else:vectorstore=readDB()
# 初始化 prompt 对象
query = "大模型长文本建模的难点是什么?"
contentList=searchSimDocs(query,vectorstore,top_k=5)
print('contentList',contentList)
LLM根据相关文档片段回答问题
预置Prompt设定LLM角色,该Prompt将与向量计算中相关文档片段进行结合,作为query输入给大模型。
prompt=
"你是善于总结归纳并结合文本回答问题的文本助理。请使用以下检索到的上下文来回答问题。如果你不知道答案,就说你不知道。最多使用三句话,并保持答案简洁。问题为:\n"+query+" \n上下文:\n"+'\n'.join(contentList) +" \n 答案:"
以下为response解析代码:
def packPrompt(query,contentList):prompt="你是善于总结归纳并结合文本回答问题的文本助理。请使用以下检索到的上下文来回答问题。如果你不知道答案,就说你不知道。最多使用三句话,并保持答案简洁。问题为:\n"+query+" \n上下文:\n"+'\n'.join(contentList) +" \n 答案:"return promptdef singleQuery(prompt,model='ernie-bot'):response = erniebot.ChatCompletion.create(model=model,messages=[{'role': 'user','content': prompt}])print('response',response)try:resFlag=response['rcode']except: resFlag=response['code']if resFlag==200:try:data=response['body']except:data=responseresult=response['result']usedToken=data['usage']['total_tokens']else:result=""usedToken=-1return result,usedTokenprompt=packPrompt(query,contentList)
res,usedToken=singleQuery(prompt,model='ernie-bot-4')
print(res)
该教程支持直接一键fork运行,点击下方链接查看。
https://aistudio.baidu.com/projectdetail/7051316
该教程项目来源于飞桨星河社区五周年开发精品教程征集,更多教程或有投稿需求请点击底部下方链接查看。
https://aistudio.baidu.com/topic/tutorial