现在各行各业纷纷选择接入大模型,其中最火且可行性最高的形式无异于智能文档问答助手,而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


飞桨PaddlePaddle
30 声望34 粉丝

飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础,集深度学习核心训练和推理框架、基础模型库、端到端开发套件、丰富的工具组件于一体,是中国首个自主研发、功能丰富、开源开放的产业级深度...