Skip to content

Commit 11e8b8e

Browse files
committed
RDBC-959: sync with 7.1.3 -> 7.1.4 diff related to AI
1 parent ad3fe10 commit 11e8b8e

File tree

8 files changed

+260
-40
lines changed

8 files changed

+260
-40
lines changed

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

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,46 @@
11
package net.ravendb.client.documents.AI;
22

3+
import net.ravendb.client.documents.operations.AI.AiUsage;
4+
import java.time.Duration;
5+
36
public class AiAnswer<TAnswer> {
7+
/**
8+
* The answer content produced by the AI.
9+
*/
410
private TAnswer answer;
11+
12+
/**
13+
* The status of the conversation.
14+
*/
515
private AiConversationResult status;
616

17+
/**
18+
* Token usage reported by the model for generating this answer (prompt/completion/total).
19+
*/
20+
private AiUsage usage;
21+
22+
/**
23+
* The total time elapsed to produce the answer(measured from the server's request to the LLM until the response was received).
24+
*/
25+
private Duration elapsed;
26+
727
public AiAnswer() {
828
}
929

10-
public AiAnswer(TAnswer answer, AiConversationResult status) {
11-
this.answer = answer;
12-
this.status = status;
30+
public AiUsage getUsage() {
31+
return usage;
32+
}
33+
34+
public void setUsage(AiUsage usage) {
35+
this.usage = usage;
36+
}
37+
38+
public Duration getElapsed() {
39+
return elapsed;
40+
}
41+
42+
public void setElapsed(Duration elapsed) {
43+
this.elapsed = elapsed;
1344
}
1445

1546
public TAnswer getAnswer() {

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

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
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.exceptions.ConcurrencyException;
78
import net.ravendb.client.extensions.expressionExtension;
89
import net.ravendb.client.util.SerializableFunction;
910
import org.apache.commons.lang3.StringUtils;
@@ -298,40 +299,47 @@ public <TAnswer> CompletableFuture<AiAnswer<TAnswer>> run() {
298299
}
299300

300301
private <TAnswer> CompletableFuture<AiAnswer<TAnswer>> runInternal(String streamPropertyPath, AiStreamCallback streamCallback) {
301-
if (this.actionRequests != null && this.promptParts.isEmpty() && this.actionResponses.isEmpty()) {
302-
AiAnswer<TAnswer> doneAnswer = new AiAnswer<>();
303-
doneAnswer.setStatus(AiConversationResult.Done);
304-
return CompletableFuture.completedFuture(doneAnswer);
305-
}
306-
307-
RunConversationOperation<TAnswer> op = new RunConversationOperation<>(
308-
this.agentId,
309-
this.conversationId,
310-
this.promptParts,
311-
this.actionResponses,
312-
this.options,
313-
this.changeVector,
314-
streamPropertyPath,
315-
streamCallback
316-
);
317-
318-
ConversationResult<TAnswer> result = this.store.maintenance()
319-
.forDatabase(this.databaseName)
320-
.send(op);
321-
322-
AiAnswer<TAnswer> answer = new AiAnswer<>();
323-
answer.setAnswer(result.getResponse());
324-
answer.setStatus(result.getActionRequests() == null || result.getActionRequests().isEmpty()
325-
? AiConversationResult.Done
326-
: AiConversationResult.ActionRequired);
327-
328-
this.changeVector = result.getChangeVector();
329-
this.conversationId = result.getConversationId();
330-
this.actionRequests = result.getActionRequests() != null ? result.getActionRequests() : new ArrayList<>();
331-
this.promptParts.clear();
332-
this.actionResponses.clear();
302+
try {
303+
if (this.actionRequests != null && this.promptParts.isEmpty() && this.actionResponses.isEmpty()) {
304+
AiAnswer<TAnswer> doneAnswer = new AiAnswer<>();
305+
doneAnswer.setStatus(AiConversationResult.Done);
306+
return CompletableFuture.completedFuture(doneAnswer);
307+
}
333308

334-
return CompletableFuture.completedFuture(answer);
309+
RunConversationOperation<TAnswer> op = new RunConversationOperation<>(
310+
this.agentId,
311+
this.conversationId,
312+
this.promptParts,
313+
this.actionResponses,
314+
this.options,
315+
this.changeVector,
316+
streamPropertyPath,
317+
streamCallback
318+
);
319+
320+
ConversationResult<TAnswer> result = this.store.maintenance()
321+
.forDatabase(this.databaseName)
322+
.send(op);
323+
this.changeVector = result.getChangeVector();
324+
this.conversationId = result.getConversationId();
325+
this.actionRequests = result.getActionRequests() != null ? result.getActionRequests() : new ArrayList<>();
326+
327+
AiAnswer<TAnswer> answer = new AiAnswer<>();
328+
answer.setAnswer(result.getResponse());
329+
answer.setStatus(result.getActionRequests() == null || result.getActionRequests().isEmpty()
330+
? AiConversationResult.Done
331+
: AiConversationResult.ActionRequired);
332+
answer.setUsage(result.getUsage());
333+
answer.setElapsed(result.getElapsed());
334+
return CompletableFuture.completedFuture(answer);
335+
} catch (ConcurrencyException e) {
336+
this.changeVector = e.getActualChangeVector();
337+
throw e;
338+
}
339+
finally {
340+
this.promptParts.clear();
341+
this.actionResponses.clear();
342+
}
335343
}
336344

337345
private Object parseArgs(String argsJson) {

src/main/java/net/ravendb/client/documents/changes/AbstractDatabaseChanges.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public abstract class AbstractDatabaseChanges<TDatabaseConnectionState extends A
5959
private final CancellationTokenSource _cts;
6060
private CompletableFuture<AbstractDatabaseChanges<TDatabaseConnectionState>> _tcs;
6161

62-
protected final ConcurrentMap<Integer, CompletableFuture<Void>> _confirmations = new ConcurrentHashMap<>();
62+
protected final ConcurrentMap<Integer, CompletableFuture> _confirmations = new ConcurrentHashMap<>();
6363

6464
protected final ConcurrentMap<DatabaseChangesOptions, DatabaseConnectionState> _states = new ConcurrentHashMap<>();
6565

src/main/java/net/ravendb/client/documents/changes/AbstractDatabaseConnectionState.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package net.ravendb.client.documents.changes;
22

33
import net.ravendb.client.primitives.EventHelper;
4-
54
import java.util.ArrayList;
65
import java.util.List;
6+
import java.util.concurrent.CompletableFuture;
77
import java.util.concurrent.atomic.AtomicInteger;
88
import java.util.function.Consumer;
99

@@ -14,9 +14,24 @@ public class AbstractDatabaseConnectionState {
1414
private final Runnable _onDisconnect;
1515
public final Runnable onConnect;
1616

17-
private final AtomicInteger _value = new AtomicInteger();
17+
private final AtomicInteger _value = new AtomicInteger(0);
1818
public Exception lastException;
1919

20+
private final CompletableFuture<Void> firstSet = new CompletableFuture<>();
21+
private CompletableFuture<Void> connected;
22+
23+
public void set(CompletableFuture<Void> connection) {
24+
if (!firstSet.isDone()) {
25+
connection.whenComplete((res, ex) -> {
26+
if (ex != null) {
27+
firstSet.completeExceptionally(ex);
28+
} else {
29+
firstSet.complete(null);
30+
}
31+
});
32+
}
33+
connected = connection;
34+
}
2035

2136
public void addOnError(Consumer<Exception> handler) {
2237
this.onError.add(handler);
@@ -32,7 +47,6 @@ protected AbstractDatabaseConnectionState(Runnable onConnect, Runnable onDisconn
3247
_value.set(0);
3348
}
3449

35-
3650
public void inc() {
3751
_value.incrementAndGet();
3852
}

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

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

3+
import com.fasterxml.jackson.databind.JsonNode;
4+
35
public class AiUsage {
46
private int promptTokens;
57
private int completionTokens;
@@ -53,5 +55,40 @@ public int getCachedTokens() {
5355
public void setCachedTokens(int cachedTokens) {
5456
this.cachedTokens = cachedTokens;
5557
}
58+
59+
void updateFrom(JsonNode json) {
60+
if (json.has("prompt_tokens")) {
61+
this.promptTokens += json.get("prompt_tokens").asInt();
62+
}
63+
if (json.has("completion_tokens")) {
64+
this.completionTokens += json.get("completion_tokens").asInt();
65+
}
66+
if (json.has("total_tokens")) {
67+
this.totalTokens += json.get("total_tokens").asInt();
68+
}
69+
70+
JsonNode promptDetails = json.get("prompt_tokens_details");
71+
if (promptDetails != null && promptDetails.has("cached_tokens")) {
72+
this.cachedTokens += promptDetails.get("cached_tokens").asInt();
73+
}
74+
75+
JsonNode completionTokensDetails = json.get("completion_tokens_details");
76+
if (completionTokensDetails != null && completionTokensDetails.has("reasoning_tokens")) {
77+
this.reasoningTokens += completionTokensDetails.get("reasoning_tokens").asInt();
78+
}
79+
}
80+
81+
static AiUsage getUsageDifference(AiUsage current, AiUsage previous) {
82+
int previousTotalWithoutReasoning = (previous.getCompletionTokens() - previous.getReasoningTokens() + previous.getPromptTokens());
83+
84+
AiUsage result = new AiUsage();
85+
result.setPromptTokens(Math.max(current.getPromptTokens() - previousTotalWithoutReasoning, 0));
86+
result.setTotalTokens(Math.max(current.getTotalTokens() - previousTotalWithoutReasoning, 0));
87+
result.setCachedTokens(current.getCachedTokens()); // keep as-is
88+
result.setCompletionTokens(current.getCompletionTokens()); // keep as-is
89+
result.setReasoningTokens(current.getReasoningTokens()); // keep as-is
90+
91+
return result;
92+
}
5693
}
5794

src/main/java/net/ravendb/client/documents/operations/AI/agents/AiAgentParameter.java

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

3+
import java.util.HashMap;
4+
import java.util.Map;
5+
36
public class AiAgentParameter {
47
private String name;
58
private String description;
@@ -20,6 +23,19 @@ public AiAgentParameter(String name, String description) {
2023
this.description = description;
2124
}
2225

26+
/**
27+
* Initializes a new agent parameter and controls whether its value should be sent to the LLM.
28+
* Use this constructor when you need to explicitly hide sensitive values
29+
* (e.g., userId, tenant, or company) from the model.
30+
*
31+
* @param name the parameter name; cannot be null or empty.
32+
* @param description a human-readable description; may be null or empty when using this constructor.
33+
* @param sendToModel when {@code false}, the parameter is hidden from the model
34+
* (it will not be included in prompts/echo messages).
35+
* When {@code true}, the parameter is exposed to the model.
36+
* If this constructor is not called, the default is {@code null}
37+
* (treated as exposed).
38+
*/
2339
public AiAgentParameter(String name, String description, Boolean sendToModel) {
2440
this(name, description);
2541
this.sendToModel = sendToModel;
@@ -48,5 +64,13 @@ public String getDescription() {
4864
public void setDescription(String description) {
4965
this.description = description;
5066
}
67+
68+
public Map<String, Object> toJson() {
69+
Map<String, Object> json = new HashMap<>();
70+
json.put("Name", this.name);
71+
json.put("Description", this.description);
72+
json.put("SendToModel", this.sendToModel);
73+
return json;
74+
}
5175
}
5276

src/main/java/net/ravendb/client/documents/operations/AI/agents/ConversationResult.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package net.ravendb.client.documents.operations.AI.agents;
22

3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import net.ravendb.client.documents.operations.AI.AiUsage;
46
import java.time.Duration;
7+
import java.util.ArrayList;
58
import java.util.List;
69

710
public class ConversationResult<TAnswer> {
@@ -16,6 +19,45 @@ public class ConversationResult<TAnswer> {
1619
public ConversationResult() {
1720
}
1821

22+
public static <TAnswer> ConversationResult<TAnswer> convert(
23+
JsonNode response,
24+
Class<TAnswer> answerClass
25+
) throws Exception {
26+
ObjectMapper mapper = new ObjectMapper();
27+
28+
JsonNode totalUsageNode = response.get("TotalUsage");
29+
JsonNode resultNode = response.get("Response");
30+
String conversationId = response.has("ConversationId") ? response.get("ConversationId").asText() : null;
31+
String changeVector = response.has("ChangeVector") ? response.get("ChangeVector").asText() : null;
32+
JsonNode usageNode = response.get("Usage");
33+
Duration elapsed = response.has("Elapsed") ? Duration.parse(response.get("Elapsed").asText()) : null;
34+
35+
List<AiAgentActionRequest> requests = null;
36+
if (response.has("ActionRequests") && response.get("ActionRequests").isArray()) {
37+
requests = new ArrayList<>();
38+
for (JsonNode actionRequestNode : response.get("ActionRequests")) {
39+
AiAgentActionRequest r = mapper.treeToValue(actionRequestNode, AiAgentActionRequest.class);
40+
requests.add(r);
41+
}
42+
}
43+
44+
TAnswer answer = null;
45+
if (resultNode != null && !resultNode.isNull()) {
46+
answer = mapper.treeToValue(resultNode, answerClass);
47+
}
48+
49+
ConversationResult<TAnswer> result = new ConversationResult<>();
50+
result.setConversationId(conversationId);
51+
result.setChangeVector(changeVector);
52+
result.setActionRequests(requests);
53+
result.setTotalUsage(totalUsageNode != null ? mapper.treeToValue(totalUsageNode, AiUsage.class) : null);
54+
result.setResponse(answer);
55+
result.setUsage(usageNode != null ? mapper.treeToValue(usageNode, AiUsage.class) : null);
56+
result.setElapsed(elapsed);
57+
58+
return result;
59+
}
60+
1961
public String getConversationId() {
2062
return conversationId;
2163
}

0 commit comments

Comments
 (0)