|
| 1 | +# OpenAI Go Migration Guide |
| 2 | + |
| 3 | +<a href="https://pkg.go.dev/github.com/openai/openai-go"><img src="https://pkg.go.dev/badge/github.com/openai/openai-go.svg" alt="Go Reference"></a> |
| 4 | + |
| 5 | +This SDK includes breaking changes to improve the ergonomics of constructing parameters and accessing responses. |
| 6 | + |
| 7 | +To reduce verbosity, the `openai.F(...)` and `param.Field[T]` have been removed. |
| 8 | +All calls to `openai.F(...)` can be deleted. |
| 9 | + |
| 10 | +The SDK now uses the <code>\`json:"...,omitzero"\`</code> struct tag to omit fields. Nested structs, arrays and maps |
| 11 | +can be declared like normal. |
| 12 | + |
| 13 | +The old SDK used interfaces for unions in requests, which required |
| 14 | +a type assertion to access variants and fields. The new design uses |
| 15 | +structs with a field for each variant, wherein only one field can be set. |
| 16 | +These struct unions also expose 'Get' methods to access and mutate subfields |
| 17 | +which may be shared by multiple variants. |
| 18 | + |
| 19 | +# Request parameters |
| 20 | + |
| 21 | +## Required primitives parameters serialize their zero values (`string`, `int64`, etc.) |
| 22 | + |
| 23 | +> [!CAUTION] |
| 24 | +> |
| 25 | +> **This change can cause new behavior in existing code, without compiler warnings.** |
| 26 | +
|
| 27 | +While migrating, ensure that all required fields are explicitly set. A required primitive |
| 28 | +field `Age` will use the <code>\`json:"age,required"\`</code> struct tag without `omitzero`. |
| 29 | + |
| 30 | +If a required primitive field is not set, the zero value will be serialized. |
| 31 | +This was not the case in with `param.Field[T]`. |
| 32 | + |
| 33 | +```diff |
| 34 | +type FooParams struct { |
| 35 | +- Age param.Field[int64] `json:"age,required"` |
| 36 | +- Name param.Field[string] `json:"name"` |
| 37 | ++ Age int64 `json:"age,required"` // <== Notice no omitzero |
| 38 | ++ Name param.Opt[string] `json:"name,omitzero"` |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +<table> |
| 43 | +<tr> |
| 44 | +<th>Previous</th> |
| 45 | +<th>New</th> |
| 46 | +</tr> |
| 47 | +<tr> |
| 48 | +<td> |
| 49 | + |
| 50 | +```go |
| 51 | +_ = FooParams{ |
| 52 | + Name: openai.String("Jerry") |
| 53 | +} |
| 54 | +`{"name": "Jerry"}` // (after serialization) |
| 55 | +``` |
| 56 | + |
| 57 | +</td> |
| 58 | +<td> |
| 59 | + |
| 60 | +```go |
| 61 | +_ = FooParams{ |
| 62 | + Name: openai.String("Jerry") |
| 63 | +} |
| 64 | +`{"name": "Jerry", "age": 0}` // <== Notice the age field |
| 65 | +``` |
| 66 | + |
| 67 | +</td> |
| 68 | +</tr> |
| 69 | +</table> |
| 70 | + |
| 71 | +The required field `"age"` is now present as `0`. Fields without the <code>\`json:"...,omitzero"\`</code> struct tag |
| 72 | +are always serialized, including their zero values. |
| 73 | + |
| 74 | +## Transition from `param.Field[T]` to `omitzero` |
| 75 | + |
| 76 | +The `openai.F(...)` function and `param.Field[T]` type are no longer present in the new SDK. |
| 77 | + |
| 78 | +To represent omitted fields, the SDK uses <a href="https://pkg.go.dev/encoding/json#Marshal"><code>\`json:"...,omitzero"\`</code> semantics</a> from Go 1.24+ for JSON encoding[^1]. `omitzero` always omits fields |
| 79 | +with zero values. |
| 80 | + |
| 81 | +In all cases other than optional primitives, `openai.F()` can simply be removed. |
| 82 | +For optional primitive types, such as `param.Opt[string]`, you can use `openai.String(string)` to construct the value. |
| 83 | +Similar functions exist for other primitive types like `openai.Int(int)`, `openai.Bool(bool)`, etc. |
| 84 | + |
| 85 | +`omitzero` is used for fields whose type is either a struct, slice, map, string enum, |
| 86 | +or wrapped optional primitive (e.g. `param.Opt[T]`). Required primitive fields don't use `omitzero`. |
| 87 | + |
| 88 | +**Example User Code: Constructing a request** |
| 89 | + |
| 90 | +```diff |
| 91 | +foo = FooParams{ |
| 92 | +- RequiredString: openai.String("hello"), |
| 93 | ++ RequiredString: "hello", |
| 94 | + |
| 95 | +- OptionalString: openai.String("hi"), |
| 96 | ++ OptionalString: openai.String("hi"), |
| 97 | + |
| 98 | +- Array: openai.F([]BarParam{ |
| 99 | +- BarParam{Prop: ... } |
| 100 | +- }), |
| 101 | ++ Array: []BarParam{ |
| 102 | ++ BarParam{Prop: ... } |
| 103 | ++ }, |
| 104 | + |
| 105 | +- RequiredObject: openai.F(BarParam{ ... }), |
| 106 | ++ RequiredObject: BarParam{ ... }, |
| 107 | + |
| 108 | +- OptionalObject: openai.F(BarParam{ ... }), |
| 109 | ++ OptionalObject: BarParam{ ... }, |
| 110 | + |
| 111 | +- StringEnum: openai.F[BazEnum]("baz-ok"), |
| 112 | ++ StringEnum: "baz-ok", |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +**Internal SDK Code: Fields of a request struct:** |
| 117 | + |
| 118 | +```diff |
| 119 | +type FooParams struct { |
| 120 | +- RequiredString param.Field[string] `json:"required_string,required"` |
| 121 | ++ RequiredString string `json:"required_string,required"` |
| 122 | + |
| 123 | +- OptionalString param.Field[string] `json:"optional_string"` |
| 124 | ++ OptionalString param.Opt[string] `json:"optional_string,omitzero"` |
| 125 | + |
| 126 | +- Array param.Field[[]BarParam] `json"array"` |
| 127 | ++ Array []BarParam `json"array,omitzero"` |
| 128 | + |
| 129 | +- Map param.Field[map[string]BarParam] `json"map"` |
| 130 | ++ Map map[string]BarParam `json"map,omitzero"` |
| 131 | + |
| 132 | +- RequiredObject param.Field[BarParam] `json:"required_object,required"` |
| 133 | ++ RequiredObject BarParam `json:"required_object,omitzero,required"` |
| 134 | + |
| 135 | +- OptionalObject param.Field[BarParam] `json:"optional_object"` |
| 136 | ++ OptionalObject BarParam `json:"optional_object,omitzero"` |
| 137 | + |
| 138 | +- StringEnum param.Field[BazEnum] `json:"string_enum"` |
| 139 | ++ StringEnum BazEnum `json:"string_enum,omitzero"` |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +## Request Unions: Removing interfaces and moving to structs |
| 144 | + |
| 145 | +For a type `AnimalUnionParam` which could be either a `CatParam | DogParam`. |
| 146 | + |
| 147 | +<table> |
| 148 | +<tr><th>Previous</th> <th>New</th></tr> |
| 149 | +<tr> |
| 150 | +<td> |
| 151 | + |
| 152 | +```go |
| 153 | +type AnimalParam interface { |
| 154 | + ImplAnimalParam() |
| 155 | +} |
| 156 | + |
| 157 | +func (Dog) ImplAnimalParam() {} |
| 158 | +func (Cat) ImplAnimalParam() {} |
| 159 | +``` |
| 160 | + |
| 161 | +</td> |
| 162 | +<td> |
| 163 | + |
| 164 | +```go |
| 165 | +type AnimalUnionParam struct { |
| 166 | + OfCat *Cat `json:",omitzero,inline` |
| 167 | + OfDog *Dog `json:",omitzero,inline` |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +</td> |
| 172 | +</tr> |
| 173 | + |
| 174 | +<tr style="background:rgb(209, 217, 224)"> |
| 175 | +<td> |
| 176 | + |
| 177 | +```go |
| 178 | +var dog AnimalParam = DogParam{ |
| 179 | + Name: "spot", ... |
| 180 | +} |
| 181 | +var cat AnimalParam = CatParam{ |
| 182 | + Name: "whiskers", ... |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +</td> |
| 187 | +<td> |
| 188 | + |
| 189 | +```go |
| 190 | +dog := AnimalUnionParam{ |
| 191 | + OfDog: &DogParam{Name: "spot", ... }, |
| 192 | +} |
| 193 | +cat := AnimalUnionParam{ |
| 194 | + OfCat: &CatParam{Name: "whiskers", ... }, |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +</td> |
| 199 | +</tr> |
| 200 | + |
| 201 | +<tr> |
| 202 | +<td> |
| 203 | + |
| 204 | +```go |
| 205 | +var name string |
| 206 | +switch v := animal.(type) { |
| 207 | +case Dog: |
| 208 | + name = v.Name |
| 209 | +case Cat: |
| 210 | + name = v.Name |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +</td> |
| 215 | +<td> |
| 216 | + |
| 217 | +```go |
| 218 | +// Accessing fields |
| 219 | +var name *string = animal.GetName() |
| 220 | +``` |
| 221 | + |
| 222 | +</td> |
| 223 | +</tr> |
| 224 | +</table> |
| 225 | + |
| 226 | +## Sending explicit `null` values |
| 227 | + |
| 228 | +The old SDK had a function `param.Null[T]()` which could set `param.Field[T]` to `null`. |
| 229 | + |
| 230 | +The new SDK uses `param.Null[T]()` for to set a `param.Opt[T]` to `null`, |
| 231 | +but `param.NullStruct[T]()` to set a param struct `T` to `null`. |
| 232 | + |
| 233 | +```diff |
| 234 | +- var nullPrimitive param.Field[int64] = param.Null[int64]() |
| 235 | ++ var nullPrimitive param.Opt[int64] = param.Null[int64]() |
| 236 | + |
| 237 | +- var nullStruct param.Field[BarParam] = param.Null[BarParam]() |
| 238 | ++ var nullStruct BarParam = param.NullStruct[BarParam]() |
| 239 | +``` |
| 240 | + |
| 241 | +## Sending custom values |
| 242 | + |
| 243 | +The `openai.Raw[T](any)` function has been removed. All request structs now support a |
| 244 | +`.WithExtraField(map[string]any)` method to customize the fields. |
| 245 | + |
| 246 | +```diff |
| 247 | +foo := FooParams{ |
| 248 | + A: param.String("hello"), |
| 249 | +- B: param.Raw[string](12) // sending `12` instead of a string |
| 250 | +} |
| 251 | ++ foo.SetExtraFields(map[string]any{ |
| 252 | ++ "B": 12, |
| 253 | ++ }) |
| 254 | +``` |
| 255 | + |
| 256 | +# Response Properties |
| 257 | + |
| 258 | +## Checking for presence of optional fields |
| 259 | + |
| 260 | +The `.IsNull()` method has been changed to `.Valid()` to better reflect its behavior. |
| 261 | + |
| 262 | +```diff |
| 263 | +- if !resp.Foo.JSON.Bar.IsNull() { |
| 264 | ++ if resp.Foo.JSON.Bar.Valid() { |
| 265 | + println("bar is present:", resp.Foo.Bar) |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +| Previous | New | Returns true for values | |
| 270 | +| -------------- | ------------------------ | ----------------------- | |
| 271 | +| `.IsNull()` | `!.Valid()` | `null` or Omitted | |
| 272 | +| `.IsMissing()` | `.Raw() == resp.Omitted` | Omitted | |
| 273 | +| | `.Raw() == resp.Null` | |
| 274 | + |
| 275 | +## Checking Raw JSON of a response |
| 276 | + |
| 277 | +The `.RawJSON()` method has moved to the parent of the `.JSON` property. |
| 278 | + |
| 279 | +```diff |
| 280 | +- resp.Foo.JSON.RawJSON() |
| 281 | ++ resp.Foo.RawJSON() |
| 282 | +``` |
| 283 | + |
| 284 | +[^1]: The SDK doesn't require Go 1.24, despite supporting the `omitzero` feature |
0 commit comments