SQL的演变:释放大型语言模型的力量

107 阅读14分钟

​​

编辑

欢迎来到雲闪世界。最近,很难想象还有什么技术比大型语言模型更有影响力、讨论更广泛。基于 LLM 的应用程序现在已成为最新趋势,就像曾经充斥市场的 Apple 或 Android 应用程序一样。

创建 AI 驱动的数据库查询系统是一项复杂的任务。您需要处理许多重要因素,例如确保安全、确保数据相关、处理错误以及正确训练 AI。在这个故事中,我探索了应对这些挑战的最快方法。

例如,我使用我的旧无服务器 API 存储库模板和 AWS Lambda 函数在 15 分钟内为 Slack 构建了这个 AI 聊天机器人:

编辑

添加图片注释,不超过 140 字(可选)

文本转 SQL、可靠性和 RAG

一般来说,简单的文本到 SQL 模型绝对不可靠,这是我过去看到的 AI 开发人员最常见的抱怨:

看起来正确但在现实生活中,这个 SQL 完全是胡说八道。

如果我们使用的是生成 SQL 查询的 LLM,如果它能够访问数据库的数据定义语言 (DDL)、元数据和一组精心设计、优化的查询,那么它的性能将得到显著提升。通过集成这些数据,LLM 可以生成不仅更可靠而且更安全、针对特定数据库进行更好优化的 SQL 查询。

为了提高 SQL 生成的可靠性,一种有效的方法是使用检索增强生成 (RAG)。

简单来说,RAG 允许 LLM 通过提取额外的相关数据来增强其响应。

这意味着模型不仅仅依赖于其预先存在的知识,还可以访问额外的信息,以便更准确地根据您的需求定制其输出。此方法有助于确保生成的查询符合数据库的实际结构和要求,从而使其更有效并降低出错风险。

文本到 SQL 模型的痛点和局限性

为 LLM 提供书面说明和上下文是情境学习的一个基本示例,其中模型根据推理过程中提供的输入得出其输出。然而,这种方法有固有的局限性:

提示敏感性:由于 LLM 根据给定的输入预测下一个标记,措辞的细微变化可能会导致截然不同的响应。LLM 的输出高度依赖于输入的确切措辞。这种对措辞而非含义的敏感性可能会导致输出不一致。

可靠性:简单的基于提示的 SQL 生成器由于不可靠,通常不适合企业使用。LLM 容易生成看似合理但完全虚构的信息。在 SQL 生成中,这可能导致查询看似正确但本质上存在缺陷,通常会创建虚构的表、列或值。

它可能看起来是正确的,但在现实生活中,它将完全是胡说八道。

上下文窗口:LLM 的输入文本或 token 容量有限,这受其架构限制。例如,ChatGPT 3.5 的 token 限制为 4096,这可能不足以全面理解包含数百个表和列的大型 SQL 数据库。

如何构建 RAG

有多个基于语言模型的通用应用程序设计的强大 Python 库,例如LangChain和LlamaIndex。这些库非常棒,但还有一些专门针对文本到 SQL 需求而定制的库,例如 WrenAI 和 Vanna。例如,Vanna.ai 提供了一种有针对性的方法,旨在简化 LLM 与数据库的集成,提供安全连接和自托管选项。此工具消除了大部分复杂性,使您更容易将 LLM 用于特定应用程序,而无需更多通用库的开销。

编辑

添加图片注释,不超过 140 字(可选)

它分两个步骤进行:

  1. 使用任何 LLM(见下文)在你的数据上训练 RAG“模型”。你只需要 API 密钥
  2. 开始提问。

编辑

添加图片注释,不超过 140 字(可选)

编辑

模型训练

或者,你可以使用预先训练的chinookVanna 模型,如下所示:

# 创建 Python 环境
python3 -m venv env 
source  env /bin/activate 
pip3 install --upgrade pip 
pip3 install vanna

# 运行 get_sql.py 
import vanna as vn 
from vanna.remote import VannaDefault 
# 从 vanna.ai 获取你的 api 密钥并替换此处:
 vn = VannaDefault(model= 'chinook' , api_key= 'your-api-key' ) 
vn.connect_to_sqlite( 'https://vanna.ai/Chinook.sqlite' ) 
vn.ask( '按销售额排名前 10 的艺术家有哪些?' )

终端输出如下:
... 
LLM 回复: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 ;
提取的 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 ;
   艺术家 ID 名称 总销量
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                    REM           39

编辑

添加图片注释,不超过 140 字(可选)

WrenAI是另一个做类似事情的优秀开源工具。它旨在通过将自然语言转换为 SQL 来简化查询数据的过程。WrenAI 与各种数据源兼容,包括 DuckDB、MySQL、Microsoft SQL Server 和 BigQuery。此外,它还支持开放和本地 LLM 推理端点,例如 OpenAI 的 GPT-3-turbo、GPT-4 和通过 Ollama 的本地 LLM 服务器。我们可以使用实体关系来训练模型。

在这种情况下,由于我们提供了有关数据库的额外数据,我们的模型变得更加准确:

编辑

添加图片注释,不超过 140 字(可选)

这种拖放式 UI 大大简化和改善了模型训练。

在每种关系中,您可以编辑、添加或删除模型之间的语义连接,使 LLM 能够了解关系是一对一、一对多还是多对多。

事实上,一旦定义了SQL 语义,我们就不需要担心它了。

语义层与模型训练

开发人工智能数据库查询系统的另一个关键考虑因素是确定应授予人工智能访问权限的适当表和列。

这些数据源的选择至关重要,因为它直接影响生成的查询的准确性和性能以及整个系统的效率。

正如我之前提到的,提供有关数据库的更详细信息对于准确性和可靠性至关重要。数据定义语言 (DDL) 捕获数据库的结构方面,详细说明表、列及其相互关系等元素。与基于提示的标准 SQL 引擎相比,Vanna 在这一领域表现出色。以下代码演示了如何检索 SQLite 的 DDL 语句。

考虑下面的代码片段。它解释了如何连接到数据库并训练 RAG 模型。在我的情况下,它将是 BigQuery:

# train.py 
# 连接到 BigQuery
 vn.connect_to_bigquery(project_id='my-project') 
# 信息模式查询可能需要根据您的数据库进行一些调整。这是一个很好的起点。
 df_information_schema = vn.run_sql( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS" ) 

# 这会将信息模式分解为 LLM 可以引用的小块
plan = vn.get_training_plan_generic(df_information_schema) 
plan 

# 如果您喜欢该计划,请取消注释并运行它进行训练
# vn.train(plan=plan) 

# 对 SQL 查询进行训练:
 question = "每个客户买了多少张专辑?"
 sql = vn.generate_sql(question) 

display(sql) 

# 如果 Vanna 的响应完全符合您的预期,则可以添加训练数据
vn.train(question = question, sql=sql)

通过使用上述代码,您可以迭代输入查询并评估其输出。然后,您可以选择让 Vanna 从结果中学习,或指定首选查询以供其适应。

系统如果包含过多的表和列,则会导致标记数量增加、成本上升,并且由于语言模型存在混淆或丢失关键细节的风险,准确性可能会降低。相反,数据访问不足会限制 AI 生成精确有效查询的能力。

这就是为什么这种方法非常有用。

需要考虑的几件事:

  • 数据质量和一致性:选择维护良好、持续更新的数据。不一致或不完整的数据可能导致结果不准确并削弱用户信任。
  • 安全和 PII:确保敏感数据受到保护。实施数据屏蔽或标记化等措施来保护机密信息,同时允许 AI 访问相关数据。
  • 与用户的相关性:选择与用户可能提出的问题最相关的表和列。这可确保 AI 拥有生成准确且有用的查询所需的数据。
  • SQL 查询性能:大型或复杂的表会降低 AI 的查询性能。选择已建立索引和优化的表和列,以保持高效的查询生成和执行。

保留互动历史

这是 LLM 开发中的另一个常见痛点。与常见的误解不同,LLM 不会通过单个查询记住或了解您的特定数据或系统,除非它们经过明确使用该信息的训练。

对未经训练的 LLM 的每个请求都根据其最新的训练数据而不是之前的用户交互进行处理。

为了生成准确的查询,每次提出请求时,向 LLM 提供您的聊天记录至关重要。这应包括有关您的架构和示例查询的相关详细信息,以确保 LLM 可以生成适合您的数据的精确查询。

训练人工智能驱动的查询系统涉及改进和增强的迭代过程。

文本到 SQL 开发最佳实践

在人工智能驱动的数据库查询中,文本转 SQL 的一个主要问题是数据库被意外修改的风险。为了解决这个问题,必须采取措施确保人工智能不会更改底层数据。

  • 确保生成的 SQL 经过验证:引入查询验证层,在执行 AI 生成的查询之前对其进行检查。此层应过滤掉任何可能修改数据库的命令,例如 INSERT、UPDATE、DROP 等。通过在处理查询之前对其进行验证,您可以防止对数据库进行意外更改。
  • AI 服务访问权限:确保仅授予 AI 系统对数据库的只读访问权限。此限制可防止对数据进行任何意外或恶意更改,从而保持数据库完整性,同时仍允许 AI 生成数据检索查询。
  • 监控 SQL 查询性能:密切关注使用情况和查询性能指标始终是一个好主意。
  • 专注于洞察:AI 生成的 SQL 查询非常适合数据检索,但只有与高级数据分析结合使用时,才能发挥其真正潜力。通过将这些查询与分析工具和工作流程相结合,您可以发现更深入的洞察并做出更明智的数据驱动决策。
  • 自定义错误处理:即使对文本到 SQL 系统进行了细致的模型训练和优化,仍然可能出现生成的查询包含解析错误或没有结果的情况。因此,在这种情况下,实现一种重试查询生成并向用户提供建设性反馈的机制至关重要。它将增强文本到 SQL 模型的有效性和弹性,并改善用户体验。

通过整合这些验证机制,您可以确保 AI 生成的查询既安全又可靠,从而降低意外修改数据库的风险并避免常见的查询相关问题。这种方法不仅可以节省时间和资源,还可以促进数据驱动的文化,在这种文化中,决策以准确和最新的见解为基础。

有了这些政策,您的组织可以有效地利用其数据的全部潜力,允许非技术用户自行访问和分析信息。

构建 AI 驱动的 Slack 机器人助手

为此,我们需要 OpenAI API 密钥、Slack 帐户和 AWS 帐户来使用 Lambda 函数部署无服务器 API。

高级应用程序逻辑:

  1. Slack 应用程序将向 API 发布文本消息
  2. 我们在 AWS 中部署的无服务器 API 将向 AWS Lambda 发送 Slack 消息
  3. AWS Lambda 将向 OpenAI API 请求响应并将其发送回 Slack

转到Slack Apps并创建一个新的应用程序:Slack API: Applications | Slack

编辑

添加图片注释,不超过 140 字(可选)

单击“从头开始”并命名:

编辑

添加图片注释,不超过 140 字(可选)

接下来,让我们添加一个斜线命令来触发我们的机器人:

编辑

添加图片注释,不超过 140 字(可选)

在编辑命令部分提供我们的无服务器 API 端点(请求 URL):

编辑

添加图片注释,不超过 140 字(可选)

最后,让我们将它安装到我们的 Slack 工作区:

编辑

添加图片注释,不超过 140 字(可选)

让我们创建一个微服务来处理机器人收到的 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 called 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 ) => { 

    /** 
     * 从 Slack 命令添加命令解析器
     */ 
    function commandParser ( slashCommand ) { 
        let hash; 
        let myJson = {};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 {
        让channel_id = commandParser( event .body).channel_id;user_name = commandParser( event .body).user_name;txt = commandParser( event .body).text; 
        // 获取响应:
        让message =   await processMessageText(txt,user_name,channel_id);
        返回{ 
            “statusCode” : 200,
            “headers” : {},
            “body” : JSON.stringify(message),
            “isBase64Encoded” : false
         }; 
        
    } catch (err) { 
        console.log (“err”)log( '错误处理事件' , err);
        返回{ 
            “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} ` 
    };

    返回消息;
}; 

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: '您目前无法获得响应。' }]; 
    } 
    
};

使用 AWS Cloudformation 或 Terraform 部署我们的 API 和无服务器应用程序,我们就可以开始了!

编辑

AI Slack 机器人

结论

最近,很难想象还有哪项技术比大型语言模型更有影响力、讨论更广泛。基于 LLM 的应用程序现在已成为最新趋势,就像曾经充斥市场的 Apple 或 Android 应用程序激增一样。使用 DDL 语句、自定义查询、元数据或文档来细化定义,可以简化 LLM 开发过程。例如,如果您的企业使用自定义指标,那么提供此附加上下文将有助于其生成更准确、更相关的输出。

构建一个由人工智能驱动的数据库查询系统并非易事。您需要处理许多重要因素,例如确保安全、确保数据相关、处理错误以及正确训练人工智能。训练人工智能驱动的查询系统涉及一个反复改进和增强的过程。通过开发全面的参考指南、提供各种示例查询以及有效管理上下文窗口,您可以构建一个强大的系统,方便用户快速准确地检索数据。

在这个故事中,我探索了应对这些挑战的最快方法,并分享了一些建立可靠高效的 AI 查询系统的技巧。

感谢关注雲闪世界。(Aws解决方案架构师vs开发人员&GCP解决方案架构师vs开发人员)

订阅频道(t.me/awsgoogvps_…) TG交流群(t.me/awsgoogvpsHost)