GPT Actions 库 - SQL 数据库

2024 年 7 月 31 日
在 Github 中打开

简介

本指南适用于希望赋予 ChatGPT 使用 GPT Action 查询 SQL 数据库能力的开发者。在阅读本指南之前,请先熟悉以下内容

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

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

价值 + 示例业务用例

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

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

示例用例:

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

应用程序设计考虑因素

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

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

设计第一个功能主要有两种方法

  1. 中间件支持一种接收 GPT 生成的任意 SQL 查询并将其转发到数据库的单一方法。这种方法的优点包括
    1. 易于开发
    2. 灵活性(不需要您预测用户将提出的查询类型)
    3. 低维护(不需要您响应数据库更改而更新 API 架构)
  2. 中间件支持多种与特定允许查询相对应的方法。这种方法的优点包括:4. 更多控制 5. 减少模型在生成 SQL 时出错的机会

本指南将重点介绍选项 1。对于那些对选项 2 感兴趣的人,请考虑实施像 PostgRESTHasura 这样的服务来简化流程。

应用程序架构图,描绘了用户、GPT、中间件和数据库之间的交互 应用程序架构图

中间件注意事项

开发者可以构建自定义中间件(通常部署为具有 AWS、GCP 或 MS Azure 等 CSP 的无服务器函数)或使用第三方解决方案(如 Mulesoft AnypointRetool Workflows)。使用第三方中间件可以加速您的开发过程,但不如自己构建灵活。

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

本指南将重点介绍中间件与 GPT 和 SQL 数据库的接口,而不是关注中间件设置的具体细节。

工作流程步骤

1) GPT 生成 SQL 查询

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

  1. 指示 GPT 首先查询数据库以检索架构(我们的 BigQuery cookbook 中更详细地演示了这种方法)。
  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。中间件需要提供一个 REST API 端点,该端点接受 SQL 查询字符串。您可以通过多种方式设计此接口。以下是一个 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 身份验证选项的信息

由于用户正在使用中间件进行身份验证,而不是直接与底层数据库进行身份验证,因此强制执行用户级访问(表级或行级权限)需要更多的工作。但是,对于用户对底层数据库具有不同访问级别的 GPT,这可能是必需的。

为了强制执行用户级权限,您的中间件应

  1. 接收 IdP 在 OAuth 流程期间提供的用户元数据,并提取其身份信息
  2. 查询数据库以检索用户的数据库权限
  3. 向数据库发出命令,以在会话的剩余时间内强制执行相关权限

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

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

您的中间件将实现数据库驱动程序或客户端库,使其能够直接查询 PostgreSQL 数据库。如果您使用第三方中间件,则中间件供应商应为 SQL 数据库提供本机连接器。如果您正在构建自己的中间件,则可能需要实现数据库供应商或第三方提供的客户端库。例如,以下是社区维护的 PostgreSQL 客户端库列表:https://wiki.postgresql.ac.cn/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 接口能够处理 base-64 编码的 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 或 issue,我们会查看。