Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -8,29 +8,18 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.Beta;
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.core.DeploymentResolutionException;
import com.sap.ai.sdk.core.common.ClientResponseHandler;
import com.sap.ai.sdk.core.common.ClientStreamingHandler;
import com.sap.ai.sdk.core.common.StreamedDelta;
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.model.CompletionPostResponseSynchronous;
import com.sap.ai.sdk.orchestration.model.EmbeddingsPostRequest;
import com.sap.ai.sdk.orchestration.model.EmbeddingsPostResponse;
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException;
import java.io.IOException;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;

/** Client to execute requests to the orchestration service. */
@Slf4j
Expand All @@ -39,12 +28,13 @@ public class OrchestrationClient {

static final ObjectMapper JACKSON = getOrchestrationObjectMapper();

@Nonnull private final Supplier<HttpDestination> destinationSupplier;
private final OrchestrationHttpExecutor executor;

/** Default constructor. */
public OrchestrationClient() {
destinationSupplier =
final Supplier<HttpDestination> destinationSupplier =
() -> new AiCoreService().getInferenceDestination().forScenario(DEFAULT_SCENARIO);
this.executor = new OrchestrationHttpExecutor(destinationSupplier);
}

/**
Expand All @@ -64,7 +54,7 @@ public OrchestrationClient() {
*/
@Beta
public OrchestrationClient(@Nonnull final HttpDestination destination) {
this.destinationSupplier = () -> destination;
this.executor = new OrchestrationHttpExecutor(() -> destination);
}

/**
Expand Down Expand Up @@ -150,15 +140,7 @@ private static void throwOnContentFilter(@Nonnull final OrchestrationChatComplet
@Nonnull
public CompletionPostResponseSynchronous executeRequest(
@Nonnull final CompletionPostRequest request) throws OrchestrationClientException {
final String jsonRequest;
try {
jsonRequest = JACKSON.writeValueAsString(request);
log.debug("Serialized request into JSON payload: {}", jsonRequest);
} catch (final JsonProcessingException e) {
throw new OrchestrationClientException("Failed to serialize request parameters", e);
}

return executeRequest(jsonRequest);
return executor.execute("/completion", request, CompletionPostResponseSynchronous.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Minor)

static constant for /completion

}

/**
Expand Down Expand Up @@ -199,38 +181,8 @@ public OrchestrationChatResponse executeRequestFromJsonModuleConfig(
}
requestJson.set("orchestration_config", moduleConfigJson);

final String body;
try {
body = JACKSON.writeValueAsString(requestJson);
} catch (JsonProcessingException e) {
throw new OrchestrationClientException("Failed to serialize request to JSON", e);
}
return new OrchestrationChatResponse(executeRequest(body));
}

@Nonnull
CompletionPostResponseSynchronous executeRequest(@Nonnull final String request) {
val postRequest = new HttpPost("/completion");
postRequest.setEntity(new StringEntity(request, ContentType.APPLICATION_JSON));

try {
val destination = destinationSupplier.get();
log.debug("Using destination {} to connect to orchestration service", destination);
val client = ApacheHttpClient5Accessor.getHttpClient(destination);
val handler =
new ClientResponseHandler<>(
CompletionPostResponseSynchronous.class,
OrchestrationError.class,
OrchestrationClientException::new)
.objectMapper(JACKSON);
return client.execute(postRequest, handler);
} catch (DeploymentResolutionException
| DestinationAccessException
| DestinationNotFoundException
| HttpClientInstantiationException
| IOException e) {
throw new OrchestrationClientException("Failed to execute request", e);
}
return new OrchestrationChatResponse(
executor.execute("/completion", requestJson, CompletionPostResponseSynchronous.class));
}

/**
Expand All @@ -245,42 +197,20 @@ CompletionPostResponseSynchronous executeRequest(@Nonnull final String request)
public Stream<OrchestrationChatCompletionDelta> streamChatCompletionDeltas(
@Nonnull final CompletionPostRequest request) throws OrchestrationClientException {
request.getOrchestrationConfig().setStream(true);
return executeStream("/completion", request, OrchestrationChatCompletionDelta.class);
}

@Nonnull
private <D extends StreamedDelta> Stream<D> executeStream(
@Nonnull final String path,
@Nonnull final Object payload,
@Nonnull final Class<D> deltaType) {
final var request = new HttpPost(path);
serializeAndSetHttpEntity(request, payload);
return streamRequest(request, deltaType);
}

private static void serializeAndSetHttpEntity(
@Nonnull final BasicClassicHttpRequest request, @Nonnull final Object payload) {
try {
final var json = JACKSON.writeValueAsString(payload);
request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
} catch (final JsonProcessingException e) {
throw new OrchestrationClientException("Failed to serialize request parameters", e);
}
return executor.stream(request);
}

/**
* Generate embeddings for the given request.
*
* @param request the request containing the input text and other parameters.
* @return the response containing the embeddings.
* @throws OrchestrationClientException if the request fails
* @since 1.9.0
*/
@Nonnull
private <D extends StreamedDelta> Stream<D> streamRequest(
final BasicClassicHttpRequest request, @Nonnull final Class<D> deltaType) {
try {
val destination = destinationSupplier.get();
log.debug("Using destination {} to connect to orchestration service", destination);
val client = ApacheHttpClient5Accessor.getHttpClient(destination);
return new ClientStreamingHandler<>(
deltaType, OrchestrationError.class, OrchestrationClientException::new)
.objectMapper(JACKSON)
.handleStreamingResponse(client.executeOpen(null, request, null));
} catch (final IOException e) {
throw new OrchestrationClientException("Request to the Orchestration service failed", e);
}
public EmbeddingsPostResponse embed(@Nonnull final EmbeddingsPostRequest request)
throws OrchestrationClientException {
return executor.execute("/v2/embeddings", request, EmbeddingsPostResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.sap.ai.sdk.orchestration;

import static com.sap.ai.sdk.orchestration.OrchestrationJacksonConfiguration.getOrchestrationObjectMapper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.ai.sdk.core.DeploymentResolutionException;
import com.sap.ai.sdk.core.common.ClientResponseHandler;
import com.sap.ai.sdk.core.common.ClientStreamingHandler;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException;
import java.io.IOException;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;

@Slf4j
class OrchestrationHttpExecutor {
private final Supplier<HttpDestination> destinationSupplier;
private static final ObjectMapper JACKSON = getOrchestrationObjectMapper();

OrchestrationHttpExecutor(@Nonnull final Supplier<HttpDestination> destinationSupplier)
throws OrchestrationClientException {
this.destinationSupplier = destinationSupplier;
}
Comment on lines +31 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question)

Why no lombok constructor? :)


@Nonnull
<T> T execute(
@Nonnull final String path,
@Nonnull final Object payload,
@Nonnull final Class<T> responseType) {
try {
val json = JACKSON.writeValueAsString(payload);
log.debug("Serialized request into JSON payload: {}", json);
val request = new HttpPost(path);
request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));

val client = getHttpClient();

val handler =
new ClientResponseHandler<>(
responseType, OrchestrationError.class, OrchestrationClientException::new)
.objectMapper(JACKSON);
return client.execute(request, handler);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

} catch (JsonProcessingException e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question)

Don't these need final?

throw new OrchestrationClientException("Failed to serialize request payload for " + path, e);
} catch (DeploymentResolutionException
| DestinationAccessException
| DestinationNotFoundException
| HttpClientInstantiationException
| IOException e) {
throw new OrchestrationClientException(
"Request to Orchestration service failed for " + path, e);
}
}

@Nonnull
Stream<OrchestrationChatCompletionDelta> stream(@Nonnull final Object payload) {
try {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

val json = JACKSON.writeValueAsString(payload);
val request = new HttpPost("/completion");
request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
val client = getHttpClient();

return new ClientStreamingHandler<>(
OrchestrationChatCompletionDelta.class,
OrchestrationError.class,
OrchestrationClientException::new)
.objectMapper(JACKSON)
.handleStreamingResponse(client.executeOpen(null, request, null));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

} catch (JsonProcessingException e) {
throw new OrchestrationClientException(
"Failed to serialize payload for streaming request", e);
} catch (IOException e) {
throw new OrchestrationClientException(
"Streaming request to the Orchestration service failed", e);
}
}

@Nonnull
private HttpClient getHttpClient() {
val destination = destinationSupplier.get();
log.debug("Using destination {} to connect to orchestration service", destination);
return ApacheHttpClient5Accessor.getHttpClient(destination);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Internal Orchestration Service API
* Orchestration is an inference service which provides common additional capabilities for business AI scenarios, such as content filtering and data masking. At the core of the service is the LLM module which allows for an easy, harmonized access to the language models of gen AI hub. The service is designed to be modular and extensible, allowing for the addition of new modules in the future. Each module can be configured independently and at runtime, allowing for a high degree of flexibility in the orchestration of AI services.
*
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package com.sap.ai.sdk.orchestration.model;

import java.math.BigDecimal;
import java.util.List;
import javax.annotation.Nonnull;

/** Embedding */
public interface Embedding {
/** Helper class to create a String that implements {@link Embedding}. */
record InnerString(@com.fasterxml.jackson.annotation.JsonValue @Nonnull String value)
implements Embedding {}

/**
* Creator to enable deserialization of a String.
*
* @param val the value to use
* @return a new instance of {@link InnerString}.
*/
@com.fasterxml.jackson.annotation.JsonCreator
@Nonnull
static InnerString create(@Nonnull final String val) {
return new InnerString(val);
}

/** Helper class to create a list of BigDecimal that implements {@link Embedding}. */
record InnerBigDecimals(
@com.fasterxml.jackson.annotation.JsonValue @Nonnull List<BigDecimal> values)
implements Embedding {}

/**
* Creator to enable deserialization of a list of BigDecimal.
*
* @param val the value to use
* @return a new instance of {@link InnerBigDecimals}.
*/
@com.fasterxml.jackson.annotation.JsonCreator
@Nonnull
static InnerBigDecimals create(@Nonnull final List<BigDecimal> val) {
return new InnerBigDecimals(val);
}
}
Loading
Loading