Skip to content

feat: OpenAI realtime api#935

Open
vladimir-a-sap wants to merge 74 commits into
mainfrom
feat/poc-openai-realtime-api
Open

feat: OpenAI realtime api#935
vladimir-a-sap wants to merge 74 commits into
mainfrom
feat/poc-openai-realtime-api

Conversation

@vladimir-a-sap

@vladimir-a-sap vladimir-a-sap commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Context

AI/ai-sdk-java-backlog#386.

Adds OpenAI realtime API support

Feature scope:

  • Implemented destination selection, connection and authorization logic
  • Added heartbeat functionality to correctly manage conversation connections without unintended TCP resets
  • Implemented SDK Realtime API operations
    • Text to Speech
    • Speech to Speech
    • Speech to Text (requires additional support on the core/orchestration end)
  • Added sample app interactive examples
    • Text to Speech
    • Speech to Speech
    • Speech to Text (a blocker to be resolved on the BE side)
  • Added smoke test
    • Text to Speech
    • Speech to Speech
    • Speech to Text (requires additional support on the core/orchestration end)

Definition of Done

  • Functionality scope stated & covered
  • Tests cover the scope above
  • Error handling created / updated & covered by the tests above
  • Aligned changes with the JavaScript SDK
  • Documentation updated
  • Release notes updated

Comment thread sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/Application.java Outdated
Comment thread sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/Application.java Outdated
Comment thread sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/Application.java Outdated
Comment thread orchestration/pom.xml Outdated
Comment thread orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java Outdated

@CharlesDuboisSAP CharlesDuboisSAP left a comment

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.

What do we do about the previous realtime PR? Is it still valid? Why is it abandonned?

@vladimir-a-sap

vladimir-a-sap commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

What do we do about the previous realtime PR? Is it still valid? Why is it abandonned?

My current understanding is that the mentioned PR cannot be completed to a SDK realtime solution. Here is the context and outcomes of my research why I think so:

There are three ways to implement realtime API with OpenAPI:

  • WebRTC
  • Websockets
  • SIP (telephony)

The previous PR implements SIP - it is a way for client and server to communicate about a connection in order to pass data, but not to establish connection or pass data in fact. The data layer should be implemented separately from the SIP protocol.

OpenAPI SIP protocol by design works with client-side web hooks as a mean to pass data and accept incoming connections (calls). Outgoing calls (client initiated) are not seem to be possible at all using solely SIP API.

We cannot expect SDK environment to have public http endpoints accessible from the internet to expose hooks. AI Core does not implement hook functionality either AFAIK.

Given that WebRTC is difficult to support on the BE and, AFAIK, our backend only supports Websockets at the moment, Websockets seem to be the only option we have.

@vladimir-a-sap vladimir-a-sap changed the title DRAFT: Feat/poc OpenAI realtime api Feat/poc OpenAI realtime api Jul 13, 2026
@vladimir-a-sap vladimir-a-sap changed the title Feat/poc OpenAI realtime api feat: OpenAI realtime api Jul 13, 2026
@vladimir-a-sap
vladimir-a-sap marked this pull request as ready for review July 13, 2026 16:48

@Jonas-Isr Jonas-Isr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The design is very cool! Thanks a lot for the detailed investigation and work :) I am reviewing this as if it would be the beta release already because I think without too much effort we turn this into a releasable state.

The two points below (and many of my comments) concern code conventions in the team.

General points:

  • Please add @Beta to all newly introduced public API. This way we have a grace period to change the API if we overlooked something in the beginning. We periodically re-evaluate and remove these annotations later on.
  • Please don’t put hand written classes into any of the model/ packages. These are only for generated code. If possible, put the SpeechOutputParam interface and the 2 classes implementing it into the open package.

Comment thread core/src/main/java/com/sap/ai/sdk/core/model/SpeechOutputParam.java Outdated
Comment thread core/src/main/java/com/sap/ai/sdk/core/model/SpeechOutputParamTurnDetection.java Outdated
Comment thread sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/WebsocketConfig.java Outdated
Comment thread sample-code/spring-app/src/main/resources/static/index.html Outdated
@vladimir-a-sap vladimir-a-sap mentioned this pull request Jul 17, 2026
9 tasks
@vladimir-a-sap

Copy link
Copy Markdown
Contributor Author

The design is very cool! Thanks a lot for the detailed investigation and work :) I am reviewing this as if it would be the beta release already because I think without too much effort we turn this into a releasable state.

The two points below (and many of my comments) concern code conventions in the team.

General points:

  • Please add @Beta to all newly introduced public API. This way we have a grace period to change the API if we overlooked something in the beginning. We periodically re-evaluate and remove these annotations later on.
  • Please don’t put hand written classes into any of the model/ packages. These are only for generated code. If possible, put the SpeechOutputParam interface and the 2 classes implementing it into the open package.

addressed both these points in addition to your comments in the PR

Comment thread foundation-models/openai/pom.xml Outdated
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
</dependency>

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.

Users of the other OpenAI clients should not have to import this dependency.
This should be optional like the existing openai-java-core that is already in this pom.

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

@@ -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!

@Jonas-Isr Jonas-Isr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ability to try it out in the sample app is really cool!

Besides Charles' points and the smaller comments below my main concern is test coverage. Can we maybe add some unit tests covering WSOpenAiRealtimeClient, ToAudioRealtimeClient, and the URL construction in OpenAiRealtimeClient.getRealtimeEndpoint()?

Comment on lines +56 to +60
final var extraHeaders = destination.asHttp().getHeaders();
final var headers = new HashMap<String, String>(extraHeaders.size() + 1);
for (Header header : extraHeaders) {
headers.put(header.getName(), header.getValue());
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Minor)

Would maybe be a bit cleaner to put this in a private method as it is used also in speechToSpeech().

try {
ws = this.ws.join();
} catch (final CompletionException e) {
throw new ClientException("failed to establish web socket connection", e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Very Minor)

Also for other exceptions.

Suggested change
throw new ClientException("failed to establish web socket connection", e);
throw new ClientException("Failed to establish web socket connection", e);

try {
ws.sendText(JACKSON.writeValueAsString(ResponseCreateEvent.builder().build()), true);
} catch (final JsonProcessingException e) {
throw new RuntimeException("Failed to serialize ask for response", e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Question)

Shouldn't this be a ClientException like the others?

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.

addressed


private static final int MAX_DATA_CHUNK_SIZE_BYTES = 8192;

private static final String TASK = "";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Question)

I don't understand the role of TASK here. Is it always empty? Or can it be set by a user?

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Question)

How many are we expecting users to create of these? Will this size be too large?

@vladimir-a-sap vladimir-a-sap Jul 24, 2026

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.

Context
This is now used for responses. There is at least three levels of data segmentation handled by software (not going too deep into networking):

  1. TCP level - packet partitioning/glueing - handled by operation system
  2. WebSocket level - partitioning made by the server, packet glueing is done by this buffer
  3. User application level data partitioning (server sends sound.delta messages in the web socket channel) - managed by the application using the realtime-client/SDK (either to wait and consume fully or play deltas/increments)

It didn't make sense to expose level 2 glueing logic to the app since partial websocket messages would be broken data-wise (not full json) and wouldn't provide any value, but would complicate the SDK exposed API

Response to the question
Users are not intended to create these buffers directly (since package local), but implicitly these buffers will be created once for every open realtime session. The total number of buffers created will depend on how many open realtime conversations a user would want at the same time. This can't normally be above ~64.5k per network interface and limited by the number of available TCP sockets.

I experimented with different requests/responses and most model audio responses were inside 1m range with short responses being typically 200-300k (20-30% of the allocated hardcoded capacity).

This might be slightly over for text responses, but two our use cases (Text-to-Speech and Speech-to-Speech) now output audio along with supplementary text.

We may later add ways to configure this if it will be needed

UPDATE:
I experimented a bit more and it seems that in practice this buffer size can be significantly reduced indeed. I set 128k instead of the previous 1m for now.

* OpenAI client implementation of Realtime API. Abstracts technical implementation, transport and
* threading and exposes business-level operations (high level interface)
*/
public class OpenAiRealtimeClient {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Major)

@Beta missing on a new public class. Same for AudioInputChannel, AudioOutputChannel, TextInputChannel, TextOutputChannel as far as I see.

Comment on lines +27 to +36
for (RealtimeParam param : params) {
if (param.getParamName() == RealtimeParam.SpeechOutputParamName.TURN_DETECTION) {
if (RealtimeParamTurnDetection.EACH_CALL_IS_A_TURN.equals(param)) {
turnDetectionEager = true;
} else if (RealtimeParamTurnDetection.BY_MODEL_AUTO.equals(param)) {
turnDetectionEager = false;
}
}
}
this.eagerTurnDetection = turnDetectionEager;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Minor)

As far as I can see this is the same as is done in the constructor of the SpeechToSpeechRealtimeClient. Maybe it makes sense to put it into ToAudioRealtimeClient as utility method?

<h2>⏰ Realtime API</h2>
</div>
Realtime API allows for various real time interactions <a
href="https://todo.todo"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Minor)

Do we have "official" docs somewhere that we can link here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants