Skip to content

Commit

Permalink
Added deployment cache
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesDuboisSAP committed Sep 6, 2024
1 parent d4ba51c commit 163212e
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 85 deletions.
88 changes: 4 additions & 84 deletions core/src/main/java/com/sap/ai/sdk/core/Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.sap.ai.sdk.core.client.DeploymentApi;
import com.sap.ai.sdk.core.client.model.AiDeployment;
import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingBuilder;
import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor;
Expand All @@ -25,8 +23,6 @@
import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -49,31 +45,8 @@ public class Core {
@Nonnull
public static ApiClient getOrchestrationClient(@Nonnull final String resourceGroup) {
return getClient(
getDestinationForDeployment(getOrchestrationDeployment(resourceGroup), resourceGroup));
}

/**
* Get the deployment id from the scenario id. If there are multiple deployments of the same
* scenario id, the first one is returned.
*
* @param resourceGroup the resource group.
* @return the deployment id
* @throws NoSuchElementException if no deployment is found for the scenario id.
*/
private static String getOrchestrationDeployment(@Nonnull final String resourceGroup)
throws NoSuchElementException {
final var deployments =
new DeploymentApi(getClient())
.deploymentQuery(
resourceGroup, null, null, "orchestration", "RUNNING", null, null, null);

return deployments.getResources().stream()
.map(AiDeployment::getId)
.findFirst()
.orElseThrow(
() ->
new NoSuchElementException(
"No running deployment found with scenario id \"orchestration\""));
getDestinationForDeployment(
DeploymentCache.getDeploymentId(resourceGroup, "orchestration"), resourceGroup));
}

/**
Expand Down Expand Up @@ -245,61 +218,8 @@ public static Destination getDestinationForDeployment(
*/
@Nonnull
public static Destination getDestinationForModel(
@Nonnull final String modelName, @Nonnull final String resourceGroup) {
@Nonnull final String resourceGroup, @Nonnull final String modelName) {
return getDestinationForDeployment(
getDeploymentForModel(modelName, resourceGroup), resourceGroup);
}

/**
* Get the deployment id from the model name. If there are multiple deployments of the same model,
* the first one is returned.
*
* @param modelName the model name.
* @param resourceGroup the resource group.
* @return the deployment id
* @throws NoSuchElementException if no deployment is found for the model name.
*/
private static String getDeploymentForModel(
@Nonnull final String modelName, @Nonnull final String resourceGroup)
throws NoSuchElementException {
final var deployments =
new DeploymentApi(getClient())
.deploymentQuery(
resourceGroup, null, null, "foundation-models", "RUNNING", null, null, null);

return deployments.getResources().stream()
.filter(deployment -> isDeploymentOfModel(modelName, deployment))
.map(AiDeployment::getId)
.findFirst()
.orElseThrow(
() ->
new NoSuchElementException(
"No running deployment found with model name " + modelName));
}

/** This exists because getBackendDetails() is broken */
private static boolean isDeploymentOfModel(
@Nonnull final String modelName, @Nonnull final AiDeployment deployment) {
final var deploymentDetails = deployment.getDetails();
// The AI Core specification doesn't mention that this is nullable, but it can be.
// Remove this check when the specification is fixed.
if (deploymentDetails == null) {
return false;
}
final var resources = deploymentDetails.getResources();
if (resources == null) {
return false;
}
if (!resources.getCustomFieldNames().contains("backend_details")) {
return false;
}
final var detailsObject = resources.getCustomField("backend_details");

if (detailsObject instanceof Map<?, ?> details
&& details.get("model") instanceof Map<?, ?> model
&& model.get("name") instanceof String name) {
return modelName.equals(name);
}
return false;
DeploymentCache.getDeploymentId(resourceGroup, modelName), resourceGroup);
}
}
123 changes: 123 additions & 0 deletions core/src/main/java/com/sap/ai/sdk/core/DeploymentCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.sap.ai.sdk.core;

import static com.sap.ai.sdk.core.Core.getClient;

import com.sap.ai.sdk.core.client.DeploymentApi;
import com.sap.ai.sdk.core.client.model.AiDeployment;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;

class DeploymentCache {
private static final DeploymentApi API = new DeploymentApi(getClient());

/**
* Cache for deployment ids. The key is the scenario + model name and the value is the deployment
* id.
*/
private static final Map<String, String> CACHE = new HashMap<>();

static {
final var deployments = API.deploymentQuery("default").getResources();
deployments.forEach(
deployment ->
CACHE.put(deployment.getScenarioId() + getModelName(deployment), deployment.getId()));
}

/**
* Get the deployment id for the given scenario or model name.
*
* @param resourceGroup the resource group, usually "default".
* @param name the scenario or model name.
* @return the deployment id.
*/
public static String getDeploymentId(
@Nonnull final String resourceGroup, @Nonnull final String name) {
return CACHE.computeIfAbsent(
name,
n -> {
if ("orchestration".equals(n)) {
return getOrchestrationDeployment(resourceGroup);
} else {
return getDeploymentForModel(resourceGroup, name);
}
});
}

/**
* Get the deployment id from the scenario id. If there are multiple deployments of the same
* scenario id, the first one is returned.
*
* @param resourceGroup the resource group.
* @return the deployment id
* @throws NoSuchElementException if no deployment is found for the scenario id.
*/
private static String getOrchestrationDeployment(@Nonnull final String resourceGroup)
throws NoSuchElementException {
final var deployments =
new DeploymentApi(getClient())
.deploymentQuery(
resourceGroup, null, null, "orchestration", "RUNNING", null, null, null);

return deployments.getResources().stream()
.map(AiDeployment::getId)
.findFirst()
.orElseThrow(
() ->
new NoSuchElementException(
"No running deployment found with scenario id \"orchestration\""));
}

/**
* Get the deployment id from the model name. If there are multiple deployments of the same model,
* the first one is returned.
*
* @param modelName the model name.
* @param resourceGroup the resource group.
* @return the deployment id
* @throws NoSuchElementException if no deployment is found for the model name.
*/
private static String getDeploymentForModel(
@Nonnull final String resourceGroup, @Nonnull final String modelName)
throws NoSuchElementException {
final var deployments =
new DeploymentApi(getClient())
.deploymentQuery(
resourceGroup, null, null, "foundation-models", "RUNNING", null, null, null);

return deployments.getResources().stream()
.filter(deployment -> modelName.equals(getModelName(deployment)))
.map(AiDeployment::getId)
.findFirst()
.orElseThrow(
() ->
new NoSuchElementException(
"No running deployment found with model name " + modelName));
}

/** This exists because getBackendDetails() is broken */
private static String getModelName(@Nonnull final AiDeployment deployment) {
final var deploymentDetails = deployment.getDetails();
// The AI Core specification doesn't mention that this is nullable, but it can be.
// Remove this check when the specification is fixed.
if (deploymentDetails == null) {
return "";
}
final var resources = deploymentDetails.getResources();
if (resources == null) {
return "";
}
if (!resources.getCustomFieldNames().contains("backend_details")) {
return "";
}
final var detailsObject = resources.getCustomField("backend_details");

if (detailsObject instanceof Map<?, ?> details
&& details.get("model") instanceof Map<?, ?> model
&& model.get("name") instanceof String name) {
return name;
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public final class OpenAiClient {
*/
@Nonnull
public static OpenAiClient forModel(@Nonnull final OpenAiModel foundationModel) {
final var destination = Core.getDestinationForModel(foundationModel.model(), "default");
final var destination = Core.getDestinationForModel("default", foundationModel.model());
final var client = new OpenAiClient(destination);
return client.withApiVersion(DEFAULT_API_VERSION);
}
Expand Down

0 comments on commit 163212e

Please sign in to comment.