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 32 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
22 changes: 22 additions & 0 deletions orchestration/.openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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/


src/main/java/com/sap/ai/sdk/orchestration/client/model/LLMModuleResult.java
src/main/java/com/sap/ai/sdk/orchestration/client/model/ModuleResultsOutputUnmaskingInner.java
111 changes: 65 additions & 46 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,54 +118,73 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>

<!-- Additional dependencies for OSS code generator resttemplate -->
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
newtork marked this conversation as resolved.
Show resolved Hide resolved
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
newtork marked this conversation as resolved.
Show resolved Hide resolved
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>

<profiles>
<profile>
<id>generate</id>
<activation>
<activeByDefault>false</activeByDefault>
<property>
<name>generate</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
<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>
<!-- Specify the input OpenAPI spec file -->
<inputSpec>${project.basedir}/src/main/resources/spec/orchestration.yaml</inputSpec>
<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>
<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>
<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>
<executions>
<execution>
<id>orchestration</id>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/spec/orchestration.yaml</inputSpec>
<apiPackage>com.sap.ai.sdk.orchestration.client</apiPackage>
<modelPackage>com.sap.ai.sdk.orchestration.client.model</modelPackage>
<additionalProperties>
<pojoBuilderMethodName>create</pojoBuilderMethodName>
<pojoBuildMethodName/>
<pojoConstructorVisibility>protected</pojoConstructorVisibility>
<enumUnknownDefaultCase>true</enumUnknownDefaultCase>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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 +26,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 +43,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 = Option.of(template).map(t -> ((Template) t).getTemplate()).getOrElse(List::of);
newtork marked this conversation as resolved.
Show resolved Hide resolved
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 +61,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,82 @@
package com.sap.ai.sdk.orchestration;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResult;
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultStreaming;
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultSynchronous;
import java.io.IOException;
import javax.annotation.Nonnull;

/**
* A deserializer for {@link LLMModuleResult} that determines the concrete implementation based on
* the structure of the JSON object.
*/
public class LLMModuleResultDeserializer extends StdDeserializer<LLMModuleResult> {
newtork marked this conversation as resolved.
Show resolved Hide resolved

public LLMModuleResultDeserializer() {
super(LLMModuleResult.class);
}

/**
* Deserialize the JSON object into one of the subtypes of the base type.
*
* <ul>
* <li>If elements of "choices" array contains "delta", deserialize into {@link
* LLMModuleResultStreaming}.
* <li>Otherwise, deserialize into {@link LLMModuleResultSynchronous}.
* </ul>
*
* @param parser The JSON parser.
* @param context The deserialization context.
* @return The deserialized object.
* @throws IOException If an I/O error occurs.
*/
@Override
public LLMModuleResult deserialize(JsonParser parser, @Nonnull DeserializationContext context)
throws IOException {

// Check if the target type is a concrete class
JavaType targetType = context.getContextualType();
if (targetType != null && !LLMModuleResult.class.equals(targetType.getRawClass())) {
return delegateToDefaultDeserializer(parser, context, targetType);
}

// Custom deserialization logic for LLMModuleResult interface
var mapper = (ObjectMapper) parser.getCodec();
var rootNode = mapper.readTree(parser);
Class<? extends LLMModuleResult> concreteClass = LLMModuleResultSynchronous.class;

// Inspect the "choices" field
var choicesNode = rootNode.get("choices");
if (choicesNode != null && choicesNode.isArray()) {
var firstChoice = (JsonNode) choicesNode.get(0);
if (firstChoice != null && firstChoice.has("delta")) {
concreteClass = LLMModuleResultStreaming.class;
}
}

// Create a new parser for the root node
var rootParser = rootNode.traverse(mapper);
rootParser.nextToken(); // Advance to the first token

// Use the default deserializer for the concrete class
return delegateToDefaultDeserializer(rootParser, context, mapper.constructType(concreteClass));
}

/**
* Delegate deserialization to the default deserializer for the given concrete type.
*
* @param parser The JSON parser.
* @param context The deserialization context.
* @param concreteType The concrete type to deserialize into.
* @return The deserialized object.
* @throws IOException If an I/O error occurs.
*/
private LLMModuleResult delegateToDefaultDeserializer(
JsonParser parser, DeserializationContext context, JavaType concreteType) throws IOException {
var defaultDeserializer = context.findRootValueDeserializer(concreteType);
return (LLMModuleResult) defaultDeserializer.deserialize(parser, context);
}
newtork marked this conversation as resolved.
Show resolved Hide resolved
}
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)
public interface NoTypeInfoMixin {}
newtork marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
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.GroundingModuleConfigConfigFiltersInner;
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.OrchestrationConfig;
import com.sap.ai.sdk.orchestration.client.model.TemplateRefTemplateRef;
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 +46,12 @@ public class OrchestrationClient {
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.deserializerByType(LLMModuleResult.class, new LLMModuleResultDeserializer())
.mixIn(FilterConfig.class, NoTypeInfoMixin.class)
.mixIn(GroundingModuleConfigConfigFiltersInner.class, NoTypeInfoMixin.class)
.mixIn(MaskingProviderConfig.class, NoTypeInfoMixin.class)
.mixIn(TemplateRefTemplateRef.class, NoTypeInfoMixin.class)
.mixIn(TemplatingModuleConfig.class, NoTypeInfoMixin.class)
.build();
}

Expand All @@ -66,6 +78,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 +144,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);
}

@SuppressWarnings("UnstableApiUsage")
@Nonnull
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
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
Loading