Skip to content

Commit d84e9ad

Browse files
committed
Add ToolCall y Stream support to Agent Object
Add logging calling saia.
1 parent a7fcc6d commit d84e9ad

File tree

5 files changed

+132
-16
lines changed

5 files changed

+132
-16
lines changed

java/src/main/java/com/genexus/GXProcedure.java

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import java.util.ArrayList;
66
import java.util.Date;
77

8+
import com.fasterxml.jackson.databind.ObjectMapper;
89
import com.genexus.db.Namespace;
910
import com.genexus.db.UserInformation;
1011
import com.genexus.diagnostics.GXDebugInfo;
1112
import com.genexus.diagnostics.GXDebugManager;
13+
import com.genexus.internet.HttpClient;
1214
import com.genexus.internet.HttpContext;
1315
import com.genexus.mock.GXMockProvider;
1416
import com.genexus.performance.ProcedureInfo;
@@ -17,6 +19,7 @@
1719
import com.genexus.util.saia.OpenAIRequest;
1820
import com.genexus.util.saia.OpenAIResponse;
1921
import com.genexus.util.saia.SaiaService;
22+
import org.json.JSONObject;
2023

2124
public abstract class GXProcedure implements IErrorHandler, ISubmitteable {
2225
public abstract void initialize();
@@ -32,7 +35,8 @@ public abstract class GXProcedure implements IErrorHandler, ISubmitteable {
3235
UserInformation ui=null;
3336

3437
private Date beginExecute;
35-
38+
private HttpClient client;
39+
3640
public static final int IN_NEW_UTL = -2;
3741

3842
public GXProcedure(int remoteHandle, ModelContext context, String location) {
@@ -271,29 +275,42 @@ protected String callAssistant(String agent, GXProperties properties, ArrayList<
271275
}
272276

273277
protected String callAgent(String agent, GXProperties properties, ArrayList<OpenAIResponse.Message> messages, CallResult result) {
278+
return callAgent(agent, false, properties, messages, result);
279+
}
280+
281+
protected String callAgent(String agent, boolean stream, GXProperties properties, ArrayList<OpenAIResponse.Message> messages, CallResult result) {
274282
OpenAIRequest aiRequest = new OpenAIRequest();
275283
aiRequest.setModel(String.format("saia:agent:%s", agent));
276284
if (!messages.isEmpty())
277285
aiRequest.setMessages(messages);
278286
aiRequest.setVariables(properties.getList());
279-
OpenAIResponse aiResponse = SaiaService.call(aiRequest, result);
287+
if (stream)
288+
aiRequest.setStream(true);
289+
client = new HttpClient();
290+
OpenAIResponse aiResponse = SaiaService.call(aiRequest, client, result);
280291
if (aiResponse != null) {
281292
for (OpenAIResponse.Choice element : aiResponse.getChoices()) {
282293
String finishReason = element.getFinishReason();
283294
if (finishReason.equals("stop"))
284295
return element.getMessage().getContent();
285296
if (finishReason.equals("tool_calls")) {
286297
messages.add(element.getMessage());
287-
for (OpenAIResponse.ToolCall tollCall :element.getMessage().getToolCalls()) {
288-
processToolCall(tollCall, messages);
289-
}
290-
return callAgent(agent, properties, messages, result);
298+
return processNotChunkedResponse(agent, stream, properties, messages, result, element.getMessage().getToolCalls());
291299
}
292300
}
301+
} else if (client.getStatusCode() == 200) {
302+
return readChunk(agent, properties, messages, result);
293303
}
294304
return "";
295305
}
296306

307+
private String processNotChunkedResponse(String agent, boolean stream, GXProperties properties, ArrayList<OpenAIResponse.Message> messages, CallResult result, ArrayList<OpenAIResponse.ToolCall> toolCalls) {
308+
for (OpenAIResponse.ToolCall tollCall : toolCalls) {
309+
processToolCall(tollCall, messages);
310+
}
311+
return callAgent(agent, stream, properties, messages, result);
312+
}
313+
297314
private void processToolCall(OpenAIResponse.ToolCall toolCall, ArrayList<OpenAIResponse.Message> messages) {
298315
String result;
299316
String functionName = toolCall.getFunction().getName();
@@ -309,4 +326,36 @@ private void processToolCall(OpenAIResponse.ToolCall toolCall, ArrayList<OpenAIR
309326
toolCallMessage.setToolCallId(toolCall.getId());
310327
messages.add(toolCallMessage);
311328
}
329+
330+
protected String readChunk() {
331+
return readChunk(null, null, null, null);
332+
}
333+
334+
protected String readChunk(String agent, GXProperties properties, ArrayList<OpenAIResponse.Message> messages, CallResult result) {
335+
String data = client.readChunk();
336+
if (data.isEmpty())
337+
return "";
338+
int index = data.indexOf("data:") + "data:".length();
339+
String chunkJson = data.substring(index).trim();
340+
try {
341+
JSONObject jsonResponse = new JSONObject(chunkJson);
342+
OpenAIResponse chunkResponse = new ObjectMapper().readValue(jsonResponse.toString(), OpenAIResponse.class);
343+
OpenAIResponse.Choice choise = chunkResponse.getChoices().get(0);
344+
if (choise.getFinishReason() != null && choise.getFinishReason().equals("tool_calls") && agent != null) {
345+
messages.add(choise.getMessage());
346+
return processNotChunkedResponse(agent, true, properties, messages, result, choise.getMessage().getToolCalls());
347+
}
348+
String chunkString = choise.getDelta().getContent();
349+
if (chunkString == null)
350+
return "";
351+
return chunkString;
352+
}
353+
catch (Exception e) {
354+
return "";
355+
}
356+
}
357+
358+
protected boolean isStreamEOF() {
359+
return client.getEof();
360+
}
312361
}

java/src/main/java/com/genexus/util/saia/OpenAIResponse.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public static class Choice {
5959
@JsonProperty("message")
6060
private Message message;
6161

62+
@JsonProperty("delta")
63+
private Message delta;
64+
6265
@JsonProperty("finish_reason")
6366
private String finishReason;
6467

@@ -68,6 +71,9 @@ public static class Choice {
6871
public Message getMessage() { return message; }
6972
public void setMessage(Message message) { this.message = message; }
7073

74+
public Message getDelta() { return message; }
75+
public void setDelta(Message message) { this.message = message; }
76+
7177
public String getFinishReason() { return finishReason; }
7278
public void setFinishReason(String finishReason) { this.finishReason = finishReason; }
7379
}

java/src/main/java/com/genexus/util/saia/SaiaService.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,31 @@
66
import com.genexus.diagnostics.core.ILogger;
77
import com.genexus.diagnostics.core.LogManager;
88
import com.genexus.internet.HttpClient;
9-
import com.genexus.util.CallResult;
109
import org.json.JSONObject;
10+
import com.genexus.util.CallResult;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
1113

1214
public class SaiaService {
1315
private static final ILogger logger = LogManager.getLogger(SaiaService.class);
1416
private static final String apiKey = (String) SpecificImplementation.Application.getProperty("AI_PROVIDER_API_KEY", "");;
1517
private static final String aiProvider = (String) SpecificImplementation.Application.getProperty("AI_PROVIDER", "");
18+
private static final Logger log = LoggerFactory.getLogger(SaiaService.class);
1619

17-
public static OpenAIResponse call(OpenAIRequest request, CallResult result) {
18-
return call(request, false, result);
20+
public static OpenAIResponse call(OpenAIRequest request, HttpClient client, CallResult result) {
21+
return call(request, false, client, result);
1922
}
2023

2124
public static OpenAIResponse call(OpenAIRequest request, boolean isEmbedding, CallResult result) {
25+
return call(request, isEmbedding, new HttpClient(), result);
26+
}
27+
28+
public static OpenAIResponse call(OpenAIRequest request, boolean isEmbedding, HttpClient client, CallResult result) {
2229
try {
2330
String jsonRequest = new ObjectMapper().writeValueAsString(request);
31+
logger.debug("Agent payload: " + jsonRequest);
2432
String providerURL = aiProvider + "/chat";;
2533

26-
HttpClient client = new HttpClient();
2734
client.setSecure(1);
2835
client.addHeader("Content-Type", "application/json");
2936
client.addHeader("Authorization", "Bearer " + apiKey);
@@ -35,15 +42,23 @@ public static OpenAIResponse call(OpenAIRequest request, boolean isEmbedding, Ca
3542
client.addString(jsonRequest);
3643
client.execute("POST", providerURL);
3744
if (client.getStatusCode() == 200) {
38-
JSONObject jsonResponse = new JSONObject(client.getString());
39-
return new ObjectMapper().readValue(jsonResponse.toString(), OpenAIResponse.class);
45+
if (client.getHeader("Content-Type").contains("text/event-stream")){
46+
return null;
47+
}
48+
else {
49+
String saiaResponse = client.getString();
50+
logger.debug("Agent response: " + saiaResponse);
51+
JSONObject jsonResponse = new JSONObject(saiaResponse);
52+
return new ObjectMapper().readValue(jsonResponse.toString(), OpenAIResponse.class);
53+
}
4054
}
4155
else {
4256
String errorDescription = String.format("Error calling Enterprise AI API, StatusCode: %d, ReasonLine: %s",
4357
client.getStatusCode(),
4458
client.getReasonLine());
4559
addResultMessage("SAIA_ERROR_CALL", (byte)1, errorDescription, result);
4660
logger.error(errorDescription);
61+
logger.debug("Agent error response: " + client.getString());
4762
}
4863
}
4964
catch (Exception e) {

java/src/test/java/com/genexus/agent/Agent.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.genexus.*;
44
import com.genexus.util.CallResult;
55
import com.genexus.util.saia.OpenAIResponse;
6-
76
import java.util.ArrayList;
87

98
public final class Agent extends GXProcedure
@@ -47,22 +46,60 @@ protected void privateExecute( )
4746
message.setRole("user");
4847
message.setContent("Que me puedes contar de la ciudad que te pedi el clima previamente?");
4948
messages.add(message);
49+
AV5OutputVariable = callAgent( "The weatherman", Gxproperties, messages, new CallResult()) ;
50+
}
51+
else if (AV3Parameter1.equals("chat_stream")) {
52+
OpenAIResponse.Message message = new OpenAIResponse.Message();
53+
message.setRole("user");
54+
message.setContent("Dime el clima en Lima - Peru");
55+
messages.add(message);
56+
message = new OpenAIResponse.Message();
57+
message.setRole("assistant");
58+
message.setContent("El clima actual en Lima, Perú, es soleado con una temperatura de 20.9°C (69.6°F). La dirección del viento es del suroeste (SSW) a 15.1 km/h (9.4 mph), y la humedad relativa es del 68%. La presión atmosférica es de 1013 mb. La visibilidad es de 10 km y el índice UV es de 12.5.");
59+
messages.add(message);
60+
message = new OpenAIResponse.Message();
61+
message.setRole("user");
62+
message.setContent("Que me puedes contar de la ciudad que te pedi el clima previamente?");
63+
messages.add(message);
64+
AV5OutputVariable = callAgent( "The weatherman", true, Gxproperties, messages, new CallResult()) ;
65+
System.out.print(AV5OutputVariable);
66+
while (!isStreamEOF()) {
67+
System.out.print(readChunk());
68+
}
5069
}
5170
else if (AV3Parameter1.equals("toolcall")) {
5271
OpenAIResponse.Message message = new OpenAIResponse.Message();
5372
message.setRole("user");
5473
message.setContent("Necesito nombre y descripcion del producto 1779");
5574
messages.add(message);
5675
AV5OutputVariable = callAgent( "ProductInfo", Gxproperties, messages, new CallResult()) ;
57-
cleanup();
58-
return;
76+
message = new OpenAIResponse.Message();
77+
message.setRole("assistant");
78+
message.setContent(AV5OutputVariable);
79+
messages.add(message);
80+
message = new OpenAIResponse.Message();
81+
message.setRole("user");
82+
message.setContent("Quiero que traduzcas la descripcion del producto que me habias enviado previamente");
83+
messages.add(message);
84+
AV5OutputVariable = callAgent( "ProductInfo", Gxproperties, messages, new CallResult()) ;
85+
}
86+
else if (AV3Parameter1.equals("toolcall_stream")) {
87+
OpenAIResponse.Message message = new OpenAIResponse.Message();
88+
message.setRole("user");
89+
message.setContent("Necesito nombre y descripcion del producto 1779");
90+
messages.add(message);
91+
AV5OutputVariable = callAgent( "ProductInfo", true, Gxproperties, messages, new CallResult()) ;
92+
System.out.print(AV5OutputVariable);
93+
while (!isStreamEOF()) {
94+
System.out.print(readChunk());
95+
}
5996
}
6097
else {
6198
Gxproperties.set("&Parameter1", AV3Parameter1);
6299
Gxproperties.set("&Parameter2", AV4Parameter2);
63100
Gxproperties.set("$context", "Los Angeles");
101+
AV5OutputVariable = callAgent( "The weatherman", Gxproperties, messages, new CallResult()) ;
64102
}
65-
AV5OutputVariable = callAgent( "The weatherman", Gxproperties, messages, new CallResult()) ;
66103
cleanup();
67104
}
68105

@@ -73,6 +110,8 @@ protected void cleanup( )
73110

74111
protected String callTool(String name, String arguments) throws Exception {
75112
switch (name) {
113+
case "TranslateDescription":
114+
return "The Panavox Television 80 inches are wonderful";
76115
case "GetProductName":
77116
return "Televisor Panavox 80 pulgadas";
78117
case "GetProductDescription":

java/src/test/java/com/genexus/agent/TestAgent.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@ public void testAPICallAgent() {
2626
new Agent(-1).execute( "chat", "", GXv_char5) ;
2727
System.out.println(GXv_char5[0]);
2828

29+
String[] GXv_char8 = new String[1] ;
30+
new Agent(-1).execute( "chat_stream", "", GXv_char8) ;
31+
2932
String[] GXv_char6 = new String[1] ;
3033
new Agent(-1).execute( "toolcall", "", GXv_char6) ;
34+
System.out.println();
3135
System.out.println(GXv_char6[0]);
36+
37+
String[] GXv_char7 = new String[1] ;
38+
new Agent(-1).execute( "toolcall_stream", "", GXv_char7) ;
3239
}
3340
}

0 commit comments

Comments
 (0)