Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b36c9d4
Update orchestration based on fix/streaming-response-type
bot-sdk-js May 26, 2025
eafed16
Merge branch 'main' into spec-update/orchestration/fix/streaming-resp…
CharlesDuboisSAP May 27, 2025
bd1ddb7
WiP
CharlesDuboisSAP May 27, 2025
5cff32a
Merge branch 'refs/heads/main' into spec-update/orchestration/fix/str…
CharlesDuboisSAP Jun 4, 2025
50add6c
Latest spec with error classes
CharlesDuboisSAP Jun 4, 2025
224fbc2
regenerate
CharlesDuboisSAP Jun 4, 2025
d4cfe63
Merge branch 'main' into spec-update/orchestration/fix/streaming-resp…
CharlesDuboisSAP Jun 4, 2025
494855e
regenerate
CharlesDuboisSAP Jun 4, 2025
e327c1b
DPI
CharlesDuboisSAP Jun 4, 2025
e62952d
feat: Add embeddings endpoint and refactor orchestration client (#464)
rpanackal Jun 18, 2025
b54ab6e
fix: [Orchestration] Spec update - Filtering, Remove "Synchronous" su…
rpanackal Jul 1, 2025
4d15b91
Make embedding client endpoint non-visible and update javadoc (minor)
rpanackal Jul 1, 2025
13c078c
Integrate v2 spec changes
rpanackal Jul 2, 2025
a8ff4c6
Merge branch 'main' into spec-update/orchestration/fix/streaming-resp…
CharlesDuboisSAP Jul 2, 2025
05ac10f
merge main
CharlesDuboisSAP Jul 2, 2025
1eecb6e
`ConfigToRequestTransformer` ready and adapt some tests (minimal)
rpanackal Jul 2, 2025
b58bc5e
Formatting
bot-sdk-js Jul 2, 2025
24e885c
Merge branch 'refs/heads/spec-update/orchestration/fix/streaming-resp…
rpanackal Jul 2, 2025
98fe7ec
Partial migration of payload to v2
rpanackal Jul 3, 2025
965dc29
All json updated
rpanackal Jul 3, 2025
39bcac7
refactor (minor) and renaming
rpanackal Jul 3, 2025
dd478b1
jacoco min threshold lowered
rpanackal Jul 3, 2025
840bbd6
Merge remote-tracking branch 'refs/remotes/origin/main' into spec-upd…
rpanackal Jul 23, 2025
77577d2
Formatting
bot-sdk-js Jul 23, 2025
79ce12f
Fix e2e test issue
rpanackal Jul 23, 2025
1ae877b
Merge branch 'spec-update/orchestration/fix/validate-v2' of https://g…
rpanackal Jul 23, 2025
60c749b
Merge branch 'main' into spec-update/orchestration/fix/validate-v2
rpanackal Aug 4, 2025
8285c31
Fix merge error
rpanackal Aug 4, 2025
6716579
Formatting
bot-sdk-js Aug 4, 2025
09d8e04
update release notes
rpanackal Aug 4, 2025
5c6bc49
Merge remote-tracking branch 'origin/spec-update/orchestration/fix/va…
rpanackal Aug 4, 2025
4b59271
Merge branch 'main' into spec-update/orchestration/fix/validate-v2
rpanackal Aug 5, 2025
64ac941
partial merge fix
rpanackal Aug 5, 2025
e0f0b62
Fix tests and add new mixin for filtering response
rpanackal Aug 6, 2025
d62156c
Include ErrorResponseStreaming
rpanackal Aug 6, 2025
362c038
Update unit testing stream completion
rpanackal Aug 6, 2025
07c3e57
better javadoc and getVersion fix -> getModelVersion
rpanackal Aug 7, 2025
9022352
Minor syntax improvement
newtork Aug 7, 2025
767754c
change from abstract class to interface
newtork Aug 7, 2025
6bfa01d
Fix message
newtork Aug 7, 2025
6980dc0
Add comment
newtork Aug 7, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class ClientResponseHandler<T, R extends ClientError, E extends ClientExc
@Nonnull final Class<T> successType;

/** The HTTP error response type */
@Nonnull final Class<R> errorType;
@Nonnull final Class<? extends R> errorType;

/** The factory to create exceptions for Http 4xx/5xx responses. */
@Nonnull final ClientExceptionFactory<E, R> exceptionFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ClientStreamingHandler<D, R, E> objectMapper(@Nonnull final ObjectMapper
*/
public ClientStreamingHandler(
@Nonnull final Class<D> deltaType,
@Nonnull final Class<R> errorType,
@Nonnull final Class<? extends R> errorType,
@Nonnull final ClientExceptionFactory<E, R> exceptionFactory) {
super(deltaType, errorType, exceptionFactory);
}
Expand Down
5 changes: 4 additions & 1 deletion docs/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
- The `OrchestrationChatOptions` have been, replacing all references to `FunctionCallback` with `ToolCallback`.
- Please follow the [official Spring AI upgrade guide](https://docs.spring.io/spring-ai/reference/upgrade-notes.html#upgrading-to-1-0-0-RC1) for further details.
- The `@Beta` annotations on all classes related to Spring AI have been removed.

- [Orchestration] The `completion` api have been moved to the latest version `/v2/completions`
- `LLMModuleConfig` is replaced by `LLMModelDetails` in `withLLmConfig` method of `OrchestrationModuleConfig` class.
- `PromptTemplatingModuleConfigPrompt` replaces `TemplatingModuleConfig` in the `withTemplateConfig` method of `OrchestrationModuleConfig` class.
- The generated model classes will reflect payload updates including restructuring of the module configurations and renaming of several fields.

### ✨ New Functionality

Expand Down
6 changes: 3 additions & 3 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
</scm>
<properties>
<project.rootdir>${project.basedir}/../</project.rootdir>
<coverage.complexity>82%</coverage.complexity>
<coverage.complexity>80%</coverage.complexity>
<coverage.line>94%</coverage.line>
<coverage.instruction>95%</coverage.instruction>
<coverage.branch>77%</coverage.branch>
<coverage.instruction>94%</coverage.instruction>
<coverage.branch>75%</coverage.branch>
<coverage.method>93%</coverage.method>
<coverage.class>100%</coverage.class>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
import com.sap.ai.sdk.orchestration.model.PromptTemplatingModuleConfig;
import com.sap.ai.sdk.orchestration.model.PromptTemplatingModuleConfigPrompt;
import com.sap.ai.sdk.orchestration.model.Template;
import com.sap.ai.sdk.orchestration.model.TemplateRef;
import com.sap.ai.sdk.orchestration.model.TemplatingModuleConfig;
import com.sap.ai.sdk.orchestration.model.TranslationModuleConfig;
import io.vavr.control.Option;
import java.util.ArrayList;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -37,14 +39,15 @@ static CompletionPostRequest toCompletionPostRequest(
val moduleConfigs = toModuleConfigs(configCopy);

return CompletionPostRequest.create()
.orchestrationConfig(OrchestrationConfig.create().moduleConfigurations(moduleConfigs))
.inputParams(prompt.getTemplateParameters())
.config(OrchestrationConfig.create().modules(moduleConfigs))
.placeholderValues(prompt.getTemplateParameters())
.messagesHistory(messageHistory);
}

@Nonnull
static TemplatingModuleConfig toTemplateModuleConfig(
@Nonnull final OrchestrationPrompt prompt, @Nullable final TemplatingModuleConfig config) {
static PromptTemplatingModuleConfigPrompt toTemplateModuleConfig(
@Nonnull final OrchestrationPrompt prompt,
@Nullable final PromptTemplatingModuleConfigPrompt config) {
/*
* Currently, we have to merge the prompt into the template configuration.
* This works around the limitation that the template config is required.
Expand Down Expand Up @@ -89,16 +92,23 @@ static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig co
//noinspection DataFlowIssue the template is always non-null here
val moduleConfig =
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(config.getTemplateConfig());

Option.of(config.getFilteringConfig()).forEach(moduleConfig::filteringModuleConfig);
Option.of(config.getMaskingConfig()).forEach(moduleConfig::maskingModuleConfig);
Option.of(config.getGroundingConfig()).forEach(moduleConfig::groundingModuleConfig);
Option.of(config.getOutputTranslationConfig())
.forEach(moduleConfig::outputTranslationModuleConfig);
Option.of(config.getInputTranslationConfig())
.forEach(moduleConfig::inputTranslationModuleConfig);
.promptTemplating(
PromptTemplatingModuleConfig.create()
.prompt(config.getTemplateConfig())
.model(llmConfig));

Option.of(config.getFilteringConfig()).forEach(moduleConfig::filtering);
Option.of(config.getMaskingConfig()).forEach(moduleConfig::masking);
Option.of(config.getGroundingConfig()).forEach(moduleConfig::grounding);

val outputTranslation = Option.of(config.getOutputTranslationConfig());
val inputTranslation = Option.of(config.getInputTranslationConfig());

if (inputTranslation.isDefined() || outputTranslation.isDefined()) {
moduleConfig.setTranslation(TranslationModuleConfig.create());
inputTranslation.forEach(moduleConfig.getTranslation()::input);
outputTranslation.forEach(moduleConfig.getTranslation()::output);
}

return moduleConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfig.TypeEnum;
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig;
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfigFiltersInner;
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfigPlaceholders;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -81,8 +82,10 @@ public OrchestrationPrompt createGroundingPrompt(@Nonnull final String message)
public GroundingModuleConfig createConfig() {
val groundingConfigConfig =
GroundingModuleConfigConfig.create()
.inputParams(List.of("userMessage"))
.outputParam("groundingContext")
.placeholders(
GroundingModuleConfigConfigPlaceholders.create()
.input(List.of("userMessage"))
.output("groundingContext"))
.filters(filters);

if (filters.contains(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.sap.ai.sdk.orchestration;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.sap.ai.sdk.orchestration.model.AzureThreshold;
import com.sap.ai.sdk.orchestration.model.LLMModuleResult;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -56,4 +59,26 @@ interface ResponseFormatSubTypesMixin {}
name = "user")
})
interface ChatMessageMixin {}

/**
* Mixin used for parsing response "data" field of
* error.intermediate_results.input_filtering.data.azure_content_safety
*/
abstract static class AzureContentSafetyCaseAgnostic {
@JsonProperty("hate")
@JsonAlias("Hate")
private AzureThreshold hate;

@JsonProperty("self_harm")
@JsonAlias("SelfHarm")
private AzureThreshold selfHarm;

@JsonProperty("sexual")
@JsonAlias("Sexual")
private AzureThreshold sexual;

@JsonProperty("violence")
@JsonAlias("Violence")
private AzureThreshold violence;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sap.ai.sdk.orchestration;

import com.sap.ai.sdk.orchestration.model.LLMModuleConfig;
import com.sap.ai.sdk.orchestration.model.LLMModelDetails;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -276,8 +276,8 @@ public class OrchestrationAiModel {
}

@Nonnull
LLMModuleConfig createConfig() {
return LLMModuleConfig.create().modelName(name).modelParams(params).modelVersion(version);
LLMModelDetails createConfig() {
return LLMModelDetails.create().name(name).params(params).version(version);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class OrchestrationChatCompletionDelta extends CompletionPostResponseStre
@Nonnull
@Override
public String getDeltaContent() {
val choices = getOrchestrationResult().getChoices();
val choices = getFinalResult().getChoices();
// Avoid the first delta: "choices":[]
if (!choices.isEmpty()
// Multiple choices are spread out on multiple deltas
Expand All @@ -29,6 +29,6 @@ public String getDeltaContent() {
@Nullable
@Override
public String getFinishReason() {
return getOrchestrationResult().getChoices().get(0).getFinishReason();
return getFinalResult().getChoices().get(0).getFinishReason();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public String getContent() throws OrchestrationFilterException.Output {

@SuppressWarnings("unchecked")
private Map<String, Object> getOutputFilteringChoices() {
final var f = getOriginalResponse().getModuleResults().getOutputFiltering();
final var f = getOriginalResponse().getIntermediateResults().getOutputFiltering();
return ((List<Map<String, Object>>) ((Map<String, Object>) f.getData()).get("choices")).get(0);
}

Expand All @@ -62,7 +62,7 @@ private Map<String, Object> getOutputFilteringChoices() {
*/
@Nonnull
public TokenUsage getTokenUsage() {
return originalResponse.getOrchestrationResult().getUsage();
return originalResponse.getFinalResult().getUsage();
}

/**
Expand All @@ -74,7 +74,8 @@ public TokenUsage getTokenUsage() {
@Nonnull
public List<Message> getAllMessages() throws IllegalArgumentException {
val messages = new ArrayList<Message>();
for (final ChatMessage chatMessage : originalResponse.getModuleResults().getTemplating()) {
for (final ChatMessage chatMessage :
originalResponse.getIntermediateResults().getTemplating()) {
if (chatMessage instanceof AssistantChatMessage assistantChatMessage) {
val toolCalls = assistantChatMessage.getToolCalls();
if (!toolCalls.isEmpty()) {
Expand Down Expand Up @@ -115,7 +116,7 @@ public List<Message> getAllMessages() throws IllegalArgumentException {
@Nonnull
public LLMChoice getChoice() {
// We expect choices to be defined and never empty.
return originalResponse.getOrchestrationResult().getChoices().get(0);
return originalResponse.getFinalResult().getChoices().get(0);
}

/**
Expand All @@ -133,12 +134,7 @@ public LLMChoice getChoice() {
@Nonnull
public <T> T asEntity(@Nonnull final Class<T> type) throws OrchestrationClientException {
final String refusal =
getOriginalResponse()
.getOrchestrationResult()
.getChoices()
.get(0)
.getMessage()
.getRefusal();
getOriginalResponse().getFinalResult().getChoices().get(0).getMessage().getRefusal();
if (refusal != null) {
throw new OrchestrationClientException(
"The model refused to answer the question: " + refusal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
import com.sap.ai.sdk.orchestration.model.EmbeddingsPostRequest;
import com.sap.ai.sdk.orchestration.model.EmbeddingsPostResponse;
import com.sap.ai.sdk.orchestration.model.GlobalStreamOptions;
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
Expand All @@ -28,6 +29,7 @@
@Slf4j
public class OrchestrationClient {
private static final String DEFAULT_SCENARIO = "orchestration";
private static final String COMPLETION_ENDPOINT = "/v2/completion";

static final ObjectMapper JACKSON = getOrchestrationObjectMapper();

Expand Down Expand Up @@ -127,7 +129,7 @@ private static void throwOnContentFilter(@Nonnull final OrchestrationChatComplet
@SuppressWarnings("unchecked")
private static Map<String, Object> getOutputFilteringChoices(
@Nonnull final OrchestrationChatCompletionDelta delta) {
final var f = delta.getModuleResults().getOutputFiltering();
final var f = delta.getIntermediateResults().getOutputFiltering();
return ((List<Map<String, Object>>) ((Map<String, Object>) f.getData()).get("choices")).get(0);
}

Expand All @@ -154,7 +156,7 @@ private static Map<String, Object> getOutputFilteringChoices(
@Nonnull
public CompletionPostResponse executeRequest(@Nonnull final CompletionPostRequest request)
throws OrchestrationClientException {
return executor.execute("/completion", request, CompletionPostResponse.class);
return executor.execute(COMPLETION_ENDPOINT, request, CompletionPostResponse.class);
}

/**
Expand Down Expand Up @@ -196,7 +198,7 @@ public OrchestrationChatResponse executeRequestFromJsonModuleConfig(
requestJson.set("orchestration_config", moduleConfigJson);

return new OrchestrationChatResponse(
executor.execute("/completion", requestJson, CompletionPostResponse.class));
executor.execute(COMPLETION_ENDPOINT, requestJson, CompletionPostResponse.class));
}

/**
Expand All @@ -210,8 +212,9 @@ public OrchestrationChatResponse executeRequestFromJsonModuleConfig(
@Nonnull
public Stream<OrchestrationChatCompletionDelta> streamChatCompletionDeltas(
@Nonnull final CompletionPostRequest request) throws OrchestrationClientException {
request.getOrchestrationConfig().setStream(true);
return executor.stream(request);
request.getConfig().setStream(GlobalStreamOptions.create().enabled(true).delimiters(null));

return executor.stream(COMPLETION_ENDPOINT, request);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.google.common.annotations.Beta;
import com.sap.ai.sdk.core.common.ClientException;
import com.sap.ai.sdk.orchestration.model.Error;
import com.sap.ai.sdk.orchestration.model.ErrorResponse;
import com.sap.ai.sdk.orchestration.model.ErrorResponseStreaming;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand All @@ -22,12 +24,29 @@ public class OrchestrationClientException extends ClientException {
* Retrieves the {@link ErrorResponse} from the orchestration service, if available.
*
* @return The {@link ErrorResponse} object, or {@code null} if not available.
* @since 1.10.0
*/
@Beta
@Nullable
public ErrorResponse getErrorResponse() {
final var clientError = super.getClientError();
if (clientError instanceof OrchestrationError orchestrationError) {
if (clientError instanceof OrchestrationError.Synchronous orchestrationError) {
return orchestrationError.getErrorResponse();
}
return null;
}

/**
* Retrieves the {@link ErrorResponseStreaming} from the orchestration service, if available.
*
* @return The {@link ErrorResponseStreaming} object, or {@code null} if not available.
* @since 1.10.0
*/
@Beta
@Nullable
public ErrorResponseStreaming getErrorResponseStreaming() {
final var clientError = super.getClientError();
if (clientError instanceof OrchestrationError.Streaming orchestrationError) {
return orchestrationError.getErrorResponse();
}
return null;
Expand All @@ -37,10 +56,14 @@ public ErrorResponse getErrorResponse() {
* Retrieves the HTTP status code from the original error response, if available.
*
* @return the HTTP status code, or {@code null} if not available
* @since 1.10.0
*/
@Beta
@Nullable
public Integer getStatusCode() {
return Optional.ofNullable(getErrorResponse()).map(ErrorResponse::getCode).orElse(null);
return Optional.ofNullable(getErrorResponse())
.map(ErrorResponse::getError)
.map(Error::getCode)
.orElse(null);
}
}
Loading