Fix things

Signed-off-by: sirivarma <siri.varma@outlook.com>
This commit is contained in:
sirivarma 2025-08-06 15:21:20 -07:00
parent fbc4ce0ce6
commit 118bd69556
14 changed files with 895 additions and 154 deletions

View File

@ -13,6 +13,7 @@ limitations under the License.
package io.dapr.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
@ -21,6 +22,7 @@ import com.google.protobuf.Message;
import io.dapr.client.domain.ActorMetadata;
import io.dapr.client.domain.AppConnectionPropertiesHealthMetadata;
import io.dapr.client.domain.AppConnectionPropertiesMetadata;
import io.dapr.client.domain.AssistantMessage;
import io.dapr.client.domain.BulkPublishEntry;
import io.dapr.client.domain.BulkPublishRequest;
import io.dapr.client.domain.BulkPublishResponse;
@ -28,7 +30,7 @@ import io.dapr.client.domain.BulkPublishResponseFailedEntry;
import io.dapr.client.domain.CloudEvent;
import io.dapr.client.domain.ComponentMetadata;
import io.dapr.client.domain.ConfigurationItem;
import io.dapr.client.domain.ConversationFunction;
import io.dapr.client.domain.ConversationToolsFunction;
import io.dapr.client.domain.ConversationInput;
import io.dapr.client.domain.ConversationInputAlpha2;
import io.dapr.client.domain.ConversationMessage;
@ -42,7 +44,7 @@ import io.dapr.client.domain.ConversationResultAlpha2;
import io.dapr.client.domain.ConversationResultChoices;
import io.dapr.client.domain.ConversationResultMessage;
import io.dapr.client.domain.ConversationToolCalls;
import io.dapr.client.domain.ConversationToolCallsFunction;
import io.dapr.client.domain.ConversationToolCallsOfFunction;
import io.dapr.client.domain.ConversationTools;
import io.dapr.client.domain.DaprMetadata;
import io.dapr.client.domain.DeleteJobRequest;
@ -73,6 +75,7 @@ import io.dapr.client.domain.StateOptions;
import io.dapr.client.domain.SubscribeConfigurationRequest;
import io.dapr.client.domain.SubscribeConfigurationResponse;
import io.dapr.client.domain.SubscriptionMetadata;
import io.dapr.client.domain.ToolMessage;
import io.dapr.client.domain.TransactionalStateOperation;
import io.dapr.client.domain.UnlockRequest;
import io.dapr.client.domain.UnlockResponseStatus;
@ -1572,6 +1575,7 @@ public class DaprClientImpl extends AbstractDaprClient {
/**
* {@inheritDoc}
*/
@Deprecated(forRemoval = true)
@Override
public Mono<ConversationResponse> converse(ConversationRequest conversationRequest) {
@ -1685,7 +1689,7 @@ public class DaprClientImpl extends AbstractDaprClient {
}
private DaprProtos.ConversationRequestAlpha2 buildConversationRequestProto(ConversationRequestAlpha2 request,
DaprProtos.ConversationRequestAlpha2.Builder builder) {
DaprProtos.ConversationRequestAlpha2.Builder builder) {
if (request.getTools() != null) {
buildConversationTools(request.getTools(), builder);
}
@ -1694,13 +1698,21 @@ public class DaprClientImpl extends AbstractDaprClient {
builder.putAllMetadata(request.getMetadata());
}
if (request.getParameters() != null) {
Map<String, Any> parameters = request.getParameters()
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> Any.pack((Message) e.getValue())
));
e -> {
try {
return Any.newBuilder().setValue(ByteString.copyFrom(objectSerializer.serialize(e.getValue())))
.build();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
})
);
builder.putAllParameters(parameters);
}
@ -1727,7 +1739,7 @@ public class DaprClientImpl extends AbstractDaprClient {
private void buildConversationTools(List<ConversationTools> tools,
DaprProtos.ConversationRequestAlpha2.Builder builder) {
for (ConversationTools tool : tools) {
ConversationFunction function = tool.getFunction();
ConversationToolsFunction function = tool.getFunction();
DaprProtos.ConversationToolsFunction.Builder protoFunction = DaprProtos.ConversationToolsFunction.newBuilder()
.setName(function.getName());
@ -1741,7 +1753,14 @@ public class DaprClientImpl extends AbstractDaprClient {
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> Any.pack((Message) e.getValue())
e -> {
try {
return Any.newBuilder().setValue(ByteString.copyFrom(objectSerializer.serialize(e.getValue())))
.build();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
));
protoFunction.putAllParameters(functionParams);
@ -1766,8 +1785,8 @@ public class DaprClientImpl extends AbstractDaprClient {
if (message.getContent() != null) {
toolMessage.addAllContent(getConversationMessageContent(message));
}
if (message.getToolId() != null) {
toolMessage.setToolId(message.getToolId());
if (((ToolMessage)message).getToolId() != null) {
toolMessage.setToolId(((ToolMessage)message).getToolId());
}
messageBuilder.setOfTool(toolMessage);
break;
@ -1785,14 +1804,15 @@ public class DaprClientImpl extends AbstractDaprClient {
case ASSISTANT:
DaprProtos.ConversationMessageOfAssistant.Builder assistantMessage =
DaprProtos.ConversationMessageOfAssistant.newBuilder();
if (message.getName() != null) {
assistantMessage.setName(message.getName());
}
if (message.getContent() != null) {
assistantMessage.addAllContent(getConversationMessageContent(message));
}
if (message.getToolCalls() != null) {
assistantMessage.addAllToolCalls(getConversationToolCalls(message));
if (((AssistantMessage)message).getToolCalls() != null) {
assistantMessage.addAllToolCalls(getConversationToolCalls((AssistantMessage)message));
}
messageBuilder.setOfAssistant(assistantMessage);
break;
@ -1851,15 +1871,18 @@ public class DaprClientImpl extends AbstractDaprClient {
List<ConversationToolCalls> toolCalls = new ArrayList<>();
for (DaprProtos.ConversationToolCalls protoToolCall : protoChoice.getMessage().getToolCallsList()) {
ConversationToolCallsFunction function = null;
ConversationToolCallsOfFunction function = null;
if (protoToolCall.hasFunction()) {
function = new ConversationToolCallsFunction(
function = new ConversationToolCallsOfFunction(
protoToolCall.getFunction().getName(),
protoToolCall.getFunction().getArguments()
);
}
toolCalls.add(new ConversationToolCalls(protoToolCall.getId(), function));
ConversationToolCalls conversationToolCalls = new ConversationToolCalls(function);
conversationToolCalls.setId(protoToolCall.getId());
toolCalls.add(conversationToolCalls);
}
return new ConversationResultMessage(
@ -1882,16 +1905,19 @@ public class DaprClientImpl extends AbstractDaprClient {
}
private List<DaprProtos.ConversationToolCalls> getConversationToolCalls(
ConversationMessage conversationMessage) {
AssistantMessage assistantMessage) {
List<DaprProtos.ConversationToolCalls> conversationToolCalls = new ArrayList<>();
for (ConversationToolCalls conversationToolCall: conversationMessage.getToolCalls()) {
conversationToolCalls.add(DaprProtos.ConversationToolCalls.newBuilder()
.setId(conversationToolCall.getId())
for (ConversationToolCalls conversationToolCall: assistantMessage.getToolCalls()) {
DaprProtos.ConversationToolCalls.Builder toolCallsBuilder = DaprProtos.ConversationToolCalls.newBuilder()
.setFunction(DaprProtos.ConversationToolCallsOfFunction.newBuilder()
.setName(conversationToolCall.getFunction().getName())
.setArguments(conversationToolCall.getFunction().getArguments())
.build())
.build());
.setName(conversationToolCall.getFunction().getName())
.setArguments(conversationToolCall.getFunction().getArguments())
.build());
if (conversationToolCall.getId() != null) {
toolCallsBuilder.setId(conversationToolCall.getId());
}
conversationToolCalls.add(toolCallsBuilder.build());
}
return conversationToolCalls;

View File

@ -315,6 +315,7 @@ public interface DaprPreviewClient extends AutoCloseable {
* @param conversationRequest request to be passed to the LLM.
* @return {@link ConversationResponse}.
*/
@Deprecated
public Mono<ConversationResponse> converse(ConversationRequest conversationRequest);
/*

View File

@ -0,0 +1,65 @@
/*
* 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.client.domain;
import java.util.List;
/**
* Assistant message containing responses from the AI model.
* Can include regular content and/or tool calls that the model wants to make.
*/
public class AssistantMessage implements ConversationMessage {
private String name;
private final List<ConversationMessageContent> content;
private final List<ConversationToolCalls> toolCalls;
/**
* Creates an assistant message with content and optional tool calls.
* @param content the content of the assistant message.
* @param toolCalls the tool calls requested by the assistant.
*/
public AssistantMessage(List<ConversationMessageContent> content, List<ConversationToolCalls> toolCalls) {
this.content = content != null ? List.copyOf(content) : null;
this.toolCalls = toolCalls != null ? List.copyOf(toolCalls) : null;
}
@Override
public ConversationMessageRole getRole() {
return ConversationMessageRole.ASSISTANT;
}
@Override
public String getName() {
return name;
}
/**
* Sets the name of the assistant participant.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
@Override
public List<ConversationMessageContent> getContent() {
return content;
}
public List<ConversationToolCalls> getToolCalls() {
return toolCalls;
}
}

View File

@ -16,118 +16,29 @@ package io.dapr.client.domain;
import java.util.List;
/**
* Represents a conversation message with role-specific content.
* Interface representing a conversation message with role-specific content.
* Supports different message types: system, user, assistant, developer, and tool.
*/
public class ConversationMessage {
/**
* Enum representing the different roles a message can have.
*/
public enum Role {
SYSTEM,
USER,
ASSISTANT,
DEVELOPER,
TOOL
}
private final Role role;
private final String name;
private final List<ConversationMessageContent> content;
private final List<ConversationToolCalls> toolCalls;
private final String toolId;
/**
* Constructor for creating a message with basic content.
*
* @param role the role of the message sender
* @param content the content of the message
*/
public ConversationMessage(Role role, List<ConversationMessageContent> content) {
this(role, null, content, null, null);
}
/**
* Constructor for creating a message with name and content.
*
* @param role the role of the message sender
* @param name the name of the participant (optional)
* @param content the content of the message
*/
public ConversationMessage(Role role, String name, List<ConversationMessageContent> content) {
this(role, name, content, null, null);
}
/**
* Full constructor for creating a message with all properties.
*
* @param role the role of the message sender
* @param name the name of the participant (optional)
* @param content the content of the message
* @param toolCalls tool calls for assistant messages (optional)
* @param toolId tool ID for tool messages (optional)
*/
public ConversationMessage(Role role, String name, List<ConversationMessageContent> content,
List<ConversationToolCalls> toolCalls, String toolId) {
this.role = role;
this.name = name;
this.content = content != null ? List.copyOf(content) : null;
this.toolCalls = toolCalls != null ? List.copyOf(toolCalls) : null;
this.toolId = toolId;
}
public interface ConversationMessage {
/**
* Gets the role of the message sender.
*
* @return the message role
*/
public Role getRole() {
return role;
}
ConversationMessageRole getRole();
/**
* Gets the name of the participant in the message.
*
* @return the participant name, or null if not specified
*/
public String getName() {
return name;
}
String getName();
/**
* Gets the content of the message.
*
* @return the message content
*/
public List<ConversationMessageContent> getContent() {
return content;
}
/**
* Gets the tool calls generated by the model (for assistant messages).
*
* @return the tool calls, or null if none
*/
public List<ConversationToolCalls> getToolCalls() {
return toolCalls;
}
/**
* Gets the tool ID (for tool messages).
*
* @return the tool ID, or null if not a tool message
*/
public String getToolId() {
return toolId;
}
/**
* Checks if this message has tool calls.
*
* @return true if the message has tool calls, false otherwise
*/
public boolean hasToolCalls() {
return toolCalls != null && !toolCalls.isEmpty();
}
List<ConversationMessageContent> getContent();
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2022 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;
/**
* Enum representing the different roles a conversation message can have.
*/
public enum ConversationMessageRole {
/**
* System message that sets the behavior or context for the conversation.
*/
SYSTEM,
/**
* User message containing input from the human user.
*/
USER,
/**
* Assistant message containing responses from the AI model.
*/
ASSISTANT,
/**
* Tool message containing results from function/tool calls.
*/
TOOL,
/**
* Developer message for development and debugging purposes.
*/
DEVELOPER
}

View File

@ -18,27 +18,17 @@ package io.dapr.client.domain;
*/
public class ConversationToolCalls {
private final String id;
private final ConversationToolCallsFunction function;
private String id;
private final ConversationToolCallsOfFunction function;
/**
* Constructor.
*
* @param id the unique identifier for the tool call (optional)
* @param function the function to call
*/
public ConversationToolCalls(String id, ConversationToolCallsFunction function) {
this.id = id;
this.function = function;
}
/**
* Constructor without ID.
*
* @param function the function to call
*/
public ConversationToolCalls(ConversationToolCallsFunction function) {
this(null, function);
public ConversationToolCalls(ConversationToolCallsOfFunction function) {
this.function = function;
}
/**
@ -50,12 +40,22 @@ public class ConversationToolCalls {
return id;
}
/**
* Set with ID.
*
* @param id the unique identifier for the tool call
*/
public ConversationToolCalls setId(String id) {
this.id = id;
return this;
}
/**
* Gets the function to call.
*
* @return the function details
*/
public ConversationToolCallsFunction getFunction() {
public ConversationToolCallsOfFunction getFunction() {
return function;
}
}

View File

@ -16,7 +16,7 @@ package io.dapr.client.domain;
/**
* Represents a function call within a tool call.
*/
public class ConversationToolCallsFunction {
public class ConversationToolCallsOfFunction {
private final String name;
private final String arguments;
@ -27,7 +27,7 @@ public class ConversationToolCallsFunction {
* @param name the name of the function to call
* @param arguments the arguments to call the function with, as generated by the model in JSON format
*/
public ConversationToolCallsFunction(String name, String arguments) {
public ConversationToolCallsOfFunction(String name, String arguments) {
this.name = name;
this.arguments = arguments;
}

View File

@ -18,35 +18,23 @@ package io.dapr.client.domain;
*/
public class ConversationTools {
private final String type;
private final ConversationFunction function;
private final ConversationToolsFunction function;
/**
* Constructor.
*
* @param type the type of tool (e.g., "function")
* @param function the function definition
*/
public ConversationTools(String type, ConversationFunction function) {
this.type = type;
public ConversationTools(ConversationToolsFunction function) {
this.function = function;
}
/**
* Gets the tool type.
*
* @return the tool type
*/
public String getType() {
return type;
}
/**
* Gets the function definition.
*
* @return the function definition
*/
public ConversationFunction getFunction() {
public ConversationToolsFunction getFunction() {
return function;
}
}

View File

@ -18,22 +18,20 @@ import java.util.Map;
/**
* Represents a function definition for conversation tools.
*/
public class ConversationFunction {
public class ConversationToolsFunction {
private String description;
private final String name;
private final String description;
private final Map<String, Object> parameters;
/**
* Constructor.
*
* @param name the function name
* @param description the function description
* @param parameters the function parameters schema
*/
public ConversationFunction(String name, String description, Map<String, Object> parameters) {
public ConversationToolsFunction(String name, Map<String, Object> parameters) {
this.name = name;
this.description = description;
this.parameters = parameters;
}
@ -55,6 +53,17 @@ public class ConversationFunction {
return description;
}
/**
* Sets the function description.
*
* @param description the function description
* @return this instance for method chaining
*/
public ConversationToolsFunction setDescription(String description) {
this.description = description;
return this;
}
/**
* Gets the function parameters schema.
*

View File

@ -0,0 +1,59 @@
/*
* 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.client.domain;
import java.util.List;
/**
* Developer message for development and debugging purposes.
* Used for providing additional context or instructions during development.
*/
public class DeveloperMessage implements ConversationMessage {
private String name;
private final List<ConversationMessageContent> content;
/**
* Creates a developer message with content.
*
* @param content the content of the developer message
*/
public DeveloperMessage(List<ConversationMessageContent> content) {
this.content = content != null ? List.copyOf(content) : null;
}
@Override
public ConversationMessageRole getRole() {
return ConversationMessageRole.DEVELOPER;
}
@Override
public String getName() {
return name;
}
/**
* Sets the name of the developer participant.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
@Override
public List<ConversationMessageContent> getContent() {
return content;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.client.domain;
import java.util.List;
/**
* System message that sets the behavior or context for the conversation.
* Used to provide instructions or context to the AI model.
*/
public class SystemMessage implements ConversationMessage {
private String name;
private final List<ConversationMessageContent> content;
/**
* Creates a system message with content.
*
* @param content the content of the system message
*/
public SystemMessage(List<ConversationMessageContent> content) {
this.content = content != null ? List.copyOf(content) : null;
}
@Override
public ConversationMessageRole getRole() {
return ConversationMessageRole.SYSTEM;
}
@Override
public String getName() {
return name;
}
/**
* Sets the name of the system participant.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
@Override
public List<ConversationMessageContent> getContent() {
return content;
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.client.domain;
import java.util.List;
/**
* Tool message containing results from function/tool calls.
* Used to provide the response from a tool execution back to the AI model.
*/
public class ToolMessage implements ConversationMessage {
private String toolId;
private String name;
private final List<ConversationMessageContent> content;
/**
* Creates a tool message with content.
*
* @param content the content containing the tool execution result
*/
public ToolMessage(List<ConversationMessageContent> content) {
this.content = content != null ? List.copyOf(content) : null;
}
@Override
public ConversationMessageRole getRole() {
return ConversationMessageRole.TOOL;
}
@Override
public String getName() {
return name;
}
/**
* Sets the tool identifier.
*
* @param toolId the tool identifier to set
*/
public void setToolId(String toolId) {
this.toolId = toolId;
}
/**
* Sets the name of the tool participant.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
@Override
public List<ConversationMessageContent> getContent() {
return content;
}
public String getToolId() {
return toolId;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.client.domain;
import java.util.List;
/**
* User message containing input from the human user.
* Represents questions, requests, or other input from the end user.
*/
public class UserMessage implements ConversationMessage {
private String name;
private final List<ConversationMessageContent> content;
/**
* Creates a user message with content.
*
* @param content the content of the user message
*/
public UserMessage(List<ConversationMessageContent> content) {
this.content = content != null ? List.copyOf(content) : null;
}
@Override
public ConversationMessageRole getRole() {
return ConversationMessageRole.USER;
}
@Override
public String getName() {
return name;
}
/**
* Sets the name of the user participant.
*
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
@Override
public List<ConversationMessageContent> getContent() {
return content;
}
}

View File

@ -18,11 +18,24 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import io.dapr.client.domain.AssistantMessage;
import io.dapr.client.domain.BulkPublishEntry;
import io.dapr.client.domain.BulkPublishRequest;
import io.dapr.client.domain.BulkPublishResponse;
import io.dapr.client.domain.CloudEvent;
import io.dapr.client.domain.ConversationToolCallsOfFunction;
import io.dapr.client.domain.ConversationToolsFunction;
import io.dapr.client.domain.ConversationInputAlpha2;
import io.dapr.client.domain.ConversationMessage;
import io.dapr.client.domain.ConversationMessageContent;
import io.dapr.client.domain.ConversationRequestAlpha2;
import io.dapr.client.domain.ConversationResponseAlpha2;
import io.dapr.client.domain.ConversationResultAlpha2;
import io.dapr.client.domain.ConversationResultChoices;
import io.dapr.client.domain.ConversationToolCalls;
import io.dapr.client.domain.ConversationTools;
import io.dapr.client.domain.DeleteJobRequest;
import io.dapr.client.domain.DeveloperMessage;
import io.dapr.client.domain.GetJobRequest;
import io.dapr.client.domain.GetJobResponse;
import io.dapr.client.domain.JobSchedule;
@ -33,7 +46,10 @@ import io.dapr.client.domain.QueryStateItem;
import io.dapr.client.domain.QueryStateRequest;
import io.dapr.client.domain.QueryStateResponse;
import io.dapr.client.domain.ScheduleJobRequest;
import io.dapr.client.domain.SystemMessage;
import io.dapr.client.domain.ToolMessage;
import io.dapr.client.domain.UnlockResponseStatus;
import io.dapr.client.domain.UserMessage;
import io.dapr.client.domain.query.Query;
import io.dapr.serializer.DaprObjectSerializer;
import io.dapr.serializer.DefaultObjectSerializer;
@ -984,6 +1000,437 @@ public class DaprPreviewClientGrpcTest {
assertEquals("Name in the request cannot be null or empty", exception.getMessage());
}
@Test
public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsNull() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2(null, null);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
previewClient.converseAlpha2(request).block());
assertEquals("LLM name cannot be null or empty.", exception.getMessage());
}
@Test
public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsEmpty() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("", null);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
previewClient.converseAlpha2(request).block());
assertEquals("LLM name cannot be null or empty.", exception.getMessage());
}
@Test
public void converseAlpha2ShouldThrowIllegalArgumentExceptionWhenNameIsWhitespace() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2(" ", null);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
previewClient.converseAlpha2(request).block());
assertEquals("LLM name cannot be null or empty.", exception.getMessage());
}
@Test
public void converseAlpha2ExceptionThrownTest() {
doAnswer((Answer<Void>) invocation -> {
throw newStatusRuntimeException("INVALID_ARGUMENT", "bad argument");
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
assertThrowsDaprException(
StatusRuntimeException.class,
"INVALID_ARGUMENT",
"INVALID_ARGUMENT: bad argument",
() -> previewClient.converseAlpha2(request).block());
}
@Test
public void converseAlpha2CallbackExceptionThrownTest() {
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onError(newStatusRuntimeException("INVALID_ARGUMENT", "bad argument"));
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
Mono<ConversationResponseAlpha2> result = previewClient.converseAlpha2(request);
assertThrowsDaprException(
ExecutionException.class,
"INVALID_ARGUMENT",
"INVALID_ARGUMENT: bad argument",
() -> result.block());
}
@Test
public void converseAlpha2MinimalRequestTest() {
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.setContextId("test-context")
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("Hello! How can I help you today?")
.build())
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
assertEquals("test-context", response.getContextId());
assertEquals(1, response.getOutputs().size());
ConversationResultAlpha2 result = response.getOutputs().get(0);
assertEquals(1, result.getChoices().size());
ConversationResultChoices choice = result.getChoices().get(0);
assertEquals("stop", choice.getFinishReason());
assertEquals(0, choice.getIndex());
assertEquals("Hello! How can I help you today?", choice.getMessage().getContent());
}
@Test
public void converseAlpha2ComplexRequestTest() {
// Create messages
List<ConversationMessage> messages = new ArrayList<>();
UserMessage userMessage = new UserMessage(List.of(new ConversationMessageContent("Hello, how are you?")));
userMessage.setName("John");
messages.add(userMessage);
// Create input
ConversationInputAlpha2 input = new ConversationInputAlpha2(messages);
input.setScrubPii(true);
// Create tools
Map<String, Object> functionParams = new HashMap<>();
functionParams.put("location", "Required location parameter");
List<ConversationTools> tools = new ArrayList<>();
ConversationToolsFunction function = new ConversationToolsFunction("get_weather", functionParams);
function.setDescription("Get current weather");
ConversationTools tool = new ConversationTools(function);
tools.add(tool);
Map<String, String> metadata = new HashMap<>();
metadata.put("key1", "value1");
Map<String, Object> parameters = new HashMap<>();
parameters.put("max_tokens", "1000");
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input));
request.setContextId("test-context");
request.setTemperature(0.7);
request.setScrubPii(true);
request.setTools(tools);
request.setToolChoice("auto");
request.setMetadata(metadata);
request.setParameters(parameters);
// Mock response with tool calls
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.setContextId("test-context")
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("tool_calls")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("I'll help you get the weather information.")
.addToolCalls(DaprProtos.ConversationToolCalls.newBuilder()
.setId("call_123")
.setFunction(DaprProtos.ConversationToolCallsOfFunction.newBuilder()
.setName("get_weather")
.setArguments("{\"location\": \"New York\"}")
.build())
.build())
.build())
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
assertEquals("test-context", response.getContextId());
ConversationResultChoices choice = response.getOutputs().get(0).getChoices().get(0);
assertEquals("tool_calls", choice.getFinishReason());
assertEquals("I'll help you get the weather information.", choice.getMessage().getContent());
assertEquals(1, choice.getMessage().getToolCalls().size());
ConversationToolCalls toolCall = choice.getMessage().getToolCalls().get(0);
assertEquals("call_123", toolCall.getId());
assertEquals("get_weather", toolCall.getFunction().getName());
assertEquals("{\"location\": \"New York\"}", toolCall.getFunction().getArguments());
// Verify the request was built correctly
ArgumentCaptor<DaprProtos.ConversationRequestAlpha2> captor =
ArgumentCaptor.forClass(DaprProtos.ConversationRequestAlpha2.class);
verify(daprStub).converseAlpha2(captor.capture(), any());
DaprProtos.ConversationRequestAlpha2 capturedRequest = captor.getValue();
assertEquals("openai", capturedRequest.getName());
assertEquals("test-context", capturedRequest.getContextId());
assertEquals(0.7, capturedRequest.getTemperature(), 0.001);
assertTrue(capturedRequest.getScrubPii());
assertEquals("auto", capturedRequest.getToolChoice());
assertEquals("value1", capturedRequest.getMetadataMap().get("key1"));
assertEquals(1, capturedRequest.getToolsCount());
assertEquals("get_weather", capturedRequest.getTools(0).getFunction().getName());
}
@Test
public void converseAlpha2AllMessageTypesTest() {
List<ConversationMessage> messages = new ArrayList<>();
// System message
SystemMessage systemMsg = new SystemMessage(List.of(new ConversationMessageContent("You are a helpful assistant.")));
systemMsg.setName("system");
messages.add(systemMsg);
// User message
UserMessage userMsg = new UserMessage(List.of(new ConversationMessageContent("Hello!")));
userMsg.setName("user");
messages.add(userMsg);
// Assistant message
AssistantMessage assistantMsg = new AssistantMessage(List.of(new ConversationMessageContent("Hi there!")),
List.of(new ConversationToolCalls(new ConversationToolCallsOfFunction("abc", "parameters"))));
assistantMsg.setName("assistant");
messages.add(assistantMsg);
// Tool message
ToolMessage toolMsg = new ToolMessage(List.of(new ConversationMessageContent("Weather data: 72F")));
toolMsg.setName("tool");
messages.add(toolMsg);
// Developer message
DeveloperMessage devMsg = new DeveloperMessage(List.of(new ConversationMessageContent("Debug info")));
devMsg.setName("developer");
messages.add(devMsg);
ConversationInputAlpha2 input = new ConversationInputAlpha2(messages);
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input));
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("Processed all message types")
.build())
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
assertEquals("Processed all message types", response.getOutputs().get(0).getChoices().get(0).getMessage().getContent());
// Verify all message types were processed
ArgumentCaptor<DaprProtos.ConversationRequestAlpha2> captor =
ArgumentCaptor.forClass(DaprProtos.ConversationRequestAlpha2.class);
verify(daprStub).converseAlpha2(captor.capture(), any());
DaprProtos.ConversationRequestAlpha2 capturedRequest = captor.getValue();
assertEquals(1, capturedRequest.getInputsCount());
assertEquals(5, capturedRequest.getInputs(0).getMessagesCount());
// Verify each message type was converted correctly
List<DaprProtos.ConversationMessage> capturedMessages = capturedRequest.getInputs(0).getMessagesList();
assertTrue(capturedMessages.get(0).hasOfSystem());
assertTrue(capturedMessages.get(1).hasOfUser());
assertTrue(capturedMessages.get(2).hasOfAssistant());
assertTrue(capturedMessages.get(3).hasOfTool());
assertTrue(capturedMessages.get(4).hasOfDeveloper());
}
@Test
public void converseAlpha2EmptyInputsTest() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", new ArrayList<>());
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(0)
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
ArgumentCaptor<DaprProtos.ConversationRequestAlpha2> captor =
ArgumentCaptor.forClass(DaprProtos.ConversationRequestAlpha2.class);
verify(daprStub).converseAlpha2(captor.capture(), any());
DaprProtos.ConversationRequestAlpha2 capturedRequest = captor.getValue();
assertEquals(0, capturedRequest.getInputsCount());
}
@Test
public void converseAlpha2ResponseWithoutMessageTest() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(0)
// No message set
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
ConversationResultChoices choice = response.getOutputs().get(0).getChoices().get(0);
assertEquals("stop", choice.getFinishReason());
assertEquals(0, choice.getIndex());
assertNull(choice.getMessage());
}
@Test
public void converseAlpha2MultipleResultsTest() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("First choice")
.build())
.build())
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("stop")
.setIndex(1)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("Second choice")
.build())
.build())
.build())
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("length")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("Third result")
.build())
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
assertEquals(2, response.getOutputs().size());
// First result with 2 choices
ConversationResultAlpha2 firstResult = response.getOutputs().get(0);
assertEquals(2, firstResult.getChoices().size());
assertEquals("First choice", firstResult.getChoices().get(0).getMessage().getContent());
assertEquals("Second choice", firstResult.getChoices().get(1).getMessage().getContent());
// Second result with 1 choice
ConversationResultAlpha2 secondResult = response.getOutputs().get(1);
assertEquals(1, secondResult.getChoices().size());
assertEquals("Third result", secondResult.getChoices().get(0).getMessage().getContent());
}
@Test
public void converseAlpha2ToolCallWithoutFunctionTest() {
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", null);
DaprProtos.ConversationResponseAlpha2 grpcResponse = DaprProtos.ConversationResponseAlpha2.newBuilder()
.addOutputs(DaprProtos.ConversationResultAlpha2.newBuilder()
.addChoices(DaprProtos.ConversationResultChoices.newBuilder()
.setFinishReason("tool_calls")
.setIndex(0)
.setMessage(DaprProtos.ConversationResultMessage.newBuilder()
.setContent("Test content")
.addToolCalls(DaprProtos.ConversationToolCalls.newBuilder()
.setId("call_123")
// No function set
.build())
.build())
.build())
.build())
.build();
doAnswer((Answer<Void>) invocation -> {
StreamObserver<DaprProtos.ConversationResponseAlpha2> observer =
(StreamObserver<DaprProtos.ConversationResponseAlpha2>) invocation.getArguments()[1];
observer.onNext(grpcResponse);
observer.onCompleted();
return null;
}).when(daprStub).converseAlpha2(any(DaprProtos.ConversationRequestAlpha2.class), any());
ConversationResponseAlpha2 response = previewClient.converseAlpha2(request).block();
assertNotNull(response);
ConversationToolCalls toolCall = response.getOutputs().get(0).getChoices().get(0)
.getMessage().getToolCalls().get(0);
assertEquals("call_123", toolCall.getId());
assertNull(toolCall.getFunction());
}
private DaprProtos.QueryStateResponse buildQueryStateResponse(List<QueryStateItem<?>> resp,String token)
throws JsonProcessingException {
List<DaprProtos.QueryStateItem> items = new ArrayList<>();