Skip to content

Commit 28fbf7f

Browse files
committed
RDBC-960: address PR comments
1 parent ee65ff8 commit 28fbf7f

34 files changed

+625
-380
lines changed

src/main/java/net/ravendb/client/documents/AI/AiConversation.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import net.ravendb.client.documents.IDocumentStore;
66
import net.ravendb.client.documents.operations.AI.agents.*;
7+
import net.ravendb.client.extensions.expressionExtension;
8+
import net.ravendb.client.util.SerializableFunction;
79
import org.apache.commons.lang3.StringUtils;
810
import java.util.ArrayList;
911
import java.util.HashMap;
@@ -23,8 +25,8 @@ public class AiConversation {
2325
private String changeVector;
2426
private List<AiAgentActionRequest> actionRequests = null;
2527
private final List<AiAgentActionResponse> actionResponses = new ArrayList<>();
26-
private String userPrompt;
2728
private final Map<String, IActionInvocation> invocations = new HashMap<>();
29+
private final List<ContentPart> promptParts = new ArrayList<>();
2830

2931
private Consumer<UnhandledActionEventArgs> onUnhandledAction;
3032

@@ -88,7 +90,17 @@ public void addActionResponse(String toolId, Object actionResponse) throws JsonP
8890

8991
public void setUserPrompt(String userPrompt) {
9092
if (userPrompt == null || userPrompt.isEmpty()) throw new IllegalArgumentException("userPrompt cannot be empty");
91-
this.userPrompt = userPrompt;
93+
this.promptParts.clear();
94+
this.addUserPrompt(userPrompt);
95+
}
96+
97+
public void addUserPrompt(String... prompts) {
98+
for (String prompt : prompts) {
99+
if (prompt == null || prompt.isEmpty()) {
100+
throw new IllegalArgumentException("prompt cannot be empty");
101+
}
102+
this.promptParts.add(new TextPart(prompt));
103+
}
92104
}
93105

94106
public <TArgs> void handle(String actionName,
@@ -168,6 +180,10 @@ public void receive(String actionName, BiConsumer<AiAgentActionRequest, Object>
168180
invocations.put(actionName, invocation);
169181
}
170182

183+
public <TAnswer> CompletableFuture<AiAnswer<TAnswer>> stream(SerializableFunction<TAnswer, ?> streamProperty, AiStreamCallback streamCallback) {
184+
return stream(expressionExtension.toPropertyPath(streamProperty,this.store.getConventions()), streamCallback);
185+
}
186+
171187
public <TAnswer> CompletableFuture<AiAnswer<TAnswer>> stream(
172188
String streamPropertyPath,
173189
AiStreamCallback streamCallback
@@ -192,7 +208,7 @@ public <TAnswer> CompletableFuture<AiAnswer<TAnswer>> stream(
192208
return;
193209
}
194210

195-
if ("Done".equals(result.getStatus())) {
211+
if (result.getStatus() == AiConversationResult.Done) {
196212
future.complete(result);
197213
return;
198214
}
@@ -282,7 +298,7 @@ public <TAnswer> CompletableFuture<AiAnswer<TAnswer>> run() {
282298
}
283299

284300
private <TAnswer> CompletableFuture<AiAnswer<TAnswer>> runInternal(String streamPropertyPath, AiStreamCallback streamCallback) {
285-
if (this.actionRequests != null && this.userPrompt == null && this.actionResponses.isEmpty()) {
301+
if (this.actionRequests != null && this.promptParts.isEmpty() && this.actionResponses.isEmpty()) {
286302
AiAnswer<TAnswer> doneAnswer = new AiAnswer<>();
287303
doneAnswer.setStatus(AiConversationResult.Done);
288304
return CompletableFuture.completedFuture(doneAnswer);
@@ -291,7 +307,7 @@ private <TAnswer> CompletableFuture<AiAnswer<TAnswer>> runInternal(String stream
291307
RunConversationOperation<TAnswer> op = new RunConversationOperation<>(
292308
this.agentId,
293309
this.conversationId,
294-
this.userPrompt,
310+
this.promptParts,
295311
this.actionResponses,
296312
this.options,
297313
this.changeVector,
@@ -312,7 +328,7 @@ private <TAnswer> CompletableFuture<AiAnswer<TAnswer>> runInternal(String stream
312328
this.changeVector = result.getChangeVector();
313329
this.conversationId = result.getConversationId();
314330
this.actionRequests = result.getActionRequests() != null ? result.getActionRequests() : new ArrayList<>();
315-
this.userPrompt = null;
331+
this.promptParts.clear();
316332
this.actionResponses.clear();
317333

318334
return CompletableFuture.completedFuture(answer);

src/main/java/net/ravendb/client/documents/AI/AiConversationCreationOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ public class AiConversationCreationOptions {
1010
public AiConversationCreationOptions() {
1111
}
1212

13+
public AiConversationCreationOptions(Map<String, Object> parameters) {
14+
this(parameters, null);
15+
}
16+
1317
public AiConversationCreationOptions(Map<String, Object> parameters, Integer expirationInSec) {
1418
this.parameters = parameters;
1519
this.expirationInSec = expirationInSec;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.ravendb.client.documents.AI;
2+
3+
public class AiMessagePrompt {
4+
class AiMessagePromptFields {
5+
public static final String TEXT = "text";
6+
public static final String TYPE = "type";
7+
private AiMessagePromptFields() {}
8+
}
9+
10+
class AiMessagePromptTypes {
11+
public static final String TEXT = "text";
12+
private AiMessagePromptTypes() {}
13+
}
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package net.ravendb.client.documents.AI;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
6+
public abstract class ContentPart {
7+
8+
@JsonProperty("type")
9+
private final String type;
10+
11+
protected ContentPart(String type) {
12+
this.type = type;
13+
}
14+
15+
public String getType() {
16+
return type;
17+
}
18+
19+
public abstract ObjectNode toJson();
20+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package net.ravendb.client.documents.AI;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
6+
7+
public final class TextPart extends ContentPart {
8+
9+
@JsonProperty("text")
10+
private String text;
11+
12+
public TextPart(String text) {
13+
super(AiMessagePrompt.AiMessagePromptTypes.TEXT);
14+
this.text = text;
15+
}
16+
17+
public String getText() {
18+
return text;
19+
}
20+
21+
public void setText(String text) {
22+
this.text = text;
23+
}
24+
25+
@Override
26+
public ObjectNode toJson() {
27+
ObjectNode json = JsonNodeFactory.instance.objectNode();
28+
json.put(AiMessagePrompt.AiMessagePromptFields.TYPE, getType());
29+
json.put(AiMessagePrompt.AiMessagePromptFields.TEXT, getText());
30+
return json;
31+
}
32+
}
33+

src/main/java/net/ravendb/client/documents/commands/RunConversationCommand.java

Lines changed: 60 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,135 +2,137 @@
22

33
import com.fasterxml.jackson.core.JsonGenerator;
44
import com.fasterxml.jackson.core.type.TypeReference;
5-
import com.fasterxml.jackson.databind.MapperFeature;
65
import com.fasterxml.jackson.databind.ObjectMapper;
76
import com.fasterxml.jackson.databind.node.ObjectNode;
87
import net.ravendb.client.documents.conventions.DocumentConventions;
9-
import net.ravendb.client.documents.AI.AiStreamCallback;
10-
import net.ravendb.client.documents.operations.AI.agents.AiAgentActionResponse;
11-
import net.ravendb.client.documents.AI.AiConversationCreationOptions;
128
import net.ravendb.client.documents.operations.AI.agents.ConversationResult;
9+
import net.ravendb.client.documents.operations.AI.agents.RunConversationOperation;
1310
import net.ravendb.client.http.IRaftCommand;
1411
import net.ravendb.client.http.RavenCommand;
1512
import net.ravendb.client.http.RavenCommandResponseType;
1613
import net.ravendb.client.http.ServerNode;
1714
import net.ravendb.client.json.ContentProviderHttpEntity;
18-
import net.ravendb.client.util.RaftIdGenerator;
1915
import net.ravendb.client.util.UrlUtils;
2016
import org.apache.hc.client5.http.classic.methods.HttpPost;
2117
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
18+
import org.apache.hc.core5.http.ClassicHttpResponse;
2219
import org.apache.hc.core5.http.ContentType;
2320
import java.io.*;
2421
import java.nio.charset.StandardCharsets;
25-
import java.util.List;
22+
import java.util.UUID;
2623
import java.util.concurrent.CompletableFuture;
2724
import java.util.concurrent.CompletionException;
25+
import java.util.concurrent.ExecutionException;
2826
import java.util.stream.Collectors;
27+
import static net.ravendb.client.extensions.JsonExtensions.createDefaultJsonSerializer;
28+
2929

3030
public class RunConversationCommand<TAnswer>
3131
extends RavenCommand<ConversationResult<TAnswer>>
3232
implements IRaftCommand {
3333

34-
private final String conversationId;
35-
private final String agentId;
36-
private final String prompt;
37-
private final List<AiAgentActionResponse> actionResponses;
38-
private final AiConversationCreationOptions options;
39-
private final String changeVector;
40-
private final String streamPropertyPath;
41-
private final AiStreamCallback streamCallback;
34+
private final RunConversationOperation<TAnswer> parent;
4235
private final DocumentConventions conventions;
4336
private String raftId;
4437

45-
public RunConversationCommand(
46-
String conversationId,
47-
String agentId,
48-
String prompt,
49-
List<AiAgentActionResponse> actionResponses,
50-
AiConversationCreationOptions options,
51-
String changeVector,
52-
DocumentConventions conventions,
53-
String streamPropertyPath,
54-
AiStreamCallback streamCallback){
38+
public RunConversationCommand(RunConversationOperation<TAnswer> parent, DocumentConventions conventions) {
5539
super((Class<ConversationResult<TAnswer>>) (Class<?>) ConversationResult.class);
56-
this.conversationId = conversationId;
57-
this.agentId = agentId;
58-
this.prompt = prompt;
59-
this.actionResponses = actionResponses;
60-
this.options = options;
61-
this.changeVector = changeVector;
62-
this.streamPropertyPath = streamPropertyPath;
63-
this.streamCallback = streamCallback;
6440
this.conventions = conventions;
41+
this.parent = parent;
6542

66-
if (this.streamPropertyPath != null && this.streamCallback != null) {
43+
if (parent.getStreamPropertyPath() != null)
6744
this.responseType = RavenCommandResponseType.RAW;
68-
}
69-
70-
if (conversationId != null && conversationId.endsWith("|")) {
71-
this.raftId = RaftIdGenerator.newId();
72-
}
7345
}
7446

7547
@Override
7648
public boolean isReadRequest() {
7749
return false;
7850
}
7951

80-
@Override
81-
public String getRaftUniqueRequestId() {
82-
return raftId;
83-
}
84-
8552
@Override
8653
public HttpUriRequestBase createRequest(ServerNode node) {
8754
StringBuilder uriBuilder = new StringBuilder();
8855
uriBuilder.append(node.getUrl())
8956
.append("/databases/")
9057
.append(node.getDatabase())
9158
.append("/ai/agent?")
92-
.append("conversationId=").append(UrlUtils.escapeDataString(this.conversationId))
93-
.append("&agentId=").append(UrlUtils.escapeDataString(this.agentId));
59+
.append("conversationId=").append(UrlUtils.escapeDataString(this.parent.getConversationId()))
60+
.append("&agentId=").append(UrlUtils.escapeDataString(this.parent.getAgentId()));
61+
62+
if (this.parent.getConversationId().charAt(this.parent.getConversationId().length() - 1) == '|') {
63+
this.raftId = UUID.randomUUID().toString();
64+
}
9465

95-
if (this.changeVector != null && !this.changeVector.isEmpty()) {
96-
uriBuilder.append("&changeVector=").append(UrlUtils.escapeDataString(this.changeVector));
66+
if (this.parent.getChangeVector() != null && !this.parent.getChangeVector().isEmpty()) {
67+
uriBuilder.append("&changeVector=").append(UrlUtils.escapeDataString(this.parent.getChangeVector()));
9768
}
98-
if (this.streamPropertyPath != null) {
99-
uriBuilder.append("&streamPropertyPath=").append(UrlUtils.escapeDataString(this.streamPropertyPath));
100-
uriBuilder.append("&streaming=").append(UrlUtils.escapeDataString("true"));
69+
if (this.parent.getStreamPropertyPath() != null) {
70+
uriBuilder.append("&streamPropertyPath=").append(UrlUtils.escapeDataString(this.parent.getStreamPropertyPath()));
71+
uriBuilder.append("&streaming=true");
10172
}
10273

10374
HttpPost request = new HttpPost(uriBuilder.toString());
10475

10576
request.setEntity(new ContentProviderHttpEntity(outputStream -> {
10677
try (JsonGenerator generator = createSafeJsonGenerator(outputStream)) {
10778
ObjectNode bodyObj = mapper.createObjectNode();
108-
bodyObj.set("ActionResponses", mapper.valueToTree(this.actionResponses));
109-
bodyObj.put("UserPrompt", this.prompt);
110-
bodyObj.set("CreationOptions", mapper.valueToTree(this.options));
79+
bodyObj.set("ActionResponses", mapper.valueToTree(this.parent.getActionResponses()));
80+
bodyObj.set("UserPrompt", mapper.valueToTree(this.parent.getPromptParts()));
81+
bodyObj.set("CreationOptions", mapper.valueToTree(this.parent.getOptions()));
11182
generator.writeTree(bodyObj);
11283
}
11384
}, ContentType.APPLICATION_JSON,conventions));
11485

11586
return request;
11687
}
11788

89+
@Override
90+
public String getRaftUniqueRequestId() {
91+
return raftId;
92+
}
93+
11894
@Override
11995
public CompletableFuture<String> setResponseAsync(InputStream bodyStream, boolean fromCache) {
12096
if (bodyStream == null ) {
12197
this.throwInvalidResponse();
12298
}
12399

124-
if (this.streamPropertyPath != null && this.streamCallback != null) {
100+
if (this.parent.getStreamPropertyPath() != null && this.parent.getStreamCallback() != null) {
125101
return processStreamingResponse(bodyStream);
126102
}
127103
return this.parseResponseDefaultAsync(bodyStream);
128104
}
129105

106+
@Override
107+
public void setResponseRaw(ClassicHttpResponse response, InputStream stream) throws IOException {
108+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) {
109+
String line;
110+
while ((line = reader.readLine()) != null) {
111+
line = line.trim();
112+
113+
if (line.startsWith("{")) {
114+
this.result = mapper.readValue(line, ConversationResult.class);
115+
break;
116+
}
117+
118+
String unescaped = mapper.readValue(line, String.class);
119+
120+
if (this.parent.getStreamCallback() != null) {
121+
this.parent.getStreamCallback().onChunk(unescaped).get();
122+
}
123+
}
124+
} catch (IOException e) {
125+
throw new RuntimeException("Failed to read conversation stream", e);
126+
} catch (ExecutionException e) {
127+
throw new RuntimeException(e);
128+
} catch (InterruptedException e) {
129+
throw new RuntimeException(e);
130+
}
131+
}
132+
130133
@Override
131134
public void setResponse(String response, boolean fromCache) throws IOException {
132-
ObjectMapper mapper = new ObjectMapper();
133-
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
135+
ObjectMapper mapper = createDefaultJsonSerializer();
134136
this.result = mapper.readValue(response, ConversationResult.class);
135137
}
136138

@@ -169,9 +171,9 @@ private CompletableFuture<String> processStreamingResponse(InputStream bodyStrea
169171
} else {
170172
chunk = new ObjectMapper().writeValueAsString(parsed);
171173
}
172-
streamCallback.onChunk(chunk).get();
174+
this.parent.getStreamCallback().onChunk(chunk).get();
173175
} catch (Exception e) {
174-
streamCallback.onChunk(line).get();
176+
this.parent.getStreamCallback().onChunk(line).get();
175177
}
176178
}
177179

src/main/java/net/ravendb/client/documents/operations/AI/AbstractAiSettings.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.ravendb.client.documents.operations.AI;
22

3+
import java.util.EnumSet;
34
import java.util.List;
45

56
/**
@@ -26,13 +27,13 @@ public void setEmbeddingsMaxConcurrentBatches(Integer embeddingsMaxConcurrentBat
2627
*
2728
* @param errors List to collect validation error messages.
2829
*/
29-
public abstract void validate(List<String> errors);
30+
public abstract void validateFields(List<String> errors);
3031

3132
/**
3233
* Compares this settings instance with another to detect differences.
3334
*
3435
* @param other The other settings instance to compare with.
3536
* @return Flags indicating which settings differ.
3637
*/
37-
public abstract AiSettingsCompareDifferences compare(AbstractAiSettings other);
38+
public abstract EnumSet<AiSettingsCompareDifferences> compare(AbstractAiSettings other);
3839
}

0 commit comments

Comments
 (0)