第22章 利用 GPT-3 与 LangChain 构建 GitHub 支持机器人
在本教程中,我们利用 OpenAI ChatGPT 的强大能力,结合 GPT3、LangChain 和 Python 构建了一个 GitHub 支持机器人。听说过 ChatGPT 吗?
ChatGPT 于几个月前发布,凭借从广泛知识源中回答问题的能力震撼了所有人。就在 ChatGPT 展示大型语言模型强大潜力的同时,Dagster 核心团队正面临一个问题。
⚡ 想直接看代码?代码在 GitHub 上。
我们将涵盖以下内容:
我们的问题 是否进行微调(Fine-tuning)? 使用 LangChain 构建提示词 应对有限的提示词窗口大小 处理大量文档 处理过大的文档 将其应用到 GitHub 仓库 试一试! 使用 Dagster 缓存嵌入(Embeddings)以节省时间和成本 使用 Dagster 构建管道 按计划重新训练及错误恢复 未来工作
我们的问题
我们开发了 Dagster,这是一个快速发展的开源数据编排解决方案,拥有一个庞大的社区 Slack 实例。提供一流的支持体验对我们项目的成功至关重要,但这需要核心团队成员投入大量精力。看到 ChatGPT 的能力后,我们想知道能否基于该技术创建一个 Slack 机器人,以回答基础问题。
虽然 OpenAI 的 ChatGPT 本身没有 API,但其底层技术——GPT-3——是有 API 的。因此,我们踏上了一段旅程,探索能否使用 GPT-3 构建一个机器人,用来回答关于 Dagster 的基础技术问题。
值得注意的是,我并非 AI 专家。本文中介绍的方案很可能还有改进空间。话虽如此,让我们继续吧!
是否进行微调?
我们需要一种方式来教 GPT-3 关于 Dagster GitHub 项目的技术细节。
显而易见的方案是寻找一种方法,在 Dagster 文档(Markdown 或文本文件)上训练 GPT-3。我们会从 Dagster 仓库中提取所有 Markdown 文件,并以某种方式喂给 GPT-3。
我们的第一直觉是利用 GPT-3 的微调能力,创建一个基于 Dagster 文档训练的定制模型。但最终我们放弃了这种做法,原因有三:
我们不确定如何基于 Markdown 文件构建最佳训练提示词,也找不到很好的资源来理解微调的最佳实践。 成本似乎很高。每次重新训练大约花费 80 美元。如果希望机器人紧跟仓库的最新变更(例如每天重新训练),这笔费用将不断累积。 我与网络中几位已将 GPT-3 部署到生产环境的人交谈,他们都对微调持悲观态度。
因此,我们决定在不进行微调的情况下继续前进。
使用开源 LangChain 构建提示词
提示词工程(Prompt engineering)是指开发优质提示词,以最大化 GPT-3 等大型语言模型效能的过程。开发提示词的挑战在于,你通常需要一连串(或称“链”)的提示词才能获得最佳答案。
我们发现了一个能帮上大忙的库:langchain。根据该库的文档:
大型语言模型(LLM)正成为一种变革性技术,使开发者能够构建以前无法实现的应用程序。但单独使用这些 LLM 往往不足以创造真正强大的应用——真正的力量来自于将它们与其他计算或知识源结合。
这正是我们试图解决的问题:我们要结合 GPT-3 大型语言模型的力量与编码在 Dagster 文档中的知识。幸运的是,LangChain 包含一个名为“数据增强生成”(Data Augmented Generation)的功能,允许你提供上下文数据来增强 LLM 的知识。它还为像我们这样的问答应用提供了预构建的提示词。
如果我们深入 LangChain 的源代码,可以看到其问答提示词如下: 给定长文档的以下摘录部分和一个问题,创建一个带有引用(“SOURCES”)的最终答案。 如果你不知道答案,就直说你不知道。不要试图编造答案。 在答案中始终包含“SOURCES”部分。
问题:{question} ========= {summaries} ========= 最终答案:
如图所示,此提示词接收一个问题和一些来源,然后返回有用的答案以及最相关的来源。请查看提示词中提供的示例,了解实际效果: 问题:哪个州/国家的法律管辖合同的解释? ========= 内容:本协议受英国法律管辖,双方同意在涉及本协议的任何争议(合同或非合同)方面,提交英国法院的专属管辖权,但任何一方可向法院申请禁令或其他救济以保护其知识产权。 来源:28-pl 内容:不豁免。未能或延迟行使本协议下的任何权利或救济,不构成对该(或其他)权利或救济的放弃。\n\n11.7 可分割性。本协议任何条款(或条款部分)的无效、违法或不可执行,不影响其余条款(如有)及本协议的持续效力。\n\n11.8 无代理权。除非明确另有说明,本协议中的任何内容不得在双方之间建立任何形式的代理、合伙或联营关系。\n\n11.9 无第三方受益人。 来源:30-pl 内容:(b) 如果 Google 善意认为,经销商违反或导致 Google 违反任何反贿赂法(如第 8.5 条定义),或此类违规合理可能发生, 来源:4-pl ========= 最终答案:本协议受英国法律管辖。 来源:28-pl
在 LangChain 中实现一个玩具示例
☝ 对于本教程,建议使用 GitPod 以获得一致的 Python 环境。 pip install langchain - 让我们开始吧!
让我们在 LangChain 中实现这一点。安装 LangChain 以及教程后续需要的依赖项: pip install langchain==0.0.55 requests openai transformers faiss-cpu
接下来,让我们开始编写代码。创建一个新的 Python 文件 langchain_bot.py,并添加一些导入: from langchain.llms import OpenAI from langchain.chains.qa_with_sources import load_qa_with_sources_chain from langchain.docstore.document import Document import 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 对象,基本上就是一个带有元数据的字符串。元数据中的 source 键很重要,因为模型在引用来源时会用到它。 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)。 它调用该链,提供要参考的来源文档和问题。 它返回一个包含答案及其使用来源的原始字符串。
$ export OPENAI_API_KEY=sk-
让我们看看实际效果!在开始之前,请确保注册一个 OpenAI API 密钥。 ⚠️ OpenAI API 不是免费的。在迭代机器人时,请留意你的支出!
现在我们已经设置好 API 密钥,让我们试试看我们的机器人。 $ python3 Python 3.8.13 (default, Oct 4 2022, 14:00:32) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from langchain_bot import print_answer >>> print_answer("Who were the writers of Seinfeld?") Seinfeld 的编剧包括 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 和 Spike Feresten。 来源:https://en.wikipedia.org/wiki/Seinfeld >>> print_answer("What are the main differences between Linux and Windows?") Linux 和 Windows 都是操作系统,但 Linux 是开源的类 Unix 系统,而 Windows 是专有系统,由 Microsoft 开发。Linux 用于服务器、嵌入式系统和桌面计算机,而 Windows 主要用于桌面计算机。 来源: https://en.wikipedia.org/wiki/Unix https://en.wikipedia.org/wiki/Microsoft_Windows https://en.wikipedia.org/wiki/Linux >>> print_answer("What are the differences between Keynesian and classical economics?") 我不知道。 来源:N/A >>>
我不知道你觉得如何,但我觉得这相当令人印象深刻。它回答了问题,提供了额外的相关背景,引用了来源,并且知道何时不知道答案。
那么,既然我们证明了这是可行的,是否只需要将所有 Dagster 文档塞入来源部分就行了?
应对有限的提示词窗口大小
不幸的是,并不是将 Dagster 文档语料库全部放入提示词这么简单。主要有两个原因:
GPT-3 API 按 token 收费,因此我们应尽可能使用最少的 token 以节省资金,因为每次用户向机器人提问时,我们都需要将整个提示词发送到 API。 GPT-3 API 的提示词限制约为 4000 个 token,因此即使我们愿意付费,也无法提供整个 Dagster 文档。信息量实在太大。
应对大量文档
让我们看看当文档过多时会发生什么。不幸的是,我们只需再添加几个文档就会触及 token 限制: 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.
有两种处理这个问题的选项。我们可以使用不同的链,或者尝试限制模型使用的来源数量。让我们先从第一个选项开始。
使用多步链
回想一下我们在玩具示例中如何创建链: chain = load_qa_with_sources_chain(OpenAI(temperature=0))
实际上有一个隐式的第二个参数来指定我们使用的链类型。到目前为止,我们使用的是 stuff 链,它只是将所有来源塞入提示词。还有两种其他类型的链可以使用:
map_reduce:映射所有来源并进行摘要,使其更有可能适应上下文窗口。这将为每个查询处理语料库中的每个 token,但可以并行运行。 refine:串行遍历每个来源,并要求底层模型基于来源完善其答案。根据我的经验,这慢到完全不可用。
那么,让我们看看如果使用 map_reduce 链会发生什么。更新我们的玩具示例以将其作为参数传入: chain = load_qa_with_sources_chain(OpenAI(temperature=0), chain_type="map_reduce")
并重新运行示例。 $ python3 -c'from langchain_bot import print_answer; print_answer("What are the main differences between Linux and Windows?")' Linux 是一个基于 Linux 内核的开源类 Unix 操作系统,而 Windows 是由 Microsoft 开发和营销的一组专有图形操作系统家族。Linux 发行版通常打包为 Linux 发行版,包括内核和支持系统软件及库,而 Windows 发行版包括 X11 或 Wayland 等窗口系统,以及 GNOME 或 KDE Plasma 等桌面环境。 来源: https://en.wikipedia.org/wiki/Unix https://en.wikipedia.org/wiki/Microsoft_Windows https://en.wikipedia.org/wiki/Linux
它起作用了!然而,这需要多次调用 OpenAI API,并且向机器人提出的每个问题都需要处理所有 token,这既缓慢又昂贵。此外,答案中存在一些不准确之处,这可能来自摘要过程。
我们发现,使用另一种方法——结合 stuff 链的向量空间搜索——是目前最好的解决方案。
使用向量空间搜索引擎提高效率
我们可以使用向量空间搜索引擎来规避 map_reduce 链的问题和 stuff 链的限制。高层逻辑如下:
事先,我们创建一个传统的搜索索引,并将所有来源添加到其中。 查询时,我们使用问题查询搜索索引,并返回前 k 个结果。 我们将这些结果作为来源,供 stuff 链使用。
让我们一步一步编写此部分的代码。首先,我们需要添加一些导入: from langchain.embeddings.openai import OpenAIEmbeddings from 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. 来源:https://en.wikipedia.org/wiki/Matchbox_Twenty
应对过大的文档
好吧,现在让我们尝试处理更大的文档。更改我们的来源列表,包含完整的维基百科页面,而不仅仅是第一章节,通过切换最后一个参数为 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.
尽管我们筛选了单个文档,但每个文档现在都太大,无法放入上下文窗口。
解决这个问题的一个非常简单但有效的方法,就是将文档分解为固定大小的块。虽然这看起来“笨得没道理”,但在实践中效果出奇地好。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. 来源:https://en.wikipedia.org/wiki/Matchbox_Twenty
将所有这些应用到 GitHub 仓库
现在,让我们把我们写好的东西应用到 GitHub 仓库上。首先,让我们添加一些必需的导入: import pathlib import subprocess import tempfile
接下来,我们需要一个函数,用于签出最新版本的 GitHub 仓库,爬取其中的 markdown 文件,并返回一些 LangChain Documents。 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(用于构建链接,模型将用在其来源列表中)。 它遍历仓库中的每个 markdown 文件(.md 或 .mdx)。 它构建指向 GitHub 上 markdown 文件的 URL,从磁盘读取文件,并返回一个 Document。
现在让我们将此连接到我们的机器人。用以下代码替换之前的 sources 列表: sources = get_github_docs("dagster-io", "dagster")
问答:试一试!
让我们玩玩这个,看看它是否理解 Dagster API 的细微差别。我们从询问软件定义资产开始。 $ python3 Python 3.8.13 (default, Oct 4 2022, 14:00:32) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from langchain_bot import print_answer >>> print_answer("what is a software defined asset") 软件定义资产是一个 Dagster 对象,它将资产与其用于产生其内容的函数及上游资产耦合在一起。它启用了一种声明式的数据管理方法,其中代码是关于应存在哪些数据资产以及如何计算这些资产的唯一事实来源。 来源: https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/assets/software-defined-assets.mdx https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/guides/dagster/enriching-with-software-defined-assets.mdx https://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 是 Dagster 中的核心计算单元,包含编排图的逻辑。Jobs 是 Dagster 中的主要执行和监控单元,包含通过数据依赖连接的 ops 图。Assets 是存储中的持久对象,如表、机器学习(ML)模型或文件。Graphs 是相互连接的 ops 或子图的集合,构成 jobs 的核心。 来源: https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/ops-jobs-graphs/graphs.mdx https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/ops-jobs-graphs/jobs.mdx https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/
我对这个响应相当满意。它能够连贯地解释小众的技术概念,而不仅仅是从文档中逐字逐句地复述。
然而,在此时你可能已经注意到,我们的小机器人变得相当慢了。让我们修复它!
使用 Dagster 缓存嵌入以节省时间和成本
我们现在有了一个运作良好的聊天机器人,但它有一个主要问题:启动时间极其缓慢。有两个特别缓慢的步骤,每次导入脚本时都会发生:
我们使用 git 克隆仓库,遍历每个 markdown 文件并将其分块 我们为每个文档调用 OpenAI API,创建嵌入并将其添加到 Faiss 索引
理想情况下,我们只偶尔运行这些步骤,并为后续运行缓存索引。这将提高性能并显著降低成本,因为我们不再需要在启动时重新计算嵌入。
此外,如果这个过程不是“全有或全无”会很好。如果我们可以迭代 Faiss 索引或嵌入而无需每次都重新克隆仓库,我们可以极大地提高迭代速度。
我们不再有一个简单的 Python 脚本。现在我们有一个数据管道,数据管道需要一个像 Dagster 这样的编排器。Dagster 使我们能够快速轻松地添加这种多步缓存能力,并支持诸如添加自动调度和传感器以在外部触发时重新运行管道等额外功能。 如果你想了解更多关于迁移到 Dagster 的信息,请查看我们之前关于迁移 ETL 脚本和软件定义资产的文章。如果你想要一个关于 Dagster 和编排的高层视图,请查看 crash course。
使用 Dagster 构建管道
在 Dagster 中,你将管道构建为资产图。我们将通过定义两个软件定义资产开始:
source_docs:从 git 仓库中提取的原始 Documents。 search_index:填充了分块的来源文档及其嵌入的 Faiss 索引。
最终的 search_index 将作为磁盘上的 pickle 文件存储,可被我们的 CLI 访问。 我们将通过安装 Dagster 及其 UI dagit 开始: from dagster import asset import pickle
当然,我们需要添加一些导入: pip install dagster dagit
接下来,让我们创建 source_docs SDA。这相当简单!只需将现有的 sources 列表包装在一个用 @asset 装饰的函数中: from dagster import asset
@asset def source_docs(): return list(get_github_docs("dagster-io", "dagster"))
既然我们有了 source_docs 资产,我们可以创建 search_index 资产。同样,我们基本上只是将一些代码移到带有 @asset 装饰器的函数中。 @asset def search_index(source_docs): source_chunks = [] splitter = CharacterTextSplitter(separator=" ", chunk_size=1024, chunk_overlap=0) for source in source_docs: for chunk in splitter.split_text(source.page_content): source_chunks.append(Document(page_content=chunk, metadata=source.metadata))
with open("search_index.pickle", "wb") as f: pickle.dump(FAISS.from_documents(source_chunks, OpenAIEmbeddings()), f)
代码大部分保持不变。然而,有两点需要注意:
函数接受 source_docs 参数名。这表明 search_index 资产依赖于 source_docs 资产,Dagster 会自动调用(并缓存)该函数。这还有一个很好的副作用,即提高了可测试性,因为在测试环境中你可以轻松地用测试数据覆盖 source_docs 资产。参阅文档以了解有关依赖的更多信息。 我们使用 Python 的 pickle 模块将搜索索引存储为磁盘上的文件,名为 search_index.pickle。
最后,由于我们不再有一个全局的 search_index,让我们更改 print_answer() 从 pickle 文件加载它: def print_answer(question): with open("search_index.pickle", "rb") as f: search_index = pickle.load(f) print( chain( { "input_documents": search_index.similarity_search(question, k=4), "question": question, }, return_only_outputs=True, )["output_text"] )
现在让我们启动 Dagster 并查看我们的管道!在 shell 中运行以下命令以启动 UI: dagit -f langchain_bot.py
现在你可以浏览 UI: 在 Dagit(Dagster 的 UI)中看到的资产图。 如果你点击“materialize all”,你可以观看管道执行的进度: Dagit 中显示管道执行的“Run”视图。 完成后(需要几分钟),你应该会在本地磁盘上看到一个 search_index.pickle 文件。运行我们的机器人应该仍然有效,但这次它应该很快返回结果: $ python3 -c'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. 来源: https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/concepts/assets/software-defined-assets.mdx https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/guides/dagster/enriching-with-software-defined-assets.mdx https://github.com/dagster-io/dagster/blob/ba3a38112867607661062a3be681244f91de11d8/docs/content/tutorial/assets/defining-an-asset.md
它起作用了!而且这次没花 5 分钟 🙂
通过这些简单的更改,我们现在可以使用 Dagster 的所有功能。包括:
单独重新运行管道的每个步骤 声明式重试策略 告警 声明式调度和 SLA 可插拔的存储后端
我今天不会展示所有这些,除了最重要的一个:调度。
按计划重新训练及错误恢复
让我们对项目进行最后一次更改。让我们配置管道,确保每 24 小时重新索引一次仓库,并在发生故障时重试几次。在 Dagster 中,这些都是单行更改。只需如下修改 search_index 资产: @asset( retry_policy=RetryPolicy(max_retries=5, delay=5), freshness_policy=FreshnessPolicy(maximum_lag_minutes=60 * 24), ) def search_index(source_docs):
现在,当我们部署到生产环境时,Dagster 知道何时重新生成此资产,并知道它应该最多重试 5 次(每次重试之间延迟 5 秒)。
未来工作
这篇文章变得非常长。我们涵盖了很多内容:
LLM 概述 如何使用 LangChain 实现问答 如何扩展 LangChain 以处理大量来源及其各种权衡 利用现代编排器(Dagster)的功能来提高开发者生产力和生产环境的稳健性。
然而,本文还有许多未涵盖的内容,我将将其留给读者作为练习。包括:
Slack 集成。我们实际上从未将其与 Slack 集成!幸运的是,有优秀的官方文档说明如何做到这一点。 处理虚假来源。有时我们的 LLM 会发明新的 URL 并将其列为来源。这可以通过解析响应并检查返回的所有 URL 是否确实作为传入链的来源出现来解决。 更好的分块。我们可以做得比当前的文档分块方法更聪明。 微调。我知道我们听说微调不值得,但实际测试一下这个假设可能是个好主意。 爬取网页而不是 markdown。爬取网站的 HTML 页面而不是 GitHub 仓库中的 markdown 文件相对简单。 支持多个来源。支持搜索多个 Faiss 索引相对简单。对于 Dagster,我们实际使用两个仓库:我们的 OSS 仓库和我们的博客仓库。 集成 Slack 历史。我们尝试添加来自社区支持 Slack 实例的消息,但输出质量真的很低。但也许别人能做得更好!
感谢你阅读至此。记得在 GitHub 上给 LangChain 和 Dagster 点个赞,并关注我在 Twitter 上,以及 Harrison(LangChain 的作者)!
我们总是乐于听取您的反馈,所以请联系我们!如果您有任何问题,请在 Dagster 社区 Slack 中提出(点击此处加入!)或发起一个 Github 讨论。如果您遇到任何 bug,请通过 Github issue 告诉我们。如果您对与我们合作感兴趣,请查看我们的开放职位!有反馈或问题?在 Slack 或 Github 中发起讨论。对与我们合作感兴趣?查看我们的开放职位。想要更多内容?关注我们的 LinkedIn。