Assign null when information not available in structured outputs

Hi,

I’m currently experimenting with structured outputs using NodeJS SDK. Is there any way to assign a null value or to catch whenever information is not available? (One of the information not the whole thing)

For example, I have this Zod schema:

const InstructionFormat = z.object({
    time: z.string(),
    task: z.string(),
    assignee: z.string(),
});

Let’s say the time is not available, I want something like
{ time: null, task: ‘finish math homework’, assignee: ‘Alice’ }

Adding .optional () didn’t work. My current workaround is by adjusting the prompt:
{ role: "system", content: "Extract the todo information from the prompt. Assign '-' if something not specified" },

So I can get something like this
{ time: '-', task: 'finish math homework', assignee: 'Alice' }

I guess it’s more reliable to have a null value than a certain string.

Not that I’m aware of at the moment.

Clever.

@tukemon according to the docs it’s possible to emulate “optional” values.

So using your schema above, in JSON format it could be:

"time": {
    "type": ["string", "null"],
}

In your prompt you have to specify that if the timestamp is not available, return an empty string for the 'time' field. Since all the fields are actually required and will be returned, time will also be returned, but if there is no data it will be an empty string, i.e. '', so you need to just add some validation for that.

@platypus it is not working. Please do not write responses based solely on what is written in their documentation, as it does not always reflect reality (Why not? Only OpenAI knows…). You should verify before writing an answer.

I am struggling with the same problem as OP, and currently, the model is failing to output “null” even when I have explicitly stated that I want “null” everywhere! (The same prompt works perfectly fine when I change “null” to “-” and add “-” as an accepted enum value.) The only time I have managed to get the model to output “null” was when it was the only type defined for the property.

OpenAI should review this issue, as it is clearly not working. The only currently available working solution to this problem is OP’s solution. Thank you for that, as I have wasted quite a bit of time trying to make “null” work!

maybe this might help you then.

It is absolutely easy to verify Union of a string, object or other JSON type with null works.

Output just received:

completion.choices[0].message.content
'{"q":null,"r":null}'

Using schema:

{
   "$defs": {
      "Q": {
         "additionalProperties": false,
         "properties": {
            "a": {
               "const": 1,
               "title": "A",
               "type": "integer"
            }
         },
         "required": [
            "a"
         ],
         "title": "Q",
         "type": "object"
      },
      "R": {
         "additionalProperties": false,
         "properties": {
            "a": {
               "const": 3,
               "title": "A",
               "type": "integer"
            }
         },
         "required": [
            "a"
         ],
         "title": "R",
         "type": "object"
      }
   },
   "additionalProperties": false,
   "properties": {
      "q": {
         "anyOf": [
            {
               "$ref": "#/$defs/Q"
            },
            {
               "type": "null"
            }
         ],
         "default": null
      },
      "r": {
         "anyOf": [
            {
               "$ref": "#/$defs/R"
            },
            {
               "type": "null"
            }
         ],
         "default": null
      }
   },
   "title": "Z",
   "type": "object"
}

and the code to produce it:

import openai
client = openai.Client()

from pydantic import BaseModel, ConfigDict
from typing import Literal, Union
import json

# First schema: q with a=1, b=2
class Q(BaseModel):
    a: Literal[1]
    model_config = ConfigDict(extra='forbid')

# Second schema: r with a=3, b=3
class R(BaseModel):
    a: Literal[3]
    model_config = ConfigDict(extra='forbid')

# Top-level schema using Union (anyOf)
class Z(BaseModel):
    q: Union[Q, None] = None
    r: Union[R, None] = None

    model_config = ConfigDict(extra='forbid')


print(json.dumps(Z.model_json_schema(), indent=3))

text = "Produce a response with no subschemas"

completion = client.beta.chat.completions.parse(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You write JSON, choosing anyOf schemas."},
        {"role": "user", "content": text},
    ],
    response_format=Z,
)

And then the enterprising person will also see it works on [str, None] even in combination with string enums, etc, only guided by the AI’s understanding of what you want.

So you can cool your jets there mad dog, and if you actually want to discover a solution (instead of badgering 8 month old topics) - present your use case, application, and then you might discover if it is a model quality issue, a schema construction issue, or the inability for the AI to understand what you want.

@_j

This was my code, which never returned “null” (OpenAI 4.90.0):

const response = await openai.responses.create({
    model: 'gpt-4o',
    input: [
        { role: 'system', content: "You are a helpful assistant who will always return 'null' for the 'name' property!" },
        { role: 'user', content: "Return 'null' for the 'name' property!" },
    ],
    text: {
        format: {
            type: 'json_schema',
            name: 'calendar_event',
            schema: {
                type: 'object',
                properties: {
                    name: {
                        type: ['string', 'null'],
                        enum: ['John', 'Joe'],
                    },
                },
                required: ['name'],
                additionalProperties: false,
            },
        },
    },
});

Anyway, after reading your answer in the other topic, I see that you have to add “null” in the enum for it to work—something that is not mentioned anywhere in their documentation or examples. This topic was the only one Google gave me, although the other one where you answered was clearly more relevant to my problem. Thank you!