GPT做SQL查询引擎的自然语言
目录
面向企业查询的生成式人工智能
步骤1:将示例数据转换为单字符字符串
步骤2:为大型语言模型(LM)创建提示符
步骤3:将数据发送到OpenAI的API
步骤4:执行GPT返回的SQL代码的结果
步骤5(可选):创建交互式应用程序
如今,得益于ChatGPT这种生成式人工智能技术,使得用简单的语句查询数据集变得非常简单。
与大多数生成式人工智能一样,OpenAI公司开发的API的结果仍然不完美,这意味着用户不能完全信任它们。幸运的是,用户现在可以编写代码来询问GPT如何计算响应,如果采用这种方法,用户可以自己运行代码。这意味着用户可以使用自然语言询问ChatGPT一些问题,例如,“某产品去年各地区的总销售额是多少?”,并对ChatGPT的回答准确性充满信心。
以下是使用GPT为数据库设置自然语言查询的一种快速而简单的技术:
- 将数据的结构、几个示例行或两者都放入单个文本字符串中。
- 用这些信息加上采用自然语言提出的问题,为GPT制作一个“提示”。
- 将提示发送到OpenAI的GPT-3.5-turbo API ,并请求SQL查询来回答问题。
- 运行返回到数据集的SQL来计算答案。
- (可选)创建一个交互式应用程序,使查询数据集变得简单。
在处理实际数据时,这种方法有几个优点。通过只发送数据结构和一些示例行(其中可能包括假数据),不需要向GPT发送实际的敏感数据。如果数据规模太大,超出了GPT的提示大小限制,也不用担心。并且,通过请求SQL而不是最终答案,检查GPT如何生成其答案的能力被嵌入到流程中。
面向企业查询的生成式人工智能
如果用户真的想使用生成式人工智能来开发企业级查询,可能想要研究像LangChain这样的工具,它是一个用于处理多种不同大型语言模型(LLM)的框架,而不仅仅是OpenAI公司的GPT。OpenAI公司最近还宣布了在API请求中包含函数调用的可能性,其目的是使查询和类似的任务更容易、更可靠。但对于快速原型或自己使用,这里描述的过程是一种简单的开始方法。这里的演示是用R语言完成的,但这种技术可以在任何编程语言中使用。
步骤1:将示例数据转换为单字符字符串
这一步骤中的示例数据可以包括数据库模式或几行数据。将其全部转换为单个字符串非常重要,因为它将成为将发送到GPT 3.5的更大的文本字符串查询的一部分。
如果用户的数据已经在SQL数据库中,那么这一步非常简单。如果不是,建议将其转换为SQL可查询的格式。为什么?在测试了R语言和SQL代码结果之后,用户对GPT生成的SQL代码比它的R语言代码更有信心。
在R语言的代码中,sqldf包允许用户在R数据帧上运行SQL查询,这就是在本例中使用的。Python中也有一个类似的sqldf库。对于性能很重要的大型数据,可能需要查看duckdb项目。
需要注意的是,在这个演示中,将使用一个包含美国人口普查州人口数据的CSV文件,可以在states.csv中找到。
下面的代码将数据文件导入R语言,使用sqldf查看数据框架是SQL数据库表时的SQL模式,使用dplyr的filter()函数提取三个示例行,并将模式和示例行都转换为字符串。免责声明:ChatGPT编写了将数据转换为单个字符串的基本R apply()部分代码(通常使用purrr完成这些任务)。
library(rio)
library(dplyr)
library(sqldf)
library(glue)
states <- rio::import("https://raw.githubusercontent.com/smach/SampleData/main/states.csv") |>
filter(!is.na(Region))
states_schema <- sqldf("PRAGMA table_info(states)")
states_schema_string <- paste(apply(states_schema, 1, paste, collapse = "\t"), collapse = "\n")
states_sample <- dplyr::sample_n(states, 3)
states_sample_string <- paste(apply(states_sample, 1, paste, collapse = "\t"), collapse = "\n")
步骤2:为大型语言模型(LM)创建提示符
格式应该类似于“表现得像一个数据科学家。有一个名为{table_name}的SQLite表,具有以下架构:```{schema}``。第一行看起来像这样:```{rows_sample}``。根据这些数据,编写一个SQL查询来回答以下问题:{query}。只返回SQL,不包括解释。”
下面的函数以这种格式创建查询,并接受数据模式、示例行、用户查询和表名的参数。
create_prompt <- function(schema, rows_sample, query, table_name) {
glue::glue("Act as if you're a data scientist. You have a SQLite table named {table_name} with the following schema:
```
{schema}
```
The first rows look like this:
```{rows_sample}```
Based on this data, write a SQL query to answer the following question: {query}. Return the SQL query ONLY. Do not include any additional explanation.")
}
步骤3:将数据发送到OpenAI的API
用户可以先将数据剪切并粘贴到OpenAI的Web界面中,然后在ChatGPT或OpenAI API中查看结果。ChatGPT不收取使用费用,但用户不能调整其结果。可以让用户设置温度之类的参数,这意味着其反应应该有多“随机”或多有创意,以及服务商想使用哪种模型。对于SQL代码,将温度设置为0。
接下来,将一个自然语言问题保存到变量my_query中,使用create_prompt()函数创建一个提示符,然后观察当将该提示符粘贴到API playground中时会发生什么:
> my_query <- "What were the highest and lowest Population changes in 2020 by Division?"
> my_prompt <- get_query(states_schema_string, states_sample_string, my_query, "states")
> cat(my_prompt)
Act as if you're a data scientist. You have a SQLite table named states with the following schema:
```
0 State TEXT 0 NA 0
1 Pop_2000 INTEGER 0 NA 0
2 Pop_2010 INTEGER 0 NA 0
3 Pop_2020 INTEGER 0 NA 0
4 PctChange_2000 REAL 0 NA 0
5 PctChange_2010 REAL 0 NA 0
6 PctChange_2020 REAL 0 NA 0
7 State Code TEXT 0 NA 0
8 Region TEXT 0 NA 0
9 Division TEXT 0 NA 0
```
The first rows look like this:
```Delaware 783600 897934 989948 17.6 14.6 10.2 DE South South Atlantic
Montana 902195 989415 1084225 12.9 9.7 9.6 MT West Mountain
Arizona 5130632 6392017 7151502 40.0 24.6 11.9 AZ West Mountain```
Based on this data, write a SQL query to answer the following question: What were the highest and lowest Population changes in 2020 by Division?. Return the SQL query ONLY. Do not include any additional explanation.
提示输入OpenAI API playground和生成的SQL代码
以下是运行建议的SQL时的结果:
sqldf("SELECT Division, MAX(PctChange_2020) AS Highest_PctChange_2020, MIN(PctChange_2020) AS Lowest_PctChange_2020 FROM states GROUP BY Division;")
Division Highest_PctChange_2020 Lowest_PctChange_2020
1 East North Central 4.7 -0.1
2 East South Central 8.9 -0.2
3 Middle Atlantic 5.7 2.4
4 Mountain 18.4 2.3
5 New England 7.4 0.9
6 Pacific 14.6 3.3
7 South Atlantic 14.6 -3.2
8 West North Central 15.8 2.8
9 West South Central 15.9 2.7
ChatGPT不仅生成了准确的SQL,而且也不必告诉GPT“2020人口变化”在Pop_2020列中。
步骤4:执行GPT返回的SQL代码的结果
以编程方式向OpenAI发送和返回数据,而不是将其剪切和粘贴到Web界面中,这将会方便得多。有几个R包可以使用OpenAI API。下面的代码块使用OpenAI包向API发送一个提示,存储API响应,提取响应中包含带有请求的SQL代码的文本的部分,打印该代码,并在数据上运行SQL。
library(openai)
my_results <- openai::create_chat_completion(model = "gpt-3.5-turbo", temperature = 0, messages = list(
list(role = "user", content = my_prompt)
))
the_answer <- my_results$choices$message.content
cat(the_answer)
SELECT Division, MAX(PctChange_2020) AS Highest_Population_Change, MIN(PctChange_2020) AS Lowest_Population_Change
FROM states
GROUP BY Division;
sqldf(the_answer)
Division Highest_Population_Change Lowest_Population_Change
1 East North Central 4.7 -0.1
2 East South Central 8.9 -0.2
3 Middle Atlantic 5.7 2.4
4 Mountain 18.4 2.3
5 New England 7.4 0.9
6 Pacific 14.6 3.3
7 South Atlantic 14.6 -3.2
8 West North Central 15.8 2.8
9 West South Central 15.9
如果用户想使用OpenAI API,需要一个OpenAI API密钥。对于这个包,密钥应该存储在一个系统环境变量中,例如OPENAI_API_KEY。需要注意的是,这个API不是免费使用的,但在把它变成编辑器之前,一天运行了这个项目十几次,而其总账户使用的费用是1美分。
步骤5(可选):创建交互式应用程序
现在,已经在脚本或终端中拥有了在R工作流中运行查询所需的所有代码。但是,如果想用简单的语言制作一个交互式应用程序来查询数据,这里已经包含了一个基本的Shiny应用程序的代码,可以使用它。
如果打算发布一个应用程序供其他人使用,那么将需要加强代码安全性以防止恶意查询,添加更优雅的错误处理和解释性标签,改进样式,或者对其进行扩展以供企业使用。
与同时,这段代码应该开始创建一个交互式应用程序,用自然语言查询数据集:
library(shiny)
library(openai)
library(dplyr)
library(sqldf)
# Load hard-coded dataset
states <- read.csv("states.csv") |>
dplyr::filter(!is.na(Region) & Region != "")
states_schema <- sqldf::sqldf("PRAGMA table_info(states)")
states_schema_string <- paste(apply(states_schema, 1, paste, collapse = "\t"), collapse = "\n")
states_sample <- dplyr::sample_n(states, 3)
states_sample_string <- paste(apply(states_sample, 1, paste, collapse = "\t"), collapse = "\n")
# Function to process user input
get_prompt <- function(query, schema = states_schema_string, rows_sample = states_sample_string, table_name = "states") {
my_prompt <- glue::glue("Act as if you're a data scientist. You have a SQLite table named {table_name} with the following schema:
```
{schema}
```
The first rows look like this:
```{rows_sample}```
Based on this data, write a SQL query to answer the following question: {query} Return the SQL query ONLY. Do not include any additional explanation.")
print(my_prompt)
return(my_prompt)
}
ui <- fluidPage(
titlePanel("Query state database"),
sidebarLayout(
sidebarPanel(
textInput("query", "Enter your query", placeholder = "e.g., What is the total 2020 population by Region?"),
actionButton("submit_btn", "Submit")
),
mainPanel(
uiOutput("the_sql"),
br(),
br(),
verbatimTextOutput("results")
)
)
)
server <- function(input, output) {
# Create the prompt from the user query to send to GPT
the_prompt <- eventReactive(input$submit_btn, {
req(input$query, states_schema_string, states_sample_string)
my_prompt <- get_prompt(query = input$query)
})
# send prompt to GPT, get SQL, run SQL, print results
observeEvent(input$submit_btn, {
req(the_prompt()) # text to send to GPT
# Send results to GPT and get response
# withProgress adds a Shiny progress bar. Commas now needed after each statement
withProgress(message = 'Getting results from GPT', value = 0, { # Add Shiny progress message
my_results <- openai::create_chat_completion(model = "gpt-3.5-turbo", temperature = 0, messages = list(
list(role = "user", content = the_prompt())
))
the_gpt_sql <- my_results$choices$message.content
# print the SQL
sql_html <- gsub("\n", "<br />", the_gpt_sql)
sql_html <- paste0("<p>", sql_html, "</p>")
# Run SQL on data to get results
gpt_answer <- sqldf(the_gpt_sql)
setProgress(value = 1, message = 'GPT results received') # Send msg to user that
})
# Print SQL and results
output$the_sql <- renderUI(HTML(sql_html))
if (is.vector(gpt_answer) ) {
output$results <- renderPrint(gpt_answer)
} else {
output$results <- renderPrint({ print(gpt_answer) })
}
})
}
shinyApp(ui = ui, server = server)