现在各行各业纷纷选择接入大模型,其中最火且可行性最高的形式无异于智能文档问答助手,而 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=pack
if score<scoreThershold:##好像设置5,基本都会返回
contentList.append(doc.page_content)
# print('content',contentList)
return contentList
复制代码
具体 Embedding 代码见下方:
import os
os.environ['access_token']=access_token
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_embedding_ErnieBotSDK import ErnieEmbeddings# Load documents
from 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 documents
ernieChunkSize=384
text_splitter = RecursiveCharacterTextSplitter(chunk_size=ernieChunkSize, chunk_overlap=0)
splits = text_splitter.split_documents(documents)
print('splits len:',len(splits))#,splits[:5])
# Embed and store splits
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)
#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 splits
embeddings=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=pack
if score<scoreThershold:##好像设置5,基本都会返回
contentList.append(doc.page_content)
# print('content',contentList)
return contentList
def 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 prompt
def 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=response
result=response['result']
usedToken=data['usage']['total_tokens']
else:
result=""
usedToken=-1
return result,usedToken
prompt=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
评论