Skip to content

Commit 99e21db

Browse files
siri-varmacicoyle
andauthored
Add Conversation AI to Java SDK (#1235)
* Conversation first commit Signed-off-by: Siri Varma Vegiraju <[email protected]> Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> * Add unit tests Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> * change ai to conv Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> * Move to single module Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> * Remove module Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> * Add Integration tests Signed-off-by: siri-varma <[email protected]> * Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java Co-authored-by: Cassie Coyle <[email protected]> Signed-off-by: Siri Varma Vegiraju <[email protected]> Signed-off-by: siri-varma <[email protected]> * Fix things Signed-off-by: siri-varma <[email protected]> * Address comments Signed-off-by: siri-varma <[email protected]> * Import tag Signed-off-by: siri-varma <[email protected]> * Address comments Signed-off-by: siri-varma <[email protected]> * Make common config Signed-off-by: siri-varma <[email protected]> * Address comments Signed-off-by: siri-varma <[email protected]> * fix constant Signed-off-by: siri-varma <[email protected]> * fix constant Signed-off-by: siri-varma <[email protected]> * fix constant Signed-off-by: siri-varma <[email protected]> * fix s Signed-off-by: siri-varma <[email protected]> * Fix things Signed-off-by: siri-varma <[email protected]> * Fix things Signed-off-by: siri-varma <[email protected]> * Fix things Signed-off-by: siri-varma <[email protected]> * Make common config Signed-off-by: siri-varma <[email protected]> * Update README.md Signed-off-by: Siri Varma Vegiraju <[email protected]> * Update README.md Signed-off-by: Siri Varma Vegiraju <[email protected]> --------- Signed-off-by: Siri Varma Vegiraju <[email protected]> Signed-off-by: sirivarma <[email protected]> Signed-off-by: siri-varma <[email protected]> Signed-off-by: Siri Varma Vegiraju <[email protected]> Co-authored-by: Cassie Coyle <[email protected]> Co-authored-by: Cassie Coyle <[email protected]>
1 parent c53f000 commit 99e21db

File tree

15 files changed

+1006
-134
lines changed

15 files changed

+1006
-134
lines changed

.github/workflows/validate.yml

+6
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ jobs:
121121
mm.py ./src/main/java/io/dapr/examples/jobs/README.md
122122
env:
123123
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
124+
- name: Validate conversation ai example
125+
working-directory: ./examples
126+
run: |
127+
mm.py ./src/main/java/io/dapr/examples/conversation/README.md
128+
env:
129+
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
124130
- name: Validate invoke http example
125131
working-directory: ./examples
126132
run: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: echo
5+
spec:
6+
type: conversation.echo
7+
version: v1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2021 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.examples.conversation;
15+
16+
import io.dapr.client.DaprClientBuilder;
17+
import io.dapr.client.DaprPreviewClient;
18+
import io.dapr.client.domain.ConversationInput;
19+
import io.dapr.client.domain.ConversationRequest;
20+
import io.dapr.client.domain.ConversationResponse;
21+
import reactor.core.publisher.Mono;
22+
23+
import java.util.List;
24+
25+
public class DemoConversationAI {
26+
/**
27+
* The main method to start the client.
28+
*
29+
* @param args Input arguments (unused).
30+
*/
31+
public static void main(String[] args) {
32+
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
33+
System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567");
34+
35+
ConversationInput daprConversationInput = new ConversationInput("Hello How are you? "
36+
+ "This is the my number 672-123-4567");
37+
38+
// Component name is the name provided in the metadata block of the conversation.yaml file.
39+
Mono<ConversationResponse> responseMono = client.converse(new ConversationRequest("echo",
40+
List.of(daprConversationInput))
41+
.setContextId("contextId")
42+
.setScrubPii(true).setTemperature(1.1d));
43+
ConversationResponse response = responseMono.block();
44+
System.out.printf("Conversation output: %s", response.getConversationOutputs().get(0).getResult());
45+
} catch (Exception e) {
46+
throw new RuntimeException(e);
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
## Manage Dapr via the Conversation API
2+
3+
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/)
4+
5+
### Using the Conversation API
6+
7+
The Java SDK exposes several methods for this -
8+
* `client.converse(...)` for conversing with an LLM through Dapr.
9+
10+
## Pre-requisites
11+
12+
* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/).
13+
* Java JDK 11 (or greater):
14+
* [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11)
15+
* [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11)
16+
* [OpenJDK 11](https://jdk.java.net/11/)
17+
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
18+
19+
### Checking out the code
20+
21+
Clone this repository:
22+
23+
```sh
24+
git clone https://github.com/dapr/java-sdk.git
25+
cd java-sdk
26+
```
27+
28+
Then build the Maven project:
29+
30+
```sh
31+
# make sure you are in the `java-sdk` directory
32+
mvn install
33+
```
34+
35+
Then get into the examples directory:
36+
37+
```sh
38+
cd examples
39+
```
40+
41+
### Initialize Dapr
42+
43+
Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized.
44+
45+
### Running the example
46+
47+
This example uses the Java SDK Dapr client in order to **Converse** with an LLM.
48+
`DemoConversationAI.java` is the example class demonstrating these features.
49+
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.
50+
51+
```java
52+
public class DemoConversationAI {
53+
/**
54+
* The main method to start the client.
55+
*
56+
* @param args Input arguments (unused).
57+
*/
58+
public static void main(String[] args) {
59+
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
60+
System.out.println("Sending the following input to LLM: Hello How are you? This is the my number 672-123-4567");
61+
62+
ConversationInput daprConversationInput = new ConversationInput("Hello How are you? "
63+
+ "This is the my number 672-123-4567");
64+
65+
// Component name is the name provided in the metadata block of the conversation.yaml file.
66+
Mono<ConversationResponse> responseMono = client.converse(new ConversationRequest("echo",
67+
List.of(daprConversationInput))
68+
.setContextId("contextId")
69+
.setScrubPii(true).setTemperature(1.1d));
70+
ConversationResponse response = responseMono.block();
71+
System.out.printf("Conversation output: %s", response.getConversationOutpus().get(0).getResult());
72+
} catch (Exception e) {
73+
throw new RuntimeException(e);
74+
}
75+
}
76+
}
77+
```
78+
79+
Use the following command to run this example-
80+
81+
<!-- STEP
82+
name: Run Demo Conversation Client example
83+
expected_stdout_lines:
84+
- "== APP == Conversation output: Hello How are you? This is the my number <ISBN>"
85+
background: true
86+
output_match_mode: substring
87+
sleep: 10
88+
-->
89+
90+
```bash
91+
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
92+
```
93+
94+
<!-- END_STEP -->
95+
96+
### Sample output
97+
```
98+
== APP == Conversation output: Hello How are you? This is the my number <ISBN>
99+
```
100+
### Cleanup
101+
102+
To stop the app, run (or press CTRL+C):
103+
104+
<!-- STEP
105+
106+
name: Cleanup
107+
-->
108+
109+
```bash
110+
dapr stop --app-id myapp
111+
```
112+
113+
<!-- END_STEP -->
114+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2021 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.it.testcontainers;
15+
16+
import io.dapr.client.DaprPreviewClient;
17+
import io.dapr.client.domain.ConversationInput;
18+
import io.dapr.client.domain.ConversationRequest;
19+
import io.dapr.client.domain.ConversationResponse;
20+
import io.dapr.testcontainers.Component;
21+
import io.dapr.testcontainers.DaprContainer;
22+
import io.dapr.testcontainers.DaprLogLevel;
23+
import org.junit.jupiter.api.Assertions;
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Tag;
26+
import org.junit.jupiter.api.Test;
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
30+
import org.springframework.test.context.DynamicPropertyRegistry;
31+
import org.springframework.test.context.DynamicPropertySource;
32+
import org.testcontainers.containers.Network;
33+
import org.testcontainers.junit.jupiter.Container;
34+
import org.testcontainers.junit.jupiter.Testcontainers;
35+
36+
import java.util.ArrayList;
37+
import java.util.HashMap;
38+
import java.util.List;
39+
import java.util.Random;
40+
41+
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
42+
43+
@SpringBootTest(
44+
webEnvironment = WebEnvironment.RANDOM_PORT,
45+
classes = {
46+
DaprPreviewClientConfiguration.class,
47+
TestConversationApplication.class
48+
}
49+
)
50+
@Testcontainers
51+
@Tag("testcontainers")
52+
public class DaprConversationIT {
53+
54+
private static final Network DAPR_NETWORK = Network.newNetwork();
55+
private static final Random RANDOM = new Random();
56+
private static final int PORT = RANDOM.nextInt(1000) + 8000;
57+
58+
@Container
59+
private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
60+
.withAppName("conversation-dapr-app")
61+
.withComponent(new Component("echo", "conversation.echo", "v1", new HashMap<>()))
62+
.withNetwork(DAPR_NETWORK)
63+
.withDaprLogLevel(DaprLogLevel.DEBUG)
64+
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
65+
.withAppChannelAddress("host.testcontainers.internal")
66+
.withAppPort(PORT);
67+
68+
/**
69+
* Expose the Dapr port to the host.
70+
*
71+
* @param registry the dynamic property registry
72+
*/
73+
@DynamicPropertySource
74+
static void daprProperties(DynamicPropertyRegistry registry) {
75+
registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
76+
registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
77+
registry.add("server.port", () -> PORT);
78+
}
79+
80+
@Autowired
81+
private DaprPreviewClient daprPreviewClient;
82+
83+
@BeforeEach
84+
public void setUp(){
85+
org.testcontainers.Testcontainers.exposeHostPorts(PORT);
86+
}
87+
88+
@Test
89+
public void testConversationSDKShouldHaveSameOutputAndInput() {
90+
ConversationInput conversationInput = new ConversationInput("input this");
91+
List<ConversationInput> conversationInputList = new ArrayList<>();
92+
conversationInputList.add(conversationInput);
93+
94+
ConversationResponse response =
95+
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block();
96+
97+
Assertions.assertEquals("", response.getContextId());
98+
Assertions.assertEquals("input this", response.getConversationOutputs().get(0).getResult());
99+
}
100+
101+
@Test
102+
public void testConversationSDKShouldScrubPIIWhenScrubPIIIsSetInRequestBody() {
103+
List<ConversationInput> conversationInputList = new ArrayList<>();
104+
conversationInputList.add(new ConversationInput("input this [email protected]"));
105+
conversationInputList.add(new ConversationInput("input this +12341567890"));
106+
107+
ConversationResponse response =
108+
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)
109+
.setScrubPii(true)).block();
110+
111+
Assertions.assertEquals("", response.getContextId());
112+
Assertions.assertEquals("input this <EMAIL_ADDRESS>",
113+
response.getConversationOutputs().get(0).getResult());
114+
Assertions.assertEquals("input this <PHONE_NUMBER>",
115+
response.getConversationOutputs().get(1).getResult());
116+
}
117+
118+
@Test
119+
public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet() {
120+
List<ConversationInput> conversationInputList = new ArrayList<>();
121+
conversationInputList.add(new ConversationInput("input this [email protected]"));
122+
conversationInputList.add(new ConversationInput("input this +12341567890").setScrubPii(true));
123+
124+
ConversationResponse response =
125+
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block();
126+
127+
Assertions.assertEquals("", response.getContextId());
128+
Assertions.assertEquals("input this [email protected]",
129+
response.getConversationOutputs().get(0).getResult());
130+
Assertions.assertEquals("input this <PHONE_NUMBER>",
131+
response.getConversationOutputs().get(1).getResult());
132+
}
133+
}

sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprJobsIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@SpringBootTest(
4646
webEnvironment = WebEnvironment.RANDOM_PORT,
4747
classes = {
48-
TestDaprJobsConfiguration.class,
48+
DaprPreviewClientConfiguration.class,
4949
TestJobsApplication.class
5050
}
5151
)

sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprJobsConfiguration.java renamed to sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPreviewClientConfiguration.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@
1414
package io.dapr.it.testcontainers;
1515

1616
import io.dapr.client.DaprClientBuilder;
17-
import io.dapr.client.DaprClientImpl;
1817
import io.dapr.client.DaprPreviewClient;
1918
import io.dapr.config.Properties;
2019
import io.dapr.config.Property;
21-
import io.dapr.serializer.DefaultObjectSerializer;
2220
import org.springframework.beans.factory.annotation.Value;
2321
import org.springframework.context.annotation.Bean;
2422
import org.springframework.context.annotation.Configuration;
2523

2624
import java.util.Map;
2725

2826
@Configuration
29-
public class TestDaprJobsConfiguration {
27+
public class DaprPreviewClientConfiguration {
3028
@Bean
3129
public DaprPreviewClient daprPreviewClient(
3230
@Value("${dapr.http.endpoint}") String daprHttpEndpoint,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.it.testcontainers;
15+
16+
import org.springframework.boot.SpringApplication;
17+
import org.springframework.boot.autoconfigure.SpringBootApplication;
18+
19+
@SpringBootApplication
20+
public class TestConversationApplication {
21+
22+
public static void main(String[] args) {
23+
SpringApplication.run(TestConversationApplication.class, args);
24+
}
25+
}

0 commit comments

Comments
 (0)