2024年7月31日

GPT 操作库 - SQL 数据库

简介

这是一份面向开发者的指南,旨在帮助您通过GPT Action赋予ChatGPT查询SQL数据库的能力。在阅读本指南前,请先熟悉以下内容:

本指南概述了通过中间件应用程序将ChatGPT连接到SQL数据库所需的工作流程。我们将以PostgreSQL数据库为例,但该过程应适用于所有SQL数据库(MySQL、MS SQL Server、Amazon Aurora、Google Cloud上的SQL Server等)。本文档概述了创建GPT Action所需的步骤,该Action可以:

  • 对SQL数据库执行读取查询
  • 通过文本响应返回记录
  • 通过CSV文件返回记录

价值与示例商业应用场景

价值: 用户现在可以利用ChatGPT的自然语言能力来回答关于SQL数据库中数据的问题:

  • 业务用户无需编写SQL或向分析师提交请求即可访问SQL数据库中包含的信息
  • 数据分析师可以通过提取数据并使用ChatGPT进行分析,执行超出SQL查询能力的复杂分析

示例用例:

  • 业务用户需要回答有关其销售漏斗的问题
  • 数据分析师需要对大型数据集执行回归分析

应用设计考量

鉴于大多数托管SQL数据库不提供用于提交查询的REST API,您需要一个中间件应用程序来执行以下功能:

  1. 通过REST API请求接收数据库查询
  2. 将查询转发到集成的SQL数据库
  3. 将数据库响应转换为CSV文件
  4. 将CSV文件返回给请求者

设计第一个函数主要有两种方法:

  1. The middleware supports a single method for receiving arbitrary SQL queries generated by the GPT and forwards them to the database. The benefits of this approach include:
    1. 开发便捷性
    2. 灵活性(无需预先预测用户会提出何种查询)
    3. 维护成本低(无需因数据库变更而更新API架构)
  2. 该中间件支持多种与特定允许查询相对应的方法。这种方法的优势包括: 4. 更强的控制力 5. 生成SQL时模型出错的机会更少

本指南将重点介绍选项1。对于对选项2感兴趣的读者,可以考虑实施类似PostgRESTHasura的服务来简化流程。

An application architecture diagram depicting the interaction between the user, GPT, middleware, and database 应用架构图

中间件注意事项

开发者可以构建自定义中间件(通常作为无服务器函数部署在AWS、GCP或MS Azure等云服务提供商上),也可以使用第三方解决方案(如Mulesoft AnypointRetool Workflows)。使用第三方中间件可以加速开发流程,但相比自主构建灵活性较低。

构建自己的中间件可以让您更好地控制应用程序的行为。有关自定义中间件的示例,请参阅我们的Azure Functions 食谱

本指南不会深入探讨中间件的具体设置细节,而是重点关注中间件与GPT和SQL数据库的交互接口。

工作流程步骤

1) GPT生成SQL查询

GPTs非常擅长根据用户的自然语言提示编写SQL查询。您可以通过以下任一方式让GPT访问数据库模式,从而提高其查询生成能力:

  1. 指示GPT首先查询数据库以获取模式(这种方法在我们的BigQuery烹饪指南中有更详细的演示)。
  2. 在GPT指令中提供模式(最适合小型静态模式)

以下是包含简单数据库模式信息的GPT指令示例:

# Context
You are a data analyst. Your job is to assist users with their business questions by analyzing the data contained in a PostgreSQL database.

## Database Schema

### Accounts Table
**Description:** Stores information about business accounts.

| Column Name  | Data Type      | Constraints                        | Description                             |
|--------------|----------------|------------------------------------|-----------------------------------------|
| account_id   | INT            | PRIMARY KEY, AUTO_INCREMENT, NOT NULL | Unique identifier for each account      |
| account_name | VARCHAR(255)   | NOT NULL                           | Name of the business account            |
| industry     | VARCHAR(255)   |                                    | Industry to which the business belongs  |
| created_at   | TIMESTAMP      | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Timestamp when the account was created  |

### Users Table
**Description:** Stores information about users associated with the accounts.

| Column Name  | Data Type      | Constraints                        | Description                             |
|--------------|----------------|------------------------------------|-----------------------------------------|
| user_id      | INT            | PRIMARY KEY, AUTO_INCREMENT, NOT NULL | Unique identifier for each user         |
| account_id   | INT            | NOT NULL, FOREIGN KEY (References Accounts(account_id)) | Foreign key referencing Accounts(account_id) |
| username     | VARCHAR(50)    | NOT NULL, UNIQUE                   | Username chosen by the user             |
| email        | VARCHAR(100)   | NOT NULL, UNIQUE                   | User's email address                    |
| role         | VARCHAR(50)    |                                    | Role of the user within the account     |
| created_at   | TIMESTAMP      | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Timestamp when the user was created     |

### Revenue Table
**Description:** Stores revenue data related to the accounts.

| Column Name  | Data Type      | Constraints                        | Description                             |
|--------------|----------------|------------------------------------|-----------------------------------------|
| revenue_id   | INT            | PRIMARY KEY, AUTO_INCREMENT, NOT NULL | Unique identifier for each revenue record |
| account_id   | INT            | NOT NULL, FOREIGN KEY (References Accounts(account_id)) | Foreign key referencing Accounts(account_id) |
| amount       | DECIMAL(10, 2) | NOT NULL                           | Revenue amount                          |
| revenue_date | DATE           | NOT NULL                           | Date when the revenue was recorded      |

# Instructions:
1. When the user asks a question, consider what data you would need to answer the question and confirm that the data should be available by consulting the database schema.
2. Write a PostgreSQL-compatible query and submit it using the `databaseQuery` API method.
3. Use the response data to answer the user's question.
4. If necessary, use code interpreter to perform additional analysis on the data until you are able to answer the user's question.

2) GPT向中间件发送SQL查询

为了让我们的GPT与中间件通信,我们将配置一个GPT Action。中间件需要提供一个接受SQL查询字符串的REST API端点。您可以通过多种方式设计此接口。以下是一个简单端点的OpenAPI模式示例,该端点在POST操作中接受"q"参数:

openapi: 3.1.0
info:
  title: PostgreSQL API
  description: API for querying a PostgreSQL database
  version: 1.0.0
servers:
  - url: https://my.middleware.com/v1
    description: middleware service
paths:
  /api/query:
    post:
      operationId: databaseQuery
      summary: Query a PostgreSQL database
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                q:
                  type: string
                  example: select * from users
      responses:
        "200":
          description: database records
          content:
            application/json:
              schema:
                type: object
                properties:
                  openaiFileResponse:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          description: The name of the file.
                        mime_type:
                          type: string
                          description: The MIME type of the file.
                        content:
                          type: string
                          format: byte
                          description: The content of the file in base64 encoding.
        "400":
          description: Bad Request. Invalid input.
        "401":
          description: Unauthorized. Invalid or missing API key.
      security:
        - ApiKey: []
components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-Api-Key
  schemas: {}

关于认证的说明: 上述示例中的API接口接受一个系统级API密钥,该密钥与GPT的配置一起存储,并用于对所有GPT用户的请求进行认证。GPT Actions还支持OAuth认证,这使得用户级认证和授权成为可能。了解更多关于GPT Action认证选项

由于用户是通过中间件而非直接与底层数据库进行身份验证,因此实施用户级访问(表或行级权限)需要更多努力。但对于GPTs来说,当用户对底层数据库拥有不同访问级别时,这可能成为必要要求。

为了实施用户级别的权限控制,您的中间件应:

  1. 接收用户在OAuth流程中由身份提供商(IdP)提供的元数据,并提取其身份信息
  2. 查询数据库以获取用户的数据库权限
  3. 向数据库发出命令,为会话的剩余部分强制执行相关权限

为了保持良好的用户体验,您需要动态获取每个用户可用的数据库架构,而不是直接将架构数据包含在GPT指令中。这样可以确保GPT只能访问代表当前用户查询的表。

3) 中间件将SQL查询转发到数据库

您的中间件将实现一个数据库驱动或客户端库,使其能够直接查询PostgreSQL数据库。如果使用第三方中间件,供应商应提供SQL数据库的原生连接器。如果是自行构建中间件,可能需要实现数据库供应商或第三方提供的客户端库。例如,以下是社区维护的PostgreSQL客户端库列表:https://wiki.postgresql.org/wiki/List_of_drivers

在此工作流程步骤中,中间件应用需要从接收到的GPT请求中提取SQL字符串,并通过客户端库提供的方法将其转发至数据库。

关于只读权限的说明: 由于此设计模式会导致您的数据库处理AI生成的任意SQL查询,您应确保中间件应用程序在数据库上仅具有只读权限。这样可以确保AI生成的查询无法插入新数据或修改现有数据。如果您的用例需要写入权限,请考虑部署特定操作端点,而不是接受任意SQL。

4) 数据库将记录返回给中间件

根据您实现的客户端库,您的中间件可能会接收多种格式的记录。一种常见模式是中间件接收一个JSON对象数组,每个对象代表与查询匹配的数据库记录:

[
  {
    "account_id": 1,
    "number_of_users": 10,
    "total_revenue": 43803.96,
    "revenue_per_user": 4380.40
  },
  {
    "account_id": 2,
    "number_of_users": 12,
    "total_revenue": 77814.84,
    "revenue_per_user": 6484.57
  },
  ...
]
import json
import csv

# Sample JSON array of objects
json_data = '''
[
    {"account_id": 1, "number_of_users": 10, "total_revenue": 43803.96, "revenue_per_user": 4380.40}, 
    {"account_id": 2, "number_of_users": 12, "total_revenue": 77814.84, "revenue_per_user": 6484.57}
]
'''

# Load JSON data
data = json.loads(json_data)

# Define the CSV file name
csv_file = 'output.csv'

# Write JSON data to CSV
with open(csv_file, 'w', newline='') as csvfile:
    # Create a CSV writer object
    csvwriter = csv.writer(csvfile)
    
    # Write the header (keys of the first dictionary)
    header = data[0].keys()
    csvwriter.writerow(header)
    
    # Write the data rows
    for row in data:
        csvwriter.writerow(row.values())

print(f"JSON data has been written to {csv_file}")

对CSV文件进行Base64编码

许多编程语言都内置了用于处理base64编码的原生库(例如Python的base64库)。

以下是一个示例,展示你的中间件如何对前一步生成的CSV文件进行base64编码:

import base64 

# Base64 encode the CSV file
encoded_string = base64.b64encode(open('output.csv', 'rb').read()).decode('utf-8')

print("Base64 Encoded CSV:")
print(encoded_string)

6) 中间件将base64编码的CSV文件返回给GPT

为了让GPT Actions接口能够处理Base64编码的CSV文件,您的中间件返回的响应必须包含一个openaiFileResponse参数。提供的值必须是文件对象数组或文件链接数组(详见Actions文档获取更多细节)。在本示例中,我们将使用文件对象数组进行操作。

以下是一个有效响应体的示例:

{
  "openaiFileResponse": [
    {
      "name": "output.csv",
      "mime_type": "text/csv",
      "content": "ImFjY291bn...NC41NyI="
    }
  ]
}

7) GPT处理返回的文件

当您的GPT接收到经过base64编码的CSV文件后,它将自动解码该文件并进行处理以回答用户的问题。这可能涉及使用代码解释器对CSV文件执行额外分析,其处理方式与用户通过提示上传CSV文件时相同。

注意: 如果您希望对返回的文件执行额外分析,必须在您的GPT中启用代码解释器 & 数据分析功能。

结论

GPT Actions提供了一个灵活的框架,用于从SQL数据库等外部来源检索数据。赋予ChatGPT查询数据库的能力,可以大幅扩展其作为知识助手和分析师的功能。

是否有您希望我们优先考虑的集成方案?我们的集成是否存在错误?请在GitHub上提交PR或问题,我们会尽快查看。