如何使用函数调用在ChatGPT中构建JSON响应结构

如何在ChatGPT中使用函数调用来构建JSON响应结构

ChatGPT对JSON的问题

打开ChatGPT UI并向其要求一些JSON。很大程度上,你会得到一个类似封面照片上所示的响应:一个用markdown格式呈现的JSON对象,两侧都有一些文本解释JSON的内容。

如果你在OpenAI Playground尝试相同的提示,你会看到JSON被包含在三个反引号(markdown语法)中。

Open AI Playground界面显示用户提示要求一些JSON
https://platform.openai.com/playground

这太棒了。ChatGPT通过易于理解的方式解释了响应,但…只对人类来说易于理解。对于机器来说并非如此。机器需要以可靠、一致和可预测的模式来提取数据。

理想情况下,你希望解析来自ChatGPT的响应,并对其进行有用的操作,就像这样:

// 从npm中使用openai包调用ChatGPT
import OpenAI from "openai";
// 使用我们的API密钥创建openai客户端的新实例
const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY });
// 调用ChatGPT的completion端点并请求一些JSON
const gptResponse = await openai.chat.completions.create({
    model: "gpt-3.5-turbo",
    temperature: 1,
    messages: [
        {
            role: "user",
            content: "给我一个表示猫的对象的JSON。"
        }
    ],
});
// 尝试以JSON格式读取响应,
// 很可能会因为语法错误而失败...
const json = JSON.parse(gptResponse.choices[0].message.content);

但只有在每次gptResponse.choices[0].message.content是有效的JSON时才会起作用。

同时,我们希望返回的JSON始终遵循一个模式:

type Cat = {
    name: string,
    colour: "brown" | "grey" | "black",
    age: number
}
// 读取响应的JSON并将其类型定义为我们的Cat对象模式
const json = <Cat>JSON.parse(gptResponse.choices[0].message.content);

无法依赖ChatGPT以可预测的格式返回有效的JSON可能会在你的应用程序中引入错误,特别是一致性至关重要时。当你编写依赖于ChatGPT实时响应触发特定动作或更新的代码时,这将成为一个真正的问题,因此我们需要找到解决方案。我们有几种方法可以应对这个问题…

如何通过提示工程解决

解决此问题的一种方法是通过提示工程。

Open AI Playground图像显示系统提示控制响应格式
https://platform.openai.com/playground

在这里,我们向用户提示和系统提示添加了一些说明。这些说明试图强制模型只返回我们想要的JSON,以及我们想要的格式。

对于许多用例来说,这是可以接受的。从上面的截图中可以看到,”Assistant”的响应只是JSON,而且JSON符合我们为其描述的模式。

但这并不是100%都起作用。

下面是完全相同的一对提示,模型的温度设置在1以上。请注意返回的JSON中color字段不再遵循用户提示中指定的允许的值之一:

Open AI Playground界面显示温度参数在较高值时导致无效响应

函数调用解救

函数调用是使用ChatGPT API的一种新方法。无需从语言模型获取消息,而是收到一个调用函数的请求。

如果您曾在ChatGPT用户界面中使用过插件,则“函数调用”是幕后的功能,它使插件能够与LLM的响应集成。

插件定义了模型可用的函数,并允许它在用户提示的响应中调用这些函数。

在您自己的代码中使用API时,您还可以利用函数调用来更好地控制从模型获取的数据,包括强制它以可预测的格式返回JSON数据。

下面是一个使用函数调用返回响应中的message.function_call对象的基本请求。

在这里,我们只是要求ChatGPT调用一个函数(“getName”),并在functions: []数组中提供函数的描述。我们还指示ChatGPT在响应中调用这个函数,通过将function_call的值设置为我们函数的名称。

const gptResponse = await openai.chat.completions.create({    model: "gpt-3.5-turbo-0613",    messages: [        {            role: "user",            content: "调用函数'getName'并告诉我结果。"        }    ],    functions: [        {            name: "getName",            parameters: {                type: "object",                properties: {}            }        }    ],    function_call: { name: "getName" }});// 将打印 "getName"...console.log(gptResponse.choices[0].message.function_call.name);

这里要注意的重要一点是,getName()函数不需要真实存在于我们的代码库中。我们只是告诉ChatGPT它存在,并且可以被调用。

如何添加函数参数

函数调用很棒,因为它使ChatGPT的响应变得可预测和结构化。

在上面的示例中,我们将在gptResponse.choices[0].message.function_call中得到一个对象,其中包含ChatGPT想要执行我们(虚构的)函数的详细信息。

该对象如下所示,包含函数的名称以及应该调用的任何函数参数的字符串 版本:

{  name: "functionName",  arguments: "{ \"arg1\": \"value\" }"}

我们可以利用这第二个arguments值,描述函数参数的形状,并让ChatGPT填充一些符合我们形状的JSON值。

以下是将原始示例作为函数调用的方式。请注意,在createCatObject函数的定义中,我们已经向ChatGPT描述了Cat对象的架构:

    type Cat = {        name: string,        colour: "brown" | "grey" | "black",        age: number    }    const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY });    const gptResponse = await openai.chat.completions.create({        model: "gpt-3.5-turbo-0613",        messages: [            {                role: "user",                content: "创建一个新的Cat对象。"            }        ],        functions: [            {                name: "createCatObject",                parameters: {                    type: "object",                    properties: {                        name: {                            type: "string"                        },                        colour: {                            type: "string",                            enum: ["brown", "grey", "black"]                        },                        age: {                            type: "integer"                        }                    },                    required: ["name", "colour", "age"]                }            }        ],        function_call: { name: "createCatObject" }    });    const functionCall = gptResponse.choices[0].message.function_call;    const json = <Cat>JSON.parse(functionCall.arguments);

这个完成请求会指示ChatGPT创建一个新的猫对象,并将其以特定的格式发送给createCatObject()函数。最后一行:

const json = <Cat>JSON.parse(functionCall.arguments);

将ChatGPT的参数解析为我们的Cat类型,该类型与我们给模型描述的预期对象的形状相匹配。

结论

通过ChatGPT进行函数调用不仅能为结果带来清晰度和可预测性,还引入了一种与机器学习模型交互的范式转变。

这种结构化方法确保了来自ChatGPT的响应是可扩展的,能够更轻松地集成到各种应用程序中,无论是简单的Web应用程序还是更复杂的机器学习流水线。


Leave a Reply

Your email address will not be published. Required fields are marked *