> 自媒体 > (AI)人工智能 > GPT-3问答机器人实战【LangChain】
GPT-3问答机器人实战【LangChain】
来源:新缸中之脑
2023-04-08 18:07:28
1135
管理

ChatGPT 几个月前问世,并以其回答来自广泛知识集的问题的能力震惊了所有人。 在 ChatGPT 展示大型语言模型的强大功能时,Dagster 核心团队遇到了一个问题。

☝ 对于本教程,我建议使用 GitPod 来获得一致的 Python 环境。

让我们在 LangChain 中实现它。 首先安装 LangChain 和本教程其余部分所需的一些依赖项:

pip install langchain==0.0.55 Requests openai transformers faiss-cpu

接下来,让我们开始编写一些代码。 创建一个新的 Python 文件 langchain_bot.py 并从一些导入开始:

from langchain.llms import OpenAIfrom langchain.chains.qa_with_sources import load_qa_with_sources_chainfrom langchain.docstore.document import Documentimport requests

接下来,我们的玩具示例需要一些示例数据。 现在,让我们使用各种维基百科页面的第一段作为我们的数据源。 有一个很棒的 Stack Overflow 答案,它给了我们一个获取这些数据的神奇咒语:

def get_wiki_data(title, first_paragraph_only): url = f"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=1&titles={title}" if first_paragraph_only: url = "&exintro=1" data = requests.get(url).json() return Document( page_content=list(data["query"]["pages"].values())[0]["extract"], metadata={"source": f"https://en.wikipedia.org/wiki/{title}"}, )

不要太担心这个的细节。 给定一个维基百科标题和一个指定你想要第一段还是整个内容的布尔值,它将返回一个 LangChain Document 对象,它基本上只是一个附加了一些元数据的字符串。 元数据中的来源键很重要,因为模型在引用其来源时会使用它。

接下来,让我们设置一个机器人将要查询的资源语料库:

sources = [ get_wiki_data("Unix", True), get_wiki_data("Microsoft_Windows", True), get_wiki_data("Linux", True), get_wiki_data("Seinfeld", True),]

最后,让我们将所有这些连接到 LangChain:

chain = load_qa_with_sources_chain(OpenAI(temperature=0))def print_answer(question): print( chain( { "input_documents": sources, "question": question, }, return_only_outputs=True, )["output_text"] )

这做了几件事:

它创建了一个 LangChain 链,该链设置了适当的问答提示。 它还表明我们应使用 OpenAI API 为链提供动力而不是其他服务(如 Cohere)它调用链,提供要查阅的源文件和问题。它返回一个原始字符串,其中包含问题的答案及其使用的来源。

让我们看看它的实际效果! 在开始之前,请务必注册一个 OpenAI API 密钥。

$ export OPENAI_API_KEY=sk-

OpenAI API 不是免费的。 当你迭代你的机器人时,一定要监控你花了多少钱!

现在我们已经设置了 API 密钥,让我们试一试我们的机器人。

$ python3Python 3.8.13 (default, Oct 4 2022, 14:00:32)[GCC 9.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from langchain_bot import print_answer>>> print_answer("Who were the writers of Seinfeld?") The writers of Seinfeld were Larry David, Jerry Seinfeld, Larry Charles, Peter Mehlman, Gregg Kavet, Carol Leifer, David Mandel, Jeff Schaffer, Steve Koren, Jennifer Crittenden, Tom Gammill, Max Pross, Dan O'Keefe, Charlie Rubin, Marjorie Gross, Alec Berg, Elaine Pope and Spike Feresten.SOURCES: https://en.wikipedia.org/wiki/Seinfeld>>> print_answer("What are the main differences between Linux and Windows?") Linux and Windows are both operating systems, but Linux is open-source and Unix-like, while Windows is proprietary and developed by Microsoft. Linux is used on servers, embedded systems, and desktop computers, while Windows is mainly used on desktop computers.SOURCES:https://en.wikipedia.org/wiki/Unixhttps://en.wikipedia.org/wiki/Microsoft_Windowshttps://en.wikipedia.org/wiki/Linux>>> print_answer("What are the differences between Keynesian and classical economics?") I don't know.SOURCES: N/A>>>

我不了解你怎么看,但我认为这令人印象深刻。 它正在回答问题,提供额外的相关上下文,引用其来源,并知道何时说不知道。

所以,既然我们已经证明这是有效的,它应该像将所有 Dagster 文档填充到源代码部分一样简单,对吧?

4、处理有限的提示窗口大小

不幸的是,这并不像将 Dagster 文档的整个语料库放入提示中那么简单。 主要有两个原因:

GPT-3 API 按令牌收费,因此我们的目标是在我们的提示中使用尽可能少的令牌以节省资金,因为我们需要在用户提出问题时将整个提示发送到 API 机器人。GPT-3 API 在提示中有大约 4000 个令牌的限制,所以即使我们愿意为此付费,我们也不能给它完整的 Dagster 文档。 信息太多了。5、处理大量文件

让我们看看当我们有太多文档时会发生什么。 不幸的是,在达到令牌限制之前,我们只需要再添加几个文档:

sources = [ get_wiki_data("Unix", True), get_wiki_data("Microsoft_Windows", True), get_wiki_data("Linux", True), get_wiki_data("Seinfeld", True), get_wiki_data("Matchbox_Twenty", True), get_wiki_data("Roman_Empire", True), get_wiki_data("London", True), get_wiki_data("Python_(programming_language)", True), get_wiki_data("Monty_Python", True),]

当重新运行示例时,我们从 OpenAI API 收到错误:

$ python3 -c'from langchain_bot import print_answer; print_answer("What are the main differences between Linux and Windows?")'openai.error.InvalidRequestError: This model's maximum context length is 4097 tokens, however you requested 6215 tokens (5959 in your prompt; 256 for the completion). Please reduce your prompt; or completion length.

有两种选择可以解决这个问题。 我们可以使用不同的链,也可以尝试限制模型使用的来源数量。 让我们从第一个选项开始。

6、使用多步链

我们可以使用向量空间搜索引擎解决 map_reduce 链的问题和 stuff 链的局限性。 在高层次上:

提前,我们创建一个传统的搜索索引并将所有源添加到其中。在查询时,我们使用问题查询搜索索引并返回前 k 个结果。我们使用这些结果作为我们在东西链中的来源。

让我们一次为这一步编写代码。 首先,我们需要添加一些导入:

from langchain.embeddings.openai import OpenAIEmbeddingsfrom langchain.vectorstores.faiss import FAISS

接下来,让我们为所有来源创建一个 Faiss 搜索索引。 幸运的是,LangChain 包含一个使它成为单行代码的帮助程序类。

search_index = FAISS.from_documents(sources, OpenAIEmbeddings())

这段代码做了三件事:

它创建一个 Faiss 内存索引。它使用 OpenAI API 为每个来源创建嵌入(即特征向量),使其易于搜索。 如果需要,你可以使用其他嵌入,但 OpenAI 会为此应用程序生成高质量的嵌入。它将每个来源添加到索引中。

最后,让我们更新其余代码以利用搜索索引。 对于这个例子,我们将使用前 4 个搜索结果来告知模型的答案:

chain = load_qa_with_sources_chain(OpenAI(temperature=0))def print_answer(question): print( chain( { "input_documents": search_index.similarity_search(question, k=4), "question": question, }, return_only_outputs=True, )["output_text"] )

当我们运行这个例子时,它起作用了! 事实上,我们现在可以在 Faiss 索引中添加尽可能多的来源(而且数量很多!),我们的模型仍然会快速执行。

$ python3 -c'from langchain_bot import print_answer; print_answer("Which members of Matchbox 20 play guitar?")' Rob Thomas, Kyle Cook, and Paul Doucette play guitar in Matchbox 20.SOURCES: https://en.wikipedia.org/wiki/Matchbox_Twenty8、处理太大的文档

好的,现在让我们尝试处理更大的文档。 通过将最后一个参数切换为 False,更改我们的来源列表以包括完整的维基百科页面,而不仅仅是第一部分:

sources = [ get_wiki_data("Unix", False), get_wiki_data("Microsoft_Windows", False), get_wiki_data("Linux", False), get_wiki_data("Seinfeld", False), get_wiki_data("Matchbox_Twenty", False), get_wiki_data("Roman_Empire", False), get_wiki_data("London", False), get_wiki_data("Python_(programming_language)", False), get_wiki_data("Monty_Python", False),]

不幸的是,我们现在在查询我们的机器人时遇到错误:

$ python3 -c'from langchain_bot import print_answer; print_answer("Who plays guitar in Matchbox 20?")'openai.error.InvalidRequestError: This model's maximum context length is 8191 tokens, however you requested 11161 tokens (11161 in your prompt; 0 for the completion). Please reduce your prompt; or completion length.Even though we are filtering down the individual documents, each document is now so big we cannot fit it

即使我们正在过滤单个文档,每个文档现在都太大了,我们无法将其放入上下文窗口。

解决此问题的一种非常简单但有效的方法是将文档简单地分成固定大小的块。 虽然这看起来“太笨了,无法工作”,但实际上它在实践中似乎工作得很好。 LangChain 包含一个有用的实用程序来为我们做这件事。 让我们从导入它开始吧。

from langchain.text_splitter import CharacterTextSplitter

接下来,让我们遍历源列表并创建一个名为 source_chunks 的新列表,Faiss 索引将使用该列表代替完整文档:

source_chunks = []splitter = CharacterTextSplitter(separator=" ", chunk_size=1024, chunk_overlap=0)for source in sources: for chunk in splitter.split_text(source.page_content): source_chunks.append(Document(page_content=chunk, metadata=source.metadata))search_index = FAISS.from_documents(source_chunks, OpenAIEmbeddings())

这里有几点需要注意:

我们已将 CharacterTextSplitter 配置为创建最大大小为 1024 个字符且无重叠的块。 此外,它们在空白边界处分裂。 LangChain 中包含其他更智能的拆分器,它们利用 NLTK 和 spaCy 等库,但对于本示例,我们将使用最简单的选项。文档中的所有块共享相同的元数据。

最后,当我们重新运行时,我们看到模型给了我们一个答案:

$ python3 -c'from langchain_bot import print_answer; print_answer("Which members of Matchbox 20 play guitar?")'Rob Thomas, Paul Doucette, and Kyle Cook play guitar in Matchbox 20.SOURCES: https://en.wikipedia.org/wiki/Matchbox_Twenty9、应用到 GitHub 存储库

现在让我们把写的东西应用到 GitHub 仓库中。 让我们首先添加一些必需的导入:

import pathlibimport subprocessimport tempfile

接下来,我们需要一个函数来检查 GitHub 存储库的最新副本,抓取markdown文件,并返回一些 LangChain 文档。

def get_github_docs(repo_owner, repo_name): with tempfile.TemporaryDirectory() as d: subprocess.check_call( f"git clone --depth 1 https://github.com/{repo_owner}/{repo_name}.git .", cwd=d, shell=True, ) git_sha = ( subprocess.check_output("git rev-parse HEAD", shell=True, cwd=d) .decode("utf-8") .strip() ) repo_path = pathlib.Path(d) markdown_files = list(repo_path.glob("*/*.md")) list( repo_path.glob("*/*.mdx") ) for markdown_file in markdown_files: with open(markdown_file, "r") as f: relative_path = markdown_file.relative_to(repo_path) github_url = f"https://github.com/{repo_owner}/{repo_name}/blob/{git_sha}/{relative_path}" yield Document(page_content=f.read(), metadata={"source": github_url})

这做了一些事情:

它将所需 GitHub 存储库的最新提交签出到一个临时目录中。它获取 git sha(用于构建链接,模型将在其源列表中使用)。它遍历 repo 中的每个降价文件(.md 或 .mdx)。它在 GitHub 上构造一个 markdown 文件的 URL,从磁盘读取文件,并返回一个 Document

现在让我们把它连接到我们的机器人上。 用以下内容替换以前的源列表:

sources = get_github_docs("dagster-io", "dagster")10、尝试一下!

让我们尝试一下,看看它是否理解 Dagster API 的细微差别。 我们将从询问有关软件定义资产的问题开始。

$ python3Python 3.8.13 (default, Oct 4 2022, 14:00:32)[GCC 9.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from langchain_bot import print_answer>>> print_answer("what is a software defined asset") A software-defined asset is a Dagster object that couples an asset to the function and upstream assets that are used to produce its contents. It enables a declarative approach to data management, in which code is the source of truth on what data assets should exist and how those assets are computed.SOURCES:https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/assets/software-defined-assets.mdxhttps://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/guides/dagster/enriching-with-software-defined-assets.mdxhttps://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/tutorial/assets/defining-an-asset.md>>> print_answer("what is the difference between ops, jobs, assets and graphs") Ops are the core unit of computation in Dagster and contain the logic of an orchestration graph. Jobs are the main unit of execution and monitoring in Dagster and contain a graph of ops connected via data dependencies. Assets are persistent objects in storage, such as a table, machine learning (ML) model, or file. Graphs are sets of interconnected ops or sub-graphs and form the core of jobs.SOURCES:https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/ops-jobs-graphs/graphs.mdxhttps://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/ops-jobs-graphs/jobs.mdxhttps://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/

我对这个回应很满意。 它能够令人信服地解释小众技术概念,而不仅仅是从文档中逐字逐句地重复句子。

但是,此时你可能已经注意到我们的小机器人变得非常慢。 让我们解决这个问题!

11、使用缓存嵌入以节省时间和金钱

我们现在有一个运行良好的聊天机器人,但它有一个主要问题:启动时间非常慢。 每次我们导入脚本时,有两个步骤特别慢:

我们使用 git 克隆 repo,爬取每个 markdown 文件并将它们分块我们为每个文档调用 OpenAI API,创建嵌入,并将其添加到 Faiss 索引

理想情况下,我们只会偶尔运行这些步骤并缓存索引以供后续运行使用。 这将提高性能并显着降低成本,因为我们将不再在启动时重新计算嵌入。

此外,如果这个过程不是“全有或全无”,那就太好了。 如果我们每次都可以迭代我们的 Faiss 索引或嵌入而不重新克隆 repo,我们可以大大提高迭代速度。

我们不再有简单的 Python 脚本。 我们现在有一个数据管道,数据管道需要像 Dagster 这样的编排器。 Dagster 使我们能够快速轻松地添加这种多步缓存功能,并支持其他功能,例如添加自动调度和传感器以在外部触发器上重新运行管道。

原文链接:http://www.bimant.com/blog/gpt-3-chatbot-hands-on/

1
点赞
赏礼
赏钱
0
收藏
免责声明:本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本网证实,对本文以及其中全部或者 部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。 凡本网注明 “来源:XXX(非本站)”的作品,均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对 其真实性负责。 如因作品内容、版权和其它问题需要同本网联系的,请在一周内进行,以便我们及时处理。 QQ:617470285 邮箱:617470285@qq.com
关于作者
冰冷的开会..(普通会员)
文章
367
关注
0
粉丝
0
点击领取今天的签到奖励!
签到排行

成员 网址收录40329 企业收录2981 印章生成186717 电子证书795 电子名片49 自媒体20815

@2022 All Rights Reserved 浙ICP备19035174号-7
1
0
分享
请选择要切换的马甲:

个人中心

每日签到

我的消息

内容搜索