Return JSON from GPT

Update October 2023: View OpenAI chat completions docs here. Click “show properties” under “functions.”

OpenAI recently annouced updates to their API that now make it possible to get properly formatted JSON in your response.

Previously, you could do a little “prompt engineering” and get stringified JSON by simply appending “provide your response in JSON format” to the end of the prompt. Though, these reponses often included incorrect trailing commas or introductory text (“Here is your recipe in JSON format:”) that led to breaking errors.

In this post, I am going to show the previous way that I was writing a prompt to get JSON output. Then, I am going to show the method we can now use to get output in JSON format. There is a lot more that we can do with function calling, but I am just going to focus here on how we can specify to the chat completions API endpoint that we want our response in JSON format.

In this example recipe app, a user puts a dish name in an input box and then clicks “get recipe.” On clicking this button, we run the getRecipe() function:


  function getRecipe() {
    // Create prompt text with user input. Include data model schema description.
    const prompt = `return a recipe for ${userInput}.
        Provide your response as a JSON object with the following schema:
        {"dish": ${userInput}, "ingredients": ["", "", ...],
        "instructions": ["", "", ... ]}`;

    openai.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [
        { role: "system", "content": "You are a helpful recipe assistant."},
        { role: "user", content: prompt }],

    })
      .then((completion) => {
        // Handle API response
        const generatedText =
          completion.data.choices[0].message.content;
        setRecipe(JSON.parse(generatedText));
      })
      .catch((error) => {
        console.log(error);
      });
  }

You can see I asked for the response in JSON format and then improvised a schema to indicate how I wanted the object formatted. The schema could be improved, but for the most part it works. However, as mentioned earlier, the responses are prone to trailing comma errors and this kind of informal schema is not scaleable or easily maintained.

This is the response I received when I asked for a buttered toast recipe with the prompt above:

Here's a recipe for buttered toast in JSON format as requested:
```

{
"dish": "buttered toast",
"ingredients": [
"2 slices of bread",
"2 tablespoons of unsalted butter"
],
"instructions": [
"Preheat your toaster or toaster oven.",
"Place the slices of bread in the toaster or toaster oven.",
"Toast the bread for 1-2 minutes, or until it is golden brown.",
"Carefully remove the toasted bread from the toaster or toaster oven.",
"Place a tablespoon of butter on each slice of toast.",
"Use a knife to spread the butter evenly over the surface of the toast.",
"Serve immediately and enjoy!"
]
}

```
I hope this helps!

You can see that the core of the response is correct and follows the schema I outlined very well, but this response includes an unnessesary intro sentence that leads to an error:

SyntaxError: Unexpected token 'H', "Here's a r"... is not valid JSON
at JSON.parse

We could further refine our prompt to account for these errors. One thing I’ve tried is simply adding “do not return anything in your response outside of curly braces.” For the most part, this seems to get rid of those intro and conclusion sentences. But the issue of trailing commas is harder to address through prompt engineering.

The recent update to the OpenAI API allows us to specify that we want our response in JSON format, but we have to do this using JSON Schema. I’ve updated out function by creating a JSON Schema object and passing this to the new functions parameter. Note that there are several other small changes I’ve annotated in the updated function below:


  function getRecipe() {
    // Create prompt text with user input
    const prompt = `return a recipe for ${userInput}`;

    //Define the JSON Schema by creating a schema object
    const schema = {
      "type": "object",
      "properties": {
        "dish": {
          "type": "string",
          "description": "Descriptive title of the dish"
        },
        "ingredients": {
          "type": "array",
          "items": {"type": "string"}
        },
        "instructions": {
          "type": "array",
          "description": "Steps to prepare the recipe.",
          "items": {"type": "string"}
        }
      }
    }

    //Note the updated model and added functions and function_call lines
    //Note that we pass our schema object to parameters
    openai.chat.completions.create({
      model: "gpt-3.5-turbo-0613",
      messages: [
        { role: "system", "content": "You are a helpful recipe assistant." },
        { role: "user", content: prompt }],
      functions: [{ name: "set_recipe", parameters: schema }],
      function_call: {name: "set_recipe"}


    })
      .then((completion) => {
        // Note the updated location for the response
        const generatedText =
          completion.data.choices[0].message.function_call.arguments;
        setRecipe(JSON.parse(generatedText));
      })
      .catch((error) => {
        console.log(error);
      });
  }

With this update, I received the following response:

{
"dish": "Buttered Toast",
"ingredients": [
"Bread slices",
"Butter"
],
"instructions": [
"Heat a non-stick skillet or griddle over medium heat.",
"Spread butter on one side of each bread slice.",
"Place the bread slices on the hot skillet or griddle, butter side down.",
"Cook for about 2-3 minutes or until the bottom side is golden brown and crispy.",
"Flip the bread slices and cook for another 1-2 minutes.",
"Remove from the skillet or griddle and serve immediately."
]
}

There were no errors when I passed this response to JSON.parse(). Now the recipe app is more reliable and less prone to errors caused by inconsistent formatting of the OpenAI API response.

This approach requires significantly more lines of code and, for now at least, JSON Schema is the only supported declarative language. Some developers may still want to try their luck with requesting JSON objects in an informal manner. But if you are building projects that depend on this JSON in order to render elements to the page, the added effort may well be worth it.