SQL 的演变
在这篇文章中,我将探讨大型语言模型(LLM)如何将自然语言转换为 SQL,使查询编写对非技术用户更加容易。讨论将包括展示开发 LLM 解决方案简便性的实际示例。我们还将涵盖各种用例,并通过创建一个简单的 Slack 应用程序来展示这个过程。构建一个由 AI 驱动的数据库查询系统涉及几个关键考虑因素,包括保持安全性、确保数据相关性、管理错误以及正确训练 AI。在这个故事中,我探讨了应对这些挑战的最快方法,并分享了一些设置稳固和高效文本到 SQL 查询系统的技巧。
最近,很难想到有任何技术比大型语言模型更具影响力和更广泛讨论。基于 LLM 的应用现在是最新趋势,就像曾经充斥市场的苹果或安卓应用激增一样。它在 BI 领域无处不在,我之前在这里写过关于它[1]的内容。
创建一个 AI 驱动的数据库查询系统是一项复杂的任务。你需要处理许多重要因素,如保持安全、确保数据的相关性、处理错误以及正确训练 AI。在这个故事中,我探讨了应对这些挑战的最快方法。
例如,我用了 15 分钟在我的旧仓库模板和 AWS Lambda 函数的基础上构建了这个 AI 聊天机器人:
图像由作者提供
文本到 SQL,可靠性及 RAGs
一般而言,简单的文本到 SQL 模型绝对不可靠,这是我在过去看到的 AI 开发者最常见的抱怨:
它看起来是对的,但在现实生活中,这个 SQL 是完全无意义的。
如果我们与一个生成 SQL 查询的 LLM 一起工作,如果它能够访问你的数据库的数据定义语言(DDL)、元数据和一系列精心制作、优化的查询,那么它可以显著提高。通过整合这些数据,LLM 可以生成不仅更可靠、更安全,而且针对特定数据库进行了优化的 SQL 查询。
为了提高 SQL 生成的可靠性,一个有效的方法是使用检索增强生成(RAG)。
简而言之,RAG 允许 LLM 通过引入额外的相关数据来增强其响应。
这意味着模型不仅依赖于其预存知识,还可以访问额外信息,以便更准确地调整其输出以满足你的需求。这种方法有助于确保生成的查询与数据库的实际结构和要求相一致,使它们更有效,并降低错误的风险。
文本到 SQL 模型痛点与局限性
为 LLM 提供书面指令和上下文是情境学习的基本示例,其中模型根据推理过程中提供的输入推导其输出。然而,这种方法有固有的局限性:
提示敏感性:由于 LLM 根据给定的输入预测下一个标记,措辞的微小变化可能导致显著不同的响应。LLM 的输出高度依赖于输入的确切措辞。对措辞的敏感性而不是对意义的敏感性可能导致输出不一致。
可靠性:简单的基于提示的 SQL 生成器通常不适合企业使用,因为它们不可靠。LLM 易于生成听起来合理但实际上是虚构的信息。在 SQL 生成中,这可能导致看似正确但实际上存在根本缺陷的查询,通常创建虚构的表、列或值。
它可能看起来正确,但在现实生活中,它将是完全无意义的。
上下文窗口:LLM 对输入文本或标记的容量有限,受其架构限制。例如,ChatGPT 3.5 的标记限制为 4096,这可能不足以全面理解包含数百张表和列的大型 SQL 数据库。
如何构建 RAG
有几个专为基于语言模型的通用应用设计的健壮的 Python 库,例如 LangChain 和 LlamaIndex。这些库很棒,但还有一些库是专门针对 Text-to-SQL 需求定制的,例如 WrenAI 和 Vanna。例如,Vanna.ai 提供了一种针对性的方法,旨在简化将 LLM 与您的 数据库 集成的过程,提供安全连接和自托管选项。这个工具消除了许多复杂性,使得在没有更多通用库开销的情况下,更容易利用 LLM 为您的特定应用提供支持。
RAG 开发流程。来源:Vanna.ai
它分为两个步骤:
-
使用任何 LLM(如下所示)在您的数据上训练一个 RAG “模型”。您需要的只是 API 密钥
-
开始提问。
LLM 模型。图片由作者提供
模型训练。图片由作者提供。
或者,您可以使用预训练的 chinook Vanna 模型,如下所示:
# create a Python environment
python3 -m venv env
source env/bin/activate
pip3 install --upgrade pip
pip3 install vanna
# Run get_sql.py
import vanna as vn
from vanna.remote import VannaDefault
# Get your api key from vanna.ai nad replace here:
vn = VannaDefault(model='chinook', api_key='your-api-key')
vn.connect_to_sqlite('https://vanna.ai/Chinook.sqlite')
vn.ask('What are the top 10 artists by sales?')
终端输出将是以下内容:
...
LLM Response: SELECT a.ArtistId, a.Name, SUM(il.Quantity) AS TotalSales
FROM Artist a
JOIN Album al ON a.ArtistId = al.ArtistId
JOIN Track t ON al.AlbumId = t.AlbumId
JOIN InvoiceLine il ON t.TrackId = il.TrackId
GROUP BY a.ArtistId, a.Name
ORDER BY TotalSales DESC
LIMIT 10;
Extracted SQL: SELECT a.ArtistId, a.Name, SUM(il.Quantity) AS TotalSales
FROM Artist a
JOIN Album al ON a.ArtistId = al.ArtistId
JOIN Track t ON al.AlbumId = t.AlbumId
JOIN InvoiceLine il ON t.TrackId = il.TrackId
GROUP BY a.ArtistId, a.Name
ORDER BY TotalSales DESC
LIMIT 10;
SELECT a.ArtistId, a.Name, SUM(il.Quantity) AS TotalSales
FROM Artist a
JOIN Album al ON a.ArtistId = al.ArtistId
JOIN Track t ON al.AlbumId = t.AlbumId
JOIN InvoiceLine il ON t.TrackId = il.TrackId
GROUP BY a.ArtistId, a.Name
ORDER BY TotalSales DESC
LIMIT 10;
ArtistId Name TotalSales
0 90 Iron Maiden 140
1 150 U2 107
2 50 Metallica 91
3 22 Led Zeppelin 87
4 113 Os Paralamas Do Sucesso 45
5 58 Deep Purple 44
6 82 Faith No More 42
7 149 Lost 41
8 81 Eric Clapton 40
9 124 R.E.M. 39
图片由作者提供。
WrenAI 是另一个做类似事情的出色开源工具。它旨在通过将自然语言转换为 SQL 来简化查询数据的过程。WrenAI 与各种数据源兼容,包括 DuckDB、MySQL、Microsoft SQL Server 和 BigQuery。此外,它支持开放和本地 LLM 推理端点,如 OpenAI 的 GPT-3-turbo、GPT-4 以及通过 Ollama 的本地 LLM 服务器。我们可以使用实体关系来训练模型。
在这种情况下,我们提供更多关于数据库的数据,我们的模型变得更加准确:
作者图片
这种拖放式 UI 极大地简化并改进了模型训练。
在每个关系中,您可以编辑、添加或删除模型之间的语义连接,使 LLM 能够理解关系是一对一、一对多还是多对多。
事实上,一旦定义了,我们就不必担心我们的SQL 语义。
语义层和模型训练
在开发一个由 AI 驱动的数据库查询系统时,另一个关键的考虑因素是确定 AI 应被授予访问权限的适当表和列。
选择这些数据源至关重要,因为它直接影响生成的查询的准确性和性能,以及整个系统的效率。
如我之前提到的,提供更多关于您数据库的详细信息对于准确性和可靠性至关重要。数据定义语言(DDL)捕获数据库的结构方面,详细说明如表、列及其相互关系等元素。与标准基于提示的 SQL 引擎相比,Vanna 在这个领域表现出色。以下代码演示了如何检索 SQLite 的 DDL 语句。
考虑下面的代码片段。它解释了如何连接到您的数据库并训练您的 RAG 模型。在我的情况下,它将是 BigQuery:
# train.py
# Connect to BigQuery
vn.connect_to_bigquery(project_id='my-project')
# The information schema query may need some tweaking depending on your database. This is a good starting point.
df_information_schema = vn.run_sql("SELECT * FROM INFORMATION_SCHEMA.COLUMNS")
# This will break up the information schema into bite-sized chunks that can be referenced by the LLM
plan = vn.get_training_plan_generic(df_information_schema)
plan
# If you like the plan, then uncomment this and run it to train
# vn.train(plan=plan)
# Training on SQL queries:
question = "How many albums did each customer buy?"
sql = vn.generate_sql(question)
display(sql)
#Optional if the response by Vanna is exactly as you intend, you can add in the training data
vn.train(question = question, sql=sql)
通过使用上面的代码,您可以迭代地输入查询并评估其输出。然后,您可以选择让 Vanna 从结果中学习,或者指定它需要适应的查询。
过度加载系统以包含过多的表和列可能会导致标记计数增加、成本上升,以及由于语言模型混淆或关键细节丢失的风险,从而可能降低准确性。相反,数据访问不足限制了 AI 生成精确和有效查询的能力。
这就是为什么这种方法非常有用的原因。
需要考虑以下几点:
-
数据质量和一致性:选择维护良好、持续更新的数据。不一致或不完整的数据可能导致结果不准确并损害用户信任。
-
安全和 PII:确保敏感数据得到保护。实施数据掩码或标记化等措施,在允许 AI 访问相关数据的同时保护机密信息。
-
与用户的关联性:选择与用户可能提出的问题最相关的表和列。这确保 AI 拥有生成准确和有用查询所需的数据。
-
SQL 查询性能:大型或复杂的表可能会降低 AI 的查询性能。选择已索引和优化的表和列,以保持高效的查询生成和执行。
维护交互历史
这是在 LLM(大型语言模型)开发中另一个常见的痛点。与常见的误解不同,LLM 不会从单个查询中记住或学习你的特定数据或系统,除非它们明确地用这些信息进行训练。
对未经过训练的 LLM 的每一次请求都是基于其最新的训练数据进行处理的,而不是基于之前的用户交互。
为了生成准确的查询,向 LLM 提供每次请求的聊天历史是必不可少的。这应该包括有关你的模式和示例查询的相关细节,确保 LLM 可以生成针对你的数据的精确查询。
训练一个由 AI 驱动的查询系统涉及一个迭代的过程,不断进行精炼和增强。
文本到 SQL 开发最佳实践
在 AI 驱动的数据库查询中,文本到 SQL 的一个主要担忧是数据库意外修改的风险。为了解决这个问题,实施确保 AI 不会更改底层数据的措施至关重要。
-
确保你的生成的 SQL 经过验证:在执行之前引入一个查询验证层,该层会审查 AI 生成的查询。这一层应该过滤掉任何可能修改数据库的命令,如 INSERT、UPDATE、DROP 等。在处理之前验证查询可以防止对数据库进行意外的更改。
-
AI 服务访问权限:确保 AI 系统只被授予对数据库的只读访问权限。这种限制可以防止对数据进行任何意外或恶意的更改,同时仍然允许 AI 生成用于数据检索的查询,从而保持数据库的完整性。
-
监控 SQL 查询性能:关注使用情况和查询性能指标总是一个好主意。
-
关注洞察:AI 生成的 SQL 查询在数据检索方面非常出色,但它们的真正潜力在于与高级数据分析相结合。通过将这些查询与分析工具和工作流程集成,你可以发现更深入的见解,并做出更明智、基于数据的决策。
-
自定义错误处理:即使经过细致的模型训练和你的文本到 SQL 系统的优化,仍然可能存在生成的查询包含解析错误或没有结果的情况。因此,在这些情况下实施一个机制来重试查询生成并向用户提供建设性反馈至关重要。这将增强你的文本到 SQL 模型的有效性和弹性,并改善用户体验。
通过整合这些验证机制,你可以确保 AI 生成的查询既安全又可靠,降低意外修改数据库的风险,并避免常见的查询相关问题。这种方法不仅节省了时间和资源,还促进了以数据为驱动的文化,其中决策基于准确和最新的洞察。
制定这些政策后,您的组织可以有效地利用其数据的全部潜力,让非技术用户能够自行访问和分析信息。
构建一个基于 AI 的 Slack 机器人助手
为了做到这一点,我们需要我们的 OpenAI API 密钥、Slack 账户和 AWS 账户来部署带有 Lambda 函数的无服务器 API。
高级应用程序逻辑:
-
Slack 应用程序将向我们的 API 发送文本消息
-
我们在 AWS 部署的无服务器 API 将向 AWS Lambda 发送 Slack 消息
-
AWS Lambda 将向 OpenAI API 请求响应,并将其发送回 Slack
前往Slack 应用并创建一个新的应用程序:api.slack.com/apps
图片由作者提供
点击“从头开始”并给它起个名字:
图片由作者提供
接下来,让我们添加一个命令行来触发我们的机器人:
图片由作者提供
在编辑命令部分提供我们的无服务器 API 端点(请求 URL):
图片由作者提供
最后,让我们将其安装到我们的 Slack 工作空间中:
图片由作者提供
让我们创建一个微服务来处理我们的机器人接收到的 Slack 消息。如果用 Node.js 编写,我们的 AWS Lambda 应用程序代码可能看起来像这样:
# app.js
const AWS = require('aws-sdk');
AWS.config.update({region: "eu-west-1"});
const axios = require('axios');
const OPENAI_API_KEY = process.env.openApiKey || 'OpenAI API key';
exports.handler = async (event, context) => {
console.log("app.handler invoked with event "+ JSON.stringify(event,null,2));
try {
context.succeed( await processEvent(event) );
} catch (e) {
console.log("Error: "+JSON.stringify(e));
context.done(e)
}
};
let processEvent = async (event) => {
/**
* Adding command parser fro Slack commands
*/
function commandParser(slashCommand) {
let hash;
let myJson = {};
let hashes = slashCommand.slice(slashCommand.indexOf('?') + 1).split('&');
for (let i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
myJson[hash[0]] = hash[1];
}
myJson.timestamp = Date.now();
return myJson;
};
try {
let channel_id = commandParser(event.body).channel_id;
let user_name = commandParser(event.body).user_name;
let txt = commandParser(event.body).text;
// Get response:
let message = await processMessageText(txt,user_name,channel_id);
return {
"statusCode": 200,
"headers": {},
"body": JSON.stringify(message),
"isBase64Encoded": false
};
} catch (err) {
console.log('Error handling event', err);
return {
"statusCode": 500,
"headers": {},
"body": '{}',
"isBase64Encoded": false
};
}
};
const processMessageText = async(txt, user_name, channel_id) => {
let Response = await fetchAi(txt);
let message = {
response_type: 'in_channel',
text: `@${user_name} , ${Response} `
};
return message;
};
const fetchAi = async(prompt) => {
try {
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: prompt }],
max_tokens: 150,
temperature: 0.7,
},
{
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
}
);
const generatedText = response.data.choices[0].message.content;
return generatedText;
} catch (e) {
return [{NOW: 'You are unable to get a response atm.'}];
}
};
使用 AWS Cloudformation 或 Terraform 部署我们的 API 和无服务器应用程序,我们即可随时出发!
AI Slack 机器人。图片由作者提供。
在我之前的一篇文章中,我讨论了使用基础设施即代码部署应用程序的好处[2]。
结论
最近,很难想到有任何技术比大型语言模型更具影响力和更广泛讨论的。基于 LLM 的应用现在是最新趋势,就像曾经充斥市场的苹果或安卓应用激增一样。使用 DDL 语句、自定义查询、元数据或文档来细化定义,简化了 LLM 开发的过程。例如,如果你的业务使用自定义指标,提供这些额外的上下文将有助于它生成更准确和相关的输出。
构建一个由人工智能驱动的数据库查询系统并非易事。你需要处理许多重要因素,如确保安全性、确保数据的相关性、处理错误以及正确训练人工智能。训练一个由人工智能驱动的查询系统涉及一个反复的优化和改进过程。通过开发一个全面的参考指南、提供多样化的示例查询以及有效地管理上下文窗口,你可以构建一个强大的系统,使用户能够快速准确地检索数据。
在这个故事中,我探讨了应对这些挑战的最快方法,并分享了一些设置稳固高效的人工智能查询系统的技巧。
推荐阅读:
[1] medium.com/towards-data-science/artificial-intelligence-in-analytics-f11d2deafdf0
[2] medium.com/gitconnected/infrastructure-as-code-for-beginners-a4e36c805316
[3] platform.openai.com/docs/quickstart?context=node
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)