随着大型语言模型 (LLM) 的出现,现在可以与机器人就各种主题进行类似人类的对话。
这是一项前所未有的创新,使得 2022 年之前的聊天机器人变得过时。 事实上,通过 这样的系统,我们已经见证了一些改进,例如增强的语言能力(包括语法、语法和写作风格)、聊天记忆以及许多学科的大量知识。
然而,尽管有这些强大的功能,但当这些系统需要回答有关非常技术性主题(例如法律或医疗保健)、他们在培训阶段没有遇到的最新数据或他们通常不会遇到的私人文档的问题时,这些系统仍然存在不足。 可使用。
为了解决最后一个问题,我将在这篇文章中向您展示如何构建一个个性化的聊天机器人,该机器人连接到您的数据源并回答有关您的内部知识库的问题。我们将介绍整体架构和构建块并实施 一切都使用。
如果您想利用公司的内部数据并提供一个不会产生幻觉的相关机器人,这篇文章提供了一个入门解决方案。
话不多说,一起来看看吧
PS:如果觉得内容有用请记得点赞。
为什么构建自定义聊天机器人很重要?
一切都与精度和生产力有关
如今,大多数公司都对基于内部数据构建问答 (QA) 系统感兴趣,因为这提供了不可否认的好处:
准确性:当您将机器人设置为仅依赖所提供的数据来提供答案时,您知道它不会产生幻觉或发明东西。 这是极其重要的,特别是如果您的数据是特定领域的
生产力:与 QA 聊天机器人交互使信息检索几乎即时进行,并为用户节省大量时间和精力
可维护性:如果您的知识库随着时间的推移而增长或更新,您可以在数据摄取期间考虑到这一点(更多内容见下文)并更新机器人
可扩展性:如果您需要摄取新的数据源(例如 PDF 文件的数据库或电子邮件转储),只需配置新的文档加载器即可(更多详细信息请参见下面的代码部分)
完全控制和隐私:如果您使用开源法学硕士和矢量数据库构建和部署个性化聊天机器人,您可以在您的基础设施上完全管理它,并避免系统中断或糟糕的 SLA
架构✏️
您可以通过多种方法在一组文档上构建和设计自定义 QA 系统。
在这篇文章中,我们将研究一个组合多个组件的架构。
为了帮助您了解事物是如何组装在一起的,我们将逐步遵循此工作流程:
0 — 收集数据
首先摄取代表您的知识库的数据。 显然,它可以来自不同的来源:PDF 文件、电子邮件、 或 导出、Web 数据等。
1 — 预处理文档
收集文档后,将它们分成多个块(有或没有重叠)。 这对于以后在嵌入时避免许多大型语言模型施加的令牌限制特别有用。
2 — 嵌入文档
使用开源(例如 、)或第三方服务(、、)的嵌入模型将先前的块转换为向量(也称为嵌入)
嵌入是一种固定大小的数字表示形式,可以轻松操作文本数据并执行相似性度量等数学运算。
3 – 向量数据库中的索引嵌入
计算出嵌入后,将它们存储在矢量数据库中,例如 、 或 。
矢量数据库可用于存储高维数据和计算矢量相似度。
4 — 提出问题
将文档嵌入矢量数据库并建立索引后,您可以从聊天界面发送查询。
5 — 嵌入输入查询
使用相同的嵌入模型将查询转换为向量
6 / 7 — 查找与查询相关的文档
使用向量数据库查找与您的查询最相似的文档块。 (正如您所期望的,相似性度量是通过嵌入计算的)
这些块不是您正在寻找的最终答案,但它们与之相关:它们基本上包含响应的元素并提供上下文。
要获取的相似块的数量是一个需要调整的参数。 在下面的代码部分中,我们将其设置为 4。
8 — 设置提示格式
使用之前提取的类似文档作为提示内的上下文。
该提示将具有以下结构,但您可以对其进行自定义以满足您的特定需求:
Answser the following question {question}
Only use this context to construct the answer:
%CONTEXT%
{context}
9 — 生成答案
创建提示后,将其提供给法学硕士以生成答案。
代码
在本节中,我们将把以前的架构转变为您可以运行和试验的实际代码。
我们在这里使用的数据将是这本电子书的第一章(PDF 格式):《有效的 :编写更好的 的 90 种具体方法》。
我们的脚本有望回答有关 技巧和最佳实践的问题(也许能让我们成为更好的开发人员)
让我们这样做吧
加载文档
让我们首先逐页加载 PDF 文件并保留我们感兴趣的页面 (23-63)。
from langchain.document_loaders import PyPDFLoader
config = ... # defined above
loader = PyPDFLoader(file_path=config.file_path)
pages = loader.load()
logger.info(f"Data loaded successuflly: {len(pages)} pages")
filtered_pages = [
clean_page(page)
for page in pages
if config.start_page <= page.metadata["page"] <= config.end_page
]
logger.info(
f"Filtred page in the following rand: [{config.start_page}, {config.end_page}]"
)
将每个页面分成块
让我们将每个页面分成 1024 个字符的块,块之间有 128 个字符的重叠。 (稍后块之间有重叠有助于构建长文本的完整上下文)
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=128)
chunks = text_splitter.split_documents(filtered_pages)
logger.info(f"Splitted documents into {len(chunks)} chunks")
嵌入块并将其嵌入索引到 数据库中
这两个步骤合并在一个命令 . 中。
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
logger.info("Embedding the chunks and indexing them into Chroma ...")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=OpenAIEmbeddings(),
persist_directory="db",
)
logger.info("Chunks indexed into Chroma")
生成答案
这些代码行中发生了很多事情,因为 抽象了许多底层任务:
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:"""
QA_CHAIN_PROMPT = PromptTemplate.from_template(template)
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm,
retriever=vectorstore.as_retriever(),
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT},
)
answer = qa_chain({"query": config.question})
print(answer["result"])
当我们对问题运行 时,神奇的事情发生了:嵌入查询,从检索器中提取相似的块,然后添加到提示中,最后,LLM 运行提示以产生答案。
演示
让我们尝试一下脚本并提出以下问题:
str 和 bytes 有什么区别?
答案是……
str 和 bytes 之间的主要区别在于 str 包含 代码点序列,而 bytes 包含 8 位值序列。 Str 用于以人类可读格式表示文本,而 bytes 用于表示二进制数据。 Str 实例可以与其他 str 实例连接,bytes 实例可以与其他 bytes 实例连接,但 str 和 bytes 实例不能连接在一起。 此外,str 和 bytes 实例无法使用比较运算符进行相互比较。
完整的代码可以在这里找到。
结论
在这篇文章中,我们介绍了问答系统的构建模块,该系统从内部知识库提供答案。
我们还使用 进行了入门 实现,以轻松编排不同的组件。
要使解决方案更加准确、稳定和可扩展,肯定还有更多的工作要做。 查看 文档以了解更多信息。