Skip to content
Open
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
14 changes: 10 additions & 4 deletions core-services/prompt-registry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
Expand Down Expand Up @@ -107,6 +103,16 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.environment.servicebinding.api</groupId>
<artifactId>java-access-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.environment.servicebinding.api</groupId>
<artifactId>java-core-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.google.common.annotations.Beta;
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.prompt.registry.client.OrchestrationConfigsApi;
import com.sap.ai.sdk.prompt.registry.model.AzureContentSafetyInputFilterConfig;
import com.sap.ai.sdk.prompt.registry.model.AzureContentSafetyOutputFilterConfig;
import com.sap.ai.sdk.prompt.registry.model.InputFilterConfig;
Expand All @@ -18,32 +16,10 @@
import lombok.NoArgsConstructor;
import lombok.val;

/**
* Client for managing Orchestration Configurations in the Prompt Registry service.
*
* @since 1.15.0
*/
@Beta
public class OrchestrationConfigClient extends OrchestrationConfigsApi {

/**
* Instantiates a client to manage Orchestration Configurations on the Prompt Registry service.
*/
public OrchestrationConfigClient() {
this(new AiCoreService());
}

/**
* Instantiates a client to manage Orchestration Configurations on the Prompt Registry service.
*
* @param aiCoreService The configured connectivity instance to AI Core
*/
public OrchestrationConfigClient(@Nonnull final AiCoreService aiCoreService) {
super(addMixin(aiCoreService));
}
class OrchestrationConfigMixin {

@Nonnull
private static ApiClient addMixin(@Nonnull final AiCoreService service) {
static ApiClient addMixin(@Nonnull final AiCoreService service) {
final var destination = service.getBaseDestination();

val objectMapper =
Expand All @@ -54,8 +30,8 @@ private static ApiClient addMixin(@Nonnull final AiCoreService service) {
return ApiClient.create(destination).withObjectMapper(objectMapper);
}

@NoArgsConstructor(access = AccessLevel.PRIVATE)
private static class JacksonMixin {
@NoArgsConstructor(access = AccessLevel.PACKAGE)
static class JacksonMixin {

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.prompt.registry.client.PromptTemplatesApi;
import com.sap.ai.sdk.prompt.registry.model.MultiChatContent;
import com.sap.ai.sdk.prompt.registry.model.MultiChatTemplate;
import com.sap.ai.sdk.prompt.registry.model.PromptTemplate;
Expand All @@ -28,34 +27,10 @@
import lombok.NoArgsConstructor;
import lombok.val;

/**
* Client for the Prompt Registry service.
*
* @since 1.6.0
*/
public class PromptClient extends PromptTemplatesApi {

/**
* Instantiates this a client to invoke operations on the Prompt Registry service.
*
* @since 1.6.0
*/
public PromptClient() {
this(new AiCoreService());
}

/**
* Instantiates this a client to invoke operations on the Prompt Registry service.
*
* @param aiCoreService The configured connectivity instance to AI Core
* @since 1.6.0
*/
public PromptClient(@Nonnull final AiCoreService aiCoreService) {
super(addMixin(aiCoreService));
}
class PromptClientMixin {

@Nonnull
private static ApiClient addMixin(@Nonnull final AiCoreService service) {
static ApiClient addMixin(@Nonnull final AiCoreService service) {
final var destination = service.getBaseDestination();

val objectMapper =
Expand All @@ -66,8 +41,8 @@ private static ApiClient addMixin(@Nonnull final AiCoreService service) {
return ApiClient.create(destination).withObjectMapper(objectMapper);
}

@NoArgsConstructor(access = AccessLevel.PRIVATE)
private static class JacksonMixin {
@NoArgsConstructor(access = AccessLevel.PACKAGE)
static class JacksonMixin {
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonDeserialize(using = PromptTemplateDeserializer.class)
interface TemplateMixIn {}
Expand All @@ -85,7 +60,7 @@ interface TemplateMixIn {}
interface ResponseFormat {}
}

private static class PromptTemplateDeserializer extends JsonDeserializer<PromptTemplate> {
static class PromptTemplateDeserializer extends JsonDeserializer<PromptTemplate> {

@Override
public PromptTemplate deserialize(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.sap.ai.sdk.prompt.registry;

import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.prompt.registry.client.OrchestrationConfigsApi;
import com.sap.ai.sdk.prompt.registry.client.PromptTemplatesApi;
import javax.annotation.Nonnull;

/**
* Unified client to use Prompt Registry API
*
* @since 2.0
*/
public class PromptRegistryClient {

private final AiCoreService aiCoreService;

/** Constructs default PromptRegistryClient */
public PromptRegistryClient() {
this(new AiCoreService());
}

/**
* Constructs PromptRegistryClient with customized AiCoreService
*
* @param service customized AiCoreService
*/
public PromptRegistryClient(@Nonnull final AiCoreService service) {
aiCoreService = service;
}

/**
* Get the prompt templates client
*
* @return the client
*/
@Nonnull
public PromptTemplatesApi prompt() {
return new PromptTemplatesApi(PromptClientMixin.addMixin(aiCoreService));
}

/**
* Get the orchestration configs client
*
* @return the client
*/
@Nonnull
public OrchestrationConfigsApi orchestration() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
public OrchestrationConfigsApi orchestration() {
public OrchestrationConfigsApi orchestrationConfig() {

return new OrchestrationConfigsApi(OrchestrationConfigMixin.addMixin(aiCoreService));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ public class OrchestrationConfigClientTest {
private static final WireMockExtension WM =
WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();

private static OrchestrationConfigClient client;
private static PromptRegistryClient client;

@BeforeEach
void setup() {
final HttpDestination destination = DefaultHttpDestination.builder(WM.baseUrl()).build();
final AiCoreService service = new AiCoreService().withBaseDestination(destination);
client = new OrchestrationConfigClient(service);
client = new PromptRegistryClient(service);
}

@Test
void testPipelines() {
final var result = client.listOrchestrationConfigs();
final var result = client.orchestration().listOrchestrationConfigs();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here

assertThat(result.getCount()).isEqualTo(2);
assertThat(result.getResources()).hasSize(2);
final var template = result.getResources().get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
Expand All @@ -14,10 +15,16 @@
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatText;
import com.sap.ai.sdk.prompt.registry.model.SingleChatTemplate;
import com.sap.ai.sdk.prompt.registry.model.TextContent;
import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingBuilder;
import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor;
import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier;
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand All @@ -27,18 +34,43 @@ class PromptRegistryClientTest {
private static final WireMockExtension WM =
WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();

private static PromptClient client;
private static PromptRegistryClient client;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could store the multiple clients here instead, like in GroundingUnitTest, it would avoid calling .prompt() repetitively.

private ServiceBindingAccessor originalAccessor;

@BeforeEach
void setup() {
originalAccessor = DefaultServiceBindingAccessor.getInstance();
final HttpDestination destination = DefaultHttpDestination.builder(WM.baseUrl()).build();
final AiCoreService service = new AiCoreService().withBaseDestination(destination);
client = new PromptClient(service);
client = new PromptRegistryClient(service);
}

@AfterEach
void teardown() {
DefaultServiceBindingAccessor.setInstance(originalAccessor);
}

@Test
void testDefaultConstructor() {
final var binding =
new DefaultServiceBindingBuilder()
.withServiceIdentifier(ServiceIdentifier.AI_CORE)
.withCredentials(
Map.of(
"clientid", "client-id",
"clientsecret", "client-secret",
"credential-type", "binding-secret",
"url", WM.baseUrl(),
"serviceurls", Map.of("AI_API_URL", WM.baseUrl())))
.build();
DefaultServiceBindingAccessor.setInstance(() -> List.of(binding));

assertThatCode(PromptRegistryClient::new).doesNotThrowAnyException();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are you testing the AiCoreService default constructor behaviour?
It is already tested in the core module

}

@Test
void testPipelines() {
final var result = client.listPromptTemplates();
final var result = client.prompt().listPromptTemplates();
assertThat(result.getCount()).isEqualTo(2);
assertThat(result.getResources()).hasSize(2);
final var template = result.getResources().get(0);
Expand All @@ -55,7 +87,7 @@ void testPipelines() {
@Test
void testGetTemplateWithResponseFormatText() {
final var uuid = UUID.fromString("22117a64-9f2c-481b-9402-8acb66eeb707");
final PromptTemplateGetResponse response = client.getPromptTemplateByUuid(uuid);
final PromptTemplateGetResponse response = client.prompt().getPromptTemplateByUuid(uuid);

assertThat(response.getName()).isEqualTo("test");
assertThat(response.getVersion()).isEqualTo("0.0.1");
Expand All @@ -70,7 +102,7 @@ void testGetTemplateWithResponseFormatText() {
@Test
void testGetTemplateWithResponseFormatJsonObject() {
final var uuid = UUID.fromString("21cb1358-0bf1-4f43-870b-00f14d0f9f16");
final var response = client.getPromptTemplateByUuid(uuid);
final var response = client.prompt().getPromptTemplateByUuid(uuid);

assertThat(response.getName()).isEqualTo("test");
assertThat(response.getVersion()).isEqualTo("0.0.1");
Expand All @@ -85,7 +117,7 @@ void testGetTemplateWithResponseFormatJsonObject() {
@Test
void testGetTemplateWithResponseFormatJsonSchema() {
final var uuid = UUID.fromString("0f79fec4-ae07-4c35-96e3-df7f4a3f1df5");
final var response = client.getPromptTemplateByUuid(uuid);
final var response = client.prompt().getPromptTemplateByUuid(uuid);

assertThat(response.getName()).isEqualTo("test");
assertThat(response.getVersion()).isEqualTo("0.0.1");
Expand All @@ -105,7 +137,7 @@ void testGetTemplateWithResponseFormatJsonSchema() {
@Test
void testGetTemplateWithMultiChatTemplate() {
final var uuid = UUID.fromString("8f79fec4-ae07-4c35-96e3-df7f4a3f1df5");
final var response = client.getPromptTemplateByUuid(uuid);
final var response = client.prompt().getPromptTemplateByUuid(uuid);

assertThat(response.getSpec()).isNotNull();
assertThat(response.getSpec().getTemplate()).hasSize(2);
Expand All @@ -126,15 +158,15 @@ void testGetTemplateWithMultiChatTemplate() {
void testGetTemplateWithInvalidRoleType() {
final var uuid = UUID.fromString("45cb1358-0bf1-4f43-870b-00f14d0f9f16");

assertThatThrownBy(() -> client.getPromptTemplateByUuid(uuid))
assertThatThrownBy(() -> client.prompt().getPromptTemplateByUuid(uuid))
.hasStackTraceContaining("PromptTemplate requires textual 'role' property.");
}

@Test
void testGetTemplateWithInvalidContentType() {
final var uuid = UUID.fromString("55cb1358-0bf1-4f43-870b-00f14d0f9f16");

assertThatThrownBy(() -> client.getPromptTemplateByUuid(uuid))
assertThatThrownBy(() -> client.prompt().getPromptTemplateByUuid(uuid))
.hasStackTraceContaining(
"PromptTemplate content must be either a string or an array, but found: BOOLEAN");
}
Expand All @@ -146,8 +178,10 @@ void testParsePromptTemplateHotPath() {
.inputParams(Map.of("inputExample", "I love football"));

final var response =
client.parsePromptTemplateByNameVersion(
"categorization", "0.0.1", "hotpath-serde", "default", null, false, request);
client
.prompt()
.parsePromptTemplateByNameVersion(
"categorization", "0.0.1", "hotpath-serde", "default", null, false, request);

assertThat(response.getParsedPrompt()).hasSize(2);
assertThat(response.getParsedPrompt().get(0)).isInstanceOf(SingleChatTemplate.class);
Expand Down
Loading