AI:自然语言转换为SQL

在考虑 AI 功能时,引用“只是因为你不能意味着你应该”是一个有用的指南。 例如,Azure OpenAI 的自然语言到 SQL 功能允许用户使用纯英语进行数据库查询,这是一种强大的工具,可提高工作效率。 但是, 强大的 并不总是意味着 适当的安全的。 本练习将演示如何使用此 AI 功能,同时讨论在决定实施此功能之前要记住的重要注意事项。

下面是可用于从数据库检索数据的自然语言查询示例:

Get the the total revenue for all companies in London.

如果出现正确的提示,Azure OpenAI 会将此查询转换为 SQL,该查询可用于从数据库返回结果。 因此,非技术用户(包括业务分析师、营销人员和高管)可以更轻松地从数据库中检索有价值的信息,而无需处理复杂的 SQL 语法或依赖于受约束的数据网格和筛选器。 这种简化的方法可以通过消除用户向技术专家寻求帮助的需要来提高工作效率。

本练习提供了一个起点,可帮助你了解 SQL 的自然语言的工作原理、向你介绍一些重要注意事项、让你思考优缺点,并向你展示入门代码。

通过学习本练习,你将能够:

  • 使用 GPT 提示将自然语言转换为 SQL。
  • 尝试使用不同的 GPT 提示。
  • 使用生成的 SQL 查询之前启动的 PostgreSQL 数据库。
  • 从 PostgreSQL 返回查询结果,并在浏览器中显示它们。

首先,试验可用于将自然语言转换为 SQL 的不同 GPT 提示。

使用自然语言到 SQL 功能

  1. 上一练习 中,你启动了数据库、API 和应用程序。 还更新了 .env 该文件。 如果未完成这些步骤,请按照练习结束时的说明操作,然后继续操作。

  2. 返回到浏览器(http://localhost:4200)并找到 datagrid 下方页面的 “自定义查询 ”部分。 请注意,已包含示例查询值: 获取所有订单的总收入。按公司分组并包括城市。

    SQL 查询的自然语言。

  3. 选择 “运行查询 ”按钮。 这会将用户的自然语言查询传递给 Azure OpenAI,它将转换为 SQL。 然后,将使用 SQL 查询查询数据库并返回任何潜在结果。

  4. 运行以下 自定义查询

    Get the total revenue for Adventure Works Cycles. Include the contact information as well.
    
  5. 查看在 Visual Studio Code 中运行 API 服务器的终端窗口,并注意到它显示从 Azure OpenAI 返回的 SQL 查询。 服务器端 API 使用 JSON 数据来查询 PostgreSQL 数据库。 查询中包含的任何字符串值将添加为参数值,以防止 SQL 注入攻击:

    { 
        "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", 
        "paramValues": ["Adventure Works Cycles"] 
    }
    
  6. 返回到浏览器并选择“ 重置数据 ”,在 datagrid 中再次查看所有客户。

探索自然语言到 SQL 代码

小窍门

如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:

  • Windows/Linux: Ctrl + P
  • Mac: Cmd + P

然后键入要打开的文件的名称。

注释

本练习的目标是展示使用自然语言实现的 SQL 功能,并演示如何开始使用它。 如前所述,在继续执行任何实现之前,请务必讨论此类 AI 是否适合组织。 此外 ,还必须规划适当的提示规则和数据库安全措施 ,以防止未经授权的访问和保护敏感数据。

  1. 现在,你已经了解了 SQL 功能的自然语言,接下来让我们来看看它是如何实现的。

  2. 打开 服务器/apiRoutes.ts 文件并找到 generateSql 路由。 此 API 路由由在浏览器中运行的客户端应用程序调用,用于从自然语言查询生成 SQL。 检索 SQL 查询后,它用于查询数据库并返回结果。

    router.post('/generateSql', async (req, res) => {
        const userPrompt = req.body.prompt;
    
        if (!userPrompt) {
            return res.status(400).json({ error: 'Missing parameter "prompt".' });
        }
    
        try {
            // Call Azure OpenAI to convert the user prompt into a SQL query
            const sqlCommandObject = await getSQLFromNLP(userPrompt);
    
            let result: any[] = [];
            // Execute the SQL query
            if (sqlCommandObject && !sqlCommandObject.error) {
                result = await queryDb(sqlCommandObject) as any[];
            }
            else {
                result = [ { query_error : sqlCommandObject.error } ];
            }
            res.json(result);
        } catch (e) {
            console.error(e);
            res.status(500).json({ error: 'Error generating or running SQL query.' });
        }
    });
    

    请注意路由中的 generateSql 以下功能:

    • 它从中检索用户查询值 req.body.prompt ,并将其分配给名为 的 userPrompt变量。 此值将在 GPT 提示符中使用。
    • 它调用函数 getSQLFromNLP() 以将自然语言转换为 SQL。
    • 它将生成的 SQL 传递给一个名为 queryDb 执行 SQL 查询的函数,并从数据库返回结果。
  3. 在编辑器中打开 服务器/openAI.ts 文件并找到函数 getSQLFromNLP() 。 此函数由 generatesql 路由调用,用于将自然语言转换为 SQL。

    async function getSQLFromNLP(userPrompt: string): Promise<QueryData> {
        // Get the high-level database schema summary to be used in the prompt.
        // The db.schema file could be generated by a background process or the 
        // schema could be dynamically retrieved.
        const dbSchema = await fs.promises.readFile('db.schema', 'utf8');
    
        const systemPrompt = `
        Assistant is a natural language to SQL bot that returns a JSON object with the SQL query and 
        the parameter values in it. The SQL will query a PostgreSQL database.
    
        PostgreSQL tables with their columns:    
    
        ${dbSchema}
    
        Rules:
        - Convert any strings to a PostgreSQL parameterized query value to avoid SQL injection attacks.
        - Return a JSON object with the following structure: { "sql": "", "paramValues": [] }
    
        Examples:
    
        User: "Display all company reviews. Group by company."      
        Assistant: { "sql": "SELECT * FROM reviews", "paramValues": [] }
    
        User: "Display all reviews for companies located in cities that start with 'L'."
        Assistant: { "sql": "SELECT r.* FROM reviews r INNER JOIN customers c ON r.customer_id = c.id WHERE c.city LIKE 'L%'", "paramValues": [] }
    
        User: "Display revenue for companies located in London. Include the company name and city."
        Assistant: { 
            "sql": "SELECT c.company, c.city, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.city = $1 GROUP BY c.company, c.city", 
            "paramValues": ["London"] 
        }
    
        User: "Get the total revenue for Adventure Works Cycles. Include the contact information as well."
        Assistant: { 
            "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", 
            "paramValues": ["Adventure Works Cycles"] 
        }
        `;
    
        let queryData: QueryData = { sql: '', paramValues: [], error: '' };
        let results = '';
    
        try {
            results = await callOpenAI(systemPrompt, userPrompt);
            if (results) {
                console.log('results', results);
                const parsedResults = JSON.parse(results);
                queryData = { ...queryData, ...parsedResults };
                if (isProhibitedQuery(queryData.sql)) {
                    queryData.sql = '';
                    queryData.error = 'Prohibited query.';
                }
            }
        } catch (error) {
            console.log(error);
            if (isProhibitedQuery(results)) {
                queryData.sql = '';
                queryData.error = 'Prohibited query.';
            } else {
                queryData.error = results;
            }
        }
    
        return queryData;
    }
    
    • 参数 userPrompt 将传递到函数中。 该值 userPrompt 是用户在浏览器中输入的自然语言查询。
    • 定义 systemPrompt 要使用的 AI 助手的类型和应遵循的规则。 这有助于 Azure OpenAI 了解数据库结构、应用的规则以及如何返回生成的 SQL 查询和参数。
    • 调用名为callOpenAI()的函数,并systemPromptuserPrompt向其传递值。
    • 检查结果以确保生成的 SQL 查询中不包含禁止的值。 如果找到禁止的值,则 SQL 查询将设置为空字符串。
  4. 让我们更详细地演练系统提示:

    const systemPrompt = `
      Assistant is a natural language to SQL bot that returns a JSON object with the SQL query and 
      the parameter values in it. The SQL will query a PostgreSQL database.
    
      PostgreSQL tables with their columns:    
    
      ${dbSchema}
    
      Rules:
      - Convert any strings to a PostgreSQL parameterized query value to avoid SQL injection attacks.
      - Return a JSON object with the following structure: { "sql": "", "paramValues": [] }
    
      Examples:
    
      User: "Display all company reviews. Group by company."      
      Assistant: { "sql": "SELECT * FROM reviews", "paramValues": [] }
    
      User: "Display all reviews for companies located in cities that start with 'L'."
      Assistant: { "sql": "SELECT r.* FROM reviews r INNER JOIN customers c ON r.customer_id = c.id WHERE c.city LIKE 'L%'", "paramValues": [] }
    
      User: "Display revenue for companies located in London. Include the company name and city."
      Assistant: { 
        "sql": "SELECT c.company, c.city, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.city = $1 GROUP BY c.company, c.city", 
        "paramValues": ["London"] 
      }
    
      User: "Get the total revenue for Adventure Works Cycles. Include the contact information as well."
      Assistant: { 
        "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", 
        "paramValues": ["Adventure Works Cycles"] 
      }
    `;
    
    • 定义了要使用的 AI 助手的类型。 在本例中,为 SQL 机器人提供“自然语言”。

    • 定义了数据库中的表名和列。 提示中包含的高级架构可以在 服务器/db.schema 文件中找到,如下所示。

      - customers (id, company, city, email)
      - orders (id, customer_id, date, total)
      - order_items (id, order_id, product_id, quantity, price)
      - reviews (id, customer_id, review, date, comment)
      

      小窍门

      可以考虑创建仅包含数据用户的只读视图,以便使用自然语言查询 SQL。

    • 定义规则以将任何字符串值转换为参数化查询值以避免 SQL 注入攻击。

    • 定义规则以始终返回包含 SQL 查询的 JSON 对象及其参数值。

    • 提供了示例用户提示和预期的 SQL 查询和参数值。 这称为 “很少的”学习。 虽然 LLM 是针对大量数据进行训练的,但它们只能适应几个示例的新任务。 另一种方法是“零射”学习,其中未提供任何示例,并且模型应生成正确的 SQL 查询和参数值。

  5. getSQLFromNLP()函数将系统和用户提示发送到也位于callOpenAI()文件中的函数。 该 callOpenAI() 函数通过检查环境变量来确定是否应调用 Azure OpenAI 服务或 OpenAI 服务。 如果环境变量中提供了密钥、终结点和模型,则会调用 Azure OpenAI,否则将调用 OpenAI。

    function callOpenAI(systemPrompt: string, userPrompt: string, temperature = 0, useBYOD = false) {
        const isAzureOpenAI = OPENAI_API_KEY && OPENAI_ENDPOINT && OPENAI_MODEL;
    
        if (isAzureOpenAI) {
            if (useBYOD) {
                return getAzureOpenAIBYODCompletion(systemPrompt, userPrompt, temperature);
            }
            return getAzureOpenAICompletion(systemPrompt, userPrompt, temperature);
        }
    
        return getOpenAICompletion(systemPrompt, userPrompt, temperature);
    }
    

    注释

    尽管我们将重点介绍整个本教程中的 Azure OpenAI,但如果仅提供 OPENAI_API_KEY.env 文件中的值,应用程序将改用 OpenAI。 如果选择使用 OpenAI 而不是 Azure OpenAI,在某些情况下可能会看到不同的结果。

  6. 找到函数 getAzureOpenAICompletion()

    async function getAzureOpenAICompletion(systemPrompt: string, userPrompt: string, temperature: number): Promise<string> {
        const completion = await createAzureOpenAICompletion(systemPrompt, userPrompt, temperature);
        let content = completion.choices[0]?.message?.content?.trim() ?? '';
        console.log('Azure OpenAI Output: \n', content);
        if (content && content.includes('{') && content.includes('}')) {
            content = extractJson(content);
        }
        return content;
    }
    

    此函数执行以下任务:

    • 参数:

      • systemPromptuserPrompttemperature 主要参数。
        • systemPrompt:通知 Azure OpenAI 模型其角色和要遵循的规则。
        • userPrompt:包含用户提供的信息,例如用于生成输出的自然语言输入或规则。
        • temperature:指示模型的响应的创造力级别。 较高的值会导致更具创造性的输出,而较低的值(例如 0)会产生更具确定性的答案。
    • 完成生成:

      • 函数使用 createAzureOpenAICompletion()systemPrompta0> 调用userPrompttemperature生成完成。
      • 它会从完成中的第一个选项中提取内容,并剪裁任何额外的空格。
      • 如果内容包含类似于 JSON 的结构(由存在 { 并且 }指示),则会提取 JSON 内容。
    • 日志记录和返回值:

      • 该函数将 Azure OpenAI 输出记录到控制台。
      • 它将处理的内容作为字符串返回。
  7. 找到函数 createAzureOpenAICompletion()

    async function createAzureOpenAICompletion(systemPrompt: string, userPrompt: string, temperature: number, dataSources?: any[]): Promise<any> {
        const baseEnvVars = ['OPENAI_API_KEY', 'OPENAI_ENDPOINT', 'OPENAI_MODEL'];
        const byodEnvVars = ['AZURE_AI_SEARCH_ENDPOINT', 'AZURE_AI_SEARCH_KEY', 'AZURE_AI_SEARCH_INDEX'];
        const requiredEnvVars = dataSources ? [...baseEnvVars, ...byodEnvVars] : baseEnvVars;
        checkRequiredEnvVars(requiredEnvVars);
    
        const config = { 
            apiKey: OPENAI_API_KEY,
            endpoint: OPENAI_ENDPOINT,
            apiVersion: OPENAI_API_VERSION,
            deployment: OPENAI_MODEL
        };
        const aoai = new AzureOpenAI(config);
        const completion = await aoai.chat.completions.create({
            model: OPENAI_MODEL, // gpt-4o, gpt-3.5-turbo, etc. Pulled from .env file
            max_tokens: 1024,
            temperature,
            response_format: {
                type: "json_object",
            },
            messages: [
                { role: 'system', content: systemPrompt },
                { role: 'user', content: userPrompt }
            ],
            // @ts-expect-error data_sources is a custom property used with the "Azure Add Your Data" feature
            data_sources: dataSources
        });
        return completion;
    }
    
    function checkRequiredEnvVars(requiredEnvVars: string[]) {
        for (const envVar of requiredEnvVars) {
            if (!process.env[envVar]) {
                throw new Error(`Missing ${envVar} in environment variables.`);
            }
        }
    }
    

    此函数执行以下任务:

    • 参数:

      • systemPromptuserPrompttemperature 前面讨论的主要参数。
      • 可选 dataSources 参数支持“Azure 自带数据”功能,本教程稍后将介绍此功能。
    • 环境变量检查:

      • 该函数验证基本环境变量的存在,如果缺少任何环境变量,则会引发错误。
    • 配置对象:

      • config使用文件中.env的值OPENAI_API_KEY(、、OPENAI_ENDPOINTOPENAI_API_VERSIONOPENAI_MODEL)创建对象。 这些值用于构造用于调用 Azure OpenAI 的 URL。
    • AzureOpenAI 实例:

      • AzureOpenAI 的实例是使用 config 对象创建的。 符号 AzureOpenAI 是包的 openai 一部分,应在文件顶部导入该包。
    • 生成完成:

      • chat.completions.create() 函数使用以下属性调用:
        • model:指定文件中定义的 .env GPT 模型(例如 gpt-4o、gpt-3.5-turbo)。
        • max_tokens:定义完成的最大令牌数。
        • temperature:设置采样温度。 较高的值(例如 0.9)会产生更多的创造性响应,而较低的值(例如,0)会产生更具确定性的答案。
        • response_format:定义响应格式。 在这里,它设置为返回 JSON 对象。 有关 JSON 模式的更多详细信息,请参阅 Azure OpenAI 参考文档
        • messages:包含用于生成聊天完成的消息。 此示例包含两条消息:一个来自系统(定义行为和规则),一个来自用户(包含提示文本)。
    • 返回值:

      • 该函数返回 Azure OpenAI 生成的完成对象。
  8. 注释掉函数中的 getSQLFromNLP() 以下行:

    // if (isProhibitedQuery(queryData.sql)) { 
    //     queryData.sql = '';
    // }
    
  9. 保存 openAI.ts。 API 服务器将自动重新生成 TypeScript 代码并重启服务器。

  10. 返回到浏览器,在自定义查询输入中输入从数据库选择所有表名。 选择“运行查询”。 是否显示表名?

  11. 返回到getSQLFromNLP()服务器/openAI.ts中的函数,将以下规则Rules:添加到系统提示的节中,然后保存该文件。

    - Do not allow the SELECT query to return table names, function names, or procedure names.
    
  12. 返回到浏览器并执行以下任务:

    • 自定义查询输入中输入从数据库选择所有表名。 选择“运行查询”。 是否显示表名?
    • 自定义查询输入中输入“选择数据库中的所有函数名称”,然后再次选择“运行查询”。 是否显示函数名称?
  13. 问:模型是否始终遵循在提示中定义的规则?

    答:不! 请务必注意,OpenAI 模型有时可能会返回可能与你定义的规则不匹配的意外结果。 在代码中规划这一点非常重要。

  14. 返回到 服务器/openAI.ts 并找到函数 isProhibitedQuery() 。 这是可在 Azure OpenAI 返回结果后运行后处理代码的示例。 请注意,如果在生成的 SQL 查询中返回禁止的关键字,它将属性 sql 设置为空字符串。 这可确保如果从 Azure OpenAI 返回意外结果,则不会对数据库运行 SQL 查询。

    function isProhibitedQuery(query: string): boolean {
        if (!query) return false;
    
        const prohibitedKeywords = [
            'insert', 'update', 'delete', 'drop', 'truncate', 'alter', 'create', 'replace',
            'information_schema', 'pg_catalog', 'pg_tables', 'pg_proc', 'pg_namespace', 'pg_class',
            'table_schema', 'table_name', 'column_name', 'column_default', 'is_nullable',
            'data_type', 'udt_name', 'character_maximum_length', 'numeric_precision',
            'numeric_scale', 'datetime_precision', 'interval_type', 'collation_name',
            'grant', 'revoke', 'rollback', 'commit', 'savepoint', 'vacuum', 'analyze'
        ];
        const queryLower = query.toLowerCase();
        return prohibitedKeywords.some(keyword => queryLower.includes(keyword));
    }
    

    注释

    请务必注意,这只是演示代码。 如果选择将自然语言转换为 SQL,可能需要其他禁止的关键字来涵盖特定的用例。 这是一项功能,必须谨慎地规划和使用,以确保仅返回有效的 SQL 查询并针对数据库运行。 除了禁止的关键字,还需要考虑安全性。

  15. 返回到 服务器/openAI.ts 并取消注释函数 getSQLFromNLP() 中的以下代码。 保存文件。

    if (isProhibitedQuery(queryData.sql)) { 
        queryData.sql = '';
    }
    
  16. systemPrompt 中删除以下规则并保存文件。

    - Do not allow the SELECT query to return table names, function names, or procedure names.
    
  17. 返回到浏览器,再次将 数据库中的所有表名称 输入到 自定义查询 输入中,然后选择 “运行查询 ”按钮。

  18. 是否显示任何表结果? 即使没有规则, isProhibitedQuery 后期处理代码也禁止对数据库运行该类型的查询。

  19. 如前所述,将自然语言集成到业务线应用程序中的 SQL 对用户非常有用,但它确实附带了自己的一组注意事项。

    优点:

    • 用户友好性:此功能可让用户更方便地进行数据库交互,而无需具备技术专业知识,从而减少对 SQL 知识的需求,并可能加快操作速度。

    • 提高工作效率:业务分析师、营销人员、管理人员和其他非技术用户无需依赖技术专家即可从数据库中检索有价值的信息,从而提高效率。

    • 广泛的应用程序:通过使用高级语言模型,应用程序可以设计为迎合广泛的用户和用例。

    注意事项:

    • 安全:最大的担忧之一是安全。 如果用户可以使用自然语言与数据库交互,则需要采取可靠的安全措施来防止未经授权的访问或恶意查询。 可以考虑实现只读模式,以防止用户修改数据。

    • 数据隐私:某些数据可能很敏感,不应易于访问,因此你需要确保适当的保护措施和用户权限到位。

    • 准确性:虽然自然语言处理已显著改善,但它并不完美。 误解用户查询可能会导致结果不准确或意外行为。 你需要计划如何处理意外的结果。

    • 效率:不能保证从自然语言查询返回的 SQL 将高效。 在某些情况下,如果处理后规则检测到 SQL 查询问题,可能需要对 Azure OpenAI 的其他调用。

    • 培训和用户适应:需要训练用户才能正确制定查询。 虽然它比学习 SQL 更容易,但仍可能涉及学习曲线。

  20. 在继续下一练习之前要考虑的几个最后要点:

    • 请记住,“只是因为你不能意味着你应该”在这里适用。 在将自然语言集成到应用程序中之前,请谨慎地进行规划。 了解潜在风险并规划风险非常重要。
    • 在使用这种类型的技术之前,请与团队、数据库管理员、安全团队、利益干系人以及任何其他相关方讨论潜在方案,以确保它适合你的组织。 请务必讨论 SQL 的自然语言是否满足安全、隐私以及组织可能满足的任何其他要求。
    • 安全性应该是一个主要问题,并内置于规划、开发和部署过程中。
    • 虽然 SQL 的自然语言可能非常强大,但仔细规划必须进入它,以确保提示具有所需的规则,并包含后期处理功能。 计划额外的时间来实施和测试这种类型的功能,并考虑返回意外结果的方案。
    • 使用 Azure OpenAI,客户可在运行与 OpenAI 相同的模型时获得 Microsoft Azure 的安全功能。 Azure OpenAI 提供专用网络、区域可用性和负责任 AI 内容筛选功能。 详细了解 Azure OpenAI 服务的数据、隐私和安全性
  21. 现在,你已了解如何使用 Azure OpenAI 将自然语言转换为 SQL,并了解了实现此类功能的优缺点。 在下一练习中,你将了解如何使用 Azure OpenAI 生成电子邮件和短信。

下一步