Skip to content

Commit a8c2386

Browse files
committed
chore: Modify conversation examples
Signed-off-by: Javier Aliaga <javier@diagrid.io>
1 parent c75ca06 commit a8c2386

File tree

9 files changed

+205
-30
lines changed

9 files changed

+205
-30
lines changed

examples/src/main/java/io/dapr/examples/conversation/AssistantMessageDemo.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ public static void main(String[] args) {
108108
// Process and display the response
109109
if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) {
110110
ConversationResultAlpha2 result = response.getOutputs().get(0);
111+
UsageUtils.printUsage(result);
112+
111113
if (result.getChoices() != null && !result.getChoices().isEmpty()) {
112114
ConversationResultChoices choice = result.getChoices().get(0);
113-
114115
if (choice.getMessage() != null && choice.getMessage().getContent() != null) {
115116
System.out.printf("Assistant Response: %s%n", choice.getMessage().getContent());
116117
}

examples/src/main/java/io/dapr/examples/conversation/ToolsCallDemo.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public static void main(String[] args) {
8080
// Process and display the response
8181
if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) {
8282
ConversationResultAlpha2 result = response.getOutputs().get(0);
83+
UsageUtils.printUsage(result);
84+
8385
if (result.getChoices() != null && !result.getChoices().isEmpty()) {
8486
ConversationResultChoices choice = result.getChoices().get(0);
8587

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2026 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.domain.ConversationResultAlpha2;
17+
import io.dapr.client.domain.ConversationResultCompletionUsage;
18+
import org.springframework.util.StringUtils;
19+
20+
public class UsageUtils {
21+
static void printUsage(ConversationResultAlpha2 result) {
22+
if (!StringUtils.hasText(result.getModel())){
23+
return;
24+
}
25+
26+
System.out.printf("Conversation model : %s\n", result.getModel());
27+
var usage = result.getUsage();
28+
printUsage(usage);
29+
30+
printCompletionDetails(usage);
31+
printPromptDetails(usage);
32+
33+
}
34+
35+
private static void printUsage(ConversationResultCompletionUsage usage) {
36+
System.out.println("Token Usage Details:");
37+
System.out.printf(" Completion tokens: %d\n", usage.getCompletionTokens());
38+
System.out.printf(" Prompt tokens: %d\n", usage.getPromptTokens());
39+
System.out.printf(" Total tokens: %d\n", usage.getTotalTokens());
40+
System.out.println();
41+
}
42+
43+
private static void printPromptDetails(ConversationResultCompletionUsage usage) {
44+
var completionDetails = usage.getCompletionTokenDetails();
45+
46+
// Display completion token breakdown if available
47+
System.out.println("Prompt Token Details:");
48+
if (completionDetails.getReasoningTokens() > 0) {
49+
System.out.printf(" Reasoning tokens: %d\n", completionDetails.getReasoningTokens());
50+
}
51+
if (completionDetails.getAudioTokens() > 0) {
52+
System.out.printf(" Audio tokens: %d\n", completionDetails.getAudioTokens());
53+
}
54+
System.out.println();
55+
}
56+
57+
private static void printCompletionDetails(ConversationResultCompletionUsage usage) {
58+
// Print detailed token usage information
59+
var completionDetails = usage.getCompletionTokenDetails();
60+
61+
System.out.println("Completion Token Details:");
62+
// If audio tokens are available, display them
63+
if ( completionDetails.getAudioTokens() > 0) {
64+
System.out.printf(" Audio tokens: %d\n", completionDetails.getAudioTokens());
65+
}
66+
67+
// Display completion token breakdown if available
68+
if (completionDetails.getReasoningTokens() > 0) {
69+
System.out.printf(" Reasoning tokens: %d\n", completionDetails.getReasoningTokens());
70+
}
71+
72+
// Display completion token breakdown if available
73+
if (completionDetails.getAcceptedPredictionTokens() > 0) {
74+
System.out.printf(" Accepted prediction tokens: %d\n", completionDetails.getAcceptedPredictionTokens());
75+
}
76+
77+
// Display completion token breakdown if available
78+
if (completionDetails.getRejectedPredictionTokens() > 0) {
79+
System.out.printf(" Rejected prediction tokens: %d\n", completionDetails.getRejectedPredictionTokens());
80+
}
81+
System.out.println();
82+
}
83+
}

examples/src/main/java/io/dapr/examples/conversation/UserMessageDemo.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@
2222
import io.dapr.client.domain.ConversationResultAlpha2;
2323
import io.dapr.client.domain.ConversationResultChoices;
2424
import io.dapr.client.domain.UserMessage;
25-
import io.dapr.config.Properties;
26-
import io.dapr.config.Property;
2725
import reactor.core.publisher.Mono;
2826

2927
import java.util.List;
30-
import java.util.Map;
3128

3229
public class UserMessageDemo {
3330
/**
@@ -46,18 +43,44 @@ public static void main(String[] args) {
4643
// Create conversation input with the user message
4744
ConversationInputAlpha2 daprConversationInput = new ConversationInputAlpha2(List.of(userMessage));
4845

46+
// Define the JSON schema for the response format
47+
String responseSchema = """
48+
{
49+
"type": "object",
50+
"properties": {
51+
"greeting": {
52+
"type": "string",
53+
"description": "A friendly greeting response"
54+
},
55+
"phone_number_detected": {
56+
"type": "boolean",
57+
"description": "Whether a phone number was detected in the input"
58+
},
59+
"detected_number": {
60+
"type": "string",
61+
"description": "The phone number that was detected, if any"
62+
}
63+
},
64+
"required": ["greeting", "phone_number_detected"],
65+
"additionalProperties": false
66+
}
67+
""";
68+
69+
4970
// Component name is the name provided in the metadata block of the conversation.yaml file.
5071
Mono<ConversationResponseAlpha2> responseMono = client.converseAlpha2(new ConversationRequestAlpha2("echo",
5172
List.of(daprConversationInput))
5273
.setContextId("contextId")
5374
.setScrubPii(true)
54-
.setTemperature(1.1d));
75+
.setTemperature(1.1d).setResponseFormat(responseSchema));
5576

5677
ConversationResponseAlpha2 response = responseMono.block();
5778

5879
// Extract and print the conversation result
5980
if (response != null && response.getOutputs() != null && !response.getOutputs().isEmpty()) {
6081
ConversationResultAlpha2 result = response.getOutputs().get(0);
82+
UsageUtils.printUsage(result);
83+
6184
if (result.getChoices() != null && !result.getChoices().isEmpty()) {
6285
ConversationResultChoices choice = result.getChoices().get(0);
6386
if (choice.getMessage() != null && choice.getMessage().getContent() != null) {

sdk/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
<groupId>com.fasterxml.jackson.core</groupId>
3838
<artifactId>jackson-databind</artifactId>
3939
</dependency>
40+
<dependency>
41+
<groupId>com.google.protobuf</groupId>
42+
<artifactId>protobuf-java-util</artifactId>
43+
</dependency>
4044
<dependency>
4145
<groupId>io.projectreactor</groupId>
4246
<artifactId>reactor-core</artifactId>

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,20 +1863,7 @@ private DaprAiProtos.ConversationRequestAlpha2 buildConversationRequestProto(Con
18631863
}
18641864

18651865
if (request.getResponseFormat() != null) {
1866-
Map<String, Value> responseParams = request.getResponseFormat()
1867-
.entrySet().stream()
1868-
.collect(Collectors.toMap(
1869-
Map.Entry::getKey,
1870-
e -> {
1871-
try {
1872-
return ProtobufValueHelper.toProtobufValue(e.getValue());
1873-
} catch (IOException ex) {
1874-
throw new RuntimeException(ex);
1875-
}
1876-
}
1877-
));
1878-
1879-
builder.setResponseFormat(Struct.newBuilder().putAllFields(responseParams).build());
1866+
builder.setResponseFormat(request.getResponseFormat());
18801867
}
18811868

18821869
if (request.getPromptCacheRetention() != null) {

sdk/src/main/java/io/dapr/client/domain/ConversationRequestAlpha2.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
package io.dapr.client.domain;
1515

16+
import com.google.protobuf.Struct;
17+
import io.dapr.utils.ProtobufUtils;
18+
1619
import java.time.Duration;
1720
import java.util.List;
1821
import java.util.Map;
@@ -32,7 +35,7 @@ public class ConversationRequestAlpha2 {
3235
private String toolChoice;
3336
private Map<String, Object> parameters;
3437
private Map<String, String> metadata;
35-
private Map<String, Object> responseFormat;
38+
private Struct responseFormat;
3639
private Duration promptCacheRetention;
3740

3841
/**
@@ -210,19 +213,52 @@ public ConversationRequestAlpha2 setMetadata(Map<String, String> metadata) {
210213
return this;
211214
}
212215

213-
public Map<String, Object> getResponseFormat() {
216+
/**
217+
* Gets the response format in JSON-Schema format.
218+
*
219+
* @return the response format
220+
*/
221+
public Struct getResponseFormat() {
214222
return responseFormat;
215223
}
216224

217-
public ConversationRequestAlpha2 setResponseFormat(Map<String, Object> responseFormat) {
225+
/**
226+
* Sets the response format in JSON-Schema format.
227+
* Structured output described using a JSON Schema object.
228+
* Use this when you want typed structured output.
229+
* Supported by Deepseek, Google AI, Hugging Face, OpenAI, and Anthropic components
230+
*
231+
* @param responseFormat the response format to set
232+
* @return the current instance of {@link ConversationRequestAlpha2}
233+
*/
234+
public ConversationRequestAlpha2 setResponseFormat(Struct responseFormat) {
218235
this.responseFormat = responseFormat;
219236
return this;
220237
}
221238

239+
public ConversationRequestAlpha2 setResponseFormat(String responseFormat) {
240+
this.responseFormat = ProtobufUtils.jsonToStruct(responseFormat);
241+
return this;
242+
}
243+
244+
/**
245+
* retention duration for the prompt cache.
246+
*
247+
* @return the prompt cache retention duration
248+
*/
222249
public Duration getPromptCacheRetention() {
223250
return promptCacheRetention;
224251
}
225252

253+
/**
254+
* Retention duration for the prompt cache.
255+
* When set, enables extended prompt caching so cached prefixes stay active longer.
256+
* With OpenAI, supports up to 24 hours.
257+
* See [OpenAI prompt caching](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention).
258+
*
259+
* @param promptCacheRetention the prompt cache retention duration
260+
* @return the current instance of {@link ConversationRequestAlpha2}
261+
*/
226262
public ConversationRequestAlpha2 setPromptCacheRetention(Duration promptCacheRetention) {
227263
this.promptCacheRetention = promptCacheRetention;
228264
return this;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2026 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.utils;
15+
16+
import com.google.protobuf.InvalidProtocolBufferException;
17+
import com.google.protobuf.Struct;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
public class ProtobufUtils {
22+
private static final Logger log = LoggerFactory.getLogger(ProtobufUtils.class);
23+
24+
/**
25+
* Converts a JSON string to a protobuf Struct.
26+
*
27+
* @param json JSON string.
28+
* @return Protobuf Struct.
29+
*/
30+
public static Struct jsonToStruct(String json) {
31+
Struct.Builder builder = Struct.newBuilder();
32+
try {
33+
com.google.protobuf.util.JsonFormat.parser()
34+
.ignoringUnknownFields() // optional
35+
.merge(json, builder);
36+
} catch (InvalidProtocolBufferException e) {
37+
log.error("Failed to parse json to protobuf struct", e);
38+
return builder.build();
39+
}
40+
return builder.build();
41+
}
42+
}

sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import io.dapr.client.domain.QueryStateRequest;
4646
import io.dapr.client.domain.QueryStateResponse;
4747
import io.dapr.client.domain.SystemMessage;
48-
import io.dapr.client.domain.TestData;
4948
import io.dapr.client.domain.ToolMessage;
5049
import io.dapr.client.domain.UnlockResponseStatus;
5150
import io.dapr.client.domain.UserMessage;
@@ -1065,9 +1064,9 @@ public void converseAlpha2ComplexRequestTest() {
10651064
Map<String, Object> parameters = new HashMap<>();
10661065
parameters.put("max_tokens", "1000");
10671066

1068-
var responseFormat = new HashMap<String, Object>();
1069-
responseFormat.put("temperature", 0.7);
1070-
responseFormat.put("data", new TestData("Peter", 40));
1067+
Struct responseFormat = Struct.newBuilder().putFields("type",
1068+
Value.newBuilder().setStringValue("text").build()).build();
1069+
10711070
ConversationRequestAlpha2 request = new ConversationRequestAlpha2("openai", List.of(input));
10721071
request.setContextId("test-context");
10731072
request.setTemperature(0.7);
@@ -1166,10 +1165,8 @@ public void converseAlpha2ComplexRequestTest() {
11661165
assertEquals("value1", capturedRequest.getMetadataMap().get("key1"));
11671166
assertEquals(1, capturedRequest.getToolsCount());
11681167
assertEquals("get_weather", capturedRequest.getTools(0).getFunction().getName());
1169-
assertEquals(Struct.newBuilder()
1170-
.putFields("temperature", Value.newBuilder().setNumberValue(0.7).build())
1171-
.putFields("data", Value.newBuilder().setStringValue("TestData{name='Peter', age=40}").build())
1172-
.build(),
1168+
assertEquals(Struct.newBuilder().putFields("type",
1169+
Value.newBuilder().setStringValue("text").build()).build(),
11731170
capturedRequest.getResponseFormat());
11741171
assertEquals(Duration.ofDays(1).getSeconds(), capturedRequest.getPromptCacheRetention().getSeconds());
11751172
assertEquals(0, capturedRequest.getPromptCacheRetention().getNanos());

0 commit comments

Comments
 (0)