Skip to content

Commit e25f9d4

Browse files
committed
Refactor AiCoreService class
1 parent ebf749d commit e25f9d4

30 files changed

+308
-400
lines changed

core/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@
156156
<plugin>
157157
<groupId>com.sap.cloud.sdk.datamodel</groupId>
158158
<artifactId>openapi-generator-maven-plugin</artifactId>
159+
<version>5.15.0-SNAPSHOT</version>
159160
<configuration>
160-
<skip>true</skip>
161+
<!-- <skip>true</skip>-->
161162
<!-- TODO: remove this once Cloud SDK 5.15.0 is released -->
162163
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
163164
<apiMaturity>released</apiMaturity>

core/src/main/java/com/sap/ai/sdk/core/AiCoreDeployment.java

Lines changed: 0 additions & 45 deletions
This file was deleted.

core/src/main/java/com/sap/ai/sdk/core/AiCoreDestination.java

Lines changed: 0 additions & 29 deletions
This file was deleted.

core/src/main/java/com/sap/ai/sdk/core/AiCoreService.java

Lines changed: 82 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77
import com.google.common.collect.Iterables;
88
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
99
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
10-
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
11-
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationProperty;
1210
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
1311
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
1412
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
1513
import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient;
1614
import java.util.NoSuchElementException;
17-
import java.util.function.BiFunction;
18-
import java.util.function.Function;
15+
import java.util.function.Supplier;
1916
import javax.annotation.Nonnull;
17+
import lombok.AccessLevel;
18+
import lombok.Getter;
19+
import lombok.RequiredArgsConstructor;
2020
import lombok.extern.slf4j.Slf4j;
2121
import lombok.val;
2222
import org.springframework.http.client.BufferingClientHttpRequestFactory;
@@ -25,123 +25,93 @@
2525
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2626
import org.springframework.web.client.RestTemplate;
2727

28-
/** Connectivity convenience methods for AI Core. */
28+
/**
29+
* Connectivity convenience methods for AI Core, offering convenient access to destinations
30+
* targeting the AI Core service. Loads base destinations from the environment or allows for setting
31+
* a custom base destination.
32+
*/
2933
@Slf4j
30-
public class AiCoreService implements AiCoreDestination {
31-
static final String AI_CLIENT_TYPE_KEY = "URL.headers.AI-Client-Type";
32-
static final String AI_CLIENT_TYPE_VALUE = "AI SDK Java";
33-
static final String AI_RESOURCE_GROUP = "URL.headers.AI-Resource-Group";
34+
@Getter(AccessLevel.PACKAGE)
35+
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
36+
public class AiCoreService {
37+
/** The default resource group. */
38+
public static final String DEFAULT_RESOURCE_GROUP = "default";
3439

35-
private static final DeploymentCache DEPLOYMENT_CACHE = new DeploymentCache();
40+
private static final String RESOURCE_GROUP_HEADER_PROPERTY = "URL.headers.AI-Resource-Group";
3641

37-
@Nonnull private final DestinationResolver destinationResolver;
38-
39-
@Nonnull private Function<AiCoreService, HttpDestination> baseDestinationHandler;
40-
@Nonnull private final BiFunction<AiCoreService, HttpDestination, ApiClient> clientHandler;
41-
42-
@Nonnull
43-
private final BiFunction<AiCoreService, HttpDestination, DefaultHttpDestination.Builder>
44-
builderHandler;
45-
46-
/** The resource group is defined by AiCoreDeployment.withResourceGroup(). */
47-
@Nonnull String resourceGroup;
48-
49-
/** The deployment id is set by AiCoreDeployment.destination() or AiCoreDeployment.client(). */
50-
@Nonnull String deploymentId;
42+
@Nonnull private final Supplier<HttpDestination> baseDestinationResolver;
43+
@Nonnull private final DeploymentResolver deploymentResolver;
5144

5245
/** The default constructor. */
5346
public AiCoreService() {
54-
this(new DestinationResolver());
55-
}
56-
57-
AiCoreService(@Nonnull final DestinationResolver destinationResolver) {
58-
this.destinationResolver = destinationResolver;
59-
baseDestinationHandler = AiCoreService::getBaseDestination;
60-
clientHandler = AiCoreService::buildApiClient;
61-
builderHandler = AiCoreService::getDestinationBuilder;
62-
resourceGroup = "default";
63-
deploymentId = "";
64-
}
65-
66-
@Nonnull
67-
@Override
68-
public ApiClient client() {
69-
val destination = destination();
70-
return clientHandler.apply(this, destination);
47+
val resolver = new DestinationResolver();
48+
this.baseDestinationResolver = resolver::getDestination;
49+
this.deploymentResolver = new DeploymentResolver(this);
7150
}
7251

73-
@Nonnull
74-
@Override
75-
public HttpDestination destination() {
76-
val dest = baseDestinationHandler.apply(this);
77-
val builder = builderHandler.apply(this, dest);
78-
if (!deploymentId.isEmpty()) {
79-
destinationSetUrl(builder, dest);
80-
destinationSetHeaders(builder);
81-
}
82-
return builder.build();
52+
AiCoreService(@Nonnull final Supplier<HttpDestination> baseDestinationResolver) {
53+
this.baseDestinationResolver = baseDestinationResolver;
54+
this.deploymentResolver = new DeploymentResolver(this);
8355
}
8456

8557
/**
86-
* Update and set the URL for the destination.
58+
* Set a specific base destination. This is useful when loading a destination from the BTP
59+
* destination service or some other source.
8760
*
88-
* @param builder The destination builder.
89-
* @param dest The original destination reference.
90-
*/
91-
protected void destinationSetUrl(
92-
@Nonnull final DefaultHttpDestination.Builder builder, @Nonnull final Destination dest) {
93-
String uri = dest.get(DestinationProperty.URI).get();
94-
if (!uri.endsWith("/")) {
95-
uri = uri + "/";
96-
}
97-
builder.uri(uri + "v2/inference/deployments/%s/".formatted(deploymentId));
98-
}
99-
100-
/**
101-
* Update and set the default request headers for the destination.
61+
* <p><b>Note:</b> For typical scenarios, the destination is expected to have the {@code /v2/}
62+
* base path set. But for special cases a different base path may be required (e.g. when consuming
63+
* AI Core via some proxy that expects a different base path).
10264
*
103-
* @param builder The destination builder.
65+
* @param destination The base destination to be used for AI Core service calls.
66+
* @return A new AI Core Service object using the provided destination as basis.
10467
*/
105-
protected void destinationSetHeaders(@Nonnull final DefaultHttpDestination.Builder builder) {
106-
builder.property(AI_RESOURCE_GROUP, resourceGroup);
68+
@Nonnull
69+
public AiCoreService withBaseDestination(@Nonnull final HttpDestination destination) {
70+
return new AiCoreService(() -> DestinationResolver.fromCustomBaseDestination(destination));
10771
}
10872

10973
/**
110-
* Set a specific base destination.
74+
* Get the base destination for AI Core service calls. This destination won't have any resource group set.
11175
*
112-
* @param destination The destination to be used for AI Core service calls.
113-
* @return The AI Core Service based on the provided destination.
76+
* @return The base destination.
77+
* @throws DestinationAccessException If there was an issue creating the base destination, e.g. in
78+
* case of invalid credentials.
79+
* @throws DestinationNotFoundException If there was an issue creating the base destination, e.g.
80+
* in case of missing credentials.
81+
* @see #withBaseDestination(HttpDestination)
11482
*/
11583
@Nonnull
116-
public AiCoreService withDestination(@Nonnull final HttpDestination destination) {
117-
baseDestinationHandler = service -> destination;
118-
return this;
84+
public HttpDestination getBaseDestination()
85+
throws DestinationAccessException, DestinationNotFoundException {
86+
return baseDestinationResolver.get();
11987
}
12088

12189
/**
122-
* Set a specific deployment by id.
90+
* Get a new endpoint object, targeting a specific deployment ID.
12391
*
124-
* @param deploymentId The deployment id to be used for AI Core service calls.
125-
* @return A new instance of the AI Core Deployment.
92+
* @param deploymentId The deployment id to be used for the new endpoint.
93+
* @return the new instance.
12694
*/
12795
@Nonnull
128-
public AiCoreDeployment forDeployment(@Nonnull final String deploymentId) {
129-
return new AiCoreDeployment(this, () -> deploymentId);
96+
public HttpDestination getDestinationForDeploymentById(
97+
@Nonnull final String resourceGroup, @Nonnull final String deploymentId) {
98+
return toInferenceDestination(resourceGroup, deploymentId);
13099
}
131100

132101
/**
133-
* Set a specific deployment by model. If there are multiple deployments of the same model, the
134-
* first one is returned.
102+
* Get a destination to perform inference calls for a specific model. If there are multiple
103+
* deployments of the same model, the first one is returned.
135104
*
136-
* @param model The model to be used for AI Core service calls.
105+
* @param model The model to be used for inference calls.
137106
* @return A new instance of the AI Core Deployment.
138107
* @throws NoSuchElementException if no running deployment is found for the model.
139108
*/
140109
@Nonnull
141-
public AiCoreDeployment forDeploymentByModel(@Nonnull final AiModel model)
110+
public HttpDestination getDestinationForDeploymentByModel(
111+
@Nonnull final String resourceGroup, @Nonnull final AiModel model)
142112
throws NoSuchElementException {
143-
return new AiCoreDeployment(
144-
this, () -> DEPLOYMENT_CACHE.getDeploymentIdByModel(this, resourceGroup, model));
113+
val deploymentId = deploymentResolver.getDeploymentIdByModel(resourceGroup, model);
114+
return toInferenceDestination(resourceGroup, deploymentId);
145115
}
146116

147117
/**
@@ -153,51 +123,20 @@ public AiCoreDeployment forDeploymentByModel(@Nonnull final AiModel model)
153123
* @throws NoSuchElementException if no running deployment is found for the scenario.
154124
*/
155125
@Nonnull
156-
public AiCoreDeployment forDeploymentByScenario(@Nonnull final String scenarioId)
126+
public HttpDestination getDestinationForDeploymentByScenario(
127+
@Nonnull final String resourceGroup, @Nonnull final String scenarioId)
157128
throws NoSuchElementException {
158-
return new AiCoreDeployment(
159-
this, () -> DEPLOYMENT_CACHE.getDeploymentIdByScenario(this, resourceGroup, scenarioId));
160-
}
161-
162-
/**
163-
* Get a destination using the default service binding loading logic.
164-
*
165-
* @return The destination.
166-
* @throws DestinationAccessException If the destination cannot be accessed.
167-
* @throws DestinationNotFoundException If the destination cannot be found.
168-
*/
169-
@Nonnull
170-
protected HttpDestination getBaseDestination()
171-
throws DestinationAccessException, DestinationNotFoundException {
172-
return destinationResolver.getDestination();
129+
val deploymentId = deploymentResolver.getDeploymentIdByScenario(resourceGroup, scenarioId);
130+
return toInferenceDestination(resourceGroup, deploymentId);
173131
}
174132

175-
/**
176-
* Get the destination builder with adjustments for AI Core.
177-
*
178-
* @param destination The destination.
179-
* @return The destination builder.
180-
*/
181133
@Nonnull
182-
protected DefaultHttpDestination.Builder getDestinationBuilder(
183-
@Nonnull final Destination destination) {
184-
val builder = DefaultHttpDestination.fromDestination(destination);
185-
String uri = destination.get(DestinationProperty.URI).get();
186-
if (!uri.endsWith("/")) {
187-
uri = uri + "/";
188-
}
189-
builder.uri(uri + "v2/").property(AI_CLIENT_TYPE_KEY, AI_CLIENT_TYPE_VALUE);
190-
return builder;
191-
}
134+
public ApiClient getApiClient() {
135+
val destination = getBaseDestination();
136+
val httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
137+
httpRequestFactory.setHttpClient(ApacheHttpClient5Accessor.getHttpClient(destination));
192138

193-
/**
194-
* Build an {@link ApiClient} that can be used for executing plain REST HTTP calls.
195-
*
196-
* @param destination The destination to use as basis for the client.
197-
* @return The new API client.
198-
*/
199-
@Nonnull
200-
protected ApiClient buildApiClient(@Nonnull final Destination destination) {
139+
val rt = new RestTemplate();
201140
val objectMapper =
202141
new Jackson2ObjectMapperBuilder()
203142
.modules(new JavaTimeModule())
@@ -206,17 +145,29 @@ protected ApiClient buildApiClient(@Nonnull final Destination destination) {
206145
.serializationInclusion(JsonInclude.Include.NON_NULL) // THIS STOPS `null` serialization
207146
.build();
208147

209-
val httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
210-
httpRequestFactory.setHttpClient(ApacheHttpClient5Accessor.getHttpClient(destination));
211-
212-
val rt = new RestTemplate();
213148
Iterables.filter(rt.getMessageConverters(), MappingJackson2HttpMessageConverter.class)
214149
.forEach(converter -> converter.setObjectMapper(objectMapper));
215150
rt.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
216151

217152
return new ApiClient(rt).setBasePath(destination.asHttp().getUri().toString());
218153
}
219154

155+
private HttpDestination toInferenceDestination(
156+
@Nonnull final String resourceGroup, @Nonnull final String deploymentId) {
157+
val destination = getBaseDestination();
158+
val path = buildDeploymentPath(deploymentId);
159+
160+
return DefaultHttpDestination.fromDestination(destination)
161+
.uri(destination.getUri().resolve(path))
162+
.property(RESOURCE_GROUP_HEADER_PROPERTY, resourceGroup)
163+
.build();
164+
}
165+
166+
@Nonnull
167+
private static String buildDeploymentPath(@Nonnull final String deploymentId) {
168+
return "inference/deployments/%s/".formatted(deploymentId);
169+
}
170+
220171
/**
221172
* Remove all entries from the cache then load all deployments into the cache.
222173
*
@@ -225,6 +176,6 @@ protected ApiClient buildApiClient(@Nonnull final Destination destination) {
225176
* @param resourceGroup the resource group of the deleted deployment, usually "default".
226177
*/
227178
public void reloadCachedDeployments(@Nonnull final String resourceGroup) {
228-
DEPLOYMENT_CACHE.resetCache(this, resourceGroup);
179+
new DeploymentResolver(this).resetCache(resourceGroup);
229180
}
230181
}

0 commit comments

Comments
 (0)