Skip to content

Add Conversation AI to Java SDK #1235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

siri-varma
Copy link
Contributor

@siri-varma siri-varma commented Mar 6, 2025

PR Overview: Conversation AI SDK Integration

This PR introduces the Conversation AI SDK with the Converse API. The implementation is structured into the following categories:

  • Core Logic
  • Models
  • Unit Tests

Core Logic

The core logic is implemented in a key class, as detailed below:

Class Description Details
DaprConversationClient Manages the Conversation API. Handles validations and async calls. - Calls the Conversation API asynchronously.
- Validates inputs and manages job execution.

API Contracts

The following is the method signature for the Dapr Converse API:

Dapr Converse API

This method interacts with the Dapr Converse API.

Mono<DaprConversationResponse> converse(
    String conversationComponentName,
    List<DaprConversationInput> daprConversationInputs,
    String contextId,
    boolean scrubPii,
    double temperature
);
Parameter Description
conversationComponentName Name of the conversation component.
daprConversationInputs List of prompts that are part of the conversation.
contextId Identifier for an existing chat session (e.g., similar to ChatGPT).
scrubPii Whether to scrub personally identifiable information (PII) from responses.
temperature Controls response randomness (lower = more predictable, higher = more creative).
Returns DaprConversationResponse

Models

The SDK follows the builder pattern for constructing models, ensuring cleaner and more maintainable object creation.


Tests

The testing strategy includes:

  • Unit Tests: Validate individual components and business logic.

Issue Reference

We ensure that all PRs are linked to a relevant issue where the problem or feature has been discussed before implementation.

This PR closes the following issue: #1101


Checklist ✅

Please confirm the following before merging:

  • Code compiles correctly
  • Created/updated tests
  • Extended the documentation

@siri-varma siri-varma requested review from a team as code owners March 6, 2025 16:51
@siri-varma siri-varma force-pushed the users/svegiraju/conversation-api-2 branch from 68d30ee to 9a8926e Compare March 6, 2025 16:52
@siri-varma
Copy link
Contributor Author

siri-varma commented Mar 6, 2025

@salaboy, @artur-ciocanu please let me know what you think. Working on integration and unit tests in the meanwhile

@yaron2
Copy link
Member

yaron2 commented Mar 6, 2025

Great contribution, thanks @siri-varma

@cicoyle cicoyle added this to the v1.15 milestone Mar 6, 2025
@siri-varma siri-varma force-pushed the users/svegiraju/conversation-api-2 branch from 9a8926e to 4cb6031 Compare March 6, 2025 19:10
Signed-off-by: Siri Varma Vegiraju <[email protected]>
Signed-off-by: sirivarma <[email protected]>
@siri-varma siri-varma force-pushed the users/svegiraju/conversation-api-2 branch from 4cb6031 to 75f487f Compare March 6, 2025 19:11
@salaboy
Copy link
Contributor

salaboy commented Mar 6, 2025

this is great @siri-varma ! I will start reviewing soon!

Signed-off-by: sirivarma <[email protected]>
@siri-varma siri-varma force-pushed the users/svegiraju/conversation-api-2 branch from bf70fd4 to 88fe015 Compare March 7, 2025 04:27
@siri-varma
Copy link
Contributor Author

siri-varma commented Mar 7, 2025

For integration tests, we must spin up a mock for third party LLM endpoint. Do we have similar patterns in the current setup? (where dapr is talking to a third party api and receiving responses)

@siri-varma siri-varma changed the title WIP: Add Conversation AI to Java SDK Add Conversation AI to Java SDK Mar 8, 2025
@salaboy salaboy mentioned this pull request Mar 10, 2025
import java.util.Map;
import java.util.function.Consumer;

public class DaprConversationClient implements AutoCloseable, DaprAiClient {
Copy link
Contributor

Choose a reason for hiding this comment

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

@siri-varma I assume that you know that we had a previewClient, why is this introducing a new separated client for the conversation API? I would assume that we want to include this in PreviewClient first. @yaron2 we might need your help here.

Copy link
Contributor

Choose a reason for hiding this comment

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

and sorry for the confusion if the PreviewClient is an old thing that we are not supposed to use anymore. As the workflows now have their own separate client, this might be the way to go.

Copy link
Contributor Author

@siri-varma siri-varma Mar 13, 2025

Choose a reason for hiding this comment

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

Just to clarify, we are fine with current approach right ?

@salaboy
Copy link
Contributor

salaboy commented Mar 13, 2025

@siri-varma this is looking good, I left some comments that basically highlight my doubts about some JDK general topics. But this is looking really good, are you planning to add integration tests inside the sdk-tests too? We have some Spring Boot tests using Testcontainers, I was thinking that you might want to test the APIs against a local model running in a docker container to see that everything works as expected.

@siri-varma
Copy link
Contributor Author

siri-varma commented Mar 14, 2025

@salaboy I have tested the code by running it locally and using my OpenAI API Key.

Regarding the integration test, I think we will need an LLM model for daprd to communicate with, which unfortunately are huge in size. That is why I did not add the integration tests

Will get back to you on the integ tests

@salaboy
Copy link
Contributor

salaboy commented Mar 14, 2025

Yeah check this https://java.testcontainers.org/modules/ollama/

@siri-varma
Copy link
Contributor Author

@salaboy thank you for providing the testcontainer urls.

Like how we have url overrides for Scheduler, I was looking one for conversation too because we will have to point dapr to the local llm model. But could not find any

I tried looking in the below places and could not find anything. https://github.com/dapr/dapr/blob/2e7c61e933099b9d40fcdefbd57cc3e81069915a/pkg/injector/annotations/annotations.go#L73
https://github.com/dapr/dapr/blob/2e7c61e933099b9d40fcdefbd57cc3e81069915a/pkg/injector/consts/consts.go#L51
https://docs.dapr.io/reference/arguments-annotations-overview/

@salaboy or @cicoyle Would you folks know about this ?

@artur-ciocanu
Copy link
Contributor

@siri-varma thanks a lot for your contribution. I have reviewed your PR and I have a few comments similar to #1255:

  • Let's make sure the Conversation API is part of the DaprPreviewClient
  • Let's make sure that we use the Alpha1 suffix for the Conversation API
  • Let's make sure we keep everything under sdk module and we don't create a separate module for Conversation API
  • For the Conversation API converse let's make sure we add private methods to validate all the input parameters, before invoking the GRPC API
  • For Conversation API converse method let's ensure we keep number of parameters to a minimum, having to pass 5 parameters is too much. I think a better option is to have a builder for ConversationRequest which should be part of the model classes for DaprClient. This way we can have sane defaults, but allow users to override some of the values if desired.

CC: @salaboy @cicoyle

Copy link
Contributor

@artur-ciocanu artur-ciocanu left a comment

Choose a reason for hiding this comment

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

@siri-varma thanks a lot for your contribution, I have left a longer comment with some of my thoughts.

Please take a look and let me know what you think.

Signed-off-by: sirivarma <[email protected]>
@siri-varma siri-varma force-pushed the users/svegiraju/conversation-api-2 branch from 115b5e7 to 842b0e7 Compare March 18, 2025 15:10
Signed-off-by: sirivarma <[email protected]>
Signed-off-by: siri-varma <[email protected]>
@siri-varma
Copy link
Contributor Author

@siri-varma thanks a lot for your contribution. I have reviewed your PR and I have a few comments similar to #1255:

  • Let's make sure the Conversation API is part of the DaprPreviewClient
  • Let's make sure that we use the Alpha1 suffix for the Conversation API
  • Let's make sure we keep everything under sdk module and we don't create a separate module for Conversation API
  • For the Conversation API converse let's make sure we add private methods to validate all the input parameters, before invoking the GRPC API
  • For Conversation API converse method let's ensure we keep number of parameters to a minimum, having to pass 5 parameters is too much. I think a better option is to have a builder for ConversationRequest which should be part of the model classes for DaprClient. This way we can have sane defaults, but allow users to override some of the values if desired.

CC: @salaboy @cicoyle

@artur-ciocanu Addressed all the comments here as well.
@salaboy Added Integration tests too.

@siri-varma
Copy link
Contributor Author

@artur-ciocanu , @salaboy All the builds are passing and addressed comments as well. Would you folks be able to do another round of reviews please ?

Copy link
Contributor

@cicoyle cicoyle left a comment

Choose a reason for hiding this comment

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

Generally this looks great 🌟 - mind adding an example for conversation api to the examples directory in the repo with the mechanical markdown in the README with a sample .java file for users to know how to use the API?

We should also document the high level things that are expected when adding new APIs to the SDK to lower the barrier to entry for future developers and for consistency. Thanks for this PR 🚀 We are nearly there!

cicoyle
cicoyle previously approved these changes Apr 14, 2025
@cicoyle
Copy link
Contributor

cicoyle commented Apr 14, 2025

Amazing work here @siri-varma 🚀 Thanks for the contribution 🌟

Signed-off-by: artur-ciocanu <[email protected]>
Copy link
Contributor

@artur-ciocanu artur-ciocanu left a comment

Choose a reason for hiding this comment

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

@siri-varma thanks a lot for all your hard work! I have a few general comments that are critical, others are mostly style related:

  • Dapr Conversation API uses a component to configure everything related to model interaction and we should make sure that this is reflected in the API like using name or componentName. I feel componentName makes more sense, but I will let @cicoyle and @salaboy share their thoughts.
  • scrubPII - I know this comes from Dapr Runtime, but honestly it looks and feels cryptic, so if we can agree on a better name and then propagate this back to Dapr Runtime that would be great.
  • ConversationRole - any LLM framework I have used, do not constraint the role in any way, since you can have different roles, depending on use case, so I think sticking to Dapr Runtime string is good enough.

Please take a look and let me know your thoughts.

throw new RuntimeException(e);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add new line

+ "This is the my number 672-123-4567");

// Component name is the name provided in the metadata block of the conversation.yaml file.
Mono<ConversationResponse> responseMono = client.converse(new ConversationRequest("echo",
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use a more modern approach from Java 11 List.of(...) instead of Collections.singleton(...)

@@ -0,0 +1,117 @@
## Manage Dapr Jobs via the Conversation API
Copy link
Contributor

Choose a reason for hiding this comment

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

Manage Dapr Jobs looks confusing ... is this related to Job API or Conversation API?

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch, lets remove jobs from this unless you are using the jobs api.

@@ -17,7 +17,7 @@
<grpc.version>1.69.0</grpc.version>
<protobuf.version>3.25.5</protobuf.version>
<protocCommand>protoc</protocCommand>
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be updated to latest

Copy link
Contributor

Choose a reason for hiding this comment

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

v1.15.4 is latest as of now - think this was just bc the PR has been open a while and we more recently released 1.15.3 && 1.15.4

Assertions.assertEquals("input this <PHONE_NUMBER>",
response.getConversationOutpus().get(1).getResult());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add new line

observer.onCompleted();
return null;
}).when(daprStub).converseAlpha1(any(DaprProtos.ConversationRequest.class), any());

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we rename it to daprConversationInput


List<ConversationInput> daprConversationInputs = new ArrayList<>();
daprConversationInputs.add(daprConversationInput);

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we rename it to response

verify(daprStub, times(1)).converseAlpha1(captor.capture(), Mockito.any());

DaprProtos.ConversationRequest conversationRequest = captor.getValue();

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use static imports so we could use just assertEquals without Assertions

.setContent(input.getContent())
.setScrubPII(input.isScrubPii());

if (input.getRole() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ConversationRole is an enum with TOOL, USER, ASSISTANT and toString() would return capital case "TOOL", "USER", "ASSISTANT". While I think enum is a good idea, I would keep it compatible with GRPC object and use an ordinary string. We want users to decide what role they want to use system, assistant, agent, etc


/**
* Conversation AI supported roles.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

As I mentioned I am not sure we need this enum. We want user to use whatever they feel is appropriate as a role. We don't want to keep changing this enum, because someone has a different use case.

Copy link
Contributor

Choose a reason for hiding this comment

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

I replied on the other thread - I agree, lets align and use string. I appreciate the enum idea here though 🙏🏻

Copy link

codecov bot commented Apr 15, 2025

Codecov Report

Attention: Patch coverage is 95.06173% with 4 lines in your changes missing coverage. Please review.

Project coverage is 75.69%. Comparing base (d759c53) to head (720a427).
Report is 127 commits behind head on master.

Files with missing lines Patch % Lines
...k/src/main/java/io/dapr/client/DaprClientImpl.java 92.50% 2 Missing and 1 partial ⚠️
...java/io/dapr/client/domain/ConversationOutput.java 83.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1235      +/-   ##
============================================
- Coverage     76.91%   75.69%   -1.23%     
- Complexity     1592     1641      +49     
============================================
  Files           145      199      +54     
  Lines          4843     5171     +328     
  Branches        562      567       +5     
============================================
+ Hits           3725     3914     +189     
- Misses          821      936     +115     
- Partials        297      321      +24     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cicoyle
Copy link
Contributor

cicoyle commented Apr 15, 2025

@siri-varma thanks a lot for all your hard work! I have a few general comments that are critical, others are mostly style related:

  • Dapr Conversation API uses a component to configure everything related to model interaction and we should make sure that this is reflected in the API like using name or componentName. I feel componentName makes more sense, but I will let @cicoyle and @salaboy share their thoughts.
  • scrubPII - I know this comes from Dapr Runtime, but honestly it looks and feels cryptic, so if we can agree on a better name and then propagate this back to Dapr Runtime that would be great.
  • ConversationRole - any LLM framework I have used, do not constraint the role in any way, since you can have different roles, depending on use case, so I think sticking to Dapr Runtime string is good enough.

Please take a look and let me know your thoughts.

I understand where you are coming from, however we want to align with dapr runtime and other sdks. For example, the scrubPII should stay as-is, bc in runtime we have it named as such, dotnet has it named that way, so for consistency in the dapr projects and ecosystem, the java sdk should as well.

Same for component name - dotnet && go-sdk for example

For role - Im inclined to agree on string as well for the same reasons. It is how we have it in the go-sdk.

@siri-varma
Copy link
Contributor Author

will update this pr in couple of days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants