在过去的几个月里,我们一直在为开发者们开发一些令人兴奋的东西:Redis Copilot。这款由 AI 提供支持的助手 提升了生产力,并让学习 Redis 变得更容易。Redis Copilot 提供两个主要功能:一个通用知识库聊天机器人,以及一个上下文感知聊天机器人,它可以创建并运行查询来回答关于您的 Redis 数据库数据的问题。在最新的 Redis Insight GUI 开发工具中访问 Redis Copilot,并在我们的 公共文档 中访问通用聊天机器人。我们非常乐意分享 构建自然语言查询生成器聊天机器人的一些见解。
人类喜欢创造和使用工具,但并非所有工具都一样。有些工具易于学习,有些则不然。说到数据库,像 SQL 这样的结构化语言是查询的首选,但编写这些语言需要专业知识。
例如,来看这个 Redis 查询
FT.AGGREGATE idx:bicycle * GROUPBY 0 REDUCE COUNT 0
此查询返回数据库索引的文档数量。这是一个直接的查询,用于查找“数据库中有多少辆自行车?” 但即使是这个简单的查询也需要学习语法并了解数据的表示方式。对于 Redis 来说,其查询语言是独一无二的,且不如 SQL 广为人知,这对新用户来说可能是一个挑战。
大型语言模型(LLM)已经证明它们可以遵循自然语言指令,从而催生了 一波新的 GenAI 应用。在我看来,最令人兴奋的应用是那些让我能够“谈论”我的数据。我无需学习新的查询语言(或重新学习我已经忘记的语言),只需提出问题即可获得答案。Redis Copilot 使这成为可能。
存在许多代码生成 LLM,包括专门训练用于从自由文本生成 SQL 查询的模型。由于 Redis 的查询语言几乎与任何其他语言都完全不同,现有模型无法生成其查询,因此我们需要新的东西。
从零开始创建新的 LLM 是一个资源密集型过程,需要丰富的领域经验。如果我们选择这条路,可能永远无法完成项目。相反,我们使用了提示工程和微调,从预训练模型中获得了所需的结果。
提示工程就像元编程——您给模型提供纯文本指令,它就会遵循。如果您使用过 ChatGPT 或类似工具,您已经知道它是如何工作的。根据手头的任务,不同的提示会产生不同的结果。一个好的提示在指导 LLM 执行任务方面大有帮助。然而,仅靠提示不足以让最先进的 LLM 学会像我们的查询语言这样复杂的新主题。
微调教会预训练 LLM 新知识。它调整模型的参数以获得我们想要的响应。这个过程依赖于数据,即数据集,用于在此过程中训练和评估模型。数据集是专门构建的,用于教授 LLM 新任务。在我们的案例中,我们通过向预训练 LLM 提供包含大量问题及其匹配查询的数据集,并根据性能调整其参数,从而对其进行微调,使其理解并生成 Redis 查询。
在 Redis Copilot 的案例中,LLM 微调数据集中的每个项都包含一个问题、一个答案和一些上下文。问题是一个英文纯文本字符串,答案是执行时能回答该问题的对应 Redis 查询。上下文包含有关数据库中文档以及查询所使用的索引模式的信息。
即使使用少量数据集,微调也能产生有希望的结果,但对于我们的任务,我们需要更大的数据集。考虑到查询语言的健壮性,数据集必须足够多样化,并包含其许多特性及其各自用途的多个示例。此外,微调模型的输出查询必须在语法和结果上都是正确的,因此我们需要更多数据。
准备数据集是团队最耗时耗力的任务。由于不存在现有的数据语料库,我们不得不创建一个。由于合成数据和 LLM 生成的数据无助于解决我们的挑战,我们单独精心制作了每个数据集项。
微调 LLM 会使其重复您教它的东西,因此数据必须由精通 Redis 查询的工程师来构建。否则,我们会得到次优结果——(“垃圾进,垃圾出。”)
在构建和完善数据集的同时,我们用它来微调模型并测试其准确性。通过在数据集的演进版本上微调模型,我们可以衡量改进程度并找出数据中的问题区域。
测试模型远非易事,特别是在确保其答案正确且准确方面。这是一个众所周知的难题,这也是为什么模型幻觉仍然普遍存在的原因。但是,由于我们试图解决的问题具有正式性质,我们可以更有效地测试我们的模型。
当我们的模型生成格式错误的查询时,例如包含语法错误和虚构命令的查询,它们很容易被捕获,因为当您尝试运行它们时,服务器会返回错误消息。您还可以通过将生成的查询结果与预期结果进行比较来发现不正确的查询。分析错误和不正确的查询有助于我们修复数据集并专注于需要更多工作的领域。
我们构建的数据集以及微调模型构成了 Redis Copilot 查询生成器的核心,但还有一点需要提及:数据集中的项目数量和质量至关重要,尤其是在它们数量庞大且都很好的情况下。
我们的微调 LLM 在生成合法且有意义的查询方面越来越好,但它开始“忘记”如何做其他事情。这是微调中一个众所周知的问题——您在加强您想要的行为,但代价是牺牲 LLM 已经知道的东西。我们仍然需要一种进行对话的方式,这是一项不仅仅需要生成查询的任务。
有些问题可以通过将其分解为更小的问题来解决。微调的 LLM 解决了查询生成的问题,我们转向另一个 LLM 来解决剩余的对话挑战。我们建立了一个 AI 代理,它使用通用 LLM 与用户聊天,并且可以调用微调模型来生成查询。
“AI 代理”可以有很多含义,但对我们来说,它是一个系统,其中语言模型控制一个循环工作流并执行动作。代理的任务和指令来自 LLM 的提示。除了提示之外,代理还可以使用其他工具来完成任务。
工具可以做任何用代码可能实现的事情。代理不了解它们的实现细节,只知道它们的预期用途和期望的输入。例如,我们的 Redis Copilot 工具之一期望单个字符串作为输入,其描述是“使用此工具评估数学表达式”。
该工具恰当地命名为“evaluate_math_expression”。LLM 的提示中包含使用它进行计算的特定说明,特别是因为 LLM 可能在基础数学方面出错。这个工具确保了结果的准确性,无论模型的能力如何。
我们的代理有另一个工具叫做“query”。其描述是:“使用此工具生成并执行数据库查询。” 它需要两个输入:一个问题和一个索引名称。您猜对了——该工具所做的只是调用微调的 LLM 来完成繁重的工作。
最后一块拼图是代理的上下文。除了聊天历史记录,我们还向代理提供关于数据库的总结信息。这包括索引和文档属性的名称,这有助于 LLM“理解”用户的查询指的是哪些数据。
这一切是如何协同工作的
当 GenAI 正确地完成一项任务时,令人印象深刻。看到我们的微调模型开始吐出第一个合法但基础的查询时,令人敬畏。而当我们将查询生成和执行与对话代理连接起来完成整个循环时,结果更是令人惊叹。
GenAI 和 AI 代理无处不在,并且每天都在改进。我几乎不记得 GitHub Copilot 耐心地坐在我的 IDE 里,修正我的拼写错误,建议代码片段,通过简单地按 Tab 键帮助我重新格式化大段代码,以及总的来说提供了巨大帮助的那些日子之前的时光了。这些智能功能,以及我最近参与其背后的技术,几乎让我们忽视了一个明显的真相。
微调模型在对话过程中以地道的 Redis CLI 语法生成查询。这种语法用于 Redis 的文档中,可以复制粘贴到 Redis Insight 中执行。但 CLI 语法不适合使用 Redis 的客户端在任何给定的编程语言中运行查询。每种语言都有自己的语法,每个客户端都提供了自己的 API。
如果 Redis Copilot 能以用户偏好的编程语言和所选的客户端提供生成的查询,那不是太棒了吗?答案显然是肯定的,考虑到我们投入的所有工作,这似乎很容易实现。
每个人都知道 LLM 擅长生成代码,尤其是那些为此训练的模型。在一个包含不同语言代码片段的数据集上微调 LLM 应该能得到我们想要的结果。我们所需要做的就是将我们的数据集翻译成我们想要支持的目标语言。从技术上讲,这是可能的,但考虑到如此大的数据集和众多的翻译目标,这需要具有每种语言专业知识的开发者付出巨大的努力。这并不容易。等等,我们想。编写代码来接收格式良好、语法正确的 CLI 编码查询并将其转换为另一种定义明确的语法,这有多难?通过相对简单的代码,我们可以解析查询并以任何我们需要的语法输出。这将大大简化微调数据集的翻译工作,并帮助我们组建一支 LLM 大军。
至此,我们意识到 LLM 并非总是解决每个问题的正确方案——即使是那些看起来完全是代码生成的问题。一旦查询解析器准备就绪,每当用户需要查询的翻译时,直接用生成的查询调用解析器就很有意义了——无需再涉及 LLM。它比任何 AI 生成的输出更便宜、更快、无限可靠。
关于这段旅程以及我们在 Redis Copilot 上所做的工作,还有很多可以分享。如果这篇文章能更长一些,我们会告诉您我们发现它“作弊”和“偷懒”的那次经历。但这些故事将不得不留待未来的博文。
了解更多关于 Redis Insight 的信息并在此 试用 Redis Copilot。