Skip to content

Commit a194f27

Browse files
committed
conversation ai
1 parent 28ae2e1 commit a194f27

15 files changed

+1125
-1
lines changed

sdk/src/main/java/io/dapr/client/DaprClientImpl.java

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.protobuf.Any;
1818
import com.google.protobuf.ByteString;
1919
import com.google.protobuf.Empty;
20+
import com.google.protobuf.Message;
2021
import io.dapr.client.domain.ActorMetadata;
2122
import io.dapr.client.domain.AppConnectionPropertiesHealthMetadata;
2223
import io.dapr.client.domain.AppConnectionPropertiesMetadata;
@@ -27,10 +28,22 @@
2728
import io.dapr.client.domain.CloudEvent;
2829
import io.dapr.client.domain.ComponentMetadata;
2930
import io.dapr.client.domain.ConfigurationItem;
31+
import io.dapr.client.domain.ConversationFunction;
3032
import io.dapr.client.domain.ConversationInput;
33+
import io.dapr.client.domain.ConversationInputAlpha2;
34+
import io.dapr.client.domain.ConversationMessage;
35+
import io.dapr.client.domain.ConversationMessageContent;
3136
import io.dapr.client.domain.ConversationOutput;
3237
import io.dapr.client.domain.ConversationRequest;
38+
import io.dapr.client.domain.ConversationRequestAlpha2;
3339
import io.dapr.client.domain.ConversationResponse;
40+
import io.dapr.client.domain.ConversationResponseAlpha2;
41+
import io.dapr.client.domain.ConversationResultAlpha2;
42+
import io.dapr.client.domain.ConversationResultChoices;
43+
import io.dapr.client.domain.ConversationResultMessage;
44+
import io.dapr.client.domain.ConversationToolCalls;
45+
import io.dapr.client.domain.ConversationToolCallsFunction;
46+
import io.dapr.client.domain.ConversationTools;
3447
import io.dapr.client.domain.DaprMetadata;
3548
import io.dapr.client.domain.DeleteJobRequest;
3649
import io.dapr.client.domain.DeleteStateRequest;
@@ -100,6 +113,7 @@
100113
import reactor.util.retry.Retry;
101114

102115
import javax.annotation.Nonnull;
116+
103117
import java.io.IOException;
104118
import java.time.Duration;
105119
import java.time.Instant;
@@ -1628,6 +1642,193 @@ private void validateConversationRequest(ConversationRequest conversationRequest
16281642
}
16291643
}
16301644

1645+
/**
1646+
* {@inheritDoc}
1647+
*/
1648+
@Override
1649+
public Mono<ConversationResponseAlpha2> converseAlpha2(ConversationRequestAlpha2 conversationRequestAlpha2) {
1650+
1651+
try {
1652+
validateConversationRequestAlpha2(conversationRequestAlpha2);
1653+
1654+
DaprProtos.ConversationRequestAlpha2.Builder protosConversationRequestBuilder =
1655+
DaprProtos.ConversationRequestAlpha2
1656+
.newBuilder()
1657+
.setTemperature(conversationRequestAlpha2.getTemperature())
1658+
.setScrubPii(conversationRequestAlpha2.isScrubPii())
1659+
.setName(conversationRequestAlpha2.getName());
1660+
1661+
if (conversationRequestAlpha2.getContextId() != null) {
1662+
protosConversationRequestBuilder.setContextId(conversationRequestAlpha2.getContextId());
1663+
}
1664+
1665+
if (conversationRequestAlpha2.getToolChoice() != null) {
1666+
protosConversationRequestBuilder.setToolChoice(conversationRequestAlpha2.getToolChoice());
1667+
}
1668+
1669+
if (conversationRequestAlpha2.getTools() != null) {
1670+
for (ConversationTools tool : conversationRequestAlpha2.getTools()) {
1671+
1672+
ConversationFunction conversationFunction = tool.getFunction();
1673+
1674+
Map<String, Any> protosConversationToolFunctionParameters = conversationFunction.getParameters()
1675+
.entrySet().stream()
1676+
.collect(Collectors.toMap(
1677+
Map.Entry::getKey,
1678+
e -> Any.pack((Message) e.getValue())
1679+
));
1680+
DaprProtos.ConversationToolsFunction protosConversationToolsFunction =
1681+
DaprProtos.ConversationToolsFunction.newBuilder()
1682+
.setName(conversationFunction.getName())
1683+
.setDescription(conversationFunction.getDescription())
1684+
.putAllParameters(protosConversationToolFunctionParameters)
1685+
.build();
1686+
1687+
DaprProtos.ConversationTools conversationTool = DaprProtos.ConversationTools.newBuilder()
1688+
.setFunction(protosConversationToolsFunction).build();
1689+
1690+
protosConversationRequestBuilder.addTools(conversationTool);
1691+
}
1692+
}
1693+
1694+
for (ConversationInputAlpha2 input : conversationRequestAlpha2.getInputs()) {
1695+
DaprProtos.ConversationInputAlpha2.Builder conversationInputBuilder = DaprProtos.ConversationInputAlpha2
1696+
.newBuilder()
1697+
.setScrubPii(input.isScrubPii());
1698+
1699+
if (input.getMessages() != null) {
1700+
1701+
for (ConversationMessage conversationMessage : input.getMessages()) {
1702+
DaprProtos.ConversationMessage.Builder messageBuilder =
1703+
DaprProtos.ConversationMessage.newBuilder();
1704+
1705+
ConversationMessage.Role role = conversationMessage.getRole();
1706+
switch (role) {
1707+
case TOOL:
1708+
messageBuilder.setOfTool(DaprProtos.ConversationMessageOfTool.newBuilder()
1709+
.setToolId(conversationMessage.getToolId())
1710+
.setName(conversationMessage.getName())
1711+
.addAllContent(getConversationMessageContent(conversationMessage)).build());
1712+
break;
1713+
case USER:
1714+
messageBuilder.setOfUser(DaprProtos.ConversationMessageOfUser.newBuilder()
1715+
.setName(conversationMessage.getName())
1716+
.addAllContent(getConversationMessageContent(conversationMessage)).build());
1717+
break;
1718+
case ASSISTANT:
1719+
messageBuilder.setOfAssistant(DaprProtos.ConversationMessageOfAssistant.newBuilder()
1720+
.setName(conversationMessage.getName())
1721+
.addAllToolCalls(getConversationToolCalls(conversationMessage))
1722+
.addAllContent(getConversationMessageContent(conversationMessage)).build());
1723+
break;
1724+
case DEVELOPER:
1725+
messageBuilder.setOfDeveloper(DaprProtos.ConversationMessageOfDeveloper.newBuilder()
1726+
.setName(conversationMessage.getName())
1727+
.addAllContent(getConversationMessageContent(conversationMessage)).build());
1728+
break;
1729+
case SYSTEM:
1730+
messageBuilder.setOfSystem(DaprProtos.ConversationMessageOfSystem.newBuilder()
1731+
.setName(conversationMessage.getName())
1732+
.addAllContent(getConversationMessageContent(conversationMessage)).build());
1733+
break;
1734+
default: throw new IllegalArgumentException("No role of type " + role + " found");
1735+
}
1736+
1737+
conversationInputBuilder.addMessages(messageBuilder.build());
1738+
}
1739+
}
1740+
1741+
protosConversationRequestBuilder.addInputs(conversationInputBuilder.build());
1742+
}
1743+
1744+
Mono<DaprProtos.ConversationResponseAlpha2> conversationResponseMono = Mono.deferContextual(
1745+
context -> this.createMono(
1746+
it -> intercept(context, asyncStub)
1747+
.converseAlpha2(protosConversationRequestBuilder.build(), it)
1748+
)
1749+
);
1750+
1751+
return conversationResponseMono.map(conversationResponse -> {
1752+
List<ConversationResultAlpha2> results = new ArrayList<>();
1753+
1754+
for (DaprProtos.ConversationResultAlpha2 result : conversationResponse.getOutputsList()) {
1755+
List<ConversationResultChoices> choices = new ArrayList<>();
1756+
1757+
for (DaprProtos.ConversationResultChoices choice : result.getChoicesList()) {
1758+
ConversationResultMessage message = null;
1759+
if (choice.hasMessage()) {
1760+
List<ConversationToolCalls> toolCalls = new ArrayList<>();
1761+
1762+
for (DaprProtos.ConversationToolCalls toolCall : choice.getMessage().getToolCallsList()) {
1763+
ConversationToolCallsFunction function = null;
1764+
if (toolCall.hasFunction()) {
1765+
function = new ConversationToolCallsFunction(
1766+
toolCall.getFunction().getName(),
1767+
toolCall.getFunction().getArguments()
1768+
);
1769+
}
1770+
1771+
toolCalls.add(new ConversationToolCalls(toolCall.getId(), function));
1772+
}
1773+
1774+
message = new ConversationResultMessage(
1775+
choice.getMessage().getContent(),
1776+
toolCalls
1777+
);
1778+
}
1779+
1780+
choices.add(new ConversationResultChoices(choice.getFinishReason(), choice.getIndex(), message));
1781+
}
1782+
1783+
results.add(new ConversationResultAlpha2(choices));
1784+
}
1785+
1786+
return new ConversationResponseAlpha2(conversationResponse.getContextId(), results);
1787+
});
1788+
} catch (Exception ex) {
1789+
return DaprException.wrapMono(ex);
1790+
}
1791+
}
1792+
1793+
private List<DaprProtos.ConversationMessageContent> getConversationMessageContent(
1794+
ConversationMessage conversationMessage) {
1795+
1796+
List<DaprProtos.ConversationMessageContent> conversationMessageContents = new ArrayList<>();
1797+
for (ConversationMessageContent conversationMessageContent: conversationMessage.getContent()) {
1798+
conversationMessageContents.add(DaprProtos.ConversationMessageContent.newBuilder()
1799+
.setText(conversationMessageContent.getText())
1800+
.build());
1801+
}
1802+
1803+
return conversationMessageContents;
1804+
}
1805+
1806+
private List<DaprProtos.ConversationToolCalls> getConversationToolCalls(
1807+
ConversationMessage conversationMessage) {
1808+
List<DaprProtos.ConversationToolCalls> conversationToolCalls = new ArrayList<>();
1809+
for (ConversationToolCalls conversationToolCall: conversationMessage.getToolCalls()) {
1810+
conversationToolCalls.add(DaprProtos.ConversationToolCalls.newBuilder()
1811+
.setId(conversationToolCall.getId())
1812+
.setFunction(DaprProtos.ConversationToolCallsOfFunction.newBuilder()
1813+
.setName(conversationToolCall.getFunction().getName())
1814+
.setArguments(conversationToolCall.getFunction().getArguments())
1815+
.build())
1816+
.build());
1817+
}
1818+
1819+
return conversationToolCalls;
1820+
}
1821+
1822+
private void validateConversationRequestAlpha2(ConversationRequestAlpha2 conversationRequest) {
1823+
if ((conversationRequest.getName() == null) || (conversationRequest.getName().trim().isEmpty())) {
1824+
throw new IllegalArgumentException("LLM name cannot be null or empty.");
1825+
}
1826+
1827+
if ((conversationRequest.getInputs() == null) || (conversationRequest.getInputs().isEmpty())) {
1828+
throw new IllegalArgumentException("Conversation inputs cannot be null or empty.");
1829+
}
1830+
}
1831+
16311832
private DaprMetadata buildDaprMetadata(DaprProtos.GetMetadataResponse response) throws IOException {
16321833
String id = response.getId();
16331834
String runtimeVersion = response.getRuntimeVersion();

sdk/src/main/java/io/dapr/client/DaprPreviewClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import io.dapr.client.domain.BulkPublishResponse;
1919
import io.dapr.client.domain.BulkPublishResponseFailedEntry;
2020
import io.dapr.client.domain.ConversationRequest;
21+
import io.dapr.client.domain.ConversationRequestAlpha2;
2122
import io.dapr.client.domain.ConversationResponse;
23+
import io.dapr.client.domain.ConversationResponseAlpha2;
2224
import io.dapr.client.domain.DeleteJobRequest;
2325
import io.dapr.client.domain.GetJobRequest;
2426
import io.dapr.client.domain.GetJobResponse;
@@ -314,4 +316,12 @@ <T> Subscription subscribeToEvents(
314316
* @return {@link ConversationResponse}.
315317
*/
316318
public Mono<ConversationResponse> converse(ConversationRequest conversationRequest);
319+
320+
/*
321+
* Converse with an LLM using Alpha2 API.
322+
*
323+
* @param conversationRequestAlpha2 request to be passed to the LLM with Alpha2 features.
324+
* @return {@link ConversationResponseAlpha2}.
325+
*/
326+
public Mono<ConversationResponseAlpha2> converseAlpha2(ConversationRequestAlpha2 conversationRequestAlpha2);
317327
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025 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.client.domain;
15+
16+
import java.util.Map;
17+
18+
/**
19+
* Represents a function definition for conversation tools.
20+
*/
21+
public class ConversationFunction {
22+
23+
private final String name;
24+
private final String description;
25+
private final Map<String, Object> parameters;
26+
27+
/**
28+
* Constructor.
29+
*
30+
* @param name the function name
31+
* @param description the function description
32+
* @param parameters the function parameters schema
33+
*/
34+
public ConversationFunction(String name, String description, Map<String, Object> parameters) {
35+
this.name = name;
36+
this.description = description;
37+
this.parameters = parameters;
38+
}
39+
40+
/**
41+
* Gets the function name.
42+
*
43+
* @return the function name
44+
*/
45+
public String getName() {
46+
return name;
47+
}
48+
49+
/**
50+
* Gets the function description.
51+
*
52+
* @return the function description
53+
*/
54+
public String getDescription() {
55+
return description;
56+
}
57+
58+
/**
59+
* Gets the function parameters schema.
60+
*
61+
* @return the function parameters
62+
*/
63+
public Map<String, Object> getParameters() {
64+
return parameters;
65+
}
66+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025 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.client.domain;
15+
16+
import java.util.List;
17+
18+
/**
19+
* Represents an Alpha2 input for conversation with enhanced message support.
20+
*/
21+
public class ConversationInputAlpha2 {
22+
23+
private final List<ConversationMessage> messages;
24+
private boolean scrubPii;
25+
26+
/**
27+
* Constructor.
28+
*
29+
* @param messages the list of conversation messages
30+
*/
31+
public ConversationInputAlpha2(List<ConversationMessage> messages) {
32+
this.messages = List.copyOf(messages);
33+
}
34+
35+
/**
36+
* Gets the list of conversation messages.
37+
*
38+
* @return the list of messages
39+
*/
40+
public List<ConversationMessage> getMessages() {
41+
return messages;
42+
}
43+
44+
/**
45+
* Checks if Personally Identifiable Information (PII) should be scrubbed before sending to the LLM.
46+
*
47+
* @return {@code true} if PII should be scrubbed, {@code false} otherwise.
48+
*/
49+
public boolean isScrubPii() {
50+
return scrubPii;
51+
}
52+
53+
/**
54+
* Enable obfuscation of sensitive information present in the content field. Optional
55+
*
56+
* @param scrubPii A boolean indicating whether to remove PII.
57+
* @return this.
58+
*/
59+
public ConversationInputAlpha2 setScrubPii(boolean scrubPii) {
60+
this.scrubPii = scrubPii;
61+
return this;
62+
}
63+
}

0 commit comments

Comments
 (0)