Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Openapi generator orchestration #146

Merged
merged 40 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e8a95fc
All hail the new orchestration client
MatKuhr Oct 22, 2024
cf711a2
Leftovers
MatKuhr Oct 22, 2024
a394527
Update code generatino ADR
MatKuhr Oct 22, 2024
5045cc1
Minor comments
MatKuhr Oct 22, 2024
e847328
Formatting
bot-sdk-js Oct 22, 2024
77b8f20
Add javadoc and logging
MatKuhr Oct 23, 2024
b9cd64f
Added. More. Javadoc.
MatKuhr Oct 23, 2024
77b034b
Update sample HTML page
MatKuhr Oct 24, 2024
2ed94b8
Switch to error for empty content filter configs
MatKuhr Oct 24, 2024
ba1a575
Simplify Exception
MatKuhr Oct 25, 2024
8a92c34
Fix finish reason
MatKuhr Oct 28, 2024
ea5f239
Fix type of Orchestration Response Messages
MatKuhr Oct 28, 2024
1335c6f
Minor improvements
MatKuhr Oct 29, 2024
66fe024
Switch to deployment
MatKuhr Oct 29, 2024
1bd9088
Update pom for using OSS generator
Nov 5, 2024
561a201
Using original orchestration spec. Test contain descriminator
Nov 7, 2024
7cdf2c8
Used OpenAPI generator for orchestration
CharlesDuboisSAP Nov 7, 2024
0952513
Using discriminator spec everywhere. test success.
Nov 7, 2024
9afba08
LLMModule specific deserializer ready. Test success.
Nov 8, 2024
9387838
Generalized Fallback deserializer ready. Test success.
Nov 8, 2024
c466d52
OSS generation decoupled from new Orchestration Client
Nov 8, 2024
4750bb8
OSS generator on customer facing orchestration spec
Nov 8, 2024
bea4d49
Merge branch 'refs/heads/main' into openapi-generator-orchestration
Nov 8, 2024
72ce5fa
Merging changes from main
Nov 8, 2024
b956afb
Merging changes from main
Nov 8, 2024
219b12d
Merging changes from main
Nov 8, 2024
1d65868
Merging changes from main
Nov 8, 2024
8a97533
Formatting
bot-sdk-js Nov 8, 2024
caf40c0
Clean up
Nov 12, 2024
b49a4a1
Merge branch 'openapi-generator-orchestration' of /~https://github.com/…
Nov 12, 2024
68902f3
Update generated models
Nov 12, 2024
4e7eb97
Add test and improve deserializer
Nov 12, 2024
67840db
Merge branch 'main' into openapi-generator-orchestration
MatKuhr Nov 13, 2024
5b4cdf6
[Orchestration] OSS Generator Improvements (#158)
MatKuhr Nov 14, 2024
f8fb522
Reduce visibility of mixin class
a-d Nov 14, 2024
b119766
replace solution with custom deserializer to mixin
a-d Nov 14, 2024
8373d88
remove unnecessary dependency jackson-databind-nullable
a-d Nov 14, 2024
930fa75
Minor newline-reduction
a-d Nov 14, 2024
7885ba6
Minor syntax sugar
a-d Nov 14, 2024
bd4b10f
Merge branch 'main' into openapi-generator-orchestration
newtork Nov 14, 2024
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@
.java-version

### Environment ###
*.env
*.env

.openapi-generator
18 changes: 18 additions & 0 deletions orchestration/.openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# OpenAPI Generator Ignore

.github/
gradle/
.gitignore
.travis.yml
build.gradle
build.sbt
git_push.sh
gradle.properties
gradlew
gradlew.bat
pom.xml
README.md
settings.gradle
src/main/AndroidManifest.xml
api/
.openapi-generator/
76 changes: 59 additions & 17 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

<!-- TODO: only needed for JsonObjectMapperBuilder, maybe we can use Jackson natively to avoid this dependency -->
<dependency>
Expand Down Expand Up @@ -132,38 +140,72 @@
<build>
<plugins>
<plugin>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
<!-- skip automatic generation until we can omit API classes from code generation -->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<apiMaturity>beta</apiMaturity>
<enableOneOfAnyOfGeneration>true</enableOneOfAnyOfGeneration>
<compileScope>COMPILE</compileScope>
<deleteOutputDirectory>true</deleteOutputDirectory>
</configuration>
<executions>
<execution>
<id>orchestration</id>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<!-- Specify the input OpenAPI spec file -->
<inputSpec>${project.basedir}/src/main/resources/spec/orchestration.yaml</inputSpec>
<apiPackage>com.sap.ai.sdk.orchestration.client</apiPackage>
<output>${project.basedir}</output>
<!-- Specify the generator to use, e.g., java, spring, kotlin, etc. -->
<generatorName>java</generatorName>
<!-- Specify the package names for models, APIs, and invokers -->
<modelPackage>com.sap.ai.sdk.orchestration.client.model</modelPackage>
<additionalProperties>
<pojoBuilderMethodName>create</pojoBuilderMethodName>
<pojoBuildMethodName/>
<pojoConstructorVisibility>protected</pojoConstructorVisibility>
<apiPackage>com.sap.ai.sdk.orchestration.client.api</apiPackage>
<invokerPackage>com.sap.ai.sdk.orchestration.client.invoker</invokerPackage>

<!-- Global properties level; can be unpacked with 'generate' prefix-->
<globalProperties>
<apiDocs>false</apiDocs>
<modelDocs>false</modelDocs>
<modelTests>false</modelTests>
<apiTests>false</apiTests>
<minimalUpdate>true</minimalUpdate>
</globalProperties>

<!-- Generator Specific properties level; some can be unpacked-->
<configOptions>
<generateBuilders>true</generateBuilders>
<failOnUnknownProperties>false</failOnUnknownProperties>
<hideGenerationTimestamp>true</hideGenerationTimestamp>
<disallowAdditionalPropertiesIfNotPresent>false</disallowAdditionalPropertiesIfNotPresent>
<enumUnknownDefaultCase>true</enumUnknownDefaultCase>
</additionalProperties>
<useBeanValidation>false</useBeanValidation>
<useOneOfInterfaces>true</useOneOfInterfaces>
<additionalModelTypeAnnotations>@com.google.common.annotations.Beta</additionalModelTypeAnnotations>
</configOptions>
<generateModels>true</generateModels>
<generateSupportingFiles>false</generateSupportingFiles>
<generateApis>false</generateApis>
<library>resttemplate</library>
<!--<configHelp>true</configHelp>-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>${project.basedir}/src/main/java/com/sap/ai/sdk/orchestration/client</directory>
<includes>
<include>**/*</include>
</includes>
</fileset>
</filesets>
</configuration>
<executions>
<execution>
<id>delete-orchestration-generated-client</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.sap.ai.sdk.orchestration;

import com.sap.ai.sdk.orchestration.client.model.ChatMessage;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
import com.sap.ai.sdk.orchestration.client.model.Template;
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
import io.vavr.control.Option;
import java.util.ArrayList;
Expand All @@ -25,9 +27,9 @@ static CompletionPostRequest toCompletionPostRequest(
// subsequent requests
val configCopy = config.withTemplateConfig(template);

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

@Nonnull
Expand All @@ -60,7 +62,7 @@ static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig co

//noinspection DataFlowIssue the template is always non-null here
val moduleConfig =
ModuleConfigs.create()
new ModuleConfigs()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(config.getTemplateConfig());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sap.ai.sdk.orchestration;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultSynchronous;

/** Mixin to enforce a specific subtype to be deserialized always. */
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonDeserialize(as = LLMModuleResultSynchronous.class)
interface LLMModuleResultMixIn {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sap.ai.sdk.orchestration;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

/** Mixin to suppress @JsonTypeInfo for oneOf interfaces. */
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
interface NoTypeInfoMixin {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostResponse;
import com.sap.ai.sdk.orchestration.client.model.FilterConfig;
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResult;
import com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig;
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.client.model.ModuleResultsOutputUnmaskingInner;
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
Expand Down Expand Up @@ -40,6 +45,11 @@ public class OrchestrationClient {
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.mixIn(LLMModuleResult.class, LLMModuleResultMixIn.class)
.mixIn(ModuleResultsOutputUnmaskingInner.class, NoTypeInfoMixin.class)
.mixIn(FilterConfig.class, NoTypeInfoMixin.class)
.mixIn(MaskingProviderConfig.class, NoTypeInfoMixin.class)
.mixIn(TemplatingModuleConfig.class, NoTypeInfoMixin.class)
.build();
}

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

/**
* Convert the given prompt and config into a low-level request data object. The data object
* allows for further customization before sending the request.
*
* @param prompt The {@link OrchestrationPrompt} to generate a completion for.
* @param config The {@link OrchestrationConfig } configuration to use for the completion.
* @return The low-level request data object to send to orchestration.
*/
@Nonnull
public static CompletionPostRequest toCompletionPostRequest(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config);
}

/**
* Generate a completion for the given prompt.
*
Expand Down Expand Up @@ -118,20 +142,6 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques
return executeRequest(postRequest);
}

/**
* Convert the given prompt and config into a low-level request data object. The data object
* allows for further customization before sending the request.
*
* @param prompt The {@link OrchestrationPrompt} to generate a completion for.
* @param config The {@link OrchestrationConfig } configuration to use for the completion.
* @return The low-level request data object to send to orchestration.
*/
@Nonnull
public static CompletionPostRequest toCompletionPostRequest(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config);
}

@Nonnull
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class OrchestrationPrompt {
* @param message A user message.
*/
public OrchestrationPrompt(@Nonnull final String message) {
messages.add(ChatMessage.create().role("user").content(message));
messages.add(new ChatMessage().role("user").content(message));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,6 @@
class OrchestrationResponseHandler<T> implements HttpClientResponseHandler<T> {
@Nonnull private final Class<T> responseType;

/**
* Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
*
* @param response The response to process
* @return A model class instantiated from the response
* @throws OrchestrationClientException in case of a problem or the connection was aborted
*/
@Override
public T handleResponse(@Nonnull final ClassicHttpResponse response)
throws OrchestrationClientException {
if (response.getCode() >= 300) {
buildExceptionAndThrow(response);
}
val result = parseResponse(response);
log.debug("Received the following response from orchestration service: {}", result);
return result;
}

// The InputStream of the HTTP entity is closed by EntityUtils.toString
@SuppressWarnings("PMD.CloseResource")
@Nonnull
private T parseResponse(@Nonnull final ClassicHttpResponse response)
throws OrchestrationClientException {
final HttpEntity responseEntity = response.getEntity();
if (responseEntity == null) {
throw new OrchestrationClientException("Response from Orchestration service was empty.");
}
val content = getContent(responseEntity);
log.debug("Parsing response from JSON response: {}", content);
try {
return JACKSON.readValue(content, responseType);
} catch (final JsonProcessingException e) {
log.error("Failed to parse the following response from orchestration service: {}", content);
throw new OrchestrationClientException(
"Failed to parse response from orchestration service", e);
}
}

@Nonnull
private static String getContent(@Nonnull final HttpEntity entity) {
try {
Expand Down Expand Up @@ -123,4 +85,42 @@ static void parseErrorAndThrow(
"%s and error message: '%s'"
.formatted(baseException.getMessage(), maybeError.get().getMessage()));
}

/**
* Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
*
* @param response The response to process
* @return A model class instantiated from the response
* @throws OrchestrationClientException in case of a problem or the connection was aborted
*/
@Override
public T handleResponse(@Nonnull final ClassicHttpResponse response)
throws OrchestrationClientException {
if (response.getCode() >= 300) {
buildExceptionAndThrow(response);
}
val result = parseResponse(response);
log.debug("Received the following response from orchestration service: {}", result);
return result;
}

// The InputStream of the HTTP entity is closed by EntityUtils.toString
@SuppressWarnings("PMD.CloseResource")
@Nonnull
private T parseResponse(@Nonnull final ClassicHttpResponse response)
throws OrchestrationClientException {
final HttpEntity responseEntity = response.getEntity();
if (responseEntity == null) {
throw new OrchestrationClientException("Response from Orchestration service was empty.");
}
val content = getContent(responseEntity);
log.debug("Parsing response from JSON response: {}", content);
try {
return JACKSON.readValue(content, responseType);
} catch (final JsonProcessingException e) {
log.error("Failed to parse the following response from orchestration service: {}", content);
throw new OrchestrationClientException(
"Failed to parse response from orchestration service", e);
}
}
}
Loading