From 5421ac1f2241eba14bc132d9117cb8a8564b8f5a Mon Sep 17 00:00:00 2001 From: sirivarma Date: Thu, 6 Mar 2025 11:10:32 -0800 Subject: [PATCH 01/23] Conversation first commit Signed-off-by: Siri Varma Vegiraju Signed-off-by: sirivarma Signed-off-by: siri-varma --- examples/pom.xml | 5 + .../conversation/DemoConversationAI.java | 32 ++++ pom.xml | 1 + sdk-ai/pom.xml | 164 ++++++++++++++++++ .../java/io/dapr/ai/client/DaprAiClient.java | 29 ++++ .../ai/client/DaprConversationClient.java | 164 ++++++++++++++++++ .../dapr/ai/client/DaprConversationInput.java | 66 +++++++ .../ai/client/DaprConversationOutput.java | 43 +++++ .../ai/client/DaprConversationResponse.java | 52 ++++++ .../dapr/ai/client/DaprConversationRole.java | 13 ++ sdk-ai/src/test/java/io/dapr/ai/AITest.java | 14 ++ 11 files changed, 583 insertions(+) create mode 100644 examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java create mode 100644 sdk-ai/pom.xml create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java create mode 100644 sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java create mode 100644 sdk-ai/src/test/java/io/dapr/ai/AITest.java diff --git a/examples/pom.xml b/examples/pom.xml index 192c7f2f7f..a3b16449ee 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -118,6 +118,11 @@ dapr-sdk ${project.version} + + io.dapr + dapr-sdk-ai + ${project.version} + com.evanlennick retry4j diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java new file mode 100644 index 0000000000..7d876c030c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -0,0 +1,32 @@ +package io.dapr.examples.conversation; + +import io.dapr.ai.client.DaprConversationClient; +import io.dapr.ai.client.DaprConversationInput; +import io.dapr.ai.client.DaprConversationResponse; +import io.dapr.v1.DaprProtos; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.Collections; + +public class DemoConversationAI { + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprConversationClient client = new DaprConversationClient(null)) { + DaprConversationInput daprConversationInput = new DaprConversationInput("11"); + + // Component name is the name provided in the metadata block of the conversation.yaml file. + Mono instanceId = client.converse("openai", new ArrayList<>(Collections.singleton(daprConversationInput)), "1234", false, 0.0d); + System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId); + DaprConversationResponse response = instanceId.block(); + + System.out.println(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 51f5070027..8a58a2991a 100644 --- a/pom.xml +++ b/pom.xml @@ -501,6 +501,7 @@ sdk-autogen sdk sdk-actors + sdk-ai sdk-workflows sdk-springboot dapr-spring diff --git a/sdk-ai/pom.xml b/sdk-ai/pom.xml new file mode 100644 index 0000000000..fe42edfea6 --- /dev/null +++ b/sdk-ai/pom.xml @@ -0,0 +1,164 @@ + + 4.0.0 + + + io.dapr + dapr-sdk-parent + 1.15.0-SNAPSHOT + + + dapr-sdk-ai + jar + 1.15.0-SNAPSHOT + dapr-sdk-ai + SDK for AI on Dapr + + + false + + + + + io.dapr + dapr-sdk + ${project.parent.version} + + + io.dapr + dapr-sdk-autogen + 1.14.0-SNAPSHOT + compile + + + org.mockito + mockito-core + test + + + org.mockito + mockito-inline + 4.2.0 + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.vintage + junit-vintage-engine + 5.7.0 + test + + + com.microsoft + durabletask-client + 1.5.0 + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + default-prepare-agent + + prepare-agent + + + + report + test + + report + + + target/jacoco-report/ + + + + check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 80% + + + + + + + + + + + + diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java new file mode 100644 index 0000000000..193132e130 --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java @@ -0,0 +1,29 @@ +package io.dapr.ai.client; + +import reactor.core.publisher.Mono; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * Defines client operations for managing Dapr AI instances. + */ +interface DaprAiClient { + + /** + * Method to call the Dapr Converse API. + * + * @param conversationComponentName name for the conversation component. + * @param daprConversationInputs prompts that are part of the conversation. + * @param contextId identifier of an existing chat (like in ChatGPT) + * @param scrubPii data that comes from the LLM. + * @param temperature to optimize from creativity or predictability. + * @return @ConversationResponse. + */ + Mono converse( + String conversationComponentName, + List daprConversationInputs, + @Nullable String contextId, + boolean scrubPii, + double temperature); +} diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java new file mode 100644 index 0000000000..1f78a2cc5f --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java @@ -0,0 +1,164 @@ +package io.dapr.ai.client; + +import com.google.protobuf.Any; +import io.dapr.client.resiliency.ResiliencyOptions; +import io.dapr.config.Properties; +import io.dapr.exceptions.DaprException; +import io.dapr.internal.exceptions.DaprHttpException; +import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor; +import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor; +import io.dapr.internal.resiliency.RetryPolicy; +import io.dapr.internal.resiliency.TimeoutPolicy; +import io.dapr.utils.NetworkUtils; +import io.dapr.v1.DaprGrpc; +import io.dapr.v1.DaprProtos; +import io.grpc.ManagedChannel; +import io.grpc.stub.StreamObserver; +import org.jetbrains.annotations.Nullable; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.util.context.ContextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class DaprConversationClient implements AutoCloseable, DaprAiClient { + + /** + * Stub that has the method to call the conversation apis. + */ + private final DaprGrpc.DaprStub asyncStub; + + /** + * The GRPC managed channel to be used. + */ + private final ManagedChannel channel; + + /** + * The retry policy. + */ + private final RetryPolicy retryPolicy; + + /** + * The timeout policy. + */ + private final TimeoutPolicy timeoutPolicy; + + /** + * ConversationClient constructor. + * + * @param resiliencyOptions timeout and retry policies. + */ + public DaprConversationClient( + @Nullable ResiliencyOptions resiliencyOptions) { + this.channel = NetworkUtils.buildGrpcManagedChannel(new Properties()); + this.asyncStub = DaprGrpc.newStub(this.channel); + this.retryPolicy = new RetryPolicy(resiliencyOptions == null ? null : resiliencyOptions.getMaxRetries()); + this.timeoutPolicy = new TimeoutPolicy(resiliencyOptions == null ? null : resiliencyOptions.getTimeout()); + } + + @Override + public Mono converse( + String conversationComponentName, + List daprConversationInputs, + @Nullable String contextId, + boolean scrubPii, + double temperature) { + + try { + if ((conversationComponentName == null) || (conversationComponentName.trim().isEmpty())) { + throw new IllegalArgumentException("Conversation component name cannot be null or empty."); + } + + if ((daprConversationInputs == null) || (daprConversationInputs.isEmpty())) { + throw new IllegalArgumentException("Conversation inputs cannot be null or empty."); + } + + DaprProtos.ConversationRequest.Builder conversationRequest = DaprProtos.ConversationRequest.newBuilder() + .setTemperature(temperature) + .setScrubPII(scrubPii) + .setName(conversationComponentName); + + if (contextId != null) { + conversationRequest.setContextID(contextId); + } + + for (DaprConversationInput input : daprConversationInputs) { + conversationRequest.addInputs(DaprProtos.ConversationInput.newBuilder() + .setContent(input.getContent()).build()); + } + + Mono conversationResponseMono = Mono.deferContextual( + context -> this.createMono( + it -> intercept(context, asyncStub) + .converseAlpha1(conversationRequest.build(), it) + ) + ); + + return conversationResponseMono.map(conversationResponse -> { + + List daprConversationOutputs = new ArrayList<>(); + for (DaprProtos.ConversationResult conversationResult : conversationResponse.getOutputsList()) { + Map parameters = new HashMap<>(); + for (Map.Entry entrySet : conversationResult.getParametersMap().entrySet()) { + parameters.put(entrySet.getKey(), entrySet.getValue().toByteArray()); + } + + DaprConversationOutput daprConversationOutput = + new DaprConversationOutput(conversationResult.getResult(), parameters); + daprConversationOutputs.add(daprConversationOutput); + } + + return new DaprConversationResponse(conversationResponse.getContextID(), daprConversationOutputs); + }); + } catch (Exception ex) { + return DaprException.wrapMono(ex); + } + } + + @Override + public void close() throws Exception { + DaprException.wrap(() -> { + if (channel != null && !channel.isShutdown()) { + channel.shutdown(); + } + + return true; + }).call(); + } + + private DaprGrpc.DaprStub intercept( + ContextView context, DaprGrpc.DaprStub client) { + return client.withInterceptors( + new DaprTimeoutInterceptor(this.timeoutPolicy), + new DaprTracingInterceptor(context)); + } + + private Mono createMono(Consumer> consumer) { + return retryPolicy.apply( + Mono.create(sink -> DaprException.wrap(() -> consumer.accept( + createStreamObserver(sink))).run())); + } + + private StreamObserver createStreamObserver(MonoSink sink) { + return new StreamObserver() { + @Override + public void onNext(T value) { + sink.success(value); + } + + @Override + public void onError(Throwable t) { + sink.error(DaprException.propagate(DaprHttpException.fromGrpcExecutionException(null, t))); + } + + @Override + public void onCompleted() { + sink.success(); + } + }; + } +} diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java new file mode 100644 index 0000000000..5189f1da40 --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java @@ -0,0 +1,66 @@ +package io.dapr.ai.client; + +/** + * Represents an input message for a conversation with an LLM. + */ +public class DaprConversationInput { + + private final String content; + + private DaprConversationRole role; + + private boolean scrubPii; + + public DaprConversationInput(String content) { + this.content = content; + } + + /** + * Retrieves the content of the conversation input. + * + * @return The content to be sent to the LLM. + */ + public String getContent() { + return content; + } + + /** + * Retrieves the role associated with the conversation input. + * + * @return this. + */ + public DaprConversationRole getRole() { + return role; + } + + /** + * Sets the role associated with the conversation input. + * + * @param role The role to assign to the message. + * @return this. + */ + public DaprConversationInput setRole(DaprConversationRole role) { + this.role = role; + return this; + } + + /** + * Checks if Personally Identifiable Information (PII) should be scrubbed before sending to the LLM. + * + * @return {@code true} if PII should be scrubbed, {@code false} otherwise. + */ + public boolean isScrubPii() { + return scrubPii; + } + + /** + * Sets whether to scrub Personally Identifiable Information (PII) before sending to the LLM. + * + * @param scrubPii A boolean indicating whether to remove PII. + * @return this. + */ + public DaprConversationInput setScrubPii(boolean scrubPii) { + this.scrubPii = scrubPii; + return this; + } +} diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java new file mode 100644 index 0000000000..a1a5d5ec76 --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java @@ -0,0 +1,43 @@ +package io.dapr.ai.client; + +import java.util.Collections; +import java.util.Map; + +/** + * Returns the conversation output. + */ +public class DaprConversationOutput { + + private final String result; + + private final Map parameters; + + /** + * Constructor. + * + * @param result result for one of the conversation input. + * @param parameters all custom fields. + */ + public DaprConversationOutput(String result, Map parameters) { + this.result = result; + this.parameters = parameters; + } + + /** + * Result for the one conversation input. + * + * @return result output from the LLM. + */ + public String getResult() { + return this.result; + } + + /** + * Parameters for all custom fields. + * + * @return parameters. + */ + public Map getParameters() { + return Collections.unmodifiableMap(this.parameters); + } +} diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java new file mode 100644 index 0000000000..677352495c --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java @@ -0,0 +1,52 @@ +package io.dapr.ai.client; + +import java.util.Collections; +import java.util.List; + +/** + * Response from the Dapr Conversation API. + */ +public class DaprConversationResponse { + + private String contextId; + + private final List daprConversationOutputs; + + /** + * Constructor. + * + * @param daprConversationOutputs outputs from the LLM. + */ + public DaprConversationResponse(List daprConversationOutputs) { + this.daprConversationOutputs = daprConversationOutputs; + } + + /** + * Constructor. + * + * @param contextId context id supplied to LLM. + * @param daprConversationOutputs outputs from the LLM. + */ + public DaprConversationResponse(String contextId, List daprConversationOutputs) { + this.contextId = contextId; + this.daprConversationOutputs = daprConversationOutputs; + } + + /** + * The ID of an existing chat (like in ChatGPT). + * + * @return String identifier. + */ + public String getContextId() { + return this.contextId; + } + + /** + * Get list of conversation outputs. + * + * @return List{@link DaprConversationOutput}. + */ + public List getDaprConversationOutputs() { + return Collections.unmodifiableList(this.daprConversationOutputs); + } +} diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java new file mode 100644 index 0000000000..11991d857d --- /dev/null +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java @@ -0,0 +1,13 @@ +package io.dapr.ai.client; + +/** + * Conversation AI supported roles. + */ +public enum DaprConversationRole { + + USER, + + TOOL, + + ASSISSTANT +} diff --git a/sdk-ai/src/test/java/io/dapr/ai/AITest.java b/sdk-ai/src/test/java/io/dapr/ai/AITest.java new file mode 100644 index 0000000000..f45217ab1f --- /dev/null +++ b/sdk-ai/src/test/java/io/dapr/ai/AITest.java @@ -0,0 +1,14 @@ +package io.dapr.ai; + +import org.junit.Test; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +public class AITest { + + @Test + public void testAI() { + } +} From 61aa209998e28780fb65aa566e6ad8f55f052dad Mon Sep 17 00:00:00 2001 From: sirivarma Date: Thu, 6 Mar 2025 19:46:01 -0800 Subject: [PATCH 02/23] Add unit tests Signed-off-by: sirivarma Signed-off-by: siri-varma --- .../conversation/DemoConversationAI.java | 2 +- .../java/io/dapr/ai/client/DaprAiClient.java | 13 +- .../ai/client/DaprConversationClient.java | 60 ++++-- .../ai/client/DaprConversationResponse.java | 9 - sdk-ai/src/test/java/io/dapr/ai/AITest.java | 14 -- .../ai/client/DaprConversationClientTest.java | 185 ++++++++++++++++++ 6 files changed, 245 insertions(+), 38 deletions(-) delete mode 100644 sdk-ai/src/test/java/io/dapr/ai/AITest.java create mode 100644 sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index 7d876c030c..014ca686ea 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -16,7 +16,7 @@ public class DemoConversationAI { * @param args Input arguments (unused). */ public static void main(String[] args) { - try (DaprConversationClient client = new DaprConversationClient(null)) { + try (DaprConversationClient client = new DaprConversationClient()) { DaprConversationInput daprConversationInput = new DaprConversationInput("11"); // Component name is the name provided in the metadata block of the conversation.yaml file. diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java index 193132e130..c3f6b536a8 100644 --- a/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java @@ -23,7 +23,18 @@ interface DaprAiClient { Mono converse( String conversationComponentName, List daprConversationInputs, - @Nullable String contextId, + String contextId, boolean scrubPii, double temperature); + + /** + * Method to call the Dapr Converse API. + * + * @param conversationComponentName name for the conversation component. + * @param daprConversationInputs prompts that are part of the conversation. + * @return @ConversationResponse. + */ + Mono converse( + String conversationComponentName, + List daprConversationInputs); } diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java index 1f78a2cc5f..55e4108a3f 100644 --- a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java @@ -12,9 +12,9 @@ import io.dapr.utils.NetworkUtils; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; +import io.grpc.Channel; import io.grpc.ManagedChannel; import io.grpc.stub.StreamObserver; -import org.jetbrains.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoSink; import reactor.util.context.ContextView; @@ -32,11 +32,6 @@ public class DaprConversationClient implements AutoCloseable, DaprAiClient { */ private final DaprGrpc.DaprStub asyncStub; - /** - * The GRPC managed channel to be used. - */ - private final ManagedChannel channel; - /** * The retry policy. */ @@ -47,24 +42,50 @@ public class DaprConversationClient implements AutoCloseable, DaprAiClient { */ private final TimeoutPolicy timeoutPolicy; + /** + * Constructor to create conversation client. + */ + public DaprConversationClient() { + this(DaprGrpc.newStub(NetworkUtils.buildGrpcManagedChannel(new Properties())), null); + } + + /** + * Constructor. + * + * @param properties with client configuration options. + * @param resiliencyOptions retry options. + */ + public DaprConversationClient( + Properties properties, + ResiliencyOptions resiliencyOptions) { + this(DaprGrpc.newStub(NetworkUtils.buildGrpcManagedChannel(properties)), resiliencyOptions); + } + /** * ConversationClient constructor. * * @param resiliencyOptions timeout and retry policies. */ - public DaprConversationClient( - @Nullable ResiliencyOptions resiliencyOptions) { - this.channel = NetworkUtils.buildGrpcManagedChannel(new Properties()); - this.asyncStub = DaprGrpc.newStub(this.channel); + protected DaprConversationClient( + DaprGrpc.DaprStub asyncStub, + ResiliencyOptions resiliencyOptions) { + this.asyncStub = asyncStub; this.retryPolicy = new RetryPolicy(resiliencyOptions == null ? null : resiliencyOptions.getMaxRetries()); this.timeoutPolicy = new TimeoutPolicy(resiliencyOptions == null ? null : resiliencyOptions.getTimeout()); } + @Override + public Mono converse( + String conversationComponentName, + List daprConversationInputs) { + return converse(conversationComponentName, daprConversationInputs, null, false, 0.0d); + } + @Override public Mono converse( String conversationComponentName, List daprConversationInputs, - @Nullable String contextId, + String contextId, boolean scrubPii, double temperature) { @@ -87,8 +108,19 @@ public Mono converse( } for (DaprConversationInput input : daprConversationInputs) { - conversationRequest.addInputs(DaprProtos.ConversationInput.newBuilder() - .setContent(input.getContent()).build()); + if (input.getContent() == null || input.getContent().isEmpty()) { + throw new IllegalArgumentException("Conversation input content cannot be null or empty."); + } + + DaprProtos.ConversationInput.Builder conversationInputOrBuilder = DaprProtos.ConversationInput.newBuilder() + .setContent(input.getContent()) + .setScrubPII(input.isScrubPii()); + + if (input.getRole() != null) { + conversationInputOrBuilder.setRole(input.getRole().toString()); + } + + conversationRequest.addInputs(conversationInputOrBuilder.build()); } Mono conversationResponseMono = Mono.deferContextual( @@ -121,6 +153,8 @@ public Mono converse( @Override public void close() throws Exception { + ManagedChannel channel = (ManagedChannel) this.asyncStub.getChannel(); + DaprException.wrap(() -> { if (channel != null && !channel.isShutdown()) { channel.shutdown(); diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java index 677352495c..e7ab001a5c 100644 --- a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java +++ b/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java @@ -12,15 +12,6 @@ public class DaprConversationResponse { private final List daprConversationOutputs; - /** - * Constructor. - * - * @param daprConversationOutputs outputs from the LLM. - */ - public DaprConversationResponse(List daprConversationOutputs) { - this.daprConversationOutputs = daprConversationOutputs; - } - /** * Constructor. * diff --git a/sdk-ai/src/test/java/io/dapr/ai/AITest.java b/sdk-ai/src/test/java/io/dapr/ai/AITest.java deleted file mode 100644 index f45217ab1f..0000000000 --- a/sdk-ai/src/test/java/io/dapr/ai/AITest.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.dapr.ai; - -import org.junit.Test; - -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -public class AITest { - - @Test - public void testAI() { - } -} diff --git a/sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java b/sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java new file mode 100644 index 0000000000..d39ef1413d --- /dev/null +++ b/sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java @@ -0,0 +1,185 @@ +package io.dapr.ai.client; + +import io.dapr.client.resiliency.ResiliencyOptions; +import io.dapr.config.Properties; +import io.dapr.v1.DaprGrpc; +import io.dapr.v1.DaprProtos; +import io.grpc.ManagedChannel; +import io.grpc.stub.StreamObserver; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.*; + +public class DaprConversationClientTest { + + private DaprGrpc.DaprStub daprStub; + + @Before + public void initialize() { + + ManagedChannel channel = mock(ManagedChannel.class); + daprStub = mock(DaprGrpc.DaprStub.class); + when(daprStub.getChannel()).thenReturn(channel); + when(daprStub.withInterceptors(Mockito.any(), Mockito.any())).thenReturn(daprStub); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenComponentNameIsNull() throws Exception { + try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new DaprConversationInput("Hello there !")); + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse(null, daprConversationInputs).block()); + Assert.assertEquals("Conversation component name cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationComponentIsEmpty() throws Exception { + try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new DaprConversationInput("Hello there !")); + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse("", daprConversationInputs).block()); + Assert.assertEquals("Conversation component name cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsEmpty() throws Exception { + try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { + List daprConversationInputs = new ArrayList<>(); + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse("openai", daprConversationInputs).block()); + Assert.assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsNull() throws Exception { + try (DaprConversationClient daprConversationClient = + new DaprConversationClient(new Properties(), null)) { + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse("openai", null).block()); + Assert.assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsNull() throws Exception { + try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new DaprConversationInput(null)); + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse("openai", daprConversationInputs).block()); + Assert.assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsEmpty() throws Exception { + try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new DaprConversationInput("")); + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, () -> + daprConversationClient.converse("openai", daprConversationInputs).block()); + Assert.assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); + } + } + + @Test + public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() throws Exception { + DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() + .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); + + doAnswer(invocation -> { + StreamObserver observer = invocation.getArgument(1); + observer.onNext(conversationResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); + + try (DaprConversationClient daprConversationClient = + new DaprConversationClient(daprStub, new ResiliencyOptions())) { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new DaprConversationInput("Hello there")); + + DaprConversationResponse daprConversationResponse = + daprConversationClient.converse("openai", daprConversationInputs).block(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); + verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); + + DaprProtos.ConversationRequest conversationRequest = captor.getValue(); + + Assert.assertEquals("openai", conversationRequest.getName()); + Assert.assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); + Assert.assertEquals("Hello How are you", + daprConversationResponse.getDaprConversationOutputs().get(0).getResult()); + } + } + + @Test + public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInputsAreValid() throws Exception { + DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() + .setContextID("contextId") + .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); + + doAnswer(invocation -> { + StreamObserver observer = invocation.getArgument(1); + observer.onNext(conversationResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); + + try (DaprConversationClient daprConversationClient = new DaprConversationClient(daprStub, null)) { + DaprConversationInput daprConversationInput = new DaprConversationInput("Hello there") + .setRole(DaprConversationRole.ASSISSTANT) + .setScrubPii(true); + + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(daprConversationInput); + + DaprConversationResponse daprConversationResponse = + daprConversationClient.converse("openai", daprConversationInputs, + "contextId", true, 1.1d).block(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); + verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); + + DaprProtos.ConversationRequest conversationRequest = captor.getValue(); + + Assert.assertEquals("openai", conversationRequest.getName()); + Assert.assertEquals("contextId", conversationRequest.getContextID()); + Assert.assertTrue(conversationRequest.getScrubPII()); + Assert.assertEquals(1.1d, conversationRequest.getTemperature(), 0d); + Assert.assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); + Assert.assertTrue(conversationRequest.getInputs(0).getScrubPII()); + Assert.assertEquals(DaprConversationRole.ASSISSTANT.toString(), conversationRequest.getInputs(0).getRole()); + Assert.assertEquals("contextId", daprConversationResponse.getContextId()); + Assert.assertEquals("Hello How are you", + daprConversationResponse.getDaprConversationOutputs().get(0).getResult()); + } + } +} From 93c13d4cedcddb0331fa9fe8d8220b072d65f9f1 Mon Sep 17 00:00:00 2001 From: sirivarma Date: Thu, 13 Mar 2025 17:30:11 -0700 Subject: [PATCH 03/23] change ai to conv Signed-off-by: sirivarma Signed-off-by: siri-varma --- examples/pom.xml | 2 +- pom.xml | 2 +- {sdk-ai => sdk-conversation}/pom.xml | 11 ++++++++--- .../src/main/java/io/dapr/ai/client/DaprAiClient.java | 0 .../io/dapr/ai/client/DaprConversationClient.java | 0 .../java/io/dapr/ai/client/DaprConversationInput.java | 0 .../io/dapr/ai/client/DaprConversationOutput.java | 0 .../io/dapr/ai/client/DaprConversationResponse.java | 0 .../java/io/dapr/ai/client/DaprConversationRole.java | 0 .../io/dapr/ai/client/DaprConversationClientTest.java | 0 10 files changed, 10 insertions(+), 5 deletions(-) rename {sdk-ai => sdk-conversation}/pom.xml (95%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprAiClient.java (100%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprConversationClient.java (100%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprConversationInput.java (100%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprConversationOutput.java (100%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprConversationResponse.java (100%) rename {sdk-ai => sdk-conversation}/src/main/java/io/dapr/ai/client/DaprConversationRole.java (100%) rename {sdk-ai => sdk-conversation}/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java (100%) diff --git a/examples/pom.xml b/examples/pom.xml index a3b16449ee..df7397ee6d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -120,7 +120,7 @@ io.dapr - dapr-sdk-ai + dapr-sdk-conversation ${project.version} diff --git a/pom.xml b/pom.xml index 8a58a2991a..fac20248f3 100644 --- a/pom.xml +++ b/pom.xml @@ -501,7 +501,7 @@ sdk-autogen sdk sdk-actors - sdk-ai + sdk-conversation sdk-workflows sdk-springboot dapr-spring diff --git a/sdk-ai/pom.xml b/sdk-conversation/pom.xml similarity index 95% rename from sdk-ai/pom.xml rename to sdk-conversation/pom.xml index fe42edfea6..a7e83674b1 100644 --- a/sdk-ai/pom.xml +++ b/sdk-conversation/pom.xml @@ -10,11 +10,11 @@ 1.15.0-SNAPSHOT - dapr-sdk-ai + dapr-sdk-conversation jar 1.15.0-SNAPSHOT - dapr-sdk-ai - SDK for AI on Dapr + dapr-sdk-conversation + SDK for Conversation false @@ -88,6 +88,11 @@ + + org.sonatype.plugins + nexus-staging-maven-plugin + + org.apache.maven.plugins maven-source-plugin diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprAiClient.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationClient.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationInput.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationOutput.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationResponse.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java diff --git a/sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java similarity index 100% rename from sdk-ai/src/main/java/io/dapr/ai/client/DaprConversationRole.java rename to sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java diff --git a/sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java b/sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java similarity index 100% rename from sdk-ai/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java rename to sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java From cd87ca57c36370f75737db7bd4a7e4acb6be5ca7 Mon Sep 17 00:00:00 2001 From: sirivarma Date: Tue, 18 Mar 2025 08:09:41 -0700 Subject: [PATCH 04/23] Move to single module Signed-off-by: sirivarma Signed-off-by: siri-varma --- pom.xml | 1 - sdk-conversation/pom.xml | 169 -------- .../java/io/dapr/ai/client/DaprAiClient.java | 40 -- .../ai/client/DaprConversationClient.java | 198 --------- .../dapr/ai/client/DaprConversationRole.java | 13 - .../ai/client/DaprConversationClientTest.java | 185 -------- .../java/io/dapr/client/DaprClientImpl.java | 77 ++++ .../io/dapr/client/DaprPreviewClient.java | 10 + .../dapr/client/domain/ConversationInput.java | 27 +- .../client/domain/ConversationOutput.java | 6 +- .../client/domain/ConversationRequest.java | 106 +++++ .../client/domain/ConversationResponse.java | 12 +- .../dapr/client/domain/ConversationRole.java | 22 + .../client/DaprPreviewClientGrpcTest.java | 410 ++++++++++++------ 14 files changed, 521 insertions(+), 755 deletions(-) delete mode 100644 sdk-conversation/pom.xml delete mode 100644 sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java delete mode 100644 sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java delete mode 100644 sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java delete mode 100644 sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java rename sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java => sdk/src/main/java/io/dapr/client/domain/ConversationInput.java (59%) rename sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java => sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java (83%) create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java rename sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java => sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java (65%) create mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationRole.java diff --git a/pom.xml b/pom.xml index fac20248f3..51f5070027 100644 --- a/pom.xml +++ b/pom.xml @@ -501,7 +501,6 @@ sdk-autogen sdk sdk-actors - sdk-conversation sdk-workflows sdk-springboot dapr-spring diff --git a/sdk-conversation/pom.xml b/sdk-conversation/pom.xml deleted file mode 100644 index a7e83674b1..0000000000 --- a/sdk-conversation/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - 4.0.0 - - - io.dapr - dapr-sdk-parent - 1.15.0-SNAPSHOT - - - dapr-sdk-conversation - jar - 1.15.0-SNAPSHOT - dapr-sdk-conversation - SDK for Conversation - - - false - - - - - io.dapr - dapr-sdk - ${project.parent.version} - - - io.dapr - dapr-sdk-autogen - 1.14.0-SNAPSHOT - compile - - - org.mockito - mockito-core - test - - - org.mockito - mockito-inline - 4.2.0 - test - - - org.junit.jupiter - junit-jupiter - test - - - org.junit.vintage - junit-vintage-engine - 5.7.0 - test - - - com.microsoft - durabletask-client - 1.5.0 - - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson.version} - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.11 - - - default-prepare-agent - - prepare-agent - - - - report - test - - report - - - target/jacoco-report/ - - - - check - - check - - - - - BUNDLE - - - LINE - COVEREDRATIO - 80% - - - - - - - - - - - - diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java deleted file mode 100644 index c3f6b536a8..0000000000 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprAiClient.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.dapr.ai.client; - -import reactor.core.publisher.Mono; - -import javax.annotation.Nullable; -import java.util.List; - -/** - * Defines client operations for managing Dapr AI instances. - */ -interface DaprAiClient { - - /** - * Method to call the Dapr Converse API. - * - * @param conversationComponentName name for the conversation component. - * @param daprConversationInputs prompts that are part of the conversation. - * @param contextId identifier of an existing chat (like in ChatGPT) - * @param scrubPii data that comes from the LLM. - * @param temperature to optimize from creativity or predictability. - * @return @ConversationResponse. - */ - Mono converse( - String conversationComponentName, - List daprConversationInputs, - String contextId, - boolean scrubPii, - double temperature); - - /** - * Method to call the Dapr Converse API. - * - * @param conversationComponentName name for the conversation component. - * @param daprConversationInputs prompts that are part of the conversation. - * @return @ConversationResponse. - */ - Mono converse( - String conversationComponentName, - List daprConversationInputs); -} diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java deleted file mode 100644 index 55e4108a3f..0000000000 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationClient.java +++ /dev/null @@ -1,198 +0,0 @@ -package io.dapr.ai.client; - -import com.google.protobuf.Any; -import io.dapr.client.resiliency.ResiliencyOptions; -import io.dapr.config.Properties; -import io.dapr.exceptions.DaprException; -import io.dapr.internal.exceptions.DaprHttpException; -import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor; -import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor; -import io.dapr.internal.resiliency.RetryPolicy; -import io.dapr.internal.resiliency.TimeoutPolicy; -import io.dapr.utils.NetworkUtils; -import io.dapr.v1.DaprGrpc; -import io.dapr.v1.DaprProtos; -import io.grpc.Channel; -import io.grpc.ManagedChannel; -import io.grpc.stub.StreamObserver; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoSink; -import reactor.util.context.ContextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -public class DaprConversationClient implements AutoCloseable, DaprAiClient { - - /** - * Stub that has the method to call the conversation apis. - */ - private final DaprGrpc.DaprStub asyncStub; - - /** - * The retry policy. - */ - private final RetryPolicy retryPolicy; - - /** - * The timeout policy. - */ - private final TimeoutPolicy timeoutPolicy; - - /** - * Constructor to create conversation client. - */ - public DaprConversationClient() { - this(DaprGrpc.newStub(NetworkUtils.buildGrpcManagedChannel(new Properties())), null); - } - - /** - * Constructor. - * - * @param properties with client configuration options. - * @param resiliencyOptions retry options. - */ - public DaprConversationClient( - Properties properties, - ResiliencyOptions resiliencyOptions) { - this(DaprGrpc.newStub(NetworkUtils.buildGrpcManagedChannel(properties)), resiliencyOptions); - } - - /** - * ConversationClient constructor. - * - * @param resiliencyOptions timeout and retry policies. - */ - protected DaprConversationClient( - DaprGrpc.DaprStub asyncStub, - ResiliencyOptions resiliencyOptions) { - this.asyncStub = asyncStub; - this.retryPolicy = new RetryPolicy(resiliencyOptions == null ? null : resiliencyOptions.getMaxRetries()); - this.timeoutPolicy = new TimeoutPolicy(resiliencyOptions == null ? null : resiliencyOptions.getTimeout()); - } - - @Override - public Mono converse( - String conversationComponentName, - List daprConversationInputs) { - return converse(conversationComponentName, daprConversationInputs, null, false, 0.0d); - } - - @Override - public Mono converse( - String conversationComponentName, - List daprConversationInputs, - String contextId, - boolean scrubPii, - double temperature) { - - try { - if ((conversationComponentName == null) || (conversationComponentName.trim().isEmpty())) { - throw new IllegalArgumentException("Conversation component name cannot be null or empty."); - } - - if ((daprConversationInputs == null) || (daprConversationInputs.isEmpty())) { - throw new IllegalArgumentException("Conversation inputs cannot be null or empty."); - } - - DaprProtos.ConversationRequest.Builder conversationRequest = DaprProtos.ConversationRequest.newBuilder() - .setTemperature(temperature) - .setScrubPII(scrubPii) - .setName(conversationComponentName); - - if (contextId != null) { - conversationRequest.setContextID(contextId); - } - - for (DaprConversationInput input : daprConversationInputs) { - if (input.getContent() == null || input.getContent().isEmpty()) { - throw new IllegalArgumentException("Conversation input content cannot be null or empty."); - } - - DaprProtos.ConversationInput.Builder conversationInputOrBuilder = DaprProtos.ConversationInput.newBuilder() - .setContent(input.getContent()) - .setScrubPII(input.isScrubPii()); - - if (input.getRole() != null) { - conversationInputOrBuilder.setRole(input.getRole().toString()); - } - - conversationRequest.addInputs(conversationInputOrBuilder.build()); - } - - Mono conversationResponseMono = Mono.deferContextual( - context -> this.createMono( - it -> intercept(context, asyncStub) - .converseAlpha1(conversationRequest.build(), it) - ) - ); - - return conversationResponseMono.map(conversationResponse -> { - - List daprConversationOutputs = new ArrayList<>(); - for (DaprProtos.ConversationResult conversationResult : conversationResponse.getOutputsList()) { - Map parameters = new HashMap<>(); - for (Map.Entry entrySet : conversationResult.getParametersMap().entrySet()) { - parameters.put(entrySet.getKey(), entrySet.getValue().toByteArray()); - } - - DaprConversationOutput daprConversationOutput = - new DaprConversationOutput(conversationResult.getResult(), parameters); - daprConversationOutputs.add(daprConversationOutput); - } - - return new DaprConversationResponse(conversationResponse.getContextID(), daprConversationOutputs); - }); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - @Override - public void close() throws Exception { - ManagedChannel channel = (ManagedChannel) this.asyncStub.getChannel(); - - DaprException.wrap(() -> { - if (channel != null && !channel.isShutdown()) { - channel.shutdown(); - } - - return true; - }).call(); - } - - private DaprGrpc.DaprStub intercept( - ContextView context, DaprGrpc.DaprStub client) { - return client.withInterceptors( - new DaprTimeoutInterceptor(this.timeoutPolicy), - new DaprTracingInterceptor(context)); - } - - private Mono createMono(Consumer> consumer) { - return retryPolicy.apply( - Mono.create(sink -> DaprException.wrap(() -> consumer.accept( - createStreamObserver(sink))).run())); - } - - private StreamObserver createStreamObserver(MonoSink sink) { - return new StreamObserver() { - @Override - public void onNext(T value) { - sink.success(value); - } - - @Override - public void onError(Throwable t) { - sink.error(DaprException.propagate(DaprHttpException.fromGrpcExecutionException(null, t))); - } - - @Override - public void onCompleted() { - sink.success(); - } - }; - } -} diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java b/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java deleted file mode 100644 index 11991d857d..0000000000 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationRole.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.dapr.ai.client; - -/** - * Conversation AI supported roles. - */ -public enum DaprConversationRole { - - USER, - - TOOL, - - ASSISSTANT -} diff --git a/sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java b/sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java deleted file mode 100644 index d39ef1413d..0000000000 --- a/sdk-conversation/src/test/java/io/dapr/ai/client/DaprConversationClientTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package io.dapr.ai.client; - -import io.dapr.client.resiliency.ResiliencyOptions; -import io.dapr.config.Properties; -import io.dapr.v1.DaprGrpc; -import io.dapr.v1.DaprProtos; -import io.grpc.ManagedChannel; -import io.grpc.stub.StreamObserver; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -import static org.mockito.Mockito.*; - -public class DaprConversationClientTest { - - private DaprGrpc.DaprStub daprStub; - - @Before - public void initialize() { - - ManagedChannel channel = mock(ManagedChannel.class); - daprStub = mock(DaprGrpc.DaprStub.class); - when(daprStub.getChannel()).thenReturn(channel); - when(daprStub.withInterceptors(Mockito.any(), Mockito.any())).thenReturn(daprStub); - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenComponentNameIsNull() throws Exception { - try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new DaprConversationInput("Hello there !")); - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse(null, daprConversationInputs).block()); - Assert.assertEquals("Conversation component name cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationComponentIsEmpty() throws Exception { - try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new DaprConversationInput("Hello there !")); - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse("", daprConversationInputs).block()); - Assert.assertEquals("Conversation component name cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsEmpty() throws Exception { - try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { - List daprConversationInputs = new ArrayList<>(); - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse("openai", daprConversationInputs).block()); - Assert.assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsNull() throws Exception { - try (DaprConversationClient daprConversationClient = - new DaprConversationClient(new Properties(), null)) { - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse("openai", null).block()); - Assert.assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsNull() throws Exception { - try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new DaprConversationInput(null)); - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse("openai", daprConversationInputs).block()); - Assert.assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsEmpty() throws Exception { - try (DaprConversationClient daprConversationClient = new DaprConversationClient()) { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new DaprConversationInput("")); - - IllegalArgumentException exception = - Assert.assertThrows(IllegalArgumentException.class, () -> - daprConversationClient.converse("openai", daprConversationInputs).block()); - Assert.assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); - } - } - - @Test - public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() throws Exception { - DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() - .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); - - doAnswer(invocation -> { - StreamObserver observer = invocation.getArgument(1); - observer.onNext(conversationResponse); - observer.onCompleted(); - return null; - }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); - - try (DaprConversationClient daprConversationClient = - new DaprConversationClient(daprStub, new ResiliencyOptions())) { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new DaprConversationInput("Hello there")); - - DaprConversationResponse daprConversationResponse = - daprConversationClient.converse("openai", daprConversationInputs).block(); - - ArgumentCaptor captor = - ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); - verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); - - DaprProtos.ConversationRequest conversationRequest = captor.getValue(); - - Assert.assertEquals("openai", conversationRequest.getName()); - Assert.assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); - Assert.assertEquals("Hello How are you", - daprConversationResponse.getDaprConversationOutputs().get(0).getResult()); - } - } - - @Test - public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInputsAreValid() throws Exception { - DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() - .setContextID("contextId") - .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); - - doAnswer(invocation -> { - StreamObserver observer = invocation.getArgument(1); - observer.onNext(conversationResponse); - observer.onCompleted(); - return null; - }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); - - try (DaprConversationClient daprConversationClient = new DaprConversationClient(daprStub, null)) { - DaprConversationInput daprConversationInput = new DaprConversationInput("Hello there") - .setRole(DaprConversationRole.ASSISSTANT) - .setScrubPii(true); - - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(daprConversationInput); - - DaprConversationResponse daprConversationResponse = - daprConversationClient.converse("openai", daprConversationInputs, - "contextId", true, 1.1d).block(); - - ArgumentCaptor captor = - ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); - verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); - - DaprProtos.ConversationRequest conversationRequest = captor.getValue(); - - Assert.assertEquals("openai", conversationRequest.getName()); - Assert.assertEquals("contextId", conversationRequest.getContextID()); - Assert.assertTrue(conversationRequest.getScrubPII()); - Assert.assertEquals(1.1d, conversationRequest.getTemperature(), 0d); - Assert.assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); - Assert.assertTrue(conversationRequest.getInputs(0).getScrubPII()); - Assert.assertEquals(DaprConversationRole.ASSISSTANT.toString(), conversationRequest.getInputs(0).getRole()); - Assert.assertEquals("contextId", daprConversationResponse.getContextId()); - Assert.assertEquals("Hello How are you", - daprConversationResponse.getDaprConversationOutputs().get(0).getResult()); - } - } -} diff --git a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java index aad67bc23d..03b9e938b5 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java @@ -27,6 +27,10 @@ import io.dapr.client.domain.CloudEvent; import io.dapr.client.domain.ComponentMetadata; import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.ConversationInput; +import io.dapr.client.domain.ConversationOutput; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; import io.dapr.client.domain.DaprMetadata; import io.dapr.client.domain.DeleteJobRequest; import io.dapr.client.domain.DeleteStateRequest; @@ -1552,6 +1556,79 @@ public Mono getMetadata() { }); } + /** + * {@inheritDoc} + */ + @Override + public Mono converse(ConversationRequest conversationRequest) { + + try { + validateConversationRequest(conversationRequest); + + DaprProtos.ConversationRequest.Builder protosConversationRequestBuilder = DaprProtos.ConversationRequest + .newBuilder().setTemperature(conversationRequest.getTemperature()) + .setScrubPII(conversationRequest.isScrubPii()) + .setName(conversationRequest.getLlmName()); + + if (conversationRequest.getContextId() != null) { + protosConversationRequestBuilder.setContextID(conversationRequest.getContextId()); + } + + for (ConversationInput input : conversationRequest.getConversationInputs()) { + if (input.getContent() == null || input.getContent().isEmpty()) { + throw new IllegalArgumentException("Conversation input content cannot be null or empty."); + } + + DaprProtos.ConversationInput.Builder conversationInputOrBuilder = DaprProtos.ConversationInput.newBuilder() + .setContent(input.getContent()) + .setScrubPII(input.isScrubPii()); + + if (input.getRole() != null) { + conversationInputOrBuilder.setRole(input.getRole().toString()); + } + + protosConversationRequestBuilder.addInputs(conversationInputOrBuilder.build()); + } + + Mono conversationResponseMono = Mono.deferContextual( + context -> this.createMono( + it -> intercept(context, asyncStub) + .converseAlpha1(protosConversationRequestBuilder.build(), it) + ) + ); + + return conversationResponseMono.map(conversationResponse -> { + + List conversationOutputs = new ArrayList<>(); + for (DaprProtos.ConversationResult conversationResult : conversationResponse.getOutputsList()) { + Map parameters = new HashMap<>(); + for (Map.Entry entrySet : conversationResult.getParametersMap().entrySet()) { + parameters.put(entrySet.getKey(), entrySet.getValue().toByteArray()); + } + + ConversationOutput conversationOutput = + new ConversationOutput(conversationResult.getResult(), parameters); + conversationOutputs.add(conversationOutput); + } + + return new ConversationResponse(conversationResponse.getContextID(), conversationOutputs); + }); + } catch (Exception ex) { + return DaprException.wrapMono(ex); + } + } + + private void validateConversationRequest(ConversationRequest conversationRequest) { + if ((conversationRequest.getLlmName() == null) || (conversationRequest.getLlmName().trim().isEmpty())) { + throw new IllegalArgumentException("LLM name cannot be null or empty."); + } + + if ((conversationRequest.getConversationInputs() == null) || (conversationRequest + .getConversationInputs().isEmpty())) { + throw new IllegalArgumentException("Conversation inputs cannot be null or empty."); + } + } + private DaprMetadata buildDaprMetadata(DaprProtos.GetMetadataResponse response) throws IOException { String id = response.getId(); String runtimeVersion = response.getRuntimeVersion(); diff --git a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java index b4fba8ef38..c8b25107c1 100644 --- a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java @@ -20,6 +20,8 @@ import io.dapr.client.domain.DeleteJobRequest; import io.dapr.client.domain.GetJobRequest; import io.dapr.client.domain.GetJobResponse; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; import io.dapr.client.domain.LockRequest; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; @@ -304,4 +306,12 @@ Subscription subscribeToEvents( * @throws IllegalArgumentException If the request or its required fields like name are null or empty. */ public Mono deleteJob(DeleteJobRequest deleteJobRequest); + + /* + * Converse with an LLM. + * + * @param conversationRequest request to be passed to the LLM. + * @return {@link ConversationResponse}. + */ + public Mono converse(ConversationRequest conversationRequest); } diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java similarity index 59% rename from sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java rename to sdk/src/main/java/io/dapr/client/domain/ConversationInput.java index 5189f1da40..a0e3729365 100644 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationInput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java @@ -1,22 +1,27 @@ -package io.dapr.ai.client; +package io.dapr.client.domain; /** * Represents an input message for a conversation with an LLM. */ -public class DaprConversationInput { +public class ConversationInput { private final String content; - private DaprConversationRole role; + private ConversationRole role; private boolean scrubPii; - public DaprConversationInput(String content) { + /** + * Constructor. + * + * @param content for the llm. + */ + public ConversationInput(String content) { this.content = content; } /** - * Retrieves the content of the conversation input. + * The message content to send to the LLM. Required * * @return The content to be sent to the LLM. */ @@ -25,21 +30,21 @@ public String getContent() { } /** - * Retrieves the role associated with the conversation input. + * The role for the LLM to assume. * * @return this. */ - public DaprConversationRole getRole() { + public ConversationRole getRole() { return role; } /** - * Sets the role associated with the conversation input. + * Sets the role for LLM to assume. * * @param role The role to assign to the message. * @return this. */ - public DaprConversationInput setRole(DaprConversationRole role) { + public ConversationInput setRole(ConversationRole role) { this.role = role; return this; } @@ -54,12 +59,12 @@ public boolean isScrubPii() { } /** - * Sets whether to scrub Personally Identifiable Information (PII) before sending to the LLM. + * Enable obfuscation of sensitive information present in the content field. Optional * * @param scrubPii A boolean indicating whether to remove PII. * @return this. */ - public DaprConversationInput setScrubPii(boolean scrubPii) { + public ConversationInput setScrubPii(boolean scrubPii) { this.scrubPii = scrubPii; return this; } diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java similarity index 83% rename from sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java rename to sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java index a1a5d5ec76..6279ca6049 100644 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationOutput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java @@ -1,4 +1,4 @@ -package io.dapr.ai.client; +package io.dapr.client.domain; import java.util.Collections; import java.util.Map; @@ -6,7 +6,7 @@ /** * Returns the conversation output. */ -public class DaprConversationOutput { +public class ConversationOutput { private final String result; @@ -18,7 +18,7 @@ public class DaprConversationOutput { * @param result result for one of the conversation input. * @param parameters all custom fields. */ - public DaprConversationOutput(String result, Map parameters) { + public ConversationOutput(String result, Map parameters) { this.result = result; this.parameters = parameters; } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java new file mode 100644 index 0000000000..de58df6827 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java @@ -0,0 +1,106 @@ +package io.dapr.client.domain; + +import java.util.List; + +/** + * Represents a conversation configuration with details about component name, + * conversation inputs, context identifier, PII scrubbing, and temperature control. + */ +public class ConversationRequest { + + private final String llmName; + private final List daprConversationInputs; + private String contextId; + private boolean scrubPii; + private double temperature; + + /** + * Constructs a DaprConversation with a component name and conversation inputs. + * + * @param llmName The name of the LLM component. See a list of all available conversation components + * @see + * @param conversationInputs the list of Dapr conversation inputs + */ + public ConversationRequest(String llmName, List conversationInputs) { + this.llmName = llmName; + this.daprConversationInputs = conversationInputs; + } + + /** + * Gets the conversation component name. + * + * @return the conversation component name + */ + public String getLlmName() { + return llmName; + } + + /** + * Gets the list of Dapr conversation inputs. + * + * @return the list of conversation inputs + */ + public List getConversationInputs() { + return daprConversationInputs; + } + + /** + * Gets the context identifier. + * + * @return the context identifier + */ + public String getContextId() { + return contextId; + } + + /** + * Sets the context identifier. + * + * @param contextId the context identifier to set + * @return the current instance of {@link ConversationRequest} + */ + public ConversationRequest setContextId(String contextId) { + this.contextId = contextId; + return this; + } + + /** + * Checks if PII scrubbing is enabled. + * + * @return true if PII scrubbing is enabled, false otherwise + */ + public boolean isScrubPii() { + return scrubPii; + } + + /** + * Enable obfuscation of sensitive information returning from the LLM. Optional. + * + * @param scrubPii whether to enable PII scrubbing + * @return the current instance of {@link ConversationRequest} + */ + public ConversationRequest setScrubPii(boolean scrubPii) { + this.scrubPii = scrubPii; + return this; + } + + /** + * Gets the temperature of the model. Used to optimize for consistency and creativity. Optional + * + * @return the temperature value + */ + public double getTemperature() { + return temperature; + } + + /** + * Sets the temperature of the model. Used to optimize for consistency and creativity. Optional + * + * @param temperature the temperature value to set + * @return the current instance of {@link ConversationRequest} + */ + public ConversationRequest setTemperature(double temperature) { + this.temperature = temperature; + return this; + } +} \ No newline at end of file diff --git a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java similarity index 65% rename from sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java rename to sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java index e7ab001a5c..9e87b8b4ff 100644 --- a/sdk-conversation/src/main/java/io/dapr/ai/client/DaprConversationResponse.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java @@ -1,4 +1,4 @@ -package io.dapr.ai.client; +package io.dapr.client.domain; import java.util.Collections; import java.util.List; @@ -6,11 +6,11 @@ /** * Response from the Dapr Conversation API. */ -public class DaprConversationResponse { +public class ConversationResponse { private String contextId; - private final List daprConversationOutputs; + private final List daprConversationOutputs; /** * Constructor. @@ -18,7 +18,7 @@ public class DaprConversationResponse { * @param contextId context id supplied to LLM. * @param daprConversationOutputs outputs from the LLM. */ - public DaprConversationResponse(String contextId, List daprConversationOutputs) { + public ConversationResponse(String contextId, List daprConversationOutputs) { this.contextId = contextId; this.daprConversationOutputs = daprConversationOutputs; } @@ -35,9 +35,9 @@ public String getContextId() { /** * Get list of conversation outputs. * - * @return List{@link DaprConversationOutput}. + * @return List{@link ConversationOutput}. */ - public List getDaprConversationOutputs() { + public List getConversationOutpus() { return Collections.unmodifiableList(this.daprConversationOutputs); } } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java new file mode 100644 index 0000000000..5374c7fa9d --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java @@ -0,0 +1,22 @@ +package io.dapr.client.domain; + +/** + * Conversation AI supported roles. + */ +public enum ConversationRole { + + /** + * User Role. + */ + USER, + + /** + * Tool Role. + */ + TOOL, + + /** + * Assistant Role. + */ + ASSISTANT, +} diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index aec0f287ae..6e123f647f 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -26,6 +26,10 @@ import io.dapr.client.domain.GetJobRequest; import io.dapr.client.domain.GetJobResponse; import io.dapr.client.domain.JobSchedule; +import io.dapr.client.domain.ConversationInput; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; +import io.dapr.client.domain.ConversationRole; import io.dapr.client.domain.QueryStateItem; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; @@ -71,6 +75,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static io.dapr.utils.TestUtils.assertThrowsDaprException; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -110,7 +115,7 @@ public void setup() throws IOException { daprHttp = mock(DaprHttp.class); when(daprStub.withInterceptors(any())).thenReturn(daprStub); previewClient = new DaprClientImpl( - channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); doNothing().when(channel).close(); } @@ -128,28 +133,28 @@ public void publishEventsExceptionThrownTest() { }).when(daprStub).bulkPublishEventAlpha1(any(DaprProtos.BulkPublishRequest.class), any()); assertThrowsDaprException( - StatusRuntimeException.class, - "INVALID_ARGUMENT", - "INVALID_ARGUMENT: bad bad argument", - () -> previewClient.publishEvents(new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.EMPTY_LIST)).block()); + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + () -> previewClient.publishEvents(new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, + Collections.EMPTY_LIST)).block()); } @Test public void publishEventsCallbackExceptionThrownTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onError(newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument")); return null; }).when(daprStub).bulkPublishEventAlpha1(any(DaprProtos.BulkPublishRequest.class), any()); assertThrowsDaprException( - ExecutionException.class, - "INVALID_ARGUMENT", - "INVALID_ARGUMENT: bad bad argument", - () -> previewClient.publishEvents(new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.EMPTY_LIST)).block()); + ExecutionException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + () -> previewClient.publishEvents(new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, + Collections.EMPTY_LIST)).block()); } @Test @@ -157,7 +162,7 @@ public void publishEventsContentTypeMismatchException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(DaprProtos.BulkPublishResponse.getDefaultInstance()); observer.onCompleted(); return null; @@ -165,9 +170,9 @@ public void publishEventsContentTypeMismatchException() throws IOException { BulkPublishEntry entry = new BulkPublishEntry<>("1", "testEntry" - , "application/octet-stream", null); + , "application/octet-stream", null); BulkPublishRequest wrongReq = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.singletonList(entry)); + Collections.singletonList(entry)); assertThrows(IllegalArgumentException.class, () -> previewClient.publishEvents(wrongReq).block()); } @@ -178,30 +183,30 @@ public void publishEventsSerializeException() throws IOException { previewClient = new DaprClientImpl(channel, daprStub, daprHttp, mockSerializer, new DefaultObjectSerializer()); doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(DaprProtos.BulkPublishResponse.getDefaultInstance()); observer.onCompleted(); return null; }).when(daprStub).publishEvent(any(DaprProtos.PublishEventRequest.class), any()); BulkPublishEntry> entry = new BulkPublishEntry<>("1", new HashMap<>(), - "application/json", null); + "application/json", null); BulkPublishRequest> req = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.singletonList(entry)); + Collections.singletonList(entry)); when(mockSerializer.serialize(any())).thenThrow(IOException.class); Mono>> result = previewClient.publishEvents(req); assertThrowsDaprException( - IOException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); + IOException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } @Test public void publishEventsTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; DaprProtos.BulkPublishResponse.Builder builder = DaprProtos.BulkPublishResponse.newBuilder(); observer.onNext(builder.build()); observer.onCompleted(); @@ -209,9 +214,9 @@ public void publishEventsTest() { }).when(daprStub).bulkPublishEventAlpha1(any(DaprProtos.BulkPublishRequest.class), any()); BulkPublishEntry entry = new BulkPublishEntry<>("1", "test", - "text/plain", null); + "text/plain", null); BulkPublishRequest req = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.singletonList(entry)); + Collections.singletonList(entry)); Mono> result = previewClient.publishEvents(req); BulkPublishResponse res = result.block(); Assertions.assertNotNull(res); @@ -222,7 +227,7 @@ public void publishEventsTest() { public void publishEventsWithoutMetaTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; DaprProtos.BulkPublishResponse.Builder builder = DaprProtos.BulkPublishResponse.newBuilder(); observer.onNext(builder.build()); observer.onCompleted(); @@ -230,7 +235,7 @@ public void publishEventsWithoutMetaTest() { }).when(daprStub).bulkPublishEventAlpha1(any(DaprProtos.BulkPublishRequest.class), any()); Mono> result = previewClient.publishEvents(PUBSUB_NAME, TOPIC_NAME, - "text/plain", Collections.singletonList("test")); + "text/plain", Collections.singletonList("test")); BulkPublishResponse res = result.block(); Assertions.assertNotNull(res); assertEquals( 0, res.getFailedEntries().size(), "expected no entries in failed entries list"); @@ -240,7 +245,7 @@ public void publishEventsWithoutMetaTest() { public void publishEventsWithRequestMetaTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; DaprProtos.BulkPublishResponse.Builder builder = DaprProtos.BulkPublishResponse.newBuilder(); observer.onNext(builder.build()); observer.onCompleted(); @@ -248,9 +253,9 @@ public void publishEventsWithRequestMetaTest() { }).when(daprStub).bulkPublishEventAlpha1(any(DaprProtos.BulkPublishRequest.class), any()); Mono> result = previewClient.publishEvents(PUBSUB_NAME, TOPIC_NAME, - "text/plain", new HashMap(){{ - put("ttlInSeconds", "123"); - }}, Collections.singletonList("test")); + "text/plain", new HashMap(){{ + put("ttlInSeconds", "123"); + }}, Collections.singletonList("test")); BulkPublishResponse res = result.block(); Assertions.assertNotNull(res); assertEquals( 0, res.getFailedEntries().size(), "expected no entry in failed entries list"); @@ -260,7 +265,7 @@ public void publishEventsWithRequestMetaTest() { public void publishEventsObjectTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(DaprProtos.BulkPublishResponse.getDefaultInstance()); observer.onCompleted(); return null; @@ -271,7 +276,7 @@ public void publishEventsObjectTest() { } if (!"{\"id\":1,\"value\":\"Event\"}".equals(new String(entry.getEvent().toByteArray())) && - !"{\"value\":\"Event\",\"id\":1}".equals(new String(entry.getEvent().toByteArray()))) { + !"{\"value\":\"Event\",\"id\":1}".equals(new String(entry.getEvent().toByteArray()))) { return false; } return true; @@ -280,9 +285,9 @@ public void publishEventsObjectTest() { DaprClientGrpcTest.MyObject event = new DaprClientGrpcTest.MyObject(1, "Event"); BulkPublishEntry entry = new BulkPublishEntry<>("1", event, - "application/json", null); + "application/json", null); BulkPublishRequest req = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.singletonList(entry)); + Collections.singletonList(entry)); BulkPublishResponse result = previewClient.publishEvents(req).block(); Assertions.assertNotNull(result); Assertions.assertEquals(0, result.getFailedEntries().size(), "expected no entries to be failed"); @@ -292,7 +297,7 @@ public void publishEventsObjectTest() { public void publishEventsContentTypeOverrideTest() { doAnswer((Answer) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(DaprProtos.BulkPublishResponse.getDefaultInstance()); observer.onCompleted(); return null; @@ -309,9 +314,9 @@ public void publishEventsContentTypeOverrideTest() { }), any()); BulkPublishEntry entry = new BulkPublishEntry<>("1", "hello", - "", null); + "", null); BulkPublishRequest req = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_NAME, - Collections.singletonList(entry)); + Collections.singletonList(entry)); BulkPublishResponse result = previewClient.publishEvents(req).block(); Assertions.assertNotNull(result); Assertions.assertEquals( 0, result.getFailedEntries().size(), "expected no entries to be failed"); @@ -351,7 +356,7 @@ public void queryState() throws JsonProcessingException { assertEquals(0, req.getMetadataCount()); StreamObserver observer = (StreamObserver) - invocation.getArguments()[1]; + invocation.getArguments()[1]; observer.onNext(responseEnvelope); observer.onCompleted(); return null; @@ -378,14 +383,14 @@ public void queryStateMetadataError() throws JsonProcessingException { assertEquals(1, req.getMetadataCount()); StreamObserver observer = (StreamObserver) - invocation.getArguments()[1]; + invocation.getArguments()[1]; observer.onNext(responseEnvelope); observer.onCompleted(); return null; }).when(daprStub).queryStateAlpha1(any(DaprProtos.QueryStateRequest.class), any()); QueryStateResponse response = previewClient.queryState(QUERY_STORE_NAME, "query", - new HashMap(){{ put("key", "error"); }}, String.class).block(); + new HashMap(){{ put("key", "error"); }}, String.class).block(); assertNotNull(response); assertEquals(1, response.getResults().size(), "result size must be 1"); assertEquals( "1", response.getResults().get(0).getKey(), "result must be same"); @@ -396,7 +401,7 @@ public void queryStateMetadataError() throws JsonProcessingException { public void tryLock() { DaprProtos.TryLockResponse.Builder builder = DaprProtos.TryLockResponse.newBuilder() - .setSuccess(true); + .setSuccess(true); DaprProtos.TryLockResponse response = builder.build(); @@ -408,7 +413,7 @@ public void tryLock() { assertEquals(10, req.getExpiryInSeconds()); StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(response); observer.onCompleted(); return null; @@ -422,7 +427,7 @@ public void tryLock() { public void unLock() { DaprProtos.UnlockResponse.Builder builder = DaprProtos.UnlockResponse.newBuilder() - .setStatus(DaprProtos.UnlockResponse.Status.SUCCESS); + .setStatus(DaprProtos.UnlockResponse.Status.SUCCESS); DaprProtos.UnlockResponse response = builder.build(); @@ -433,7 +438,7 @@ public void unLock() { assertEquals("owner", req.getLockOwner()); StreamObserver observer = - (StreamObserver) invocation.getArguments()[1]; + (StreamObserver) invocation.getArguments()[1]; observer.onNext(response); observer.onCompleted(); return null; @@ -457,7 +462,7 @@ public void subscribeEventTest() throws Exception { doAnswer((Answer>) invocation -> { StreamObserver observer = - (StreamObserver) invocation.getArguments()[0]; + (StreamObserver) invocation.getArguments()[0]; var emitterThread = new Thread(() -> { try { started.acquire(); @@ -467,27 +472,27 @@ public void subscribeEventTest() throws Exception { observer.onNext(DaprProtos.SubscribeTopicEventsResponseAlpha1.getDefaultInstance()); for (int i = 0; i < numEvents; i++) { observer.onNext(DaprProtos.SubscribeTopicEventsResponseAlpha1.newBuilder() - .setEventMessage(DaprAppCallbackProtos.TopicEventRequest.newBuilder() - .setId(Integer.toString(i)) - .setPubsubName(pubsubName) - .setTopic(topicName) - .setData(ByteString.copyFromUtf8("\"" + data + "\"")) - .setDataContentType("application/json") - .build()) - .build()); + .setEventMessage(DaprAppCallbackProtos.TopicEventRequest.newBuilder() + .setId(Integer.toString(i)) + .setPubsubName(pubsubName) + .setTopic(topicName) + .setData(ByteString.copyFromUtf8("\"" + data + "\"")) + .setDataContentType("application/json") + .build()) + .build()); } for (int i = 0; i < numDrops; i++) { // Bad messages observer.onNext(DaprProtos.SubscribeTopicEventsResponseAlpha1.newBuilder() - .setEventMessage(DaprAppCallbackProtos.TopicEventRequest.newBuilder() - .setId(UUID.randomUUID().toString()) - .setPubsubName("bad pubsub") - .setTopic("bad topic") - .setData(ByteString.copyFromUtf8("\"\"")) - .setDataContentType("application/json") - .build()) - .build()); + .setEventMessage(DaprAppCallbackProtos.TopicEventRequest.newBuilder() + .setId(UUID.randomUUID().toString()) + .setPubsubName("bad pubsub") + .setTopic("bad topic") + .setData(ByteString.copyFromUtf8("\"\"")) + .setDataContentType("application/json") + .build()) + .build()); } observer.onCompleted(); }); @@ -517,38 +522,38 @@ public void onCompleted() { final AtomicInteger errorsToBeEmitted = new AtomicInteger(numErrors); var subscription = previewClient.subscribeToEvents( - "pubsubname", - "topic", - new SubscriptionListener<>() { - @Override - public Mono onEvent(CloudEvent event) { - if (event.getPubsubName().equals(pubsubName) && - event.getTopic().equals(topicName) && - event.getData().equals(data)) { - - // Simulate an error - if ((success.size() == 4 /* some random entry */) && errorsToBeEmitted.decrementAndGet() >= 0) { - throw new RuntimeException("simulated exception on event " + event.getId()); + "pubsubname", + "topic", + new SubscriptionListener<>() { + @Override + public Mono onEvent(CloudEvent event) { + if (event.getPubsubName().equals(pubsubName) && + event.getTopic().equals(topicName) && + event.getData().equals(data)) { + + // Simulate an error + if ((success.size() == 4 /* some random entry */) && errorsToBeEmitted.decrementAndGet() >= 0) { + throw new RuntimeException("simulated exception on event " + event.getId()); + } + + success.add(event.getId()); + if (success.size() >= numEvents) { + gotAll.release(); + } + return Mono.just(Status.SUCCESS); + } + + dropCounter.incrementAndGet(); + return Mono.just(Status.DROP); } - success.add(event.getId()); - if (success.size() >= numEvents) { - gotAll.release(); + @Override + public void onError(RuntimeException exception) { + errors.add(exception.getMessage()); } - return Mono.just(Status.SUCCESS); - } - - dropCounter.incrementAndGet(); - return Mono.just(Status.DROP); - } - - @Override - public void onError(RuntimeException exception) { - errors.add(exception.getMessage()); - } - }, - TypeRef.STRING); + }, + TypeRef.STRING); gotAll.acquire(); subscription.close(); @@ -558,17 +563,164 @@ public void onError(RuntimeException exception) { assertEquals(numErrors, errors.size()); } + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenComponentNameIsNull() throws Exception { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new ConversationInput("Hello there !")); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest(null, daprConversationInputs)).block()); + assertEquals("LLM name cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationComponentIsEmpty() throws Exception { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new ConversationInput("Hello there !")); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest("", daprConversationInputs)).block()); + assertEquals("LLM name cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsEmpty() throws Exception { + List daprConversationInputs = new ArrayList<>(); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsNull() throws Exception { + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest("openai", null)).block()); + assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsNull() throws Exception { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new ConversationInput(null)); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsEmpty() throws Exception { + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new ConversationInput("")); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> + previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); + } + + @Test + public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() throws Exception { + DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() + .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); + + doAnswer(invocation -> { + StreamObserver observer = invocation.getArgument(1); + observer.onNext(conversationResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); + + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(new ConversationInput("Hello there")); + ConversationResponse daprConversationResponse = + previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); + verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); + + DaprProtos.ConversationRequest conversationRequest = captor.getValue(); + + assertEquals("openai", conversationRequest.getName()); + assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); + assertEquals("Hello How are you", + daprConversationResponse.getConversationOutpus().get(0).getResult()); + } + + @Test + public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInputsAreValid() throws Exception { + DaprProtos.ConversationResponse conversationResponse = DaprProtos.ConversationResponse.newBuilder() + .setContextID("contextId") + .addOutputs(DaprProtos.ConversationResult.newBuilder().setResult("Hello How are you").build()).build(); + + doAnswer(invocation -> { + StreamObserver observer = invocation.getArgument(1); + observer.onNext(conversationResponse); + observer.onCompleted(); + return null; + }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); + + ConversationInput daprConversationInput = new ConversationInput("Hello there") + .setRole(ConversationRole.ASSISTANT) + .setScrubPii(true); + + List daprConversationInputs = new ArrayList<>(); + daprConversationInputs.add(daprConversationInput); + + ConversationResponse daprConversationResponse = + previewClient.converse(new ConversationRequest("openai", daprConversationInputs) + .setContextId("contextId") + .setScrubPii(true) + .setTemperature(1.1d)).block(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); + verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any()); + + DaprProtos.ConversationRequest conversationRequest = captor.getValue(); + + assertEquals("openai", conversationRequest.getName()); + assertEquals("contextId", conversationRequest.getContextID()); + assertTrue(conversationRequest.getScrubPII()); + assertEquals(1.1d, conversationRequest.getTemperature(), 0d); + assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); + assertTrue(conversationRequest.getInputs(0).getScrubPII()); + assertEquals(ConversationRole.ASSISTANT.toString(), conversationRequest.getInputs(0).getRole()); + assertEquals("contextId", daprConversationResponse.getContextId()); + assertEquals("Hello How are you", + daprConversationResponse.getConversationOutpus().get(0).getResult()); + } + + private DaprProtos.QueryStateResponse buildQueryStateResponse(List> resp,String token) + throws JsonProcessingException { + List items = new ArrayList<>(); + for (QueryStateItem item: resp) { + items.add(buildQueryStateItem(item)); + } + return DaprProtos.QueryStateResponse.newBuilder() + .addAllResults(items) + .setToken(token) + .build(); + } + @Test public void scheduleJobShouldSucceedWhenAllFieldsArePresentInRequest() { DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - .withZone(ZoneOffset.UTC); + .withZone(ZoneOffset.UTC); ScheduleJobRequest expectedScheduleJobRequest = new ScheduleJobRequest("testJob", - JobSchedule.fromString("*/5 * * * *")) - .setData("testData".getBytes()) - .setTtl(Instant.now().plus(1, ChronoUnit.DAYS)) - .setRepeat(5) - .setDueTime(Instant.now().plus(10, ChronoUnit.MINUTES)); + JobSchedule.fromString("*/5 * * * *")) + .setData("testData".getBytes()) + .setTtl(Instant.now().plus(1, ChronoUnit.DAYS)) + .setRepeat(5) + .setDueTime(Instant.now().plus(10, ChronoUnit.MINUTES)); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); @@ -579,14 +731,14 @@ public void scheduleJobShouldSucceedWhenAllFieldsArePresentInRequest() { assertDoesNotThrow(() -> previewClient.scheduleJob(expectedScheduleJobRequest).block()); ArgumentCaptor captor = - ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); + ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); verify(daprStub, times(1)).scheduleJobAlpha1(captor.capture(), Mockito.any()); DaprProtos.ScheduleJobRequest actualScheduleJobReq = captor.getValue(); assertEquals("testJob", actualScheduleJobReq.getJob().getName()); assertEquals("testData", - new String(actualScheduleJobReq.getJob().getData().getValue().toByteArray(), StandardCharsets.UTF_8)); + new String(actualScheduleJobReq.getJob().getData().getValue().toByteArray(), StandardCharsets.UTF_8)); assertEquals("*/5 * * * *", actualScheduleJobReq.getJob().getSchedule()); assertEquals(iso8601Formatter.format(expectedScheduleJobRequest.getTtl()), actualScheduleJobReq.getJob().getTtl()); assertEquals(expectedScheduleJobRequest.getRepeats(), actualScheduleJobReq.getJob().getRepeats()); @@ -596,7 +748,7 @@ public void scheduleJobShouldSucceedWhenAllFieldsArePresentInRequest() { @Test public void scheduleJobShouldSucceedWhenRequiredFieldsNameAndDueTimeArePresentInRequest() { DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - .withZone(ZoneOffset.UTC); + .withZone(ZoneOffset.UTC); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); @@ -605,11 +757,11 @@ public void scheduleJobShouldSucceedWhenRequiredFieldsNameAndDueTimeArePresentIn }).when(daprStub).scheduleJobAlpha1(any(DaprProtos.ScheduleJobRequest.class), any()); ScheduleJobRequest expectedScheduleJobRequest = - new ScheduleJobRequest("testJob", Instant.now().plus(10, ChronoUnit.MINUTES)); + new ScheduleJobRequest("testJob", Instant.now().plus(10, ChronoUnit.MINUTES)); assertDoesNotThrow(() -> previewClient.scheduleJob(expectedScheduleJobRequest).block()); ArgumentCaptor captor = - ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); + ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); verify(daprStub, times(1)).scheduleJobAlpha1(captor.capture(), Mockito.any()); DaprProtos.ScheduleJobRequest actualScheduleJobRequest = captor.getValue(); @@ -620,13 +772,13 @@ public void scheduleJobShouldSucceedWhenRequiredFieldsNameAndDueTimeArePresentIn assertEquals(0, job.getRepeats()); assertFalse(job.hasTtl()); assertEquals(iso8601Formatter.format(expectedScheduleJobRequest.getDueTime()), - actualScheduleJobRequest.getJob().getDueTime()); + actualScheduleJobRequest.getJob().getDueTime()); } @Test public void scheduleJobShouldSucceedWhenRequiredFieldsNameAndScheduleArePresentInRequest() { DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - .withZone(ZoneOffset.UTC); + .withZone(ZoneOffset.UTC); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); @@ -635,11 +787,11 @@ public void scheduleJobShouldSucceedWhenRequiredFieldsNameAndScheduleArePresentI }).when(daprStub).scheduleJobAlpha1(any(DaprProtos.ScheduleJobRequest.class), any()); ScheduleJobRequest expectedScheduleJobRequest = new ScheduleJobRequest("testJob", - JobSchedule.fromString("* * * * * *")); + JobSchedule.fromString("* * * * * *")); assertDoesNotThrow(() -> previewClient.scheduleJob(expectedScheduleJobRequest).block()); ArgumentCaptor captor = - ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); + ArgumentCaptor.forClass(DaprProtos.ScheduleJobRequest.class); verify(daprStub, times(1)).scheduleJobAlpha1(captor.capture(), Mockito.any()); DaprProtos.ScheduleJobRequest actualScheduleJobRequest = captor.getValue(); @@ -681,24 +833,24 @@ public void scheduleJobShouldThrowWhenNameInRequestIsEmpty() { @Test public void getJobShouldReturnResponseWhenAllFieldsArePresentInRequest() { DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - .withZone(ZoneOffset.UTC); + .withZone(ZoneOffset.UTC); GetJobRequest getJobRequest = new GetJobRequest("testJob"); DaprProtos.Job job = DaprProtos.Job.newBuilder() - .setName("testJob") - .setTtl(OffsetDateTime.now().format(iso8601Formatter)) - .setData(Any.newBuilder().setValue(ByteString.copyFrom("testData".getBytes())).build()) - .setSchedule("*/5 * * * *") - .setRepeats(5) - .setDueTime(iso8601Formatter.format(Instant.now().plus(10, ChronoUnit.MINUTES))) - .build(); + .setName("testJob") + .setTtl(OffsetDateTime.now().format(iso8601Formatter)) + .setData(Any.newBuilder().setValue(ByteString.copyFrom("testData".getBytes())).build()) + .setSchedule("*/5 * * * *") + .setRepeats(5) + .setDueTime(iso8601Formatter.format(Instant.now().plus(10, ChronoUnit.MINUTES))) + .build(); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); observer.onNext(DaprProtos.GetJobResponse.newBuilder() - .setJob(job) - .build()); + .setJob(job) + .build()); observer.onCompleted(); return null; }).when(daprStub).getJobAlpha1(any(DaprProtos.GetJobRequest.class), any()); @@ -720,15 +872,15 @@ public void getJobShouldReturnResponseWithScheduleSetWhenResponseHasSchedule() { GetJobRequest getJobRequest = new GetJobRequest("testJob"); DaprProtos.Job job = DaprProtos.Job.newBuilder() - .setName("testJob") - .setSchedule("0 0 0 1 1 *") - .build(); + .setName("testJob") + .setSchedule("0 0 0 1 1 *") + .build(); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); observer.onNext(DaprProtos.GetJobResponse.newBuilder() - .setJob(job) - .build()); + .setJob(job) + .build()); observer.onCompleted(); return null; }).when(daprStub).getJobAlpha1(any(DaprProtos.GetJobRequest.class), any()); @@ -751,15 +903,15 @@ public void getJobShouldReturnResponseWithDueTimeSetWhenResponseHasDueTime() { String datetime = OffsetDateTime.now().toString(); DaprProtos.Job job = DaprProtos.Job.newBuilder() - .setName("testJob") - .setDueTime(datetime) - .build(); + .setName("testJob") + .setDueTime(datetime) + .build(); doAnswer(invocation -> { StreamObserver observer = invocation.getArgument(1); observer.onNext(DaprProtos.GetJobResponse.newBuilder() - .setJob(job) - .build()); + .setJob(job) + .build()); observer.onCompleted(); return null; }).when(daprStub).getJobAlpha1(any(DaprProtos.GetJobRequest.class), any()); @@ -846,15 +998,15 @@ public void deleteJobShouldThrowWhenNameIsEmptyRequest() { } private DaprProtos.QueryStateResponse buildQueryStateResponse(List> resp,String token) - throws JsonProcessingException { + throws JsonProcessingException { List items = new ArrayList<>(); for (QueryStateItem item: resp) { items.add(buildQueryStateItem(item)); } return DaprProtos.QueryStateResponse.newBuilder() - .addAllResults(items) - .setToken(token) - .build(); + .addAllResults(items) + .setToken(token) + .build(); } private DaprProtos.QueryStateItem buildQueryStateItem(QueryStateItem item) throws JsonProcessingException { From 00a02800b7c672babb91459db71cecdc6a6affd7 Mon Sep 17 00:00:00 2001 From: sirivarma Date: Tue, 18 Mar 2025 08:14:25 -0700 Subject: [PATCH 05/23] Remove module Signed-off-by: sirivarma Signed-off-by: siri-varma --- examples/pom.xml | 5 ----- .../conversation/DemoConversationAI.java | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index df7397ee6d..192c7f2f7f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -118,11 +118,6 @@ dapr-sdk ${project.version} - - io.dapr - dapr-sdk-conversation - ${project.version} - com.evanlennick retry4j diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index 014ca686ea..07652ef223 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -1,9 +1,10 @@ package io.dapr.examples.conversation; -import io.dapr.ai.client.DaprConversationClient; -import io.dapr.ai.client.DaprConversationInput; -import io.dapr.ai.client.DaprConversationResponse; -import io.dapr.v1.DaprProtos; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.ConversationInput; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; import reactor.core.publisher.Mono; import java.util.ArrayList; @@ -16,13 +17,15 @@ public class DemoConversationAI { * @param args Input arguments (unused). */ public static void main(String[] args) { - try (DaprConversationClient client = new DaprConversationClient()) { - DaprConversationInput daprConversationInput = new DaprConversationInput("11"); + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + ConversationInput daprConversationInput = new ConversationInput("11"); // Component name is the name provided in the metadata block of the conversation.yaml file. - Mono instanceId = client.converse("openai", new ArrayList<>(Collections.singleton(daprConversationInput)), "1234", false, 0.0d); + Mono instanceId = client.converse(new ConversationRequest("openai", new ArrayList<>(Collections.singleton(daprConversationInput))) + .setContextId("contextId") + .setScrubPii(true).setTemperature(1.1d)); System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId); - DaprConversationResponse response = instanceId.block(); + ConversationResponse response = instanceId.block(); System.out.println(response); } catch (Exception e) { From 90a68ea084dbe48f6ac42d09ac83ce4939975eaf Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 18 Mar 2025 13:33:50 -0700 Subject: [PATCH 06/23] Add Integration tests Signed-off-by: siri-varma --- .../conversation/DemoConversationAI.java | 2 +- .../it/testcontainers/DaprConversationIT.java | 119 ++++++++++++++++++ .../TestConversationApplication.java | 26 ++++ .../TestDaprConversationConfiguration.java | 41 ++++++ 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index 07652ef223..574de137a7 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -18,7 +18,7 @@ public class DemoConversationAI { */ public static void main(String[] args) { try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { - ConversationInput daprConversationInput = new ConversationInput("11"); + ConversationInput daprConversationInput = new ConversationInput("Hello How are you ?"); // Component name is the name provided in the metadata block of the conversation.yaml file. Mono instanceId = client.converse(new ConversationRequest("openai", new ArrayList<>(Collections.singleton(daprConversationInput))) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java new file mode 100644 index 0000000000..2f3c3b3a86 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -0,0 +1,119 @@ +package io.dapr.it.testcontainers; + +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.ConversationInput; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +@SpringBootTest( + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = { + TestDaprConversationConfiguration.class, + TestConversationApplication.class + } +) +@Testcontainers +@Tag("testcontainers") +public class DaprConversationIT { + + private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final Random RANDOM = new Random(); + private static final int PORT = RANDOM.nextInt(1000) + 8000; + + @Container + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.15.2") + .withAppName("conversation-dapr-app") + .withComponent(new Component("echo", "conversation.echo", "v1", new HashMap<>())) + .withNetwork(DAPR_NETWORK) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal") + .withAppPort(PORT); + + /** + * Expose the Dapr ports to the host. + * + * @param registry the dynamic property registry + */ + @DynamicPropertySource + static void daprProperties(DynamicPropertyRegistry registry) { + registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint); + registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); + registry.add("server.port", () -> PORT); + } + + @Autowired + private DaprPreviewClient daprPreviewClient; + + @BeforeEach + public void setUp(){ + org.testcontainers.Testcontainers.exposeHostPorts(PORT); + // Ensure the subscriptions are registered + } + + @Test + public void testConversationSDKShouldHaveSameOutputAndInput() { + ConversationInput conversationInput = new ConversationInput("input this"); + List conversationInputList = new ArrayList<>(); + conversationInputList.add(conversationInput); + + ConversationResponse response = + this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block(); + + Assertions.assertEquals("", response.getContextId()); + Assertions.assertEquals("input this", response.getConversationOutpus().get(0).getResult()); + } + + @Test + public void testConversationSDKShouldScrubPIIEntirelyWhenScrubPIIIsSetInRequestBody() { + List conversationInputList = new ArrayList<>(); + conversationInputList.add(new ConversationInput("input this abcd@gmail.com")); + conversationInputList.add(new ConversationInput("input this +12341567890")); + + ConversationResponse response = + this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList) + .setScrubPii(true)).block(); + + Assertions.assertEquals("", response.getContextId()); + Assertions.assertEquals("input this ", + response.getConversationOutpus().get(0).getResult()); + Assertions.assertEquals("input this ", + response.getConversationOutpus().get(1).getResult()); + } + + @Test + public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet() { + List conversationInputList = new ArrayList<>(); + conversationInputList.add(new ConversationInput("input this abcd@gmail.com")); + conversationInputList.add(new ConversationInput("input this +12341567890").setScrubPii(true)); + + ConversationResponse response = + this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block(); + + Assertions.assertEquals("", response.getContextId()); + Assertions.assertEquals("input this abcd@gmail.com", + response.getConversationOutpus().get(0).getResult()); + Assertions.assertEquals("input this ", + response.getConversationOutpus().get(1).getResult()); + } +} \ No newline at end of file diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java new file mode 100644 index 0000000000..ec33bdf791 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java @@ -0,0 +1,26 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.it.testcontainers; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestConversationApplication { + + public static void main(String[] args) { + SpringApplication.run(TestConversationApplication.class, args); + } + +} \ No newline at end of file diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java new file mode 100644 index 0000000000..6a096c0584 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.it.testcontainers; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.config.Properties; +import io.dapr.config.Property; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +@Configuration +public class TestDaprConversationConfiguration { + + @Bean + public DaprPreviewClient daprPreviewClient( + @Value("${dapr.http.endpoint}") String daprHttpEndpoint, + @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint + ){ + Map, String> overrides = Map.of( + Properties.HTTP_ENDPOINT, daprHttpEndpoint, + Properties.GRPC_ENDPOINT, daprGrpcEndpoint + ); + + return new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient(); + } +} \ No newline at end of file From fcb3177dc25436448597aced35e070c01ee74a09 Mon Sep 17 00:00:00 2001 From: Siri Varma Vegiraju Date: Sat, 12 Apr 2025 15:06:07 +0530 Subject: [PATCH 07/23] Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java Co-authored-by: Cassie Coyle Signed-off-by: Siri Varma Vegiraju Signed-off-by: siri-varma --- .github/workflows/validate.yml | 6 + .../components/conversation/conversation.yaml | 7 ++ .../conversation/DemoConversationAI.java | 51 +++++--- .../io/dapr/examples/conversation/README.md | 117 ++++++++++++++++++ .../it/testcontainers/DaprConversationIT.java | 14 ++- .../dapr/client/domain/ConversationInput.java | 13 ++ .../client/domain/ConversationOutput.java | 13 ++ .../client/domain/ConversationRequest.java | 13 ++ .../client/domain/ConversationResponse.java | 13 ++ .../dapr/client/domain/ConversationRole.java | 13 ++ 10 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 examples/components/conversation/conversation.yaml create mode 100644 examples/src/main/java/io/dapr/examples/conversation/README.md diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7f09a2325a..42a07c8785 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -121,6 +121,12 @@ jobs: mm.py ./src/main/java/io/dapr/examples/jobs/README.md env: DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} + - name: Validate conversation ai example + working-directory: ./examples + run: | + mm.py ./src/main/java/io/dapr/examples/conversation/README.md + env: + DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} - name: Validate invoke http example working-directory: ./examples run: | diff --git a/examples/components/conversation/conversation.yaml b/examples/components/conversation/conversation.yaml new file mode 100644 index 0000000000..9a8b3072d7 --- /dev/null +++ b/examples/components/conversation/conversation.yaml @@ -0,0 +1,7 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: echo +spec: + type: conversation.echo + version: v1 \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index 574de137a7..f691916bf2 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.examples.conversation; import io.dapr.client.DaprClientBuilder; @@ -11,25 +24,25 @@ import java.util.Collections; public class DemoConversationAI { - /** - * The main method to start the client. - * - * @param args Input arguments (unused). - */ - public static void main(String[] args) { - try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { - ConversationInput daprConversationInput = new ConversationInput("Hello How are you ?"); - - // Component name is the name provided in the metadata block of the conversation.yaml file. - Mono instanceId = client.converse(new ConversationRequest("openai", new ArrayList<>(Collections.singleton(daprConversationInput))) - .setContextId("contextId") - .setScrubPii(true).setTemperature(1.1d)); - System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId); - ConversationResponse response = instanceId.block(); + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " + + "This is the my number 672-123-4567"); - System.out.println(response); - } catch (Exception e) { - throw new RuntimeException(e); - } + // Component name is the name provided in the metadata block of the conversation.yaml file. + Mono responseMono = client.converse(new ConversationRequest("echo", + new ArrayList<>(Collections.singleton(daprConversationInput))) + .setContextId("contextId") + .setScrubPii(true).setTemperature(1.1d)); + ConversationResponse response = responseMono.block(); + System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); + } catch (Exception e) { + throw new RuntimeException(e); } + } } \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/conversation/README.md b/examples/src/main/java/io/dapr/examples/conversation/README.md new file mode 100644 index 0000000000..557a4d1aba --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/conversation/README.md @@ -0,0 +1,117 @@ +## Manage Dapr Jobs via the Conversation API + +This example provides the different capabilities provided by Dapr Java SDK for Conversation. For further information about Job APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/) + +### Using the Conversation API + +The Java SDK exposes several methods for this - +* `client.converse(...)` for conversing with an LLM through Dapr. + +## Pre-requisites + +* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/). +* Java JDK 11 (or greater): + * [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + * [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) + * [OpenJDK 11](https://jdk.java.net/11/) +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` + +Then get into the examples directory: + +```sh +cd examples +``` + +### Initialize Dapr + +Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. + +### Running the example + +This example uses the Java SDK Dapr client in order to **Converse** with an LLM. +`DemoConversationAI.java` is the example class demonstrating these features. +Kindly check [DaprPreviewClient.java](https://github.com/dapr/java-sdk/blob/master/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java) for a detailed description of the supported APIs. + +```java +public class DemoConversationAI { + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + */ + public static void main(String[] args) { + try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " + + "This is the my number 672-123-4567"); + + // Component name is the name provided in the metadata block of the conversation.yaml file. + Mono responseMono = client.converse(new ConversationRequest("echo", + new ArrayList<>(Collections.singleton(daprConversationInput))) + .setContextId("contextId") + .setScrubPii(true).setTemperature(1.1d)); + ConversationResponse response = responseMono.block(); + System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} +``` + +Get into the examples' directory: +```sh +cd examples +``` + +Use the following command to run this example- + + + +```bash +dapr run --resources-path ./components/conversation --app-id myapp --app-port 8080 --dapr-http-port 3500 --dapr-grpc-port 51439 --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.conversation.DemoConversationAI +``` + + + +### Sample output +``` +== APP == Conversation output: Hello How are you? This is the my number +``` +### Cleanup + +To stop the app, run (or press CTRL+C): + + + +```bash +dapr stop --app-id myapp +``` + + + diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index 2f3c3b3a86..7469b8db3d 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.it.testcontainers; import io.dapr.client.DaprPreviewClient; @@ -68,7 +81,6 @@ static void daprProperties(DynamicPropertyRegistry registry) { @BeforeEach public void setUp(){ org.testcontainers.Testcontainers.exposeHostPorts(PORT); - // Ensure the subscriptions are registered } @Test diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java index a0e3729365..44958b1cd3 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client.domain; /** diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java index 6279ca6049..9fe70d36a3 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client.domain; import java.util.Collections; diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java index de58df6827..7e52aadbfa 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client.domain; import java.util.List; diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java index 9e87b8b4ff..835025bfc9 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client.domain; import java.util.Collections; diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java index 5374c7fa9d..bc0834010d 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java @@ -1,3 +1,16 @@ +/* + * Copyright 2021 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client.domain; /** From 9bb922ecf498614d7fc7a034811eef8daed5aa7b Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 06:31:12 -0700 Subject: [PATCH 08/23] Fix things Signed-off-by: siri-varma --- .../main/java/io/dapr/client/DaprPreviewClient.java | 4 ++-- .../io/dapr/client/DaprPreviewClientGrpcTest.java | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java index c8b25107c1..89c6eded8f 100644 --- a/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprPreviewClient.java @@ -17,11 +17,11 @@ import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; import io.dapr.client.domain.BulkPublishResponseFailedEntry; +import io.dapr.client.domain.ConversationRequest; +import io.dapr.client.domain.ConversationResponse; import io.dapr.client.domain.DeleteJobRequest; import io.dapr.client.domain.GetJobRequest; import io.dapr.client.domain.GetJobResponse; -import io.dapr.client.domain.ConversationRequest; -import io.dapr.client.domain.ConversationResponse; import io.dapr.client.domain.LockRequest; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index 6e123f647f..15d0e62183 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -698,18 +698,6 @@ public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInput daprConversationResponse.getConversationOutpus().get(0).getResult()); } - private DaprProtos.QueryStateResponse buildQueryStateResponse(List> resp,String token) - throws JsonProcessingException { - List items = new ArrayList<>(); - for (QueryStateItem item: resp) { - items.add(buildQueryStateItem(item)); - } - return DaprProtos.QueryStateResponse.newBuilder() - .addAllResults(items) - .setToken(token) - .build(); - } - @Test public void scheduleJobShouldSucceedWhenAllFieldsArePresentInRequest() { DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") From 1d6f7c78e3bc0c49b3033f5fe45c1549bdd384a2 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 07:18:47 -0700 Subject: [PATCH 09/23] Address comments Signed-off-by: siri-varma --- .../conversation/DemoConversationAI.java | 15 ++--- .../io/dapr/examples/conversation/README.md | 14 +++-- .../it/testcontainers/DaprConversationIT.java | 2 +- .../TestConversationApplication.java | 3 +- .../TestDaprConversationConfiguration.java | 2 +- .../java/io/dapr/client/DaprClientImpl.java | 11 ++-- .../dapr/client/domain/ConversationInput.java | 6 +- .../client/domain/ConversationRequest.java | 28 ++++----- .../client/domain/ConversationResponse.java | 10 +-- .../dapr/client/domain/ConversationRole.java | 35 ----------- .../client/DaprPreviewClientGrpcTest.java | 63 +++++++++---------- 11 files changed, 77 insertions(+), 112 deletions(-) delete mode 100644 sdk/src/main/java/io/dapr/client/domain/ConversationRole.java diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index f691916bf2..604e16b864 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -20,8 +20,7 @@ import io.dapr.client.domain.ConversationResponse; import reactor.core.publisher.Mono; -import java.util.ArrayList; -import java.util.Collections; +import java.util.List; public class DemoConversationAI { /** @@ -31,18 +30,20 @@ public class DemoConversationAI { */ public static void main(String[] args) { try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567"); + ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " - + "This is the my number 672-123-4567"); + + "This is the my number 672-123-4567"); // Component name is the name provided in the metadata block of the conversation.yaml file. Mono responseMono = client.converse(new ConversationRequest("echo", - new ArrayList<>(Collections.singleton(daprConversationInput))) - .setContextId("contextId") - .setScrubPii(true).setTemperature(1.1d)); + List.of(daprConversationInput)) + .setContextId("contextId") + .setScrubPii(true).setTemperature(1.1d)); ConversationResponse response = responseMono.block(); System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); } catch (Exception e) { throw new RuntimeException(e); } } -} \ No newline at end of file +} diff --git a/examples/src/main/java/io/dapr/examples/conversation/README.md b/examples/src/main/java/io/dapr/examples/conversation/README.md index 557a4d1aba..599ea5cbbe 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/README.md +++ b/examples/src/main/java/io/dapr/examples/conversation/README.md @@ -1,6 +1,6 @@ -## Manage Dapr Jobs via the Conversation API +## Manage Dapr via the Conversation API -This example provides the different capabilities provided by Dapr Java SDK for Conversation. For further information about Job APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/) +This example provides the different capabilities provided by Dapr Java SDK for Conversation. For further information about Conversation APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/) ### Using the Conversation API @@ -57,14 +57,16 @@ public class DemoConversationAI { */ public static void main(String[] args) { try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { + System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567"); + ConversationInput daprConversationInput = new ConversationInput("Hello How are you? " - + "This is the my number 672-123-4567"); + + "This is the my number 672-123-4567"); // Component name is the name provided in the metadata block of the conversation.yaml file. Mono responseMono = client.converse(new ConversationRequest("echo", - new ArrayList<>(Collections.singleton(daprConversationInput))) - .setContextId("contextId") - .setScrubPii(true).setTemperature(1.1d)); + List.of(daprConversationInput)) + .setContextId("contextId") + .setScrubPii(true).setTemperature(1.1d)); ConversationResponse response = responseMono.block(); System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); } catch (Exception e) { diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index 7469b8db3d..ef5a99bc6b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -128,4 +128,4 @@ public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet() Assertions.assertEquals("input this ", response.getConversationOutpus().get(1).getResult()); } -} \ No newline at end of file +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java index ec33bdf791..2bb9eeac10 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestConversationApplication.java @@ -22,5 +22,4 @@ public class TestConversationApplication { public static void main(String[] args) { SpringApplication.run(TestConversationApplication.class, args); } - -} \ No newline at end of file +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java index 6a096c0584..ac1c2457dd 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java @@ -38,4 +38,4 @@ public DaprPreviewClient daprPreviewClient( return new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient(); } -} \ No newline at end of file +} diff --git a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java index 03b9e938b5..66c8772d66 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientImpl.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java @@ -103,7 +103,6 @@ import java.io.IOException; import java.time.Duration; import java.time.Instant; -import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -1568,13 +1567,13 @@ public Mono converse(ConversationRequest conversationReque DaprProtos.ConversationRequest.Builder protosConversationRequestBuilder = DaprProtos.ConversationRequest .newBuilder().setTemperature(conversationRequest.getTemperature()) .setScrubPII(conversationRequest.isScrubPii()) - .setName(conversationRequest.getLlmName()); + .setName(conversationRequest.getName()); if (conversationRequest.getContextId() != null) { protosConversationRequestBuilder.setContextID(conversationRequest.getContextId()); } - for (ConversationInput input : conversationRequest.getConversationInputs()) { + for (ConversationInput input : conversationRequest.getInputs()) { if (input.getContent() == null || input.getContent().isEmpty()) { throw new IllegalArgumentException("Conversation input content cannot be null or empty."); } @@ -1619,12 +1618,12 @@ public Mono converse(ConversationRequest conversationReque } private void validateConversationRequest(ConversationRequest conversationRequest) { - if ((conversationRequest.getLlmName() == null) || (conversationRequest.getLlmName().trim().isEmpty())) { + if ((conversationRequest.getName() == null) || (conversationRequest.getName().trim().isEmpty())) { throw new IllegalArgumentException("LLM name cannot be null or empty."); } - if ((conversationRequest.getConversationInputs() == null) || (conversationRequest - .getConversationInputs().isEmpty())) { + if ((conversationRequest.getInputs() == null) || (conversationRequest + .getInputs().isEmpty())) { throw new IllegalArgumentException("Conversation inputs cannot be null or empty."); } } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java index 44958b1cd3..70f9d8ccaf 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java @@ -20,7 +20,7 @@ public class ConversationInput { private final String content; - private ConversationRole role; + private String role; private boolean scrubPii; @@ -47,7 +47,7 @@ public String getContent() { * * @return this. */ - public ConversationRole getRole() { + public String getRole() { return role; } @@ -57,7 +57,7 @@ public ConversationRole getRole() { * @param role The role to assign to the message. * @return this. */ - public ConversationInput setRole(ConversationRole role) { + public ConversationInput setRole(String role) { this.role = role; return this; } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java index 7e52aadbfa..8bac65b9a2 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java @@ -21,8 +21,8 @@ */ public class ConversationRequest { - private final String llmName; - private final List daprConversationInputs; + private final String name; + private final List inputs; private String contextId; private boolean scrubPii; private double temperature; @@ -30,13 +30,13 @@ public class ConversationRequest { /** * Constructs a DaprConversation with a component name and conversation inputs. * - * @param llmName The name of the LLM component. See a list of all available conversation components + * @param name The name of the Dapr conversation component. See a list of all available conversation components * @see - * @param conversationInputs the list of Dapr conversation inputs + * @param inputs the list of Dapr conversation inputs */ - public ConversationRequest(String llmName, List conversationInputs) { - this.llmName = llmName; - this.daprConversationInputs = conversationInputs; + public ConversationRequest(String name, List inputs) { + this.name = name; + this.inputs = inputs; } /** @@ -44,17 +44,17 @@ public ConversationRequest(String llmName, List conversationI * * @return the conversation component name */ - public String getLlmName() { - return llmName; + public String getName() { + return name; } /** - * Gets the list of Dapr conversation inputs. + * Gets the list of Dapr conversation input. * - * @return the list of conversation inputs + * @return the list of conversation input */ - public List getConversationInputs() { - return daprConversationInputs; + public List getInputs() { + return inputs; } /** @@ -116,4 +116,4 @@ public ConversationRequest setTemperature(double temperature) { this.temperature = temperature; return this; } -} \ No newline at end of file +} diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java index 835025bfc9..0147f72152 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java @@ -23,17 +23,17 @@ public class ConversationResponse { private String contextId; - private final List daprConversationOutputs; + private final List outputs; /** * Constructor. * * @param contextId context id supplied to LLM. - * @param daprConversationOutputs outputs from the LLM. + * @param outputs outputs from the LLM. */ - public ConversationResponse(String contextId, List daprConversationOutputs) { + public ConversationResponse(String contextId, List outputs) { this.contextId = contextId; - this.daprConversationOutputs = daprConversationOutputs; + this.outputs = outputs; } /** @@ -51,6 +51,6 @@ public String getContextId() { * @return List{@link ConversationOutput}. */ public List getConversationOutpus() { - return Collections.unmodifiableList(this.daprConversationOutputs); + return Collections.unmodifiableList(this.outputs); } } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java deleted file mode 100644 index bc0834010d..0000000000 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRole.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.client.domain; - -/** - * Conversation AI supported roles. - */ -public enum ConversationRole { - - /** - * User Role. - */ - USER, - - /** - * Tool Role. - */ - TOOL, - - /** - * Assistant Role. - */ - ASSISTANT, -} diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index 15d0e62183..93243e8547 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -29,7 +29,6 @@ import io.dapr.client.domain.ConversationInput; import io.dapr.client.domain.ConversationRequest; import io.dapr.client.domain.ConversationResponse; -import io.dapr.client.domain.ConversationRole; import io.dapr.client.domain.QueryStateItem; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; @@ -565,38 +564,38 @@ public void onError(RuntimeException exception) { @Test public void converseShouldThrowIllegalArgumentExceptionWhenComponentNameIsNull() throws Exception { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new ConversationInput("Hello there !")); + List inputs = new ArrayList<>(); + inputs.add(new ConversationInput("Hello there !")); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - previewClient.converse(new ConversationRequest(null, daprConversationInputs)).block()); + previewClient.converse(new ConversationRequest(null, inputs)).block()); assertEquals("LLM name cannot be null or empty.", exception.getMessage()); } @Test public void converseShouldThrowIllegalArgumentExceptionWhenConversationComponentIsEmpty() throws Exception { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new ConversationInput("Hello there !")); + List inputs = new ArrayList<>(); + inputs.add(new ConversationInput("Hello there !")); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - previewClient.converse(new ConversationRequest("", daprConversationInputs)).block()); + previewClient.converse(new ConversationRequest("", inputs)).block()); assertEquals("LLM name cannot be null or empty.", exception.getMessage()); } @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsEmpty() throws Exception { - List daprConversationInputs = new ArrayList<>(); + public void converseShouldThrowIllegalArgumentExceptionWhenInputsIsEmpty() throws Exception { + List inputs = new ArrayList<>(); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + previewClient.converse(new ConversationRequest("openai", inputs)).block()); assertEquals("Conversation inputs cannot be null or empty.", exception.getMessage()); } @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsNull() throws Exception { + public void converseShouldThrowIllegalArgumentExceptionWhenInputsIsNull() throws Exception { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> previewClient.converse(new ConversationRequest("openai", null)).block()); @@ -604,24 +603,24 @@ public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputIsNu } @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsNull() throws Exception { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new ConversationInput(null)); + public void converseShouldThrowIllegalArgumentExceptionWhenInputContentIsNull() throws Exception { + List inputs = new ArrayList<>(); + inputs.add(new ConversationInput(null)); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + previewClient.converse(new ConversationRequest("openai", inputs)).block()); assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); } @Test - public void converseShouldThrowIllegalArgumentExceptionWhenConversationInputContentIsEmpty() throws Exception { - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new ConversationInput("")); + public void converseShouldThrowIllegalArgumentExceptionWhenInputContentIsEmpty() throws Exception { + List inputs = new ArrayList<>(); + inputs.add(new ConversationInput("")); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block()); + previewClient.converse(new ConversationRequest("openai", inputs)).block()); assertEquals("Conversation input content cannot be null or empty.", exception.getMessage()); } @@ -637,10 +636,10 @@ public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() return null; }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(new ConversationInput("Hello there")); - ConversationResponse daprConversationResponse = - previewClient.converse(new ConversationRequest("openai", daprConversationInputs)).block(); + List inputs = new ArrayList<>(); + inputs.add(new ConversationInput("Hello there")); + ConversationResponse response = + previewClient.converse(new ConversationRequest("openai", inputs)).block(); ArgumentCaptor captor = ArgumentCaptor.forClass(DaprProtos.ConversationRequest.class); @@ -651,7 +650,7 @@ public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() assertEquals("openai", conversationRequest.getName()); assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); assertEquals("Hello How are you", - daprConversationResponse.getConversationOutpus().get(0).getResult()); + response.getConversationOutpus().get(0).getResult()); } @Test @@ -668,14 +667,14 @@ public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInput }).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any()); ConversationInput daprConversationInput = new ConversationInput("Hello there") - .setRole(ConversationRole.ASSISTANT) + .setRole("Assistant") .setScrubPii(true); - List daprConversationInputs = new ArrayList<>(); - daprConversationInputs.add(daprConversationInput); + List inputs = new ArrayList<>(); + inputs.add(daprConversationInput); - ConversationResponse daprConversationResponse = - previewClient.converse(new ConversationRequest("openai", daprConversationInputs) + ConversationResponse response = + previewClient.converse(new ConversationRequest("openai", inputs) .setContextId("contextId") .setScrubPii(true) .setTemperature(1.1d)).block(); @@ -692,10 +691,10 @@ public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInput assertEquals(1.1d, conversationRequest.getTemperature(), 0d); assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); assertTrue(conversationRequest.getInputs(0).getScrubPII()); - assertEquals(ConversationRole.ASSISTANT.toString(), conversationRequest.getInputs(0).getRole()); - assertEquals("contextId", daprConversationResponse.getContextId()); + assertEquals("Assistant", conversationRequest.getInputs(0).getRole()); + assertEquals("contextId", response.getContextId()); assertEquals("Hello How are you", - daprConversationResponse.getConversationOutpus().get(0).getResult()); + response.getConversationOutpus().get(0).getResult()); } @Test From f646a26a41ac102caab216e5e51d0e4f9ae4a091 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 07:50:54 -0700 Subject: [PATCH 10/23] Import tag Signed-off-by: siri-varma --- .../java/io/dapr/it/testcontainers/DaprConversationIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index ef5a99bc6b..7c906354b2 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -38,6 +38,8 @@ import java.util.List; import java.util.Random; +import static io.dapr.it.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; + @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = { @@ -54,7 +56,7 @@ public class DaprConversationIT { private static final int PORT = RANDOM.nextInt(1000) + 8000; @Container - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.15.2") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) .withAppName("conversation-dapr-app") .withComponent(new Component("echo", "conversation.echo", "v1", new HashMap<>())) .withNetwork(DAPR_NETWORK) From 6498cd1e8b577e987a04c69697b77d6398040b36 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 08:21:00 -0700 Subject: [PATCH 11/23] Address comments Signed-off-by: siri-varma --- examples/components/conversation/conversation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/components/conversation/conversation.yaml b/examples/components/conversation/conversation.yaml index 9a8b3072d7..efb651fef1 100644 --- a/examples/components/conversation/conversation.yaml +++ b/examples/components/conversation/conversation.yaml @@ -4,4 +4,4 @@ metadata: name: echo spec: type: conversation.echo - version: v1 \ No newline at end of file + version: v1 From e29c06b0e92de1968f26b029865d678c7ece6e3a Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 09:52:20 -0700 Subject: [PATCH 12/23] Make common config Signed-off-by: siri-varma --- .../it/testcontainers/DaprConversationIT.java | 2 +- .../io/dapr/it/testcontainers/DaprJobsIT.java | 2 +- ...va => DaprPreviewClientConfiguration.java} | 4 +- .../TestDaprConversationConfiguration.java | 41 ------------------- 4 files changed, 3 insertions(+), 46 deletions(-) rename sdk-tests/src/test/java/io/dapr/it/testcontainers/{TestDaprJobsConfiguration.java => DaprPreviewClientConfiguration.java} (91%) delete mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index 7c906354b2..bdc5066612 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -43,7 +43,7 @@ @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = { - TestDaprConversationConfiguration.class, + DaprPreviewClientConfiguration.class, TestConversationApplication.class } ) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprJobsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprJobsIT.java index 3cb433cf13..5b52c0267b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprJobsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprJobsIT.java @@ -45,7 +45,7 @@ @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = { - TestDaprJobsConfiguration.class, + DaprPreviewClientConfiguration.class, TestJobsApplication.class } ) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprJobsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPreviewClientConfiguration.java similarity index 91% rename from sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprJobsConfiguration.java rename to sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPreviewClientConfiguration.java index 5e0e2e8c89..66dce6d726 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprJobsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPreviewClientConfiguration.java @@ -14,11 +14,9 @@ package io.dapr.it.testcontainers; import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprClientImpl; import io.dapr.client.DaprPreviewClient; import io.dapr.config.Properties; import io.dapr.config.Property; -import io.dapr.serializer.DefaultObjectSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,7 +24,7 @@ import java.util.Map; @Configuration -public class TestDaprJobsConfiguration { +public class DaprPreviewClientConfiguration { @Bean public DaprPreviewClient daprPreviewClient( @Value("${dapr.http.endpoint}") String daprHttpEndpoint, diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java deleted file mode 100644 index ac1c2457dd..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprConversationConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2025 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.it.testcontainers; - -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprPreviewClient; -import io.dapr.config.Properties; -import io.dapr.config.Property; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -@Configuration -public class TestDaprConversationConfiguration { - - @Bean - public DaprPreviewClient daprPreviewClient( - @Value("${dapr.http.endpoint}") String daprHttpEndpoint, - @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint - ){ - Map, String> overrides = Map.of( - Properties.HTTP_ENDPOINT, daprHttpEndpoint, - Properties.GRPC_ENDPOINT, daprGrpcEndpoint - ); - - return new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient(); - } -} From 38e3f8286c2408d5dd9c20d1e3ca6f8bb61fc849 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 10:35:01 -0700 Subject: [PATCH 13/23] Address comments Signed-off-by: siri-varma --- .../test/java/io/dapr/it/testcontainers/DaprConversationIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index bdc5066612..c29169187a 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -99,7 +99,7 @@ public void testConversationSDKShouldHaveSameOutputAndInput() { } @Test - public void testConversationSDKShouldScrubPIIEntirelyWhenScrubPIIIsSetInRequestBody() { + public void testConversationSDKShouldScrubPIIWhenScrubPIIIsSetInRequestBody() { List conversationInputList = new ArrayList<>(); conversationInputList.add(new ConversationInput("input this abcd@gmail.com")); conversationInputList.add(new ConversationInput("input this +12341567890")); From 9e23ce13f1649095cf37d578eb08f26bf1ed86d1 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 14:33:35 -0700 Subject: [PATCH 14/23] fix constant Signed-off-by: siri-varma --- .../test/java/io/dapr/it/testcontainers/DaprConversationIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index c29169187a..cd49c0bd9d 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -38,7 +38,7 @@ import java.util.List; import java.util.Random; -import static io.dapr.it.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, From 3b6b0a577e25838111c112ca75b8e8d069e4fa7f Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 15:10:13 -0700 Subject: [PATCH 15/23] fix constant Signed-off-by: siri-varma --- .../test/java/io/dapr/it/testcontainers/DaprConversationIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index cd49c0bd9d..7abf944ff3 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -66,7 +66,7 @@ public class DaprConversationIT { .withAppPort(PORT); /** - * Expose the Dapr ports to the host. + * Expose the Dapr port to the host. * * @param registry the dynamic property registry */ From c0c3f63f7656d95d02b87aae67b1b169ce958963 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 21:40:28 -0700 Subject: [PATCH 16/23] fix constant Signed-off-by: siri-varma --- .../test/java/io/dapr/it/testcontainers/DaprConversationIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index 7abf944ff3..cd49c0bd9d 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -66,7 +66,7 @@ public class DaprConversationIT { .withAppPort(PORT); /** - * Expose the Dapr port to the host. + * Expose the Dapr ports to the host. * * @param registry the dynamic property registry */ From 6e303fa3bcbeef8dd5f7b87dd7853045b5f4f2e7 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Tue, 22 Apr 2025 22:19:32 -0700 Subject: [PATCH 17/23] fix s Signed-off-by: siri-varma --- .../test/java/io/dapr/it/testcontainers/DaprConversationIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index cd49c0bd9d..7abf944ff3 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -66,7 +66,7 @@ public class DaprConversationIT { .withAppPort(PORT); /** - * Expose the Dapr ports to the host. + * Expose the Dapr port to the host. * * @param registry the dynamic property registry */ From 655ef70ba16d2d6312908ea81e3ee0660ca45b57 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Wed, 23 Apr 2025 16:37:03 -0700 Subject: [PATCH 18/23] Fix things Signed-off-by: siri-varma --- .../io/dapr/examples/conversation/DemoConversationAI.java | 2 +- .../main/java/io/dapr/client/domain/ConversationOutput.java | 4 ++-- .../java/io/dapr/client/domain/ConversationRequest.java | 2 +- .../java/io/dapr/client/domain/ConversationResponse.java | 6 +++--- .../test/java/io/dapr/client/DaprPreviewClientGrpcTest.java | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java index 604e16b864..09c9570262 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java +++ b/examples/src/main/java/io/dapr/examples/conversation/DemoConversationAI.java @@ -41,7 +41,7 @@ public static void main(String[] args) { .setContextId("contextId") .setScrubPii(true).setTemperature(1.1d)); ConversationResponse response = responseMono.block(); - System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult()); + System.out.printf("Conversation output: %s", response.getConversationOutputs().get(0).getResult()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java index 9fe70d36a3..efe82e2eb3 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationOutput.java @@ -33,7 +33,7 @@ public class ConversationOutput { */ public ConversationOutput(String result, Map parameters) { this.result = result; - this.parameters = parameters; + this.parameters = Map.copyOf(parameters); } /** @@ -51,6 +51,6 @@ public String getResult() { * @return parameters. */ public Map getParameters() { - return Collections.unmodifiableMap(this.parameters); + return this.parameters; } } diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java index 8bac65b9a2..a5db405813 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java @@ -36,7 +36,7 @@ public class ConversationRequest { */ public ConversationRequest(String name, List inputs) { this.name = name; - this.inputs = inputs; + this.inputs = List.copyOf(inputs); } /** diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java index 0147f72152..8059365544 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationResponse.java @@ -33,7 +33,7 @@ public class ConversationResponse { */ public ConversationResponse(String contextId, List outputs) { this.contextId = contextId; - this.outputs = outputs; + this.outputs = List.copyOf(outputs); } /** @@ -50,7 +50,7 @@ public String getContextId() { * * @return List{@link ConversationOutput}. */ - public List getConversationOutpus() { - return Collections.unmodifiableList(this.outputs); + public List getConversationOutputs() { + return this.outputs; } } diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index 93243e8547..5cc49edfdd 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -650,7 +650,7 @@ public void converseShouldReturnConversationResponseWhenRequiredInputsAreValid() assertEquals("openai", conversationRequest.getName()); assertEquals("Hello there", conversationRequest.getInputs(0).getContent()); assertEquals("Hello How are you", - response.getConversationOutpus().get(0).getResult()); + response.getConversationOutputs().get(0).getResult()); } @Test @@ -694,7 +694,7 @@ public void converseShouldReturnConversationResponseWhenRequiredAndOptionalInput assertEquals("Assistant", conversationRequest.getInputs(0).getRole()); assertEquals("contextId", response.getContextId()); assertEquals("Hello How are you", - response.getConversationOutpus().get(0).getResult()); + response.getConversationOutputs().get(0).getResult()); } @Test From bff8932d123276b749993bafb7345382a4b14970 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Wed, 23 Apr 2025 18:44:40 -0700 Subject: [PATCH 19/23] Fix things Signed-off-by: siri-varma --- sdk/src/main/java/io/dapr/client/domain/ConversationInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java index 70f9d8ccaf..0a1dbfe8a6 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationInput.java @@ -52,7 +52,7 @@ public String getRole() { } /** - * Sets the role for LLM to assume. + * Set the role for LLM to assume. * * @param role The role to assign to the message. * @return this. From 6a1e09a26e253fa56af56dd2d4c0ee11ad40ef77 Mon Sep 17 00:00:00 2001 From: siri-varma Date: Wed, 23 Apr 2025 19:28:35 -0700 Subject: [PATCH 20/23] Fix things Signed-off-by: siri-varma --- .../main/java/io/dapr/client/domain/ConversationRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java index a5db405813..8bac65b9a2 100644 --- a/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java +++ b/sdk/src/main/java/io/dapr/client/domain/ConversationRequest.java @@ -36,7 +36,7 @@ public class ConversationRequest { */ public ConversationRequest(String name, List inputs) { this.name = name; - this.inputs = List.copyOf(inputs); + this.inputs = inputs; } /** From e618070d9147d451b4baeb4a92434c6dd315416d Mon Sep 17 00:00:00 2001 From: siri-varma Date: Wed, 23 Apr 2025 21:25:48 -0700 Subject: [PATCH 21/23] Make common config Signed-off-by: siri-varma --- .../io/dapr/it/testcontainers/DaprConversationIT.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java index 7abf944ff3..013a5cdf0e 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java @@ -95,7 +95,7 @@ public void testConversationSDKShouldHaveSameOutputAndInput() { this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block(); Assertions.assertEquals("", response.getContextId()); - Assertions.assertEquals("input this", response.getConversationOutpus().get(0).getResult()); + Assertions.assertEquals("input this", response.getConversationOutputs().get(0).getResult()); } @Test @@ -110,9 +110,9 @@ public void testConversationSDKShouldScrubPIIWhenScrubPIIIsSetInRequestBody() { Assertions.assertEquals("", response.getContextId()); Assertions.assertEquals("input this ", - response.getConversationOutpus().get(0).getResult()); + response.getConversationOutputs().get(0).getResult()); Assertions.assertEquals("input this ", - response.getConversationOutpus().get(1).getResult()); + response.getConversationOutputs().get(1).getResult()); } @Test @@ -126,8 +126,8 @@ public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet() Assertions.assertEquals("", response.getContextId()); Assertions.assertEquals("input this abcd@gmail.com", - response.getConversationOutpus().get(0).getResult()); + response.getConversationOutputs().get(0).getResult()); Assertions.assertEquals("input this ", - response.getConversationOutpus().get(1).getResult()); + response.getConversationOutputs().get(1).getResult()); } } From f994c8a6add96cdd8aee24db1a52724d4938498f Mon Sep 17 00:00:00 2001 From: Siri Varma Vegiraju Date: Tue, 29 Apr 2025 16:06:19 -0700 Subject: [PATCH 22/23] Update README.md Signed-off-by: Siri Varma Vegiraju --- .../src/main/java/io/dapr/examples/conversation/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/conversation/README.md b/examples/src/main/java/io/dapr/examples/conversation/README.md index 599ea5cbbe..909a5b724f 100644 --- a/examples/src/main/java/io/dapr/examples/conversation/README.md +++ b/examples/src/main/java/io/dapr/examples/conversation/README.md @@ -76,11 +76,6 @@ public class DemoConversationAI { } ``` -Get into the examples' directory: -```sh -cd examples -``` - Use the following command to run this example-