Skip to content

Commit a71d721

Browse files
rpanackalMatKuhrbot-sdk-jsCharlesDuboisSAPa-d
authored
Openapi generator orchestration (#146)
* All hail the new orchestration client * Leftovers * Update code generatino ADR * Minor comments * Formatting * Add javadoc and logging * Added. More. Javadoc. * Update sample HTML page * Switch to error for empty content filter configs * Simplify Exception * Fix finish reason * Fix type of Orchestration Response Messages * Minor improvements * Switch to deployment * Update pom for using OSS generator * Using original orchestration spec. Test contain descriminator * Used OpenAPI generator for orchestration * Using discriminator spec everywhere. test success. * LLMModule specific deserializer ready. Test success. * Generalized Fallback deserializer ready. Test success. * OSS generator on customer facing orchestration spec - Introduce a generic fallback and LLMModuleResult specific deserializers. - Generator ignoring all interfaces - Adapt jackson annotations * Merging changes from main * Merging changes from main * Merging changes from main * Formatting * Clean up - Remove Generic fallback deserializer - Beta Annotation on all concrete model classes. - Introduce Mixin to override model annotations - Improve variable naming * Update generated models * Add test and improve deserializer - Make deserializer lenient to allowed values * [Orchestration] OSS Generator Improvements (#158) * Simplifications and cleanup * Remove parsing for streaming * Reduce visibility of mixin class * replace solution with custom deserializer to mixin * remove unnecessary dependency jackson-databind-nullable * Minor newline-reduction * Minor syntax sugar --------- Co-authored-by: Matthias Kuhr <[email protected]> Co-authored-by: SAP Cloud SDK Bot <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]> Co-authored-by: I538344 <[email protected]> Co-authored-by: Matthias Kuhr <[email protected]> Co-authored-by: Alexander Dümont <[email protected]> Co-authored-by: Alexander Dümont <[email protected]>
1 parent f25c22b commit a71d721

File tree

68 files changed

+3364
-9929
lines changed

Some content is hidden

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

68 files changed

+3364
-9929
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@
2626
.java-version
2727

2828
### Environment ###
29-
*.env
29+
*.env
30+
31+
.openapi-generator
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# OpenAPI Generator Ignore
2+
3+
.github/
4+
gradle/
5+
.gitignore
6+
.travis.yml
7+
build.gradle
8+
build.sbt
9+
git_push.sh
10+
gradle.properties
11+
gradlew
12+
gradlew.bat
13+
pom.xml
14+
README.md
15+
settings.gradle
16+
src/main/AndroidManifest.xml
17+
api/
18+
.openapi-generator/

orchestration/pom.xml

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@
8585
<groupId>org.slf4j</groupId>
8686
<artifactId>slf4j-api</artifactId>
8787
</dependency>
88+
<dependency>
89+
<groupId>javax.annotation</groupId>
90+
<artifactId>javax.annotation-api</artifactId>
91+
</dependency>
92+
<dependency>
93+
<groupId>com.google.guava</groupId>
94+
<artifactId>guava</artifactId>
95+
</dependency>
8896

8997
<!-- TODO: only needed for JsonObjectMapperBuilder, maybe we can use Jackson natively to avoid this dependency -->
9098
<dependency>
@@ -132,38 +140,72 @@
132140
<build>
133141
<plugins>
134142
<plugin>
135-
<groupId>com.sap.cloud.sdk.datamodel</groupId>
143+
<groupId>org.openapitools</groupId>
136144
<artifactId>openapi-generator-maven-plugin</artifactId>
137-
<configuration>
138-
<skip>true</skip>
139-
<!-- skip automatic generation until we can omit API classes from code generation -->
140-
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
141-
<apiMaturity>beta</apiMaturity>
142-
<enableOneOfAnyOfGeneration>true</enableOneOfAnyOfGeneration>
143-
<compileScope>COMPILE</compileScope>
144-
<deleteOutputDirectory>true</deleteOutputDirectory>
145-
</configuration>
146145
<executions>
147146
<execution>
148-
<id>orchestration</id>
149147
<goals>
150148
<goal>generate</goal>
151149
</goals>
152150
<phase>generate-sources</phase>
153151
<configuration>
152+
<!-- Specify the input OpenAPI spec file -->
154153
<inputSpec>${project.basedir}/src/main/resources/spec/orchestration.yaml</inputSpec>
155-
<apiPackage>com.sap.ai.sdk.orchestration.client</apiPackage>
154+
<output>${project.basedir}</output>
155+
<!-- Specify the generator to use, e.g., java, spring, kotlin, etc. -->
156+
<generatorName>java</generatorName>
157+
<!-- Specify the package names for models, APIs, and invokers -->
156158
<modelPackage>com.sap.ai.sdk.orchestration.client.model</modelPackage>
157-
<additionalProperties>
158-
<pojoBuilderMethodName>create</pojoBuilderMethodName>
159-
<pojoBuildMethodName/>
160-
<pojoConstructorVisibility>protected</pojoConstructorVisibility>
159+
<apiPackage>com.sap.ai.sdk.orchestration.client.api</apiPackage>
160+
<invokerPackage>com.sap.ai.sdk.orchestration.client.invoker</invokerPackage>
161+
162+
<!-- Global properties level; can be unpacked with 'generate' prefix-->
163+
<globalProperties>
164+
<apiDocs>false</apiDocs>
165+
<modelDocs>false</modelDocs>
166+
<modelTests>false</modelTests>
167+
<apiTests>false</apiTests>
168+
<minimalUpdate>true</minimalUpdate>
169+
</globalProperties>
170+
171+
<!-- Generator Specific properties level; some can be unpacked-->
172+
<configOptions>
173+
<generateBuilders>true</generateBuilders>
174+
<failOnUnknownProperties>false</failOnUnknownProperties>
175+
<hideGenerationTimestamp>true</hideGenerationTimestamp>
176+
<disallowAdditionalPropertiesIfNotPresent>false</disallowAdditionalPropertiesIfNotPresent>
161177
<enumUnknownDefaultCase>true</enumUnknownDefaultCase>
162-
</additionalProperties>
178+
<useBeanValidation>false</useBeanValidation>
179+
<useOneOfInterfaces>true</useOneOfInterfaces>
180+
<additionalModelTypeAnnotations>@com.google.common.annotations.Beta</additionalModelTypeAnnotations>
181+
</configOptions>
182+
<generateModels>true</generateModels>
183+
<generateSupportingFiles>false</generateSupportingFiles>
184+
<generateApis>false</generateApis>
185+
<library>resttemplate</library>
186+
<!--<configHelp>true</configHelp>-->
163187
</configuration>
164188
</execution>
165189
</executions>
166190
</plugin>
191+
<plugin>
192+
<artifactId>maven-clean-plugin</artifactId>
193+
<configuration>
194+
<filesets>
195+
<fileset>
196+
<directory>${project.basedir}/src/main/java/com/sap/ai/sdk/orchestration/client</directory>
197+
<includes>
198+
<include>**/*</include>
199+
</includes>
200+
</fileset>
201+
</filesets>
202+
</configuration>
203+
<executions>
204+
<execution>
205+
<id>delete-orchestration-generated-client</id>
206+
</execution>
207+
</executions>
208+
</plugin>
167209
</plugins>
168210
</build>
169211
</profile>

orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.sap.ai.sdk.orchestration;
22

3+
import com.sap.ai.sdk.orchestration.client.model.ChatMessage;
34
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
45
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
56
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
7+
import com.sap.ai.sdk.orchestration.client.model.Template;
68
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
79
import io.vavr.control.Option;
810
import java.util.ArrayList;
@@ -25,9 +27,9 @@ static CompletionPostRequest toCompletionPostRequest(
2527
// subsequent requests
2628
val configCopy = config.withTemplateConfig(template);
2729

28-
return CompletionPostRequest.create()
30+
return new CompletionPostRequest()
2931
.orchestrationConfig(
30-
OrchestrationConfig.create().moduleConfigurations(toModuleConfigs(configCopy)))
32+
new OrchestrationConfig().moduleConfigurations(toModuleConfigs(configCopy)))
3133
.inputParams(prompt.getTemplateParameters())
3234
.messagesHistory(prompt.getMessagesHistory());
3335
}
@@ -42,14 +44,14 @@ static TemplatingModuleConfig toTemplateModuleConfig(
4244
* In this case, the request will fail, since the templating module will try to resolve the parameter.
4345
* To be fixed with https://github.tools.sap/AI/llm-orchestration/issues/662
4446
*/
45-
val messages = Option.of(template).map(TemplatingModuleConfig::getTemplate).getOrElse(List::of);
47+
val messages = template instanceof Template t ? t.getTemplate() : List.<ChatMessage>of();
4648
val messagesWithPrompt = new ArrayList<>(messages);
4749
messagesWithPrompt.addAll(prompt.getMessages());
4850
if (messagesWithPrompt.isEmpty()) {
4951
throw new IllegalStateException(
5052
"A prompt is required. Pass at least one message or configure a template with messages or a template reference.");
5153
}
52-
return TemplatingModuleConfig.create().template(messagesWithPrompt);
54+
return new Template().template(messagesWithPrompt);
5355
}
5456

5557
@Nonnull
@@ -60,7 +62,7 @@ static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig co
6062

6163
//noinspection DataFlowIssue the template is always non-null here
6264
val moduleConfig =
63-
ModuleConfigs.create()
65+
new ModuleConfigs()
6466
.llmModuleConfig(llmConfig)
6567
.templatingModuleConfig(config.getTemplateConfig());
6668

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5+
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultSynchronous;
6+
7+
/** Mixin to enforce a specific subtype to be deserialized always. */
8+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
9+
@JsonDeserialize(as = LLMModuleResultSynchronous.class)
10+
interface LLMModuleResultMixIn {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4+
5+
/** Mixin to suppress @JsonTypeInfo for oneOf interfaces. */
6+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
7+
interface NoTypeInfoMixin {}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
import com.sap.ai.sdk.core.AiCoreService;
1111
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
1212
import com.sap.ai.sdk.orchestration.client.model.CompletionPostResponse;
13+
import com.sap.ai.sdk.orchestration.client.model.FilterConfig;
14+
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResult;
15+
import com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig;
1316
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
17+
import com.sap.ai.sdk.orchestration.client.model.ModuleResultsOutputUnmaskingInner;
1418
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
19+
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
1520
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
1621
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
1722
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
@@ -40,6 +45,11 @@ public class OrchestrationClient {
4045
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
4146
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
4247
.serializationInclusion(JsonInclude.Include.NON_NULL)
48+
.mixIn(LLMModuleResult.class, LLMModuleResultMixIn.class)
49+
.mixIn(ModuleResultsOutputUnmaskingInner.class, NoTypeInfoMixin.class)
50+
.mixIn(FilterConfig.class, NoTypeInfoMixin.class)
51+
.mixIn(MaskingProviderConfig.class, NoTypeInfoMixin.class)
52+
.mixIn(TemplatingModuleConfig.class, NoTypeInfoMixin.class)
4353
.build();
4454
}
4555

@@ -66,6 +76,20 @@ public OrchestrationClient(@Nonnull final AiCoreDeployment deployment) {
6676
this.deployment = () -> deployment;
6777
}
6878

79+
/**
80+
* Convert the given prompt and config into a low-level request data object. The data object
81+
* allows for further customization before sending the request.
82+
*
83+
* @param prompt The {@link OrchestrationPrompt} to generate a completion for.
84+
* @param config The {@link OrchestrationConfig } configuration to use for the completion.
85+
* @return The low-level request data object to send to orchestration.
86+
*/
87+
@Nonnull
88+
public static CompletionPostRequest toCompletionPostRequest(
89+
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
90+
return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config);
91+
}
92+
6993
/**
7094
* Generate a completion for the given prompt.
7195
*
@@ -118,20 +142,6 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques
118142
return executeRequest(postRequest);
119143
}
120144

121-
/**
122-
* Convert the given prompt and config into a low-level request data object. The data object
123-
* allows for further customization before sending the request.
124-
*
125-
* @param prompt The {@link OrchestrationPrompt} to generate a completion for.
126-
* @param config The {@link OrchestrationConfig } configuration to use for the completion.
127-
* @return The low-level request data object to send to orchestration.
128-
*/
129-
@Nonnull
130-
public static CompletionPostRequest toCompletionPostRequest(
131-
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
132-
return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config);
133-
}
134-
135145
@Nonnull
136146
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
137147
try {

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationPrompt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class OrchestrationPrompt {
3232
* @param message A user message.
3333
*/
3434
public OrchestrationPrompt(@Nonnull final String message) {
35-
messages.add(ChatMessage.create().role("user").content(message));
35+
messages.add(new ChatMessage().role("user").content(message));
3636
}
3737

3838
/**

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationResponseHandler.java

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,44 +23,6 @@
2323
class OrchestrationResponseHandler<T> implements HttpClientResponseHandler<T> {
2424
@Nonnull private final Class<T> responseType;
2525

26-
/**
27-
* Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
28-
*
29-
* @param response The response to process
30-
* @return A model class instantiated from the response
31-
* @throws OrchestrationClientException in case of a problem or the connection was aborted
32-
*/
33-
@Override
34-
public T handleResponse(@Nonnull final ClassicHttpResponse response)
35-
throws OrchestrationClientException {
36-
if (response.getCode() >= 300) {
37-
buildExceptionAndThrow(response);
38-
}
39-
val result = parseResponse(response);
40-
log.debug("Received the following response from orchestration service: {}", result);
41-
return result;
42-
}
43-
44-
// The InputStream of the HTTP entity is closed by EntityUtils.toString
45-
@SuppressWarnings("PMD.CloseResource")
46-
@Nonnull
47-
private T parseResponse(@Nonnull final ClassicHttpResponse response)
48-
throws OrchestrationClientException {
49-
final HttpEntity responseEntity = response.getEntity();
50-
if (responseEntity == null) {
51-
throw new OrchestrationClientException("Response from Orchestration service was empty.");
52-
}
53-
val content = getContent(responseEntity);
54-
log.debug("Parsing response from JSON response: {}", content);
55-
try {
56-
return JACKSON.readValue(content, responseType);
57-
} catch (final JsonProcessingException e) {
58-
log.error("Failed to parse the following response from orchestration service: {}", content);
59-
throw new OrchestrationClientException(
60-
"Failed to parse response from orchestration service", e);
61-
}
62-
}
63-
6426
@Nonnull
6527
private static String getContent(@Nonnull final HttpEntity entity) {
6628
try {
@@ -123,4 +85,42 @@ static void parseErrorAndThrow(
12385
"%s and error message: '%s'"
12486
.formatted(baseException.getMessage(), maybeError.get().getMessage()));
12587
}
88+
89+
/**
90+
* Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
91+
*
92+
* @param response The response to process
93+
* @return A model class instantiated from the response
94+
* @throws OrchestrationClientException in case of a problem or the connection was aborted
95+
*/
96+
@Override
97+
public T handleResponse(@Nonnull final ClassicHttpResponse response)
98+
throws OrchestrationClientException {
99+
if (response.getCode() >= 300) {
100+
buildExceptionAndThrow(response);
101+
}
102+
val result = parseResponse(response);
103+
log.debug("Received the following response from orchestration service: {}", result);
104+
return result;
105+
}
106+
107+
// The InputStream of the HTTP entity is closed by EntityUtils.toString
108+
@SuppressWarnings("PMD.CloseResource")
109+
@Nonnull
110+
private T parseResponse(@Nonnull final ClassicHttpResponse response)
111+
throws OrchestrationClientException {
112+
final HttpEntity responseEntity = response.getEntity();
113+
if (responseEntity == null) {
114+
throw new OrchestrationClientException("Response from Orchestration service was empty.");
115+
}
116+
val content = getContent(responseEntity);
117+
log.debug("Parsing response from JSON response: {}", content);
118+
try {
119+
return JACKSON.readValue(content, responseType);
120+
} catch (final JsonProcessingException e) {
121+
log.error("Failed to parse the following response from orchestration service: {}", content);
122+
throw new OrchestrationClientException(
123+
"Failed to parse response from orchestration service", e);
124+
}
125+
}
126126
}

0 commit comments

Comments
 (0)