diff --git a/foundation-models/openai-wrapper/pom.xml b/foundation-models/openai-wrapper/pom.xml
new file mode 100644
index 000000000..1ab8455d0
--- /dev/null
+++ b/foundation-models/openai-wrapper/pom.xml
@@ -0,0 +1,120 @@
+
+
This is a thin wrapper around the official + * OpenAI Java SDK that handles: + * + *
The returned {@link OpenAIClient} is the official SDK client and can be used with the full + * OpenAI API surface (chat completions, embeddings, streaming, function calling, etc.) as + * documented in the OpenAI API documentation. + * + *
No additional abstraction layers are added. Request and response types are the official + * SDK types. + * + *
Example usage: + * + *
{@code
+ * import com.openai.models.chat.completions.ChatCompletionCreateParams;
+ * import com.openai.models.ChatModel;
+ * import com.openai.models.chat.completions.ChatCompletion;
+ *
+ * OpenAIClient client = OpenAiClientProvider.forModel(OpenAiModel.GPT_4O);
+ *
+ * ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
+ * .addUserMessage("Hello, world!")
+ * .model(ChatModel.GPT_4O)
+ * .build();
+ *
+ * ChatCompletion completion = client.chat().completions().create(params);
+ * }
+ *
+ * @see OpenAI Java SDK
+ * @see SAP AI Core
+ */
+@Slf4j
+public final class OpenAiClientProvider {
+
+ private OpenAiClientProvider() {}
+
+ /**
+ * Creates an {@link OpenAIClient} for the given model using the default {@link AiCoreService}.
+ *
+ * This resolves the deployment URL for the given model from SAP AI Core and configures the + * official OpenAI client with the correct base URL and authentication. + * + * @param model The {@link AiModel} to use (e.g. an {@code OpenAiModel} constant). + * @return A configured {@link OpenAIClient} ready to make requests via SAP AI Core. + */ + @Nonnull + public static OpenAIClient forModel(@Nonnull final AiModel model) { + return forModel(new AiCoreService(), model); + } + + /** + * Creates an {@link OpenAIClient} for the given model using the provided {@link AiCoreService}. + * + * @param aiCoreService The {@link AiCoreService} to use for deployment resolution. + * @param model The {@link AiModel} to use. + * @return A configured {@link OpenAIClient} ready to make requests via SAP AI Core. + */ + @Nonnull + public static OpenAIClient forModel( + @Nonnull final AiCoreService aiCoreService, @Nonnull final AiModel model) { + final HttpDestination destination = aiCoreService.getInferenceDestination().forModel(model); + return buildClient(destination); + } + + /** + * Creates an {@link OpenAIClient} using a custom {@link HttpDestination}. + * + *
This is useful for advanced scenarios where you need full control over the destination + * configuration. + * + * @param destination The destination to use. + * @return A configured {@link OpenAIClient}. + */ + @Nonnull + public static OpenAIClient withCustomDestination(@Nonnull final HttpDestination destination) { + return buildClient(destination); + } + + @Nonnull + private static OpenAIClient buildClient(@Nonnull final HttpDestination destination) { + final DefaultHttpDestination defaultDest = (DefaultHttpDestination) destination; + final String baseUrl = defaultDest.getUri().toString() + "v1/"; + log.debug("Building OpenAI client with base URL: {}", baseUrl); + + return OpenAIOkHttpClient.builder() + .baseUrl(baseUrl) + .credential(BearerTokenCredential.create(() -> extractBearerToken(defaultDest))) + .putHeader("AI-Resource-Group", getResourceGroupHeader(defaultDest)) + .build(); + } + + /** + * Extracts the bearer token from the destination's authorization headers. + * + *
The SAP Cloud SDK destination resolves the OAuth token from the service binding. This method
+ * extracts it so it can be passed to the OpenAI SDK's credential mechanism.
+ */
+ @Nonnull
+ static String extractBearerToken(@Nonnull final DefaultHttpDestination destination) {
+ final Collection Please note that the template of models provided in this class might be outdated. To check the
+ * latest availability of OpenAI models in AI Core, please refer to SAP Availability of Generative AI Models .
+ *
+ * @param name The name of the model.
+ * @param version The version of the model (optional).
+ */
+public record OpenAiModel(@Nonnull String name, @Nullable String version) implements AiModel {
+
+ /**
+ * internal [Azure OpenAI dall-e-3 model]
+ *
+ * @deprecated This model is deprecated on AI Core.
+ */
+ @Deprecated public static final OpenAiModel DALL_E_3 = new OpenAiModel("dall-e-3", null);
+
+ /**
+ * Azure OpenAI GPT-3.5 Turbo model
+ *
+ * @deprecated This model is deprecated on AI Core with a planned retirement on 2025-02-13. The
+ * suggested replacement model is {@link OpenAiModel#GPT_4O_MINI}.
+ */
+ @Deprecated public static final OpenAiModel GPT_35_TURBO = new OpenAiModel("gpt-35-turbo", null);
+
+ /**
+ * Azure OpenAI GPT-3.5 Turbo model
+ *
+ * @deprecated This model is deprecated on AI Core with a planned retirement on 2025-02-22. The
+ * suggested replacement model is {@link OpenAiModel#GPT_4O_MINI}.
+ */
+ @Deprecated
+ public static final OpenAiModel GPT_35_TURBO_1025 = new OpenAiModel("gpt-35-turbo-0125", null);
+
+ /**
+ * Azure OpenAI GPT-3.5 Turbo model
+ *
+ * @deprecated This model is deprecated on AI Core with a planned retirement on 2025-02-13. The
+ * suggested replacement model is {@link OpenAiModel#GPT_4O_MINI}.
+ */
+ @Deprecated
+ public static final OpenAiModel GPT_35_TURBO_16K = new OpenAiModel("gpt-35-turbo-16k", null);
+
+ /**
+ * Azure OpenAI GPT-4 model
+ *
+ * @deprecated This model is deprecated on AI Core with a planned retirement on 2025-09-01. The
+ * suggested replacement model is {@link OpenAiModel#GPT_4O} or {@link OpenAiModel#GPT_41}.
+ */
+ @Deprecated public static final OpenAiModel GPT_4 = new OpenAiModel("gpt-4", null);
+
+ /**
+ * Azure OpenAI GPT-4 model
+ *
+ * @deprecated This model is deprecated on AI Core.
+ */
+ @Deprecated public static final OpenAiModel GPT_4_32K = new OpenAiModel("gpt-4-32k", null);
+
+ /** Azure OpenAI GPT-4o model */
+ public static final OpenAiModel GPT_4O = new OpenAiModel("gpt-4o", null);
+
+ /**
+ * Azure OpenAI GPT-4o Mini model
+ *
+ * @deprecated This model is deprecated on AI Core. The suggested replacement model is {@link
+ * OpenAiModel#GPT_5_MINI}.
+ */
+ @Deprecated public static final OpenAiModel GPT_4O_MINI = new OpenAiModel("gpt-4o-mini", null);
+
+ /** Azure OpenAI GPT-o3 Mini model */
+ public static final OpenAiModel O3_MINI = new OpenAiModel("o3-mini", null);
+
+ /** Azure OpenAI GPT-o1 model */
+ public static final OpenAiModel O1 = new OpenAiModel("o1", null);
+
+ /** Azure OpenAI Text Embedding 3 Large model */
+ public static final OpenAiModel TEXT_EMBEDDING_3_LARGE =
+ new OpenAiModel("text-embedding-3-large", null);
+
+ /** Azure OpenAI Text Embedding 3 Small model */
+ public static final OpenAiModel TEXT_EMBEDDING_3_SMALL =
+ new OpenAiModel("text-embedding-3-small", null);
+
+ /** Azure OpenAI GPT-o4 Mini model */
+ public static final OpenAiModel O4_MINI = new OpenAiModel("o4-mini", null);
+
+ /** Azure OpenAI GPT-o3 model */
+ public static final OpenAiModel O3 = new OpenAiModel("o3", null);
+
+ /** Azure OpenAI GPT-4.1 model */
+ public static final OpenAiModel GPT_41 = new OpenAiModel("gpt-4.1", null);
+
+ /** Azure OpenAI GPT-4.1-nano model */
+ public static final OpenAiModel GPT_41_NANO = new OpenAiModel("gpt-4.1-nano", null);
+
+ /** Azure OpenAI GPT-4.1-mini model */
+ public static final OpenAiModel GPT_41_MINI = new OpenAiModel("gpt-4.1-mini", null);
+
+ /** Azure OpenAI GPT-5 model */
+ public static final OpenAiModel GPT_5 = new OpenAiModel("gpt-5", null);
+
+ /** Azure OpenAI GPT-5-mini model */
+ public static final OpenAiModel GPT_5_MINI = new OpenAiModel("gpt-5-mini", null);
+
+ /** Azure OpenAI GPT-5-nano model */
+ public static final OpenAiModel GPT_5_NANO = new OpenAiModel("gpt-5-nano", null);
+
+ /** Azure OpenAI GPT-5-nano model */
+ public static final OpenAiModel GPT_REALTIME = new OpenAiModel("gpt-realtime", null);
+
+ /** Azure OpenAI GPT-5.2 model */
+ public static final OpenAiModel GPT_52 = new OpenAiModel("gpt-5.2", null);
+
+ /**
+ * Azure OpenAI Text Embedding ADA 002 model
+ *
+ * @deprecated This model is deprecated on AI Core with a planned retirement on 2025-10-03. The
+ * suggested replacement models are {@link OpenAiModel#TEXT_EMBEDDING_3_SMALL} and {@link
+ * OpenAiModel#TEXT_EMBEDDING_3_LARGE}.
+ */
+ @Deprecated
+ public static final OpenAiModel TEXT_EMBEDDING_ADA_002 =
+ new OpenAiModel("text-embedding-ada-002", null);
+
+ /**
+ * Create a new instance of OpenAiModel with the provided version.
+ *
+ * @param version The version of the model.
+ * @return The new instance of OpenAiModel.
+ */
+ @Nonnull
+ public OpenAiModel withVersion(@Nonnull final String version) {
+ return new OpenAiModel(name, version);
+ }
+}
diff --git a/foundation-models/openai-wrapper/src/test/java/com/sap/ai/sdk/foundationmodels/openaiwrapper/OpenAiClientProviderTest.java b/foundation-models/openai-wrapper/src/test/java/com/sap/ai/sdk/foundationmodels/openaiwrapper/OpenAiClientProviderTest.java
new file mode 100644
index 000000000..589fa0623
--- /dev/null
+++ b/foundation-models/openai-wrapper/src/test/java/com/sap/ai/sdk/foundationmodels/openaiwrapper/OpenAiClientProviderTest.java
@@ -0,0 +1,93 @@
+package com.sap.ai.sdk.foundationmodels.openaiwrapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.openai.client.OpenAIClient;
+import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
+import com.sap.cloud.sdk.cloudplatform.connectivity.Header;
+import java.net.URI;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class OpenAiClientProviderTest {
+
+ @Test
+ void testWithCustomDestinationCreatesClient() {
+ final var destination = mock(DefaultHttpDestination.class);
+
+ doReturn(URI.create("https://api.ai.sap/v2/inference/deployments/d123/"))
+ .when(destination)
+ .getUri();
+ doReturn(
+ List.of(
+ new Header("Authorization", "Bearer test-token-123"),
+ new Header("AI-Resource-Group", "my-rg")))
+ .when(destination)
+ .getHeaders(any(URI.class));
+ doReturn(destination).when(destination).asHttp();
+
+ final OpenAIClient client = OpenAiClientProvider.withCustomDestination(destination);
+
+ assertThat(client).isNotNull();
+ }
+
+ @Test
+ void testExtractBearerToken() {
+ final var destination = mock(DefaultHttpDestination.class);
+
+ doReturn(URI.create("https://api.ai.sap/")).when(destination).getUri();
+ doReturn(List.of(new Header("Authorization", "Bearer my-secret-token")))
+ .when(destination)
+ .getHeaders(any(URI.class));
+
+ final String token = OpenAiClientProvider.extractBearerToken(destination);
+
+ assertThat(token).isEqualTo("my-secret-token");
+ }
+
+ @Test
+ void testExtractBearerTokenThrowsWhenMissing() {
+ final var destination = mock(DefaultHttpDestination.class);
+
+ doReturn(URI.create("https://api.ai.sap/")).when(destination).getUri();
+ doReturn(List.of(new Header("AI-Resource-Group", "default")))
+ .when(destination)
+ .getHeaders(any(URI.class));
+
+ assertThatThrownBy(() -> OpenAiClientProvider.extractBearerToken(destination))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("No Bearer token found");
+ }
+
+ @Test
+ void testGetResourceGroupHeader() {
+ final var destination = mock(DefaultHttpDestination.class);
+
+ doReturn(URI.create("https://api.ai.sap/")).when(destination).getUri();
+ doReturn(List.of(new Header("AI-Resource-Group", "custom-rg")))
+ .when(destination)
+ .getHeaders(any(URI.class));
+
+ final String rg = OpenAiClientProvider.getResourceGroupHeader(destination);
+
+ assertThat(rg).isEqualTo("custom-rg");
+ }
+
+ @Test
+ void testGetResourceGroupHeaderDefaultsToDefault() {
+ final var destination = mock(DefaultHttpDestination.class);
+
+ doReturn(URI.create("https://api.ai.sap/")).when(destination).getUri();
+ doReturn(List.of(new Header("Authorization", "Bearer token")))
+ .when(destination)
+ .getHeaders(any(URI.class));
+
+ final String rg = OpenAiClientProvider.getResourceGroupHeader(destination);
+
+ assertThat(rg).isEqualTo("default");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 8baa46508..fa97c9089 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
Uses {@link OpenAiClientProvider} to get an {@link OpenAIClient} backed by SAP AI Core, then
+ * interacts with the official SDK types directly.
+ */
+@Service
+@Slf4j
+public class OpenAiWrapperService {
+
+ /**
+ * Chat completion request using the official OpenAI Java SDK via SAP AI Core.
+ *
+ * @return the chat completion response
+ */
+ @Nonnull
+ public ChatCompletion chatCompletion() {
+ final OpenAIClient client = OpenAiClientProvider.forModel(OpenAiModel.GPT_5);
+
+ final ChatCompletionCreateParams params =
+ ChatCompletionCreateParams.builder()
+ .model(ChatModel.GPT_5)
+ .addSystemMessage("Be a helpful assistant")
+ .addUserMessage("Hello World! Why is this phrase so famous?")
+ .build();
+
+ return client.chat().completions().create(params);
+ }
+
+ /**
+ * Chat completion request with streaming disabled, using structured output.
+ *
+ * @param userMessage the user message to send
+ * @return the chat completion response
+ */
+ @Nonnull
+ public ChatCompletion chatCompletion(@Nonnull final String userMessage) {
+ final OpenAIClient client = OpenAiClientProvider.forModel(OpenAiModel.GPT_5);
+
+ final ChatCompletionCreateParams params =
+ ChatCompletionCreateParams.builder()
+ .model(ChatModel.GPT_5)
+ .addUserMessage(userMessage)
+ .build();
+
+ return client.chat().completions().create(params);
+ }
+
+ /**
+ * Chat completion request using the official OpenAI Java SDK via SAP AI Core, filtering by
+ * resource group.
+ *
+ * @param resourceGroup the resource group to use
+ * @param prompt the prompt to send to the assistant
+ * @return the chat completion response
+ */
+ @Nonnull
+ public ChatCompletion chatCompletionWithResource(
+ @Nonnull final String resourceGroup, @Nonnull final String prompt) {
+
+ final var destination =
+ new AiCoreService().getInferenceDestination(resourceGroup).forModel(OpenAiModel.GPT_5);
+
+ final OpenAIClient client = OpenAiClientProvider.withCustomDestination(destination);
+
+ final ChatCompletionCreateParams params =
+ ChatCompletionCreateParams.builder().model(ChatModel.GPT_5).addUserMessage(prompt).build();
+
+ return client.chat().completions().create(params);
+ }
+}