openai/openai-java

Public

mirrored from https://github.com/openai/openai-javaAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.30.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

README.md

666lines · modecode

1# OpenAI Java API Library
2
3> [!NOTE]
4> The OpenAI Java API Library is currently in _beta_.
5>
6> There may be minor breaking changes.
7>
8> Have thoughts or feedback? [File an issue](https://github.com/openai/openai-java/issues/new) or comment on [this thread](https://community.openai.com/t/your-feedback-requested-java-sdk/1061029).
9
10<!-- x-release-please-start-version -->
11
12[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/0.30.0)
13[![javadoc](https://javadoc.io/badge2/com.openai/openai-java/0.30.0/javadoc.svg)](https://javadoc.io/doc/com.openai/openai-java/0.30.0)
14
15<!-- x-release-please-end -->
16
17The OpenAI Java SDK provides convenient access to the OpenAI REST API from applications written in Java.
18
19The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are also available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/0.0.1).
20
21## Installation
22
23<!-- x-release-please-start-version -->
24
25### Gradle
26
27```kotlin
28implementation("com.openai:openai-java:0.30.0")
29```
30
31### Maven
32
33```xml
34<dependency>
35 <groupId>com.openai</groupId>
36 <artifactId>openai-java</artifactId>
37 <version>0.30.0</version>
38</dependency>
39```
40
41<!-- x-release-please-end -->
42
43## Requirements
44
45This library requires Java 8 or later.
46
47## Usage
48
49See the [`openai-java-example`](openai-java-example/src/main/java/com/openai/example) directory for complete and runnable examples.
50
51```java
52import com.openai.client.OpenAIClient;
53import com.openai.client.okhttp.OpenAIOkHttpClient;
54import com.openai.models.ChatCompletion;
55import com.openai.models.ChatCompletionCreateParams;
56import com.openai.models.ChatModel;
57
58// Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
59OpenAIClient client = OpenAIOkHttpClient.fromEnv();
60
61ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
62 .addUserMessage("Say this is a test")
63 .model(ChatModel.O3_MINI)
64 .build();
65ChatCompletion chatCompletion = client.chat().completions().create(params);
66```
67
68## Client configuration
69
70Configure the client using environment variables:
71
72```java
73import com.openai.client.OpenAIClient;
74import com.openai.client.okhttp.OpenAIOkHttpClient;
75
76// Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
77OpenAIClient client = OpenAIOkHttpClient.fromEnv();
78```
79
80Or manually:
81
82```java
83import com.openai.client.OpenAIClient;
84import com.openai.client.okhttp.OpenAIOkHttpClient;
85
86OpenAIClient client = OpenAIOkHttpClient.builder()
87 .apiKey("My API Key")
88 .build();
89```
90
91Or using a combination of the two approaches:
92
93```java
94import com.openai.client.OpenAIClient;
95import com.openai.client.okhttp.OpenAIOkHttpClient;
96
97OpenAIClient client = OpenAIOkHttpClient.builder()
98 // Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
99 .fromEnv()
100 .apiKey("My API Key")
101 .build();
102```
103
104See this table for the available options:
105
106| Setter | Environment variable | Required | Default value |
107| -------------- | -------------------- | -------- | ------------- |
108| `apiKey` | `OPENAI_API_KEY` | true | - |
109| `organization` | `OPENAI_ORG_ID` | false | - |
110| `project` | `OPENAI_PROJECT_ID` | false | - |
111
112> [!TIP]
113> Don't create more than one client in the same application. Each client has a connection pool and
114> thread pools, which are more efficient to share between requests.
115
116## Requests and responses
117
118To send a request to the OpenAI API, build an instance of some `Params` class and pass it to the corresponding client method. When the response is received, it will be deserialized into an instance of a Java class.
119
120For example, `client.chat().completions().create(...)` should be called with an instance of `ChatCompletionCreateParams`, and it will return an instance of `ChatCompletion`.
121
122## Immutability
123
124Each class in the SDK has an associated [builder](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) or factory method for constructing it.
125
126Each class is [immutable](https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html) once constructed. If the class has an associated builder, then it has a `toBuilder()` method, which can be used to convert it back to a builder for making a modified copy.
127
128Because each class is immutable, builder modification will _never_ affect already built class instances.
129
130## Asynchronous execution
131
132The default client is synchronous. To switch to asynchronous execution, call the `async()` method:
133
134```java
135import com.openai.client.OpenAIClient;
136import com.openai.client.okhttp.OpenAIOkHttpClient;
137import com.openai.models.ChatCompletion;
138import com.openai.models.ChatCompletionCreateParams;
139import com.openai.models.ChatModel;
140import java.util.concurrent.CompletableFuture;
141
142// Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
143OpenAIClient client = OpenAIOkHttpClient.fromEnv();
144
145ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
146 .addUserMessage("Say this is a test")
147 .model(ChatModel.O3_MINI)
148 .build();
149CompletableFuture<ChatCompletion> chatCompletion = client.async().chat().completions().create(params);
150```
151
152Or create an asynchronous client from the beginning:
153
154```java
155import com.openai.client.OpenAIClientAsync;
156import com.openai.client.okhttp.OpenAIOkHttpClientAsync;
157import com.openai.models.ChatCompletion;
158import com.openai.models.ChatCompletionCreateParams;
159import com.openai.models.ChatModel;
160import java.util.concurrent.CompletableFuture;
161
162// Configures using the `OPENAI_API_KEY`, `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` environment variables
163OpenAIClientAsync client = OpenAIOkHttpClientAsync.fromEnv();
164
165ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
166 .addUserMessage("Say this is a test")
167 .model(ChatModel.O3_MINI)
168 .build();
169CompletableFuture<ChatCompletion> chatCompletion = client.chat().completions().create(params);
170```
171
172The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
173
174## Streaming
175
176The SDK defines methods that return response "chunk" streams, where each chunk can be individually processed as soon as it arrives instead of waiting on the full response. Streaming methods generally correspond to [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) or [JSONL](https://jsonlines.org) responses.
177
178Some of these methods may have streaming and non-streaming variants, but a streaming method will always have a `Streaming` suffix in its name, even if it doesn't have a non-streaming variant.
179
180These streaming methods return [`StreamResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt) for synchronous clients:
181
182```java
183import com.openai.core.http.StreamResponse;
184import com.openai.models.ChatCompletionChunk;
185
186try (StreamResponse<ChatCompletionChunk> streamResponse = client.chat().completions().createStreaming(params)) {
187 streamResponse.stream().forEach(chunk -> {
188 System.out.println(chunk);
189 });
190 System.out.println("No more chunks!");
191}
192```
193
194Or [`AsyncStreamResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/AsyncStreamResponse.kt) for asynchronous clients:
195
196```java
197import com.openai.core.http.AsyncStreamResponse;
198import com.openai.models.ChatCompletionChunk;
199import java.util.Optional;
200
201client.async().chat().completions().createStreaming(params).subscribe(chunk -> {
202 System.out.println(chunk);
203});
204
205// If you need to handle errors or completion of the stream
206client.async().chat().completions().createStreaming(params).subscribe(new AsyncStreamResponse.Handler<>() {
207 @Override
208 public void onNext(ChatCompletionChunk chunk) {
209 System.out.println(chunk);
210 }
211
212 @Override
213 public void onComplete(Optional<Throwable> error) {
214 if (error.isPresent()) {
215 System.out.println("Something went wrong!");
216 throw new RuntimeException(error.get());
217 } else {
218 System.out.println("No more chunks!");
219 }
220 }
221});
222
223// Or use futures
224client.async().chat().completions().createStreaming(params)
225 .subscribe(chunk -> {
226 System.out.println(chunk);
227 })
228 .onCompleteFuture();
229 .whenComplete((unused, error) -> {
230 if (error != null) {
231 System.out.println("Something went wrong!");
232 throw new RuntimeException(error);
233 } else {
234 System.out.println("No more chunks!");
235 }
236 });
237```
238
239Async streaming uses a dedicated per-client cached thread pool [`Executor`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) to stream without blocking the current thread. This default is suitable for most purposes.
240
241To use a different `Executor`, configure the subscription using the `executor` parameter:
242
243```java
244import java.util.concurrent.Executor;
245import java.util.concurrent.Executors;
246
247Executor executor = Executors.newFixedThreadPool(4);
248client.async().chat().completions().createStreaming(params).subscribe(
249 chunk -> System.out.println(chunk), executor
250);
251```
252
253Or configure the client globally using the `streamHandlerExecutor` method:
254
255```java
256import com.openai.client.OpenAIClient;
257import com.openai.client.okhttp.OpenAIOkHttpClient;
258import java.util.concurrent.Executors;
259
260OpenAIClient client = OpenAIOkHttpClient.builder()
261 .fromEnv()
262 .streamHandlerExecutor(Executors.newFixedThreadPool(4))
263 .build();
264```
265
266## Binary responses
267
268The SDK defines methods that return binary responses, which are used for API responses that shouldn't necessarily be parsed, like non-JSON data.
269
270These methods return [`HttpResponse`](openai-java-core/src/main/kotlin/com/openai/core/http/HttpResponse.kt):
271
272```java
273import com.openai.core.http.HttpResponse;
274import com.openai.models.FileContentParams;
275
276FileContentParams params = FileContentParams.builder()
277 .fileId("file_id")
278 .build();
279HttpResponse response = client.files().content(params);
280```
281
282To save the response content to a file, use the [`Files.copy(...)`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#copy-java.io.InputStream-java.nio.file.Path-java.nio.file.CopyOption...-) method:
283
284```java
285import com.openai.core.http.HttpResponse;
286import java.nio.file.Files;
287import java.nio.file.Paths;
288import java.nio.file.StandardCopyOption;
289
290try (HttpResponse response = client.files().content(params)) {
291 Files.copy(
292 response.body(),
293 Paths.get(path),
294 StandardCopyOption.REPLACE_EXISTING
295 );
296} catch (Exception e) {
297 System.out.println("Something went wrong!");
298 throw new RuntimeException(e);
299}
300```
301
302Or transfer the response content to any [`OutputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html):
303
304```java
305import com.openai.core.http.HttpResponse;
306import java.nio.file.Files;
307import java.nio.file.Paths;
308
309try (HttpResponse response = client.files().content(params)) {
310 response.body().transferTo(Files.newOutputStream(Paths.get(path)));
311} catch (Exception e) {
312 System.out.println("Something went wrong!");
313 throw new RuntimeException(e);
314}
315```
316
317## Error handling
318
319The SDK throws custom unchecked exception types:
320
321- [`OpenAIServiceException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIServiceException.kt): Base class for HTTP errors. See this table for which exception subclass is thrown for each HTTP status code:
322
323 | Status | Exception |
324 | ------ | ------------------------------- |
325 | 400 | `BadRequestException` |
326 | 401 | `AuthenticationException` |
327 | 403 | `PermissionDeniedException` |
328 | 404 | `NotFoundException` |
329 | 422 | `UnprocessableEntityException` |
330 | 429 | `RateLimitException` |
331 | 5xx | `InternalServerException` |
332 | others | `UnexpectedStatusCodeException` |
333
334- [`OpenAIIoException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt): I/O networking errors.
335
336- [`OpenAIInvalidDataException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response.
337
338- [`OpenAIException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class.
339
340## Pagination
341
342For methods that return a paginated list of results, this library provides convenient ways access the results either one page at a time, or item-by-item across all pages.
343
344### Auto-pagination
345
346To iterate through all results across all pages, you can use `autoPager`, which automatically handles fetching more pages for you:
347
348### Synchronous
349
350```java
351import com.openai.models.FineTuningJob;
352import com.openai.models.FineTuningJobListPage;
353
354// As an Iterable:
355FineTuningJobListPage page = client.fineTuning().jobs().list(params);
356for (FineTuningJob job : page.autoPager()) {
357 System.out.println(job);
358};
359
360// As a Stream:
361client.fineTuning().jobs().list(params).autoPager().stream()
362 .limit(50)
363 .forEach(job -> System.out.println(job));
364```
365
366### Asynchronous
367
368```java
369// Using forEach, which returns CompletableFuture<Void>:
370asyncClient.fineTuning().jobs().list(params).autoPager()
371 .forEach(job -> System.out.println(job), executor);
372```
373
374### Manual pagination
375
376If none of the above helpers meet your needs, you can also manually request pages one-by-one. A page of results has a `data()` method to fetch the list of objects, as well as top-level `response` and other methods to fetch top-level data about the page. It also has methods `hasNextPage`, `getNextPage`, and `getNextPageParams` methods to help with pagination.
377
378```java
379import com.openai.models.FineTuningJob;
380import com.openai.models.FineTuningJobListPage;
381
382FineTuningJobListPage page = client.fineTuning().jobs().list(params);
383while (page != null) {
384 for (FineTuningJob job : page.data()) {
385 System.out.println(job);
386 }
387
388 page = page.getNextPage().orElse(null);
389}
390```
391
392## Logging
393
394The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
395
396Enable logging by setting the `OPENAI_LOG` environment variable to `info`:
397
398```sh
399$ export OPENAI_LOG=info
400```
401
402Or to `debug` for more verbose logging:
403
404```sh
405$ export OPENAI_LOG=debug
406```
407
408## Microsoft Azure
409
410To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the same
411OpenAI client builder but with the Azure-specific configuration.
412
413```java
414OpenAIClient client = OpenAIOkHttpClient.builder()
415 // Gets the API key from the `AZURE_OPENAI_KEY` environment variable
416 .fromEnv()
417 // Set the Azure Entra ID
418 .credential(BearerTokenCredential.create(AuthenticationUtil.getBearerTokenSupplier(
419 new DefaultAzureCredentialBuilder().build(), "https://cognitiveservices.azure.com/.default")))
420 .build();
421```
422
423See the complete Azure OpenAI example in the [`openai-java-example`](openai-java-example/src/main/java/com/openai/example/AzureEntraIdExample.java) directory. The other examples in the directory also work with Azure as long as the client is configured to use it.
424
425## Network options
426
427### Retries
428
429The SDK automatically retries 2 times by default, with a short exponential backoff.
430
431Only the following error types are retried:
432
433- Connection errors (for example, due to a network connectivity problem)
434- 408 Request Timeout
435- 409 Conflict
436- 429 Rate Limit
437- 5xx Internal
438
439The API may also explicitly instruct the SDK to retry or not retry a response.
440
441To set a custom number of retries, configure the client using the `maxRetries` method:
442
443```java
444import com.openai.client.OpenAIClient;
445import com.openai.client.okhttp.OpenAIOkHttpClient;
446
447OpenAIClient client = OpenAIOkHttpClient.builder()
448 .fromEnv()
449 .maxRetries(4)
450 .build();
451```
452
453### Timeouts
454
455Requests time out after 10 minutes by default.
456
457To set a custom timeout, configure the method call using the `timeout` method:
458
459```java
460import com.openai.models.ChatCompletion;
461import com.openai.models.ChatCompletionCreateParams;
462import com.openai.models.ChatModel;
463
464ChatCompletion chatCompletion = client.chat().completions().create(
465 params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
466);
467```
468
469Or configure the default for all method calls at the client level:
470
471```java
472import com.openai.client.OpenAIClient;
473import com.openai.client.okhttp.OpenAIOkHttpClient;
474import java.time.Duration;
475
476OpenAIClient client = OpenAIOkHttpClient.builder()
477 .fromEnv()
478 .timeout(Duration.ofSeconds(30))
479 .build();
480```
481
482### Proxies
483
484To route requests through a proxy, configure the client using the `proxy` method:
485
486```java
487import com.openai.client.OpenAIClient;
488import com.openai.client.okhttp.OpenAIOkHttpClient;
489import java.net.InetSocketAddress;
490import java.net.Proxy;
491
492OpenAIClient client = OpenAIOkHttpClient.builder()
493 .fromEnv()
494 .proxy(new Proxy(
495 Proxy.Type.HTTP, new InetSocketAddress(
496 "https://example.com", 8080
497 )
498 ))
499 .build();
500```
501
502## Undocumented API functionality
503
504The SDK is typed for convenient usage of the documented API. However, it also supports working with undocumented or not yet supported parts of the API.
505
506### Parameters
507
508To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQueryParam`, or `putAdditionalBodyProperty` methods on any `Params` class:
509
510```java
511import com.openai.core.JsonValue;
512import com.openai.models.ChatCompletionCreateParams;
513
514ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
515 .putAdditionalHeader("Secret-Header", "42")
516 .putAdditionalQueryParam("secret_query_param", "42")
517 .putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
518 .build();
519```
520
521These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods. You can also set undocumented parameters on nested headers, query params, or body classes using the `putAdditionalProperty` method. These properties can be accessed on the built object later using the `_additionalProperties()` method.
522
523To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](openai-java-core/src/main/kotlin/com/openai/core/JsonValue.kt) object to its setter:
524
525```java
526import com.openai.core.JsonValue;
527import com.openai.models.ChatCompletionCreateParams;
528
529ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
530 .addUserMessage("Say this is a test")
531 .model(JsonValue.from(42))
532 .build();
533```
534
535### Response properties
536
537To access undocumented response properties, call the `_additionalProperties()` method:
538
539```java
540import com.openai.core.JsonValue;
541import java.util.Map;
542
543Map<String, JsonValue> additionalProperties = client.chat().completions().create(params)._additionalProperties();
544JsonValue secretPropertyValue = additionalProperties.get("secretProperty");
545
546String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
547 @Override
548 public String visitNull() {
549 return "It's null!";
550 }
551
552 @Override
553 public String visitBoolean(boolean value) {
554 return "It's a boolean!";
555 }
556
557 @Override
558 public String visitNumber(Number value) {
559 return "It's a number!";
560 }
561
562 // Other methods include `visitMissing`, `visitString`, `visitArray`, and `visitObject`
563 // The default implementation of each unimplemented method delegates to `visitDefault`, which throws by default, but can also be overridden
564});
565```
566
567To access a property's raw JSON value, which may be undocumented, call its `_` prefixed method:
568
569```java
570import com.openai.core.JsonField;
571import com.openai.models.ChatCompletionMessageParam;
572import java.util.Optional;
573
574JsonField<List<ChatCompletionMessageParam>> messages = client.chat().completions().create(params)._messages();
575
576if (messages.isMissing()) {
577 // The property is absent from the JSON response
578} else if (messages.isNull()) {
579 // The property was set to literal null
580} else {
581 // Check if value was provided as a string
582 // Other methods include `asNumber()`, `asBoolean()`, etc.
583 Optional<String> jsonString = messages.asString();
584
585 // Try to deserialize into a custom type
586 MyClass myObject = messages.asUnknown().orElseThrow().convert(MyClass.class);
587}
588```
589
590### Response validation
591
592In rare cases, the API may return a response that doesn't match the expected type. For example, the SDK may expect a property to contain a `String`, but the API could return something else.
593
594By default, the SDK will not throw an exception in this case. It will throw [`OpenAIInvalidDataException`](openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt) only if you directly access the property.
595
596If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`:
597
598```java
599import com.openai.models.ChatCompletion;
600
601ChatCompletion chatCompletion = client.chat().completions().create(params).validate();
602```
603
604Or configure the method call to validate the response using the `responseValidation` method:
605
606```java
607import com.openai.models.ChatCompletion;
608import com.openai.models.ChatCompletionCreateParams;
609import com.openai.models.ChatModel;
610
611ChatCompletion chatCompletion = client.chat().completions().create(
612 params, RequestOptions.builder().responseValidation(true).build()
613);
614```
615
616Or configure the default for all method calls at the client level:
617
618```java
619import com.openai.client.OpenAIClient;
620import com.openai.client.okhttp.OpenAIOkHttpClient;
621
622OpenAIClient client = OpenAIOkHttpClient.builder()
623 .fromEnv()
624 .responseValidation(true)
625 .build();
626```
627
628## FAQ
629
630### Why don't you use plain `enum` classes?
631
632Java `enum` classes are not trivially [forwards compatible](https://www.stainless.com/blog/making-java-enums-forwards-compatible). Using them in the SDK could cause runtime exceptions if the API is updated to respond with a new enum value.
633
634### Why do you represent fields using `JsonField<T>` instead of just plain `T`?
635
636Using `JsonField<T>` enables a few features:
637
638- Allowing usage of [undocumented API functionality](#undocumented-api-functionality)
639- Lazily [validating the API response against the expected shape](#response-validation)
640- Representing absent vs explicitly null values
641
642### Why don't you use [`data` classes](https://kotlinlang.org/docs/data-classes.html)?
643
644It is not [backwards compatible to add new fields to a data class](https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api) and we don't want to introduce a breaking change every time we add a field to a class.
645
646### Why don't you use checked exceptions?
647
648Checked exceptions are widely considered a mistake in the Java programming language. In fact, they were omitted from Kotlin for this reason.
649
650Checked exceptions:
651
652- Are verbose to handle
653- Encourage error handling at the wrong level of abstraction, where nothing can be done about the error
654- Are tedious to propagate due to the [function coloring problem](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function)
655- Don't play well with lambdas (also due to the function coloring problem)
656
657## Semantic versioning
658
659This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
660
6611. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
6622. Changes that we do not expect to impact the vast majority of users in practice.
663
664We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
665
666We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-java/issues) with questions, bugs, or suggestions.
667