Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
6f303a3
add realtime text to speech client, WIP
vladimir-a-sap Jul 7, 2026
ecd953e
polish minimap text-to-speech realtime poc
vladimir-a-sap Jul 7, 2026
71d4a4c
add incomplete interim packets buffering and glueing
vladimir-a-sap Jul 7, 2026
232725d
remove obsolete debug constants
vladimir-a-sap Jul 7, 2026
6b872d4
Formatting
bot-sdk-js Jul 7, 2026
54b06a5
add speech to speech realtime support and text to speech example
vladimir-a-sap Jul 10, 2026
df2098a
merge changes from upstream
vladimir-a-sap Jul 10, 2026
80a5d3e
add speech to speech demo scenario
vladimir-a-sap Jul 10, 2026
d6ad031
add smoke test for realtime api and configurability
vladimir-a-sap Jul 13, 2026
2ed77e0
rework sample application so it no longer use deprecated browser micr…
vladimir-a-sap Jul 13, 2026
c212c1e
make realtime implementation package local
vladimir-a-sap Jul 13, 2026
d144a3a
Merge branch 'main' into feat/poc-openai-realtime-api
vladimir-a-sap Jul 13, 2026
03d1e70
Formatting
bot-sdk-js Jul 13, 2026
5415795
address pr feedback, add tests
vladimir-a-sap Jul 17, 2026
a0cd474
Formatting
bot-sdk-js Jul 17, 2026
25d673b
Merge branch 'main' into feat/poc-openai-realtime-api
vladimir-a-sap Jul 17, 2026
8dc8ae6
remove duplicated dependency
vladimir-a-sap Jul 17, 2026
4240910
improve code style
vladimir-a-sap Jul 17, 2026
0603218
improve code style
vladimir-a-sap Jul 17, 2026
fcebe1e
Formatting
bot-sdk-js Jul 17, 2026
9a6d251
extract common realtime api functionality to a superclass
vladimir-a-sap Jul 17, 2026
de69739
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 17, 2026
0f2cd62
Formatting
bot-sdk-js Jul 17, 2026
6082105
fix misleading javadocs
vladimir-a-sap Jul 17, 2026
0c2038d
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 17, 2026
537203d
Formatting
bot-sdk-js Jul 17, 2026
7a5220e
replace debug output in test with actual assertions
vladimir-a-sap Jul 17, 2026
e7d844d
fix annotations from invalid package
vladimir-a-sap Jul 20, 2026
7c6dced
Formatting
bot-sdk-js Jul 20, 2026
eb8a451
Merge branch 'main' into feat/poc-openai-realtime-api
vladimir-a-sap Jul 20, 2026
671f960
add missing javadoc comments
vladimir-a-sap Jul 20, 2026
625f72a
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 20, 2026
b4ba471
Formatting
bot-sdk-js Jul 20, 2026
85120c1
add missing javadocs
vladimir-a-sap Jul 23, 2026
fa4bb64
Formatting
bot-sdk-js Jul 23, 2026
e4e04b3
fix style issues
vladimir-a-sap Jul 23, 2026
473a3e2
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 23, 2026
94a0eef
fix dependency issue
vladimir-a-sap Jul 23, 2026
5fbe0da
fix dependency issue
vladimir-a-sap Jul 23, 2026
50392d6
fix dependency issue
vladimir-a-sap Jul 23, 2026
86cd068
fix dependency issue
vladimir-a-sap Jul 23, 2026
e95a5df
fix dependency issue
vladimir-a-sap Jul 23, 2026
ac27bb7
fix dependency issue
vladimir-a-sap Jul 23, 2026
e38a42a
add sample app docs and hints
vladimir-a-sap Jul 23, 2026
4b789d3
improve dependency declarations
vladimir-a-sap Jul 23, 2026
c4f597b
improve code and docs style
vladimir-a-sap Jul 23, 2026
611c0eb
Formatting
bot-sdk-js Jul 23, 2026
b90ab43
use open-ai-core sub dependency as nothing from open-ai dep is explic…
vladimir-a-sap Jul 24, 2026
4ad44ff
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
dc53160
address code style pmd issues
vladimir-a-sap Jul 24, 2026
c6a0e94
Formatting
bot-sdk-js Jul 24, 2026
1400efc
address code style pmd issues
vladimir-a-sap Jul 24, 2026
86ab775
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
51b35a0
Formatting
bot-sdk-js Jul 24, 2026
f92d327
reduce default response buffer size
vladimir-a-sap Jul 24, 2026
a0e5eef
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
ff3007b
move realtime api classed to a dedicated package
vladimir-a-sap Jul 24, 2026
2060668
Formatting
bot-sdk-js Jul 24, 2026
1e74df5
fix pmd issues
vladimir-a-sap Jul 24, 2026
fc6bd75
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
0fb2646
address spotbugs plugin issues
vladimir-a-sap Jul 24, 2026
54b151d
Formatting
bot-sdk-js Jul 24, 2026
f6698c5
address dependency issue
vladimir-a-sap Jul 24, 2026
f36c8c7
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
40971d4
fix checkstyle issues
vladimir-a-sap Jul 24, 2026
7f656aa
Formatting
bot-sdk-js Jul 24, 2026
7eed889
fix checkstyle issues
vladimir-a-sap Jul 24, 2026
f7def36
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
a3d4641
Formatting
bot-sdk-js Jul 24, 2026
bd8cb90
fix checkstyle issues
vladimir-a-sap Jul 24, 2026
4d7cf2c
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
7e008bb
Formatting
bot-sdk-js Jul 24, 2026
b36ed95
fix checkstyle issues
vladimir-a-sap Jul 24, 2026
143c01f
Merge branch 'feat/poc-openai-realtime-api' of github.com:SAP/ai-sdk-…
vladimir-a-sap Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions core/src/main/java/com/sap/ai/sdk/core/RealtimeParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sap.ai.sdk.core;

import javax.annotation.Nonnull;

/** Represents possible configuration params of realtime client */
public interface RealtimeParam {
/** Represents configurable options */
enum SpeechOutputParamName {
/** Voice name to use to produce sound */
VOICE,
/**
* How model will recognize that it is its turn to respond (e.g. explicitly asked, automatically
* detected)
*/
TURN_DETECTION,
}

/**
* Returns param name
*
* @return name
*/
@Nonnull
SpeechOutputParamName getParamName();

/**
* Returns string value representation of the param
*
* @return string value
*/
@Nonnull
String getValueAsString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.sap.ai.sdk.core;

import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/** Allows to configure turn detection (how model responds). */
public final class RealtimeParamTurnDetection implements RealtimeParam {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are realtime classes in the core module.
If they cannot be used outside of the realtime client they belong in the same folder as the realtime client


/** Model tries to recognize if/when it should respond automatically */
public static final RealtimeParamTurnDetection BY_MODEL_AUTO =
new RealtimeParamTurnDetection("BY_MODEL_AUTO");

/**
* Each call to the provided realtime client is considered a turn (eager explicit turn detection).
* Less convenient than the automatic option but may give lower latency in some cases (model does
* not need to perform additional turn detection analysis).
*/
public static final RealtimeParamTurnDetection EACH_CALL_IS_A_TURN =
new RealtimeParamTurnDetection("EACH_CALL_IS_A_TURN");

private final String turnDetectionKind;

private RealtimeParamTurnDetection(final String turnDetectionKind) {
this.turnDetectionKind = turnDetectionKind;
}

@Override
public @Nonnull SpeechOutputParamName getParamName() {
return SpeechOutputParamName.TURN_DETECTION;
}

@Override
public @Nonnull String getValueAsString() {
return turnDetectionKind;
}

@Override
public boolean equals(@Nullable final Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
final RealtimeParamTurnDetection that = (RealtimeParamTurnDetection) o;
return Objects.equals(turnDetectionKind, that.turnDetectionKind);
}

@Override
public int hashCode() {
return Objects.hashCode(turnDetectionKind);
}
}
57 changes: 57 additions & 0 deletions core/src/main/java/com/sap/ai/sdk/core/RealtimeParamVoice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sap.ai.sdk.core;

import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/** Allows to configure model output voice */
public final class RealtimeParamVoice implements RealtimeParam {

/** Standard voice 1 */
public static final RealtimeParamVoice DEFAULT_1 = new RealtimeParamVoice("DEFAULT_1");

/** Standard voice 2 */
public static final RealtimeParamVoice DEFAULT_2 = new RealtimeParamVoice("DEFAULT_2");

private final String voice;

private RealtimeParamVoice(@Nonnull final String voice) {
this.voice = voice;
}

/**
* Allows to configure raw voice name as named by model provider. Unsafe because SDK cannot verify
* in advance if the provided voice name is correct and supported by the chosen model and use case
*
* @param voiceName as named by model provider
* @return typed voice client configuration param
*/
@Nonnull
public static RealtimeParamVoice unsafeWithExplicitVoice(@Nonnull final String voiceName) {
return new RealtimeParamVoice(voiceName);
}

@Override
public @Nonnull SpeechOutputParamName getParamName() {
return SpeechOutputParamName.VOICE;
}

@Override
public @Nonnull String getValueAsString() {
return voice;
}

@Override
public boolean equals(@Nullable final Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
final RealtimeParamVoice that = (RealtimeParamVoice) o;
return Objects.equals(voice, that.voice);
}

@Override
public int hashCode() {
return Objects.hashCode(voice);
}
}
1 change: 0 additions & 1 deletion foundation-models/openai/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java-core</artifactId>
<optional>true</optional>
</dependency>
<!-- scope "provided" -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.sap.ai.sdk.foundationmodels.openai;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Minor) We had a discussion that it could be great to put every class in its own realtime folder to make it easier to maintain and not make the same mistake that we did in the orchestration package that became very crowded.
package com.sap.ai.sdk.foundationmodels.openai.realtime

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed/forgot the part about the dedicated sub-package

thanks, now moved!


/**
* Functional interface representing audio input channel (audio data consumer)
*
* <p>Should be closed by application (try-with-resources) when not needed anymore
*/
public interface AudioInputChannel extends AutoCloseable {

/**
* This method is sequentially invoked by audio data provider to supply implementer (consumer)
* with the audio data. Exact audio format (encoding, sampling rate, etc.) depends on the usage
* context
*
* @param rawBytesChunk binary data in the depending on the use case format
*/
void inputAudio(byte[] rawBytesChunk);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sap.ai.sdk.foundationmodels.openai;

/** Functional interface representing audio output channel (audio data consumer) */
public interface AudioOutputChannel {

/**
* This method is sequentially invoked by audio data provider to supply implementer (consumer)
* with the audio data. Exact audio format (encoding, sampling rate, etc.) depends on the usage
* context
*
* @param rawBytesChunk binary data in the depending on the use case format
* @param isLast true if this call logically concludes previous and this passed bytes data into a
* single logical entity (e.g. gets called at the end when all byte parts of a single message
* get passed)
*/
void outputAudio(byte[] rawBytesChunk, boolean isLast);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage;
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput;
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters;
import com.sap.ai.sdk.foundationmodels.openai.realtime.OpenAiRealtimeClient;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
Expand Down Expand Up @@ -76,6 +77,18 @@ public static OpenAiClient forModel(@Nonnull final OpenAiModel foundationModel)
return client.withApiVersion(DEFAULT_API_VERSION);
}

/**
* Creates and configures OpenAI Realtime API client
*
* @return created client
*/
@Beta
@Nonnull
public static OpenAiRealtimeClient realtimeClient() {
final var withResolvedDestination = OpenAiClient.forModel(OpenAiModel.GPT_REALTIME);
return new OpenAiRealtimeClient(withResolvedDestination.destination);
}

/**
* Create a new OpenAI client targeting the specified API version.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public record OpenAiModel(@Nonnull String name, @Nullable String version) implem
/** 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 */
/** Azure OpenAI GPT-realtime model */
public static final OpenAiModel GPT_REALTIME = new OpenAiModel("gpt-realtime", null);

/** Azure OpenAI GPT-5.2 model */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sap.ai.sdk.foundationmodels.openai;

import javax.annotation.Nonnull;

/**
* Allows to input (send) text to the open channel, must be closed when not needed anymore (e.g.
* try-with-resources)
*/
public interface TextInputChannel extends AutoCloseable {

/**
* Sends input text
*
* @param text text to send
*/
void sendText(@Nonnull final String text);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.sap.ai.sdk.foundationmodels.openai;

import javax.annotation.Nonnull;

/** Functional interface representing text output channel (text data consumer) */
public interface TextOutputChannel {

/**
* This method is sequentially invoked by text data provider to supply implementer (consumer) with
* the text data.
*
* @param textChunk chunk of the text (possibly partial content)
* @param isLast true if this call logically concludes previous and this passed text data into a
* single logical entity (e.g. gets called at the end when all parts of a single message get
* passed)
*/
void outputText(@Nonnull final CharSequence textChunk, final boolean isLast);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.sap.ai.sdk.foundationmodels.openai.realtime;

import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;

@Slf4j
class BufferedWebSocketListener implements WebSocket.Listener {

private final Consumer<WebSocket> onOpen;
private final BiConsumer<WebSocket, CharSequence> onText;
private final StringBuilder buffer;

BufferedWebSocketListener(
@Nonnull final Consumer<WebSocket> onOpen,
@Nonnull final BiConsumer<WebSocket, CharSequence> onText) {
this.onOpen = onOpen;
this.onText = onText;
this.buffer = new StringBuilder(128 * 1024);
}

@Override
public void onOpen(@Nonnull final WebSocket webSocket) {
this.onOpen.accept(webSocket);
webSocket.request(1);
}

@Override
@Nonnull
public CompletionStage<?> onText(
final @Nonnull WebSocket webSocket, final @Nonnull CharSequence data, final boolean isLast) {
buffer.append(data);
webSocket.request(1);
if (isLast) {
final var completeMessage = buffer.toString();
buffer.setLength(0);
this.onText.accept(webSocket, completeMessage);
}

return CompletableFuture.completedStage(null);
}

@Override
public void onError(@Nonnull final WebSocket webSocket, @Nonnull final Throwable error) {
log.error("Websocket error occurred during realtime communication", error);
}

@Override
@Nonnull
public CompletionStage<?> onBinary(
@Nonnull final WebSocket webSocket, @Nonnull final ByteBuffer data, final boolean isLast) {
log.warn("Received unexpected binary bytes for WebSocket connection");
return CompletableFuture.completedStage(null);
}
}
Loading
Loading