Skip to content

Commit 49d530e

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/main' into chore/openai-embedding-float
2 parents cb10d25 + 6948a05 commit 49d530e

File tree

50 files changed

+8925
-70
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+8925
-70
lines changed

.github/workflows/pr-lint.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
OpenAI
2424
Orchestration
2525
DevOps
26+
PromptRegistry
2627
headerPattern: '^(\w.+): (?:\[(\w.+)\] )?(.+)$'
2728
headerPatternCorrespondence: type, scope, subject
2829
# for available types, check:

.github/workflows/spec-update.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- core
1212
- grounding
1313
- orchestration
14+
- prompt-registry
1415
default: orchestration
1516
file-ref:
1617
description: "Branch or tag to checkout the spec file from"
@@ -78,6 +79,10 @@ jobs:
7879
API_URL="$API_BASE_URL/AI/llm-orchestration/contents/src/spec/api.yaml?ref=$REF"
7980
FILE_PATH='orchestration/src/main/resources/spec/orchestration.yaml'
8081
;;
82+
prompt-registry)
83+
API_URL="$API_BASE_URL/AI/prompt-registry/contents/src/spec/prompt-registry.yaml?ref=$REF"
84+
FILE_PATH='prompt-registry/src/main/resources/spec/prompt-registry.yaml'
85+
;;
8186
esac
8287
8388
echo "Downloading $CHOICE specification file from $API_URL ..."
@@ -187,4 +192,4 @@ jobs:
187192
echo "| Client Testing | ${{ steps.compile.outputs.test_result == 'success' && '✅' || steps.compile.outputs.test_result == 'skipped' && '⏩' || '❌' }} ${{ steps.compile.outputs.test_result }}" >> $GITHUB_STEP_SUMMARY
188193
echo "| Branch Creation | ${{ steps.push.outcome == 'success' && '✅ [Branch Link]($DIFF_URL)' || '❌ failure' }}" >> $GITHUB_STEP_SUMMARY
189194
echo "| Pull Request Creation | ${{ env.CREATE_PR == 'false' && '⏩ skipped' || '' }}${{ env.CREATE_PR == 'true' && steps.push.outcome == 'success' && '✅ [PR Link]($PR_URL)' || '' }}" >> $GITHUB_STEP_SUMMARY
190-
fi
195+
fi

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiChatCompletionRequest.java

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import com.google.common.annotations.Beta;
44
import com.google.common.collect.Lists;
5-
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionNamedToolChoice;
6-
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionNamedToolChoiceFunction;
75
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionStreamOptions;
86
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionTool;
97
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionToolChoiceOption;
@@ -253,48 +251,24 @@ public OpenAiChatCompletionRequest withLogprobs(@Nonnull final Boolean logprobs)
253251
}
254252

255253
/**
256-
* Only message generation will be performed without calling any tool.
254+
* Define the model behavior towards calling functions.
257255
*
258-
* @return the current OpenAiChatCompletionRequest instance.
259-
*/
260-
@Nonnull
261-
public OpenAiChatCompletionRequest withToolChoiceNone() {
262-
return this.withToolChoice(ChatCompletionToolChoiceOption.create("none"));
263-
}
264-
265-
/**
266-
* The model may decide whether to call a (one or more) tool.
267-
*
268-
* @return the current OpenAiChatCompletionRequest instance.
269-
*/
270-
@Nonnull
271-
public OpenAiChatCompletionRequest withToolChoiceOptional() {
272-
return this.withToolChoice(ChatCompletionToolChoiceOption.create("auto"));
273-
}
274-
275-
/**
276-
* The model must call one or more tools as part of its processing.
256+
* <p>Example:
277257
*
278-
* @return the current OpenAiChatCompletionRequest instance.
279-
*/
280-
@Nonnull
281-
public OpenAiChatCompletionRequest withToolChoiceRequired() {
282-
return this.withToolChoice(ChatCompletionToolChoiceOption.create("required"));
283-
}
284-
285-
/**
286-
* The model must call the function specified by {@code functionName}.
258+
* <ul>
259+
* <li><code>.withToolChoice(OpenAiToolChoice.NONE)</code>
260+
* <li><code>.withToolChoice(OpenAiToolChoice.OPTIONAL)</code>
261+
* <li><code>.withToolChoice(OpenAiToolChoice.REQUIRED)</code>
262+
* <li><code>.withToolChoice(OpenAiToolChoice.function("fibonacci")</code>
263+
* </ul>
287264
*
288-
* @param functionName the name of the function that must be called.
265+
* @param choice the generic tool choice.
289266
* @return the current OpenAiChatCompletionRequest instance.
290267
*/
291268
@Nonnull
292-
public OpenAiChatCompletionRequest withToolChoiceFunction(@Nonnull final String functionName) {
293-
return this.withToolChoice(
294-
ChatCompletionToolChoiceOption.create(
295-
new ChatCompletionNamedToolChoice()
296-
.type(ChatCompletionNamedToolChoice.TypeEnum.FUNCTION)
297-
.function(new ChatCompletionNamedToolChoiceFunction().name(functionName))));
269+
@Tolerate
270+
public OpenAiChatCompletionRequest withToolChoice(@Nonnull final OpenAiToolChoice choice) {
271+
return this.withToolChoice(choice.toolChoice);
298272
}
299273

300274
/**

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,29 @@ private void warnIfUnsupportedUsage() {
341341
}
342342

343343
/**
344-
* Get a vector representation of a given request with input that can be easily consumed by
345-
* machine learning models and algorithms.
344+
* Get a vector representation of a given request that can be easily consumed by machine learning
345+
* models and algorithms using high-level request object.
346+
*
347+
* @param request the request with input text.
348+
* @return the embedding response convenience object
349+
* @throws OpenAiClientException if the request fails
350+
* @see #embedding(EmbeddingsCreateRequest) for full confgurability.
351+
* @since 1.4.0
352+
*/
353+
@Beta
354+
@Nonnull
355+
public OpenAiEmbeddingResponse embedding(@Nonnull final OpenAiEmbeddingRequest request)
356+
throws OpenAiClientException {
357+
return new OpenAiEmbeddingResponse(embedding(request.createEmbeddingsCreateRequest()));
358+
}
359+
360+
/**
361+
* Get a vector representation of a given inputs using low-level request.
346362
*
347363
* @param request the request with input text.
348364
* @return the embedding output
349365
* @throws OpenAiClientException if the request fails
366+
* @see #embedding(OpenAiEmbeddingRequest) for conveninece api
350367
* @since 1.4.0
351368
*/
352369
@Beta
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.sap.ai.sdk.foundationmodels.openai;
2+
3+
import com.google.common.annotations.Beta;
4+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.EmbeddingsCreateRequest;
5+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.EmbeddingsCreateRequestInput;
6+
import java.util.Collections;
7+
import java.util.List;
8+
import javax.annotation.Nonnull;
9+
import lombok.Value;
10+
11+
/**
12+
* Represents a request to create embeddings using OpenAI's API.
13+
*
14+
* <p>A high-level wrapper over the generated model class {@code EmbeddingsCreateRequest}, *
15+
* improving API usability for common use cases such as creation from a list of tokens.
16+
*
17+
* @since 1.4.0
18+
*/
19+
@Beta
20+
@Value
21+
public class OpenAiEmbeddingRequest {
22+
/** List of tokens to be embedded. */
23+
@Nonnull private final List<String> tokens;
24+
25+
/**
26+
* Constructs an OpenAiEmbeddingRequest from a list of strings.
27+
*
28+
* @param tokens a list of tokens to be embedded
29+
*/
30+
public OpenAiEmbeddingRequest(@Nonnull final List<String> tokens) {
31+
this.tokens = Collections.unmodifiableList(tokens);
32+
}
33+
34+
/**
35+
* Converts this request to an EmbeddingsCreateRequest.
36+
*
37+
* @return an EmbeddingsCreateRequest with the tokens to be embedded
38+
*/
39+
@Nonnull
40+
EmbeddingsCreateRequest createEmbeddingsCreateRequest() {
41+
if (tokens.size() == 1) {
42+
return new EmbeddingsCreateRequest()
43+
.input(EmbeddingsCreateRequestInput.create(tokens.get(0)));
44+
}
45+
46+
return new EmbeddingsCreateRequest().input(EmbeddingsCreateRequestInput.create(tokens));
47+
}
48+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.sap.ai.sdk.foundationmodels.openai;
2+
3+
import static lombok.AccessLevel.NONE;
4+
import static lombok.AccessLevel.PACKAGE;
5+
6+
import com.google.common.annotations.Beta;
7+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.EmbeddingsCreate200Response;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import javax.annotation.Nonnull;
11+
import lombok.AllArgsConstructor;
12+
import lombok.Setter;
13+
import lombok.Value;
14+
15+
/**
16+
* Represents a response from the OpenAI Embedding API.
17+
*
18+
* <p>A high-level wrapper over the generated model class {@code EmbeddingsCreate200Response},
19+
* improving API usability for common use cases, such as extracting embeddings.
20+
*
21+
* @since 1.4.0
22+
*/
23+
@Beta
24+
@Value
25+
@AllArgsConstructor(access = PACKAGE)
26+
@Setter(value = NONE)
27+
public class OpenAiEmbeddingResponse {
28+
29+
/** The original response from the OpenAI Embedding API. */
30+
@Nonnull EmbeddingsCreate200Response originalResponse;
31+
32+
/**
33+
* Read the embeddings from the response as a list of float arrays.
34+
*
35+
* @return a list of float arrays
36+
*/
37+
@Nonnull
38+
public List<float[]> getEmbeddingVectors() {
39+
final var embeddings = new ArrayList<float[]>();
40+
for (final var container : originalResponse.getData()) {
41+
42+
final var embeddingDecimals = container.getEmbedding();
43+
final var embeddingFloats = new float[embeddingDecimals.size()];
44+
45+
for (int i = 0; i < embeddingDecimals.size(); i++) {
46+
embeddingFloats[i] = embeddingDecimals.get(i).floatValue();
47+
}
48+
embeddings.add(embeddingFloats);
49+
}
50+
return embeddings;
51+
}
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.sap.ai.sdk.foundationmodels.openai;
2+
3+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionNamedToolChoice;
4+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionNamedToolChoiceFunction;
5+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionToolChoiceOption;
6+
import javax.annotation.Nonnull;
7+
import lombok.AccessLevel;
8+
import lombok.RequiredArgsConstructor;
9+
10+
/**
11+
* OpenAi ToolChoice to specify whether to call which tool.
12+
*
13+
* @since 1.4.0
14+
*/
15+
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
16+
public class OpenAiToolChoice {
17+
@Nonnull final ChatCompletionToolChoiceOption toolChoice;
18+
19+
/** Only message generation will be performed without calling any tool. */
20+
public static final OpenAiToolChoice NONE =
21+
new OpenAiToolChoice(ChatCompletionToolChoiceOption.create("none"));
22+
23+
/** The model may decide whether to call a (one or more) tool. */
24+
public static final OpenAiToolChoice OPTIONAL =
25+
new OpenAiToolChoice(ChatCompletionToolChoiceOption.create("auto"));
26+
27+
/** The model must call one or more tools as part of its processing. */
28+
public static final OpenAiToolChoice REQUIRED =
29+
new OpenAiToolChoice(ChatCompletionToolChoiceOption.create("required"));
30+
31+
/**
32+
* The model must call the function specified by {@code functionName}.
33+
*
34+
* @param functionName the name of the function that must be called.
35+
* @return the OpenAI tool choice.
36+
*/
37+
@Nonnull
38+
public static OpenAiToolChoice function(@Nonnull final String functionName) {
39+
return new OpenAiToolChoice(
40+
ChatCompletionToolChoiceOption.create(
41+
new ChatCompletionNamedToolChoice()
42+
.type(ChatCompletionNamedToolChoice.TypeEnum.FUNCTION)
43+
.function(new ChatCompletionNamedToolChoiceFunction().name(functionName))));
44+
}
45+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.sap.ai.sdk.foundationmodels.openai;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
6+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.EmbeddingsCreate200Response;
7+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.EmbeddingsCreateRequestInput;
8+
import java.util.List;
9+
import lombok.SneakyThrows;
10+
import org.junit.jupiter.api.Test;
11+
12+
@WireMockTest
13+
class EmbeddingConvenienceTest {
14+
15+
@Test
16+
void createEmbeddingRequestWithMultipleTokens() {
17+
var request = new OpenAiEmbeddingRequest(List.of("token1", "token2", "token3"));
18+
var lowLevelRequest = request.createEmbeddingsCreateRequest();
19+
20+
assertThat(((EmbeddingsCreateRequestInput.InnerStrings) lowLevelRequest.getInput()).values())
21+
.containsExactly("token1", "token2", "token3");
22+
}
23+
24+
@SneakyThrows
25+
@Test
26+
void getEmbeddings() {
27+
var originalResponse =
28+
OpenAiUtils.getOpenAiObjectMapper()
29+
.readValue(
30+
getClass().getClassLoader().getResource("__files/embeddingResponse.json"),
31+
EmbeddingsCreate200Response.class);
32+
33+
var embeddings = new OpenAiEmbeddingResponse(originalResponse).getEmbeddingVectors();
34+
35+
assertThat(embeddings).isInstanceOf(List.class);
36+
assertThat(embeddings).hasSize(1);
37+
assertThat(embeddings)
38+
.containsExactly(new float[] {0.0f, 3.4028235E38f, 1.4E-45f, 1.23f, -4.56f});
39+
}
40+
}

foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiChatCompletionRequestTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ void createWithExistingRequest() {
5050
@Test
5151
void withToolChoiceNone() {
5252
OpenAiChatCompletionRequest request =
53-
new OpenAiChatCompletionRequest("message").withToolChoiceNone();
53+
new OpenAiChatCompletionRequest("message").withToolChoice(OpenAiToolChoice.NONE);
5454

5555
var lowLevelRequest = request.createCreateChatCompletionRequest();
5656
var choice =
@@ -61,7 +61,7 @@ void withToolChoiceNone() {
6161
@Test
6262
void withToolChoiceOptional() {
6363
OpenAiChatCompletionRequest request =
64-
new OpenAiChatCompletionRequest("message").withToolChoiceOptional();
64+
new OpenAiChatCompletionRequest("message").withToolChoice(OpenAiToolChoice.OPTIONAL);
6565

6666
var lowLevelRequest = request.createCreateChatCompletionRequest();
6767
var choice =
@@ -72,7 +72,7 @@ void withToolChoiceOptional() {
7272
@Test
7373
void withToolChoiceRequired() {
7474
OpenAiChatCompletionRequest request =
75-
new OpenAiChatCompletionRequest("message").withToolChoiceRequired();
75+
new OpenAiChatCompletionRequest("message").withToolChoice(OpenAiToolChoice.REQUIRED);
7676

7777
var lowLevelRequest = request.createCreateChatCompletionRequest();
7878
var choice =
@@ -83,7 +83,8 @@ void withToolChoiceRequired() {
8383
@Test
8484
void withToolChoiceFunction() {
8585
OpenAiChatCompletionRequest request =
86-
new OpenAiChatCompletionRequest("message").withToolChoiceFunction("functionName");
86+
new OpenAiChatCompletionRequest("message")
87+
.withToolChoice(OpenAiToolChoice.function("functionName"));
8788

8889
var lowLevelRequest = request.createCreateChatCompletionRequest();
8990
var choice =

0 commit comments

Comments
 (0)