openai-go icon indicating copy to clipboard operation
openai-go copied to clipboard

Union type deserialization bug: response_format variants incorrectly parsed as OfText

Open JulieLiu99 opened this issue 2 weeks ago • 0 comments

Bug Description

The OpenAI Go SDK incorrectly deserializes all response_format types as OfText, causing json_object and json_schema formats to lose their schema data.

Impact

  • Structured output (json_object, json_schema) completely broken when deserializing from JSON
  • Any application loading chat completion parameters from JSON loses schema functionality
  • Applications storing/forwarding OpenAI requests cannot use structured output
  • Silent data loss - no errors thrown, just wrong behavior

Reproduction

package main

import (
    "encoding/json"
    "fmt"

    openai "github.com/openai/openai-go"
)

func main() {
    // Test broken response_format types
    tests := []struct {
        name     string
        json     string
        expected string
    }{
        {"json_object", `{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}], "response_format": {"type": "json_object"}}`, "OfJSONObject"},
        {"json_schema", `{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}], "response_format": {"type": "json_schema", "json_schema": {"name": "test", "schema": {"type": "object", "properties": {"answer": {"type": "string"}}}}}}`, "OfJSONSchema"},
    }

    fmt.Println("JSON → SDK deserialization:")

    for _, test := range tests {
        var params openai.ChatCompletionNewParams
        if err := json.Unmarshal([]byte(test.json), &params); err != nil {
            fmt.Println("unmarshal err:", err)
            continue
        }

        actual := "none"
        if params.ResponseFormat.OfText != nil {
            actual = "OfText"
        } else if params.ResponseFormat.OfJSONObject != nil {
            actual = "OfJSONObject"
        } else if params.ResponseFormat.OfJSONSchema != nil {
            actual = "OfJSONSchema"
        }

        fmt.Printf("  FAIL %s: expected %s, got %s\n", test.name, test.expected, actual)
    }

    // Round-trip test
    fmt.Println("\nRound-trip test:")
    input := `{"type": "json_schema", "json_schema": {"name": "test", "schema": {"type": "object"}}}`

    var format openai.ChatCompletionNewParamsResponseFormatUnion
    json.Unmarshal([]byte(input), &format)
    output, _ := json.Marshal(format)

    fmt.Printf("  Input:  %s\n", input)
    fmt.Printf("  Output: %s\n", string(output))
    fmt.Println("  FAIL Data corrupted - schema information lost")
}

Test Output

JSON → SDK deserialization:
  FAIL json_object: expected OfJSONObject, got OfText
  FAIL json_schema: expected OfJSONSchema, got OfText

Round-trip test:
  Input:  {"type": "json_schema", "json_schema": {"name": "test", "schema": {"type": "object"}}}
  Output: {"type":"json_schema"}
  FAIL Data corrupted - schema information lost

Environment

  • Go version: go1.25.3 darwin/arm64
  • openai-go version: v1.12.0
  • OS: macOS (Darwin 24.6.0)

Root Cause

Union type deserialization logic appears to default to OfText regardless of actual JSON content.

JulieLiu99 avatar Nov 25 '25 22:11 JulieLiu99