Skip to content

Commit

Permalink
Cherry pick branch 'genexuslabs:embedding-data-type' into beta
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiamurialdo authored and Beta Bot committed Nov 28, 2024
1 parent 2841f48 commit bb04cc4
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 58 deletions.
14 changes: 1 addition & 13 deletions dotnet/src/dotnetcore/Providers/AI/Model/GXAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,11 @@ protected string CallAssistant(string assistant, GXProperties properties, object
}
protected string CallAgent(string assistant, GXProperties gxproperties, IList chatMessages, object result)
{
ChatCompletion chatCompletion = null;
CallResult callResult = result as CallResult;
try
{
GXLogging.Debug(log, "Calling Agent: ", assistant);
List<ChatMessage> messages = ChatMessagesToOpenAiChatMessages(chatMessages);
chatCompletion = AgentService.AgentHandlerInstance.Assistant(assistant, messages, gxproperties).GetAwaiter().GetResult();
if (chatCompletion != null && chatCompletion.Content != null && chatCompletion.Content.Count > 0)
{
GXLogging.Debug(log, "Agent response:", chatCompletion.Content[0].Text);
return chatCompletion.Content[0].Text;
}
else
{
GXLogging.Debug(log, "Agent response is empty");
return string.Empty;
}
return AgentService.AgentHandlerInstance.Assistant(assistant, (List<Chat.ChatMessage>) chatMessages, gxproperties).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Expand Down
197 changes: 160 additions & 37 deletions dotnet/src/dotnetcore/Providers/AI/Services/AgentService.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using GeneXus.AI.Chat;
using GeneXus.Configuration;
using GeneXus.Utils;
using OpenAI;
using OpenAI.Chat;
namespace GeneXus.AI
{
internal class AgentService
{
private static readonly IGXLogger log = GXLoggerFactory.GetLogger<AgentService>();
const string SerializedAdditionalRawDataPty = "SerializedAdditionalRawData";
const string VARIABLES = "variables";
const string SAIA_AGENT = "saia:agent:";
private HttpClient _httpClient;
protected string API_KEY;
protected OpenAIClient _openAIClient;
protected const string AI_PROVIDER = "AI_PROVIDER";
protected const string AI_PROVIDER_API_KEY = "AI_PROVIDER_API_KEY";
const string FINISH_REASON_STOP = "stop";
const string FINISH_REASON_TOOL_CALLS = "toolcalls";

protected string DEFAULT_API_KEY => "default_";
protected string DEFAULT_PROVIDER => "https://api.beta.saia.ai";
protected string DEFAULT_PROVIDER => "https://api.qa.saia.ai";
protected string _providerUri;
internal AgentService()
{
string val;
Uri providerUri = new Uri(DEFAULT_PROVIDER);
_providerUri = DEFAULT_PROVIDER;
API_KEY = DEFAULT_API_KEY;
if (Config.GetValueOf(AI_PROVIDER, out val))
{
providerUri = new Uri(val);
_providerUri = val;
}
_providerUri = AddChatToUrl(_providerUri);
if (Config.GetValueOf(AI_PROVIDER_API_KEY, out val))
{
API_KEY = val;
Expand All @@ -52,45 +53,95 @@ internal AgentService()
_httpClient = new HttpClient(noAuthHandler);
_httpClient.DefaultRequestHeaders.Add("Saia-Auth", API_KEY);

OpenAIClientOptions options = new OpenAIClientOptions()
}
static string AddChatToUrl(string url)
{
var uriBuilder = new UriBuilder(url);
if (!uriBuilder.Path.EndsWith("/"))
{
Endpoint = providerUri
};
options.Transport = new HttpClientPipelineTransport(_httpClient);

_openAIClient = new OpenAIClient(new ApiKeyCredential(API_KEY), options);
uriBuilder.Path += "/";
}
uriBuilder.Path += "chat";
return uriBuilder.Uri.ToString();
}

internal async Task<ChatCompletion> Assistant(string assistant, List<ChatMessage> messages, GXProperties properties)
internal async Task<string> Assistant(string assistant, List<Chat.ChatMessage> messages, GXProperties properties)
{
try
{

ChatRequestPayload requestBody = new ChatRequestPayload();
requestBody.Model = $"{SAIA_AGENT}{assistant}";
requestBody.Messages = messages;
requestBody.Variables = properties.ToList();
requestBody.Stream = false;

ChatCompletionOptions customOptions = new CustomChatCompletionOptions();
if (properties != null && properties.Count > 0)
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyInfo fieldInfo = customOptions.GetType().GetProperty(SerializedAdditionalRawDataPty, BindingFlags.Instance | BindingFlags.NonPublic);
IDictionary<string, BinaryData> SerializedAdditionalRawData = (IDictionary<string, BinaryData>)fieldInfo.GetValue(customOptions);
SerializedAdditionalRawData = new Dictionary<string, BinaryData>
{
{ VARIABLES, BinaryData.FromString(properties.ToJSonString()) }
WriteIndented = true
};
fieldInfo.SetValue(customOptions, SerializedAdditionalRawData);
string requestJson = JsonSerializer.Serialize(requestBody, options);

GXLogging.Debug(log, "Agent payload:", requestJson);

var content = new StringContent(requestJson, Encoding.UTF8, "application/json");

HttpResponseMessage response = await _httpClient.PostAsync(_providerUri, content);

if (!response.IsSuccessStatusCode)
{
throw new Exception($"Request failed with status code: {response.StatusCode}");
}
ChatClient client = _openAIClient.GetChatClient($"{SAIA_AGENT}{assistant}");

ClientResult<ChatCompletion> response = await client.CompleteChatAsync(messages, customOptions);
//Console.Write(response.GetRawResponse().Content.ToString());
return response.Value;
string responseJson = await response.Content.ReadAsStringAsync();
GXLogging.Debug(log, "Agent response:", responseJson);
ChatCompletionResult chatCompletion = JsonSerializer.Deserialize<ChatCompletionResult>(responseJson);

if (chatCompletion != null)
{
foreach (Choice choice in chatCompletion.Choices)
{
switch (choice.FinishReason.ToLower())
{
case FINISH_REASON_STOP:
return choice.Message.Content;
case FINISH_REASON_TOOL_CALLS:
messages.Add(choice.Message);
foreach (ToolCall toolCall in choice.Message.ToolCalls)
ProcessTollCall(toolCall, messages);
return await Assistant(assistant, messages, properties);
}
}
}
return string.Empty;
}
catch (Exception ex)
{
GXLogging.Error(log, "Error calling Agent ",assistant, ex);
GXLogging.Error(log, "Error calling Agent ", assistant, ex);
throw;
}

}

private void ProcessTollCall(ToolCall toolCall, List<ChatMessage> messages)
{
string result = string.Empty;
string functionName = toolCall.Function.Name;
/*try
{
result = CallTool(functionName, toolCall.Function.Arguments);
}
catch (Exception ex)
{
GXLogging.Error(log, "Error calling tool ", functionName, ex);
result = $"Error calling tool {functionName}";
}*/
ChatMessage toolCallMessage = new ChatMessage();
toolCallMessage.Role = "tool";
toolCallMessage.Content = result;
toolCallMessage.ToolCallId = toolCall.Id;
messages.Add(toolCallMessage);
}


private static volatile AgentService m_Instance;
private static object m_SyncRoot = new Object();
internal static AgentService AgentHandlerInstance
Expand Down Expand Up @@ -118,10 +169,6 @@ internal class Variable
public string Value { get; set; }
}

internal class CustomChatCompletionOptions : ChatCompletionOptions
{
public List<Variable> Variables { get; set; }
}
internal class NoAuthHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
Expand All @@ -130,4 +177,80 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
return await base.SendAsync(request, cancellationToken);
}
}

internal class ChatRequestPayload
{
[JsonPropertyName("model")]
public string Model { get; set; }

[JsonPropertyName("messages")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<Chat.ChatMessage> Messages { get; set; }

[JsonPropertyName("stream")]
public bool? Stream { get; set; }

[JsonPropertyName("variables")]
public List<GxKeyValuePair> Variables { get; set; }
}

internal class ChatCompletionResult
{
[JsonPropertyName("id")]
public string Id { get; set; }

[JsonPropertyName("object")]
public string Object { get; set; }

[JsonPropertyName("created")]
public long Created { get; set; }

[JsonPropertyName("choices")]
public List<Choice> Choices { get; set; }

[JsonPropertyName("usage")]
public Usage Usage { get; set; }

[JsonPropertyName("tool_calls")]
public List<ChatMessage> ToolCalls { get; set; }

[JsonPropertyName("data")]
public List<DataItem> Data { get; set; }
}
public class Choice
{
[JsonPropertyName("index")]
public int Index { get; set; }

[JsonPropertyName("message")]
public ChatMessage Message { get; set; }

[JsonPropertyName("finish_reason")]
public string FinishReason { get; set; }
}


public class Usage
{
[JsonPropertyName("prompt_tokens")]
public int PromptTokens { get; set; }

[JsonPropertyName("completion_tokens")]
public int CompletionTokens { get; set; }

[JsonPropertyName("total_tokens")]
public int TotalTokens { get; set; }
}

public class DataItem
{
[JsonPropertyName("id")]
public string Id { get; set; }

[JsonPropertyName("object")]
public string Object { get; set; }

[JsonPropertyName("embedding")]
public List<double> Embedding { get; set; }
}
}
19 changes: 18 additions & 1 deletion dotnet/src/dotnetframework/GxClasses/Domain/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace GeneXus.AI.Chat
{
public class ChatMessage
{
[JsonPropertyName("role")]
public string Role { get; set; }

[JsonPropertyName("content")]
public string Content { get; set; }

[JsonPropertyName("tool_calls")]
public List<ToolCall> ToolCalls { get; set; }
public string ToolCallId{ get; set; }

[JsonPropertyName("tool_call_id")]
public string ToolCallId { get; set; }

}
public class ToolCall
{
[JsonPropertyName("id")]
public string Id { get; set; }

[JsonPropertyName("type")]
public string Type { get; set; }

[JsonPropertyName("function")]
public Function Function { get; set; }

}
public class Function
{
[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("arguments")]
public string Arguments { get; set; }
}
}
12 changes: 12 additions & 0 deletions dotnet/src/dotnetframework/GxClasses/Domain/GxCollections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace GeneXus.Utils
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Text.Json.Serialization;
using System.Xml;
using System.Xml.Serialization;
using GeneXus.Application;
Expand Down Expand Up @@ -2300,6 +2301,15 @@ public GxKeyValuePair GetNext()
current++;
return getKeyValuePair(current);
}
internal List<GxKeyValuePair> ToList()
{
List<GxKeyValuePair> list = new List<GxKeyValuePair>();
for (int i = 0; i < this.Count; i++)
{
list.Add(getKeyValuePair(i));
}
return list;
}
public bool Eof()
{
return eof;
Expand Down Expand Up @@ -2440,11 +2450,13 @@ public GxKeyValuePair(string key, string value)
_key = key;
_value = value;
}
[JsonPropertyName("key")]
public string Key
{
get { return _key; }
set { _key = value; }
}
[JsonPropertyName("value")]
public string Value
{
get { return _value; }
Expand Down
10 changes: 3 additions & 7 deletions dotnet/test/DotNetCoreUnitTest/Domain/GxEmbeddingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@ public async Task EmbeddingTest()
public async Task AssistantTest()
{
AgentService agentService = AgentService.AgentHandlerInstance;
string userMessage = "What's the weather like in Buenos Aires today?";
//string userMessage = "What's the weather like in Buenos Aires today?";
string modelId = "e4e7a837-b8ad-4d25-b2db-431dda9af0af";
GXProperties properties = new GXProperties();
properties.Set("$context", "context for reference");
List<ChatMessage> messages = new List<ChatMessage>
{
new UserChatMessage(userMessage)
};

ChatCompletion embedding = await agentService.Assistant(modelId, messages, properties);
ChatCompletionResult embedding = await agentService.Assistant(modelId, null, properties);

Check failure on line 29 in dotnet/test/DotNetCoreUnitTest/Domain/GxEmbeddingTest.cs

View workflow job for this annotation

GitHub Actions / test-external-storages

Cannot implicitly convert type 'string' to 'GeneXus.AI.ChatCompletionResult'

Check failure on line 29 in dotnet/test/DotNetCoreUnitTest/Domain/GxEmbeddingTest.cs

View workflow job for this annotation

GitHub Actions / test-external-storages

Cannot implicitly convert type 'string' to 'GeneXus.AI.ChatCompletionResult'

Check failure on line 29 in dotnet/test/DotNetCoreUnitTest/Domain/GxEmbeddingTest.cs

View workflow job for this annotation

GitHub Actions / build

Cannot implicitly convert type 'string' to 'GeneXus.AI.ChatCompletionResult'

Check failure on line 29 in dotnet/test/DotNetCoreUnitTest/Domain/GxEmbeddingTest.cs

View workflow job for this annotation

GitHub Actions / build

Cannot implicitly convert type 'string' to 'GeneXus.AI.ChatCompletionResult'
Assert.NotNull(embedding);
Assert.NotNull(embedding.Content);
//Assert.NotNull(embedding.Content);
}
}
}

0 comments on commit bb04cc4

Please sign in to comment.