CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-openai

OpenAI models support for Spring AI, providing comprehensive integration for chat completion, embeddings, image generation, audio transcription, text-to-speech, and content moderation capabilities within Spring Boot applications.

Overview
Eval results
Files

api-clients.mddocs/reference/

API Clients

Low-level REST API clients for direct interaction with OpenAI's APIs, providing fine-grained control over requests and responses for chat, embeddings, images, audio, moderation, and file operations.

Overview

The spring-ai-openai module provides low-level API client classes for direct communication with OpenAI services. These clients offer maximum flexibility and control, handling HTTP communication, request/response serialization, and error handling.

When to use API clients:

  • Need direct access to OpenAI API features not exposed in high-level models
  • Building custom abstractions or integrations
  • Require fine-grained control over request parameters
  • Working with raw API responses
  • Implementing custom retry logic or error handling

For most use cases, prefer the high-level model classes (OpenAiChatModel, OpenAiEmbeddingModel, etc.) which provide Spring-idiomatic APIs and better integration with Spring AI features.


OpenAiApi

REST API client for chat completions and embeddings.

/**
 * Low-level OpenAI API client for chat and embeddings
 */
public class OpenAiApi {
    /**
     * Default chat model constant (value: GPT_4_O)
     */
    public static final ChatModel DEFAULT_CHAT_MODEL = ChatModel.GPT_4_O;

    /**
     * Default embedding model constant (value: "text-embedding-ada-002")
     */
    public static final String DEFAULT_EMBEDDING_MODEL = "text-embedding-ada-002";

    /**
     * Create a builder for configuring OpenAiApi
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Create a builder from this API instance
     * @return Builder pre-populated with current configuration
     */
    public Builder mutate();

    /**
     * Synchronous chat completion request
     * @param chatRequest Chat completion request
     * @return ResponseEntity with ChatCompletion
     */
    public ResponseEntity<ChatCompletion> chatCompletionEntity(ChatCompletionRequest chatRequest);

    /**
     * Synchronous chat completion with custom headers
     * @param chatRequest Chat completion request
     * @param headers HTTP headers to include
     * @return ResponseEntity with ChatCompletion
     */
    public ResponseEntity<ChatCompletion> chatCompletionEntity(
        ChatCompletionRequest chatRequest,
        MultiValueMap<String, String> headers
    );

    /**
     * Streaming chat completion request
     * @param chatRequest Chat completion request
     * @return Flux of ChatCompletionChunk
     */
    public Flux<ChatCompletionChunk> chatCompletionStream(ChatCompletionRequest chatRequest);

    /**
     * Streaming chat completion with custom headers
     * @param chatRequest Chat completion request
     * @param headers HTTP headers to include
     * @return Flux of ChatCompletionChunk
     */
    public Flux<ChatCompletionChunk> chatCompletionStream(
        ChatCompletionRequest chatRequest,
        MultiValueMap<String, String> headers
    );

    /**
     * Generate embeddings for text input(s)
     * @param embeddingRequest Embedding request
     * @return ResponseEntity with EmbeddingList
     */
    public <T> ResponseEntity<EmbeddingList<Embedding>> embeddings(EmbeddingRequest<T> embeddingRequest);

    /**
     * Extract text content from media content list
     * @param content List of media content items
     * @return Extracted text content
     */
    public static String getTextContent(List<ChatCompletionMessage.MediaContent> content);
}

Builder Pattern:

public static class Builder {
    /**
     * Set the OpenAI API key
     * @param apiKey API key
     * @return Builder
     */
    public Builder apiKey(String apiKey);

    /**
     * Set the base URL for API requests
     * @param baseUrl Base URL (default: https://api.openai.com)
     * @return Builder
     */
    public Builder baseUrl(String baseUrl);

    /**
     * Set the organization ID
     * @param organizationId Organization ID
     * @return Builder
     */
    public Builder organizationId(String organizationId);

    /**
     * Set the project ID
     * @param projectId Project ID
     * @return Builder
     */
    public Builder projectId(String projectId);

    /**
     * Set custom HTTP headers
     * @param headers Headers to include in all requests
     * @return Builder
     */
    public Builder headers(Map<String, String> headers);

    /**
     * Set custom RestClient for HTTP communication
     * @param restClient Configured RestClient
     * @return Builder
     */
    public Builder restClient(RestClient restClient);

    /**
     * Build the OpenAiApi instance
     * @return Configured OpenAiApi
     */
    public OpenAiApi build();
}

Usage Example:

import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion;
import org.springframework.http.ResponseEntity;

// Create API client
var api = OpenAiApi.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .organizationId("org-123")
    .baseUrl("https://api.openai.com")
    .build();

// Synchronous chat completion
var request = new ChatCompletionRequest(
    List.of(
        new ChatCompletionMessage(
            "What is the capital of France?",
            ChatCompletionMessage.Role.USER,
            null, null, null, null, null, null
        )
    ),
    OpenAiApi.ChatModel.GPT_4_O.getValue(),
    null,  // frequencyPenalty
    null,  // logitBias
    null,  // logprobs
    null,  // topLogprobs
    500,   // maxTokens
    null,  // maxCompletionTokens
    1,     // n
    null,  // presencePenalty
    null,  // responseFormat
    null,  // seed
    null,  // stop
    false, // stream
    null,  // streamOptions
    0.7,   // temperature
    null,  // topP
    null,  // tools
    null,  // toolChoice
    null,  // parallelToolCalls
    null,  // user
    null,  // outputModalities
    null,  // outputAudio
    null,  // store
    null,  // metadata
    null,  // reasoningEffort
    null,  // verbosity
    null,  // webSearchOptions
    null   // serviceTier
);

ResponseEntity<ChatCompletion> response = api.chatCompletionEntity(request);
ChatCompletion completion = response.getBody();
String answer = completion.choices().get(0).message().content().toString();
System.out.println("Answer: " + answer);

Streaming Chat:

// Streaming chat completion
var streamRequest = new ChatCompletionRequest(
    List.of(
        new ChatCompletionMessage(
            "Write a short story",
            ChatCompletionMessage.Role.USER,
            null, null, null, null, null, null
        )
    ),
    OpenAiApi.ChatModel.GPT_4_O.getValue(),
    null, null, null, null, 1000, null, 1, null, null, null, null,
    true,  // Enable streaming
    new ChatCompletionRequest.StreamOptions(true),  // Include usage
    0.8, null, null, null, null, null, null, null, null, null, null, null, null, null
);

api.chatCompletionStream(streamRequest)
    .subscribe(chunk -> {
        var delta = chunk.choices().get(0).delta();
        if (delta.content() != null) {
            System.out.print(delta.content());
        }
    });

Embeddings:

import org.springframework.ai.openai.api.OpenAiApi.EmbeddingRequest;

// Single text embedding
var embeddingRequest = new EmbeddingRequest<>(
    "The quick brown fox jumps over the lazy dog",
    OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_SMALL.getValue(),
    "float",
    null,  // Default dimensions
    null   // User
);

var embeddingResponse = api.embeddings(embeddingRequest);
float[] embedding = embeddingResponse.getBody().data().get(0).embedding();

// Multiple texts
var multiRequest = new EmbeddingRequest<>(
    List.of("Text 1", "Text 2", "Text 3"),
    OpenAiApi.EmbeddingModel.TEXT_EMBEDDING_3_LARGE.getValue(),
    "float",
    1024,  // Reduced dimensions
    "user-123"
);

var multiResponse = api.embeddings(multiRequest);

OpenAiImageApi

REST API client for image generation.

/**
 * Low-level OpenAI API client for image generation
 */
public class OpenAiImageApi {
    /**
     * Default image model constant (value: DALL_E_3)
     */
    public static final String DEFAULT_IMAGE_MODEL = "dall-e-3";

    /**
     * Create a builder for configuring OpenAiImageApi
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Generate images from text prompt
     * @param imageRequest Image generation request
     * @return ResponseEntity with OpenAiImageResponse
     */
    public ResponseEntity<OpenAiImageResponse> createImage(OpenAiImageRequest imageRequest);
}

Builder Pattern:

public static class Builder {
    public Builder apiKey(String apiKey);
    public Builder baseUrl(String baseUrl);
    public Builder organizationId(String organizationId);
    public Builder projectId(String projectId);
    public Builder headers(Map<String, String> headers);
    public Builder restClient(RestClient restClient);
    public OpenAiImageApi build();
}

Usage Example:

import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.api.OpenAiImageApi.OpenAiImageRequest;
import org.springframework.ai.openai.api.OpenAiImageApi.OpenAiImageResponse;

// Create API client
var imageApi = OpenAiImageApi.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .build();

// Generate image
var request = new OpenAiImageRequest(
    "A serene mountain landscape at sunset",
    OpenAiImageApi.ImageModel.DALL_E_3.getValue(),
    1,           // n
    "hd",        // quality
    "url",       // responseFormat
    "1024x1024", // size
    "vivid",     // style
    null         // user
);

var response = imageApi.createImage(request);
OpenAiImageResponse imageResponse = response.getBody();

imageResponse.data().forEach(data -> {
    System.out.println("Image URL: " + data.url());
    System.out.println("Revised prompt: " + data.revisedPrompt());
});

OpenAiAudioApi

REST API client for audio operations (speech synthesis and transcription).

/**
 * Low-level OpenAI API client for audio operations
 */
public class OpenAiAudioApi {
    /**
     * Create a builder for configuring OpenAiAudioApi
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Generate speech audio from text
     * @param speechRequest Speech generation request
     * @return ResponseEntity with audio bytes
     */
    public ResponseEntity<byte[]> createSpeech(SpeechRequest speechRequest);

    /**
     * Stream speech audio generation
     * @param speechRequest Speech generation request
     * @return Flux of ResponseEntity with audio chunks
     */
    public Flux<ResponseEntity<byte[]>> stream(SpeechRequest speechRequest);

    /**
     * Transcribe audio to text
     * @param transcriptionRequest Transcription request
     * @param responseType Expected response type class
     * @return ResponseEntity with transcription result
     */
    public <T> ResponseEntity<T> createTranscription(
        TranscriptionRequest transcriptionRequest,
        Class<T> responseType
    );

    /**
     * Translate audio to English text
     * @param translationRequest Translation request
     * @param responseType Expected response type class
     * @return ResponseEntity with translation result
     */
    public <T> ResponseEntity<T> createTranslation(
        TranslationRequest translationRequest,
        Class<T> responseType
    );
}

Builder Pattern:

public static class Builder {
    public Builder apiKey(String apiKey);
    public Builder baseUrl(String baseUrl);
    public Builder organizationId(String organizationId);
    public Builder projectId(String projectId);
    public Builder headers(Map<String, String> headers);
    public Builder restClient(RestClient restClient);
    public OpenAiAudioApi build();
}

Usage Example - Text-to-Speech:

import org.springframework.ai.openai.api.OpenAiAudioApi;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest.Voice;
import org.springframework.ai.openai.api.OpenAiAudioApi.SpeechRequest.AudioResponseFormat;

// Create API client
var audioApi = OpenAiAudioApi.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .build();

// Generate speech
var speechRequest = new SpeechRequest(
    OpenAiAudioApi.TtsModel.TTS_1_HD.getValue(),
    "Hello, this is a test of text to speech",
    Voice.NOVA,
    AudioResponseFormat.MP3,
    1.0  // speed
);

var response = audioApi.createSpeech(speechRequest);
byte[] audioData = response.getBody();

// Save to file
Files.write(Paths.get("speech.mp3"), audioData);

Usage Example - Transcription:

import org.springframework.ai.openai.api.OpenAiAudioApi.TranscriptionRequest;
import org.springframework.ai.openai.api.OpenAiAudioApi.TranscriptResponseFormat;
import org.springframework.ai.openai.api.OpenAiAudioApi.StructuredResponse;
import org.springframework.core.io.FileSystemResource;

// Simple text transcription
var audioFile = new FileSystemResource("recording.mp3");
var textRequest = new TranscriptionRequest(
    audioFile,
    OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
    "en",    // language
    null,    // prompt
    TranscriptResponseFormat.TEXT,
    0.0f,    // temperature
    null     // timestampGranularities
);

var textResponse = audioApi.createTranscription(textRequest, String.class);
String transcript = textResponse.getBody();
System.out.println("Transcript: " + transcript);

// Verbose transcription with metadata
var verboseRequest = new TranscriptionRequest(
    audioFile,
    OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
    "en",
    null,
    TranscriptResponseFormat.VERBOSE_JSON,
    0.0f,
    List.of(TranscriptionRequest.GranularityType.WORD)
);

var verboseResponse = audioApi.createTranscription(verboseRequest, StructuredResponse.class);
StructuredResponse structured = verboseResponse.getBody();

System.out.println("Language: " + structured.language());
System.out.println("Duration: " + structured.duration() + " seconds");
System.out.println("Text: " + structured.text());

structured.words().forEach(word -> {
    System.out.printf("%s [%.2f - %.2f]%n",
        word.word(), word.start(), word.end());
});

Usage Example - Translation:

import org.springframework.ai.openai.api.OpenAiAudioApi.TranslationRequest;

// Translate non-English audio to English
var audioFile = new FileSystemResource("spanish_audio.mp3");
var translationRequest = new TranslationRequest(
    audioFile,
    OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(),
    null,    // prompt
    "text",  // responseFormat
    0.0f     // temperature
);

var translationResponse = audioApi.createTranslation(translationRequest, String.class);
String englishTranslation = translationResponse.getBody();
System.out.println("Translation: " + englishTranslation);

OpenAiModerationApi

REST API client for content moderation.

/**
 * Low-level OpenAI API client for content moderation
 */
public class OpenAiModerationApi {
    /**
     * Default moderation model constant (value: "omni-moderation-latest")
     */
    public static final String DEFAULT_MODERATION_MODEL = "omni-moderation-latest";

    /**
     * Create a builder for configuring OpenAiModerationApi
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Moderate content for policy violations
     * @param moderationRequest Moderation request
     * @return ResponseEntity with OpenAiModerationResponse
     */
    public ResponseEntity<OpenAiModerationResponse> createModeration(
        OpenAiModerationRequest moderationRequest
    );
}

Builder Pattern:

public static class Builder {
    public Builder apiKey(String apiKey);
    public Builder baseUrl(String baseUrl);
    public Builder organizationId(String organizationId);
    public Builder projectId(String projectId);
    public Builder headers(Map<String, String> headers);
    public Builder restClient(RestClient restClient);
    public OpenAiModerationApi build();
}

Usage Example:

import org.springframework.ai.openai.api.OpenAiModerationApi;
import org.springframework.ai.openai.api.OpenAiModerationApi.OpenAiModerationRequest;
import org.springframework.ai.openai.api.OpenAiModerationApi.OpenAiModerationResponse;

// Create API client
var moderationApi = OpenAiModerationApi.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .build();

// Moderate single text
var request = new OpenAiModerationRequest(
    "I want to hurt someone",
    "text-moderation-latest"
);

var response = moderationApi.createModeration(request);
OpenAiModerationResponse moderationResponse = response.getBody();

moderationResponse.results().forEach(result -> {
    System.out.println("Flagged: " + result.flagged());

    var categories = result.categories();
    var scores = result.categoryScores();

    System.out.println("Violence: " + categories.violence() +
                       " (score: " + scores.violence() + ")");
    System.out.println("Hate: " + categories.hate() +
                       " (score: " + scores.hate() + ")");
});

// Moderate multiple texts
var multiRequest = new OpenAiModerationRequest(
    List.of("Text 1", "Text 2", "Text 3"),
    "text-moderation-stable"
);

var multiResponse = moderationApi.createModeration(multiRequest);

OpenAiFileApi

REST API client for file management operations.

/**
 * Low-level OpenAI API client for file operations
 */
public class OpenAiFileApi {
    /**
     * Create a builder for configuring OpenAiFileApi
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Upload a file to OpenAI
     * @param uploadRequest File upload request
     * @return ResponseEntity with FileObject
     */
    public ResponseEntity<FileObject> uploadFile(UploadFileRequest uploadRequest);

    /**
     * List files
     * @param listRequest List files request
     * @return ResponseEntity with FileObjectResponse
     */
    public ResponseEntity<FileObjectResponse> listFiles(ListFileRequest listRequest);

    /**
     * Retrieve file metadata
     * @param fileId File ID
     * @return ResponseEntity with FileObject
     */
    public ResponseEntity<FileObject> retrieveFile(String fileId);

    /**
     * Delete a file
     * @param fileId File ID
     * @return ResponseEntity with DeleteFileResponse
     */
    public ResponseEntity<DeleteFileResponse> deleteFile(String fileId);

    /**
     * Download file content
     * @param fileId File ID
     * @return ResponseEntity with file content as String
     */
    public ResponseEntity<String> retrieveFileContent(String fileId);
}

Builder Pattern:

public static class Builder {
    public Builder apiKey(String apiKey);
    public Builder baseUrl(String baseUrl);
    public Builder organizationId(String organizationId);
    public Builder projectId(String projectId);
    public Builder headers(Map<String, String> headers);
    public Builder restClient(RestClient restClient);
    public OpenAiFileApi build();
}

Usage Example:

import org.springframework.ai.openai.api.OpenAiFileApi;
import org.springframework.ai.openai.api.OpenAiFileApi.*;
import org.springframework.core.io.FileSystemResource;

// Create API client
var fileApi = OpenAiFileApi.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .build();

// Upload file
var uploadRequest = new UploadFileRequest(
    new FileSystemResource("training_data.jsonl"),
    Purpose.FINE_TUNE
);

var uploadResponse = fileApi.uploadFile(uploadRequest);
FileObject uploadedFile = uploadResponse.getBody();
System.out.println("File ID: " + uploadedFile.id());
System.out.println("Status: " + uploadedFile.status());

// List files
var listRequest = new ListFileRequest("fine-tune");
var listResponse = fileApi.listFiles(listRequest);
FileObjectResponse fileList = listResponse.getBody();

fileList.data().forEach(file -> {
    System.out.println("File: " + file.filename());
    System.out.println("  ID: " + file.id());
    System.out.println("  Size: " + file.bytes() + " bytes");
    System.out.println("  Purpose: " + file.purpose());
});

// Retrieve file metadata
var fileResponse = fileApi.retrieveFile(uploadedFile.id());
FileObject fileMetadata = fileResponse.getBody();
System.out.println("Filename: " + fileMetadata.filename());
System.out.println("Created at: " + fileMetadata.createdAt());

// Download file content
var contentResponse = fileApi.retrieveFileContent(uploadedFile.id());
String fileContent = contentResponse.getBody();
System.out.println("Content: " + fileContent);

// Delete file
var deleteResponse = fileApi.deleteFile(uploadedFile.id());
DeleteFileResponse deleteResult = deleteResponse.getBody();
System.out.println("Deleted: " + deleteResult.deleted());

Supporting Types

Chat Types

// Chat model enum
public enum ChatModel implements ChatModelDescription {
    // Reasoning models
    O4_MINI("o4-mini"),
    O3("o3"),
    O3_MINI("o3-mini"),
    O1("o1"),
    O1_MINI("o1-mini"),
    O1_PRO("o1-pro"),

    // Flagship models
    GPT_4_1("gpt-4.1"),
    GPT_5("gpt-5"),
    GPT_5_MINI("gpt-5-mini"),
    GPT_5_NANO("gpt-5-nano"),
    GPT_5_CHAT_LATEST("gpt-5-chat-latest"),
    GPT_4_O("gpt-4o"),
    CHATGPT_4_O_LATEST("chatgpt-4o-latest"),

    // Audio models
    GPT_4_O_AUDIO_PREVIEW("gpt-4o-audio-preview"),

    // Cost-optimized models
    GPT_4_1_MINI("gpt-4.1-mini"),
    GPT_4_1_NANO("gpt-4.1-nano"),
    GPT_4_O_MINI("gpt-4o-mini"),
    GPT_4_O_MINI_AUDIO_PREVIEW("gpt-4o-mini-audio-preview"),

    // Realtime models
    GPT_4O_REALTIME_PREVIEW("gpt-4o-realtime-preview"),
    GPT_4O_MINI_REALTIME_PREVIEW("gpt-4o-mini-realtime-preview"),

    // Older models
    GPT_4_TURBO("gpt-4-turbo"),
    GPT_4("gpt-4"),
    GPT_3_5_TURBO("gpt-3.5-turbo"),
    GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct"),

    // Search models
    GPT_4_O_SEARCH_PREVIEW("gpt-4o-search-preview"),
    GPT_4_O_MINI_SEARCH_PREVIEW("gpt-4o-mini-search-preview");

    public String getValue();
}

// Finish reason enum
public enum ChatCompletionFinishReason {
    STOP,           // Natural completion
    LENGTH,         // Max tokens reached
    TOOL_CALLS,     // Tool call requested
    CONTENT_FILTER, // Content filtered
    FUNCTION_CALL   // Function call (deprecated)
}

// Output modality enum
public enum OutputModality {
    TEXT,
    AUDIO
}

// Service tier enum
public enum ServiceTier {
    AUTO,
    DEFAULT
}

Embedding Types

// Embedding model enum
public enum EmbeddingModel {
    TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
    TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"),
    TEXT_EMBEDDING_3_LARGE("text-embedding-3-large");

    public String getValue();
}

Image Types

// Image model enum
public enum ImageModel {
    DALL_E_2("dall-e-2"),
    DALL_E_3("dall-e-3");

    public String getValue();
}

Audio Types

// TTS model enum
public enum TtsModel {
    TTS_1("tts-1"),
    TTS_1_HD("tts-1-hd"),
    GPT_4_O_MINI_TTS("gpt-4o-mini-tts"),
    GPT_4_O_TTS("gpt-4o-tts");

    public String getValue();
}

// Whisper model enum
public enum WhisperModel {
    WHISPER_1("whisper-1");

    public String getValue();
}

// Transcription model enum
public enum TranscriptionModels implements ChatModelDescription {
    WHISPER_1("whisper-1");

    public String getValue();
}

File Types

// File purpose enum
public enum Purpose {
    FINE_TUNE("fine-tune"),
    FINE_TUNE_RESULTS("fine-tune-results"),
    ASSISTANTS("assistants"),
    ASSISTANTS_OUTPUT("assistants_output"),
    BATCH("batch"),
    BATCH_OUTPUT("batch_output"),
    VISION("vision");

    public String getValue();
}

Common Patterns

Custom Headers

// Add custom headers to requests
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

var headers = new LinkedMultiValueMap<String, String>();
headers.add("X-Custom-Header", "value");
headers.add("X-Request-ID", UUID.randomUUID().toString());

var response = api.chatCompletionEntity(request, headers);

Error Handling

import org.springframework.ai.openai.api.common.OpenAiApiClientErrorException;

try {
    var response = api.chatCompletionEntity(request);
    // Process response
} catch (OpenAiApiClientErrorException e) {
    System.err.println("API error: " + e.getMessage());
    System.err.println("Status code: " + e.getStatusCode());
    // Handle specific error types
}

Custom Base URL (OpenAI-compatible endpoints)

// Use with Azure OpenAI or other OpenAI-compatible services
var api = OpenAiApi.builder()
    .apiKey(apiKey)
    .baseUrl("https://your-custom-endpoint.com")
    .build();

Organization and Project IDs

// For multi-tenant or team-based usage
var api = OpenAiApi.builder()
    .apiKey(apiKey)
    .organizationId("org-xxx")
    .projectId("proj-yyy")
    .build();

Custom RestClient

import org.springframework.web.client.RestClient;

// Configure custom RestClient for advanced scenarios
var restClient = RestClient.builder()
    .requestInterceptor((request, body, execution) -> {
        // Add custom logging, metrics, etc.
        return execution.execute(request, body);
    })
    .build();

var api = OpenAiApi.builder()
    .apiKey(apiKey)
    .restClient(restClient)
    .build();

Rate Limit Handling

// Extract rate limit information from response headers
var response = api.chatCompletionEntity(request);

var headers = response.getHeaders();
var requestsLimit = headers.getFirst("x-ratelimit-limit-requests");
var requestsRemaining = headers.getFirst("x-ratelimit-remaining-requests");
var tokensLimit = headers.getFirst("x-ratelimit-limit-tokens");
var tokensRemaining = headers.getFirst("x-ratelimit-remaining-tokens");

System.out.println("Requests remaining: " + requestsRemaining);
System.out.println("Tokens remaining: " + tokensRemaining);

Streaming with Backpressure

import reactor.core.scheduler.Schedulers;

// Handle streaming with backpressure control
api.chatCompletionStream(request)
    .publishOn(Schedulers.boundedElastic())
    .onBackpressureBuffer(100)
    .subscribe(
        chunk -> processChunk(chunk),
        error -> handleError(error),
        () -> System.out.println("Stream complete")
    );

API Constants

// Common API constants
public class OpenAiApiConstants {
    /**
     * Default OpenAI API base URL
     */
    public static final String DEFAULT_BASE_URL = "https://api.openai.com";

    /**
     * Provider name identifier
     */
    public static final String PROVIDER_NAME = "openai";
}

Best Practices

Use High-Level Models When Possible

The API clients are low-level and require more boilerplate. For most use cases, prefer:

  • OpenAiChatModel over OpenAiApi
  • OpenAiEmbeddingModel over OpenAiApi.embeddings()
  • OpenAiImageModel over OpenAiImageApi

Connection Pooling

Configure RestClient with proper connection pooling for production use:

import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;

var connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);

var httpClient = HttpClients.custom()
    .setConnectionManager(connectionManager)
    .build();

// Configure RestClient with custom HttpClient

Timeout Configuration

Set appropriate timeouts for API calls:

var restClient = RestClient.builder()
    .requestFactory(clientHttpRequestFactory -> {
        // Configure timeouts
        clientHttpRequestFactory.setConnectTimeout(Duration.ofSeconds(10));
        clientHttpRequestFactory.setReadTimeout(Duration.ofSeconds(60));
    })
    .build();

Retry Logic

Implement exponential backoff for transient failures:

import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;

var retryTemplate = RetryTemplate.builder()
    .maxAttempts(3)
    .exponentialBackoff(1000, 2.0, 10000)
    .retryOn(OpenAiApiClientErrorException.class)
    .build();

retryTemplate.execute(context -> {
    return api.chatCompletionEntity(request);
});

Secure API Keys

Never hardcode API keys. Use environment variables or secure configuration:

// Good - from environment
String apiKey = System.getenv("OPENAI_API_KEY");

// Good - from Spring configuration
@Value("${openai.api.key}")
private String apiKey;

// Bad - hardcoded
String apiKey = "sk-...";  // Never do this!

Monitor Usage

Track token usage and costs:

var response = api.chatCompletionEntity(request);
var usage = response.getBody().usage();

System.out.println("Prompt tokens: " + usage.promptTokens());
System.out.println("Completion tokens: " + usage.completionTokens());
System.out.println("Total tokens: " + usage.totalTokens());

// Calculate approximate cost based on model pricing

Handle Streaming Errors

Properly handle errors in streaming responses:

api.chatCompletionStream(request)
    .doOnError(error -> {
        System.err.println("Stream error: " + error.getMessage());
        // Implement fallback or retry logic
    })
    .retry(3)
    .subscribe(chunk -> processChunk(chunk));

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-openai

docs

index.md

tile.json