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

feat: Orchestration, Spring AI and Everything Else #96

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
2 changes: 2 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@
<!-- Do not delete the output directory because it contains non-generated code -->
<!-- The generated client is instead deleted by the maven-clean-plugin here above -->
<deleteOutputDirectory>false</deleteOutputDirectory>
<!-- TODO: revert or move to profile -->
<skip>true</skip>
</configuration>
<executions>
<execution>
Expand Down
88 changes: 70 additions & 18 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,88 @@
<dependencies>
<!-- scope "compile" -->
<dependency>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>openapi-core</artifactId>
<groupId>com.sap.ai.sdk</groupId>
<artifactId>core</artifactId>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>cloudplatform-connectivity</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>connectivity-apache-httpclient5</artifactId>
</dependency>

<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>cloudplatform-connectivity</artifactId>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- TODO: only needed for JsonObjectMapperBuilder, maybe we can use Jackson natively to avoid this dependency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>

<!-- optional dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<!-- scope "provided" -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- scope "test" -->
<dependency>
Expand All @@ -71,19 +127,14 @@
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.ai.sdk</groupId>
<artifactId>core</artifactId>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down Expand Up @@ -114,6 +165,7 @@
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${cloud-sdk.version}</version>
<configuration>
<skip>true</skip>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<apiMaturity>released</apiMaturity>
<enableOneOfAnyOfGeneration>true</enableOneOfAnyOfGeneration>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.sap.ai.sdk.orchestration;

import static com.sap.ai.sdk.orchestration.client.model.AzureThreshold.fromValue;

import com.sap.ai.sdk.orchestration.client.model.AzureContentSafety;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(fluent = true)
public class AzureContentFilter implements ContentFilter {
@Nullable private Setting hate;
@Nullable private Setting selfHarm;
@Nullable private Setting sexual;
@Nullable private Setting violence;

@RequiredArgsConstructor
public enum Setting {
VERY_STRICT(0),
STRICT(2),
MODERATE(4),
LENIENT(6);

private final int value;
}

@Nonnull
com.sap.ai.sdk.orchestration.client.model.FilterConfig toFilterConfigDTO() {
var dto = AzureContentSafety.create();
if (hate != null) {
dto.hate(fromValue(hate.value));
}
if (selfHarm != null) {
dto.selfHarm(fromValue(selfHarm.value));
}
if (sexual != null) {
dto.sexual(fromValue(sexual.value));
}
if (violence != null) {
dto.violence(fromValue(violence.value));
}
return com.sap.ai.sdk.orchestration.client.model.FilterConfig.create()
.type(com.sap.ai.sdk.orchestration.client.model.FilterConfig.TypeEnum.AZURE_CONTENT_SAFETY)
.config(dto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.sap.ai.sdk.orchestration;

public interface ContentFilter {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.sap.ai.sdk.orchestration;

import com.sap.ai.sdk.orchestration.client.model.LLMModuleConfig;
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
import io.vavr.control.Option;
import javax.annotation.Nonnull;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Data
@Setter(AccessLevel.PRIVATE)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DefaultOrchestrationConfig<T extends OrchestrationConfig<T>>
implements OrchestrationConfig<T> {

@Nonnull private Option<LLMModuleConfig> llmConfig = Option.none();
@Nonnull private Option<TemplatingModuleConfig> template = Option.none();
@Nonnull private Option<MaskingConfig> maskingConfig = Option.none();
@Nonnull private Option<ContentFilter> inputContentFilter = Option.none();
@Nonnull private Option<ContentFilter> outputContentFilter = Option.none();

@EqualsAndHashCode.Exclude
@ToString.Exclude
@Getter(AccessLevel.NONE)
@Nonnull
private final T wrapper;

@SuppressWarnings("unchecked")
private DefaultOrchestrationConfig() {
wrapper = (T) this;
}

/**
* Create a new instance of {@link DefaultOrchestrationConfig} to delegate to. This is useful when
* exposing the {@link OrchestrationConfig} in other objects, without re-implementing it. To
* maintain fluent API usage, the given wrapper object will be returned by the fluent methods,
* instead of this instance.
*
* @param wrapper The wrapper that delegates to this object.
* @param <T> The type of the wrapper object.
* @return The new instance.
* @see #standalone()
*/
@Nonnull
public static <T extends OrchestrationConfig<T>> DefaultOrchestrationConfig<T> asDelegateFor(
@Nonnull final T wrapper) {
return new DefaultOrchestrationConfig<>(wrapper);
}

/**
* Create an implementation without any object delegating to it. The fluent API will return this
* object itself.
*
* @return The new instance.
* @see #asDelegateFor(OrchestrationConfig)
*/
@Nonnull
public static DefaultOrchestrationConfig<?> standalone() {
return new DefaultOrchestrationConfig<>();
}

@Nonnull
@Override
public T withLlmConfig(@Nonnull final LLMModuleConfig llm) {
this.llmConfig = Option.some(llm);
return wrapper;
}

@Nonnull
@Override
public T withTemplate(@Nonnull final TemplatingModuleConfig template) {
this.template = Option.some(template);
return wrapper;
}

@Nonnull
@Override
public T withMaskingConfig(@Nonnull final MaskingConfig maskingConfig) {
this.maskingConfig = Option.some(maskingConfig);
return wrapper;
}

@Nonnull
@Override
public T withInputContentFilter(@Nonnull final ContentFilter filter) {
this.inputContentFilter = Option.some(filter);
return wrapper;
}

@Nonnull
@Override
public T withOutputContentFilter(@Nonnull final ContentFilter filter) {
this.outputContentFilter = Option.some(filter);
return wrapper;
}

/**
* Copy the configuration into the given target configuration. The copy is
* <strong>shallow</strong> and does <strong>not override</strong> any existing configuration.
*
* <p>This has two main use cases:
*
* <ol>
* <li>Duplicating a config
* <li>Applying defaults to a config
* </ol>
*
* @param source The source configuration to copy from.
* @return This (delegate) object.
*/
@Nonnull
public DefaultOrchestrationConfig<T> copyFrom(@Nonnull final OrchestrationConfig<?> source) {
llmConfig.orElse(source::getLlmConfig).forEach(this::withLlmConfig);
template.orElse(source::getTemplate).forEach(this::withTemplate);
maskingConfig.orElse(source::getMaskingConfig).forEach(this::withMaskingConfig);
inputContentFilter.orElse(source::getInputContentFilter).forEach(this::withInputContentFilter);
outputContentFilter
.orElse(source::getOutputContentFilter)
.forEach(this::withOutputContentFilter);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.sap.ai.sdk.orchestration;

import static com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig.MethodEnum.ANONYMIZATION;
import static com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig.MethodEnum.PSEUDONYMIZATION;
import static com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig.TypeEnum.SAP_DATA_PRIVACY_INTEGRATION;

import com.sap.ai.sdk.orchestration.client.model.DPIEntities;
import com.sap.ai.sdk.orchestration.client.model.DPIEntityConfig;
import com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig;
import java.util.List;
import javax.annotation.Nonnull;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;

@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DpiMaskingConfig implements MaskingConfig {
@Nonnull MaskingProviderConfig.MethodEnum maskingMethod;
@Nonnull List<DPIEntities> entities;

@Nonnull
public static Builder forAnonymization() {
return new DpiMaskingConfig.Builder(ANONYMIZATION);
}

@Nonnull
public static Builder forPseudonymization() {
return new DpiMaskingConfig.Builder(PSEUDONYMIZATION);
}

@Nonnull
MaskingProviderConfig toMaskingProviderDTO() {
var entities = this.entities.stream().map(it -> DPIEntityConfig.create().type(it)).toList();
return MaskingProviderConfig.create()
.type(SAP_DATA_PRIVACY_INTEGRATION)
.method(maskingMethod)
.entities(entities);
}

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Builder {
private final MaskingProviderConfig.MethodEnum maskingMethod;

@Nonnull
public DpiMaskingConfig withEntities(@Nonnull final DPIEntities... entities) {
return new DpiMaskingConfig(maskingMethod, List.of(entities));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.sap.ai.sdk.orchestration;

public interface MaskingConfig {}
Loading