diff --git a/src/VirgilAgent.Aspire.AppHost/Program.cs b/src/VirgilAgent.Aspire.AppHost/Program.cs index bcd2a88..694f0f0 100644 --- a/src/VirgilAgent.Aspire.AppHost/Program.cs +++ b/src/VirgilAgent.Aspire.AppHost/Program.cs @@ -1,6 +1,6 @@ var builder = DistributedApplication.CreateBuilder(args); -var redis = builder.AddRedisContainer("redis"); +var redis = builder.AddRedis("redis"); var chatService = builder.AddProject("chatservice") .WithReference(redis); diff --git a/src/VirgilAgent.Aspire.AppHost/Properties/launchSettings.json b/src/VirgilAgent.Aspire.AppHost/Properties/launchSettings.json index 3f551e0..6282554 100644 --- a/src/VirgilAgent.Aspire.AppHost/Properties/launchSettings.json +++ b/src/VirgilAgent.Aspire.AppHost/Properties/launchSettings.json @@ -6,11 +6,12 @@ "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:15173", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16036" - } + "environmentVariables": { + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true", + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16036" + } } } } diff --git a/src/VirgilAgent.Aspire.AppHost/VirgilAgent.Aspire.AppHost.csproj b/src/VirgilAgent.Aspire.AppHost/VirgilAgent.Aspire.AppHost.csproj index 943e303..4aeead7 100644 --- a/src/VirgilAgent.Aspire.AppHost/VirgilAgent.Aspire.AppHost.csproj +++ b/src/VirgilAgent.Aspire.AppHost/VirgilAgent.Aspire.AppHost.csproj @@ -9,7 +9,8 @@ - + + diff --git a/src/VirgilAgent.Aspire.ServiceDefaults/Extensions.cs b/src/VirgilAgent.Aspire.ServiceDefaults/Extensions.cs index 067690a..1ea36c3 100644 --- a/src/VirgilAgent.Aspire.ServiceDefaults/Extensions.cs +++ b/src/VirgilAgent.Aspire.ServiceDefaults/Extensions.cs @@ -25,7 +25,7 @@ public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBu http.AddStandardResilienceHandler(); // Turn on service discovery by default - http.UseServiceDiscovery(); + http.AddServiceDiscovery(); }); return builder; diff --git a/src/VirgilAgent.Aspire.ServiceDefaults/VirgilAgent.Aspire.ServiceDefaults.csproj b/src/VirgilAgent.Aspire.ServiceDefaults/VirgilAgent.Aspire.ServiceDefaults.csproj index e3a87cd..7a3d570 100644 --- a/src/VirgilAgent.Aspire.ServiceDefaults/VirgilAgent.Aspire.ServiceDefaults.csproj +++ b/src/VirgilAgent.Aspire.ServiceDefaults/VirgilAgent.Aspire.ServiceDefaults.csproj @@ -11,14 +11,14 @@ - - - - - + + + + + - - + + diff --git a/src/VirgilAgent.BotService/Bots/VirgilBot.cs b/src/VirgilAgent.BotService/Bots/VirgilBot.cs index 5b27e09..1eb9f55 100644 --- a/src/VirgilAgent.BotService/Bots/VirgilBot.cs +++ b/src/VirgilAgent.BotService/Bots/VirgilBot.cs @@ -44,6 +44,12 @@ protected override async Task OnMessageActivityAsync(ITurnContext membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) { + // No new members that are not the bot itself, so no need to send a welcome message. + if (!membersAdded.Any(m => m.Id != turnContext.Activity.Recipient.Id)) + { + return; + } + // Try to get the user locale string? locale = turnContext.Activity.GetLocale(); @@ -52,13 +58,7 @@ protected override async Task OnMembersAddedAsync(IList membersA // Get the start message from the chat API. string welcomeText = await StartConversationAsync(locale, conversationId); - foreach (var member in membersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken); - } - } + await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText), cancellationToken); } private async Task GetChatResponseAsync(string message, string conversationId) @@ -94,7 +94,8 @@ private async Task> TryGetSuggestedActions(string m } catch (Exception ex) { - // Log the error and return an empty list, without surfacing the error to the user. + // Any error while getting suggestions should not block the main flow. + // The error is only logged and an empty list is returned. _logger.LogError(ex, "An error occurred while trying to get suggestions from API: {errorMessage}", ex.Message); return []; } @@ -104,7 +105,8 @@ private async Task StartConversationAsync(string? locale = null, string? { try { - ChatMessageResponse chatResponse = await _chatApiClient.StartConversationAsync(locale, conversationId); + StartConversationRequest request = new(locale, conversationId); + ChatMessageResponse chatResponse = await _chatApiClient.StartConversationAsync(request); string responseMessage = chatResponse.Message; diff --git a/src/VirgilAgent.BotService/Services/ChatApiClient.cs b/src/VirgilAgent.BotService/Services/ChatApiClient.cs index 4ef31c5..0191a2d 100644 --- a/src/VirgilAgent.BotService/Services/ChatApiClient.cs +++ b/src/VirgilAgent.BotService/Services/ChatApiClient.cs @@ -9,9 +9,12 @@ internal class ChatApiClient(HttpClient httpClient) { private readonly HttpClient _httpClient = httpClient; - public async Task StartConversationAsync(string? locale = null, string? conversationId = null) + public async Task StartConversationAsync(StartConversationRequest? request) { - HttpResponseMessage response = await _httpClient.GetAsync("/start"); + string jsonBody = JsonSerializer.Serialize(request); + using HttpContent content = new StringContent(jsonBody, Encoding.UTF8, MediaTypeNames.Application.Json); + + HttpResponseMessage response = await _httpClient.PostAsync("/start", content); if (!response.IsSuccessStatusCode) { @@ -28,7 +31,7 @@ public async Task StartConversationAsync(string? locale = n public async Task SendMessageAsync(ChatMessageRequest request) { string jsonBody = JsonSerializer.Serialize(request); - HttpContent content = new StringContent(jsonBody, Encoding.UTF8, MediaTypeNames.Application.Json); + using HttpContent content = new StringContent(jsonBody, Encoding.UTF8, MediaTypeNames.Application.Json); HttpResponseMessage response = await _httpClient.PostAsync("/chat", content); diff --git a/src/VirgilAgent.BotService/Services/SuggestionsApiClient.cs b/src/VirgilAgent.BotService/Services/SuggestionsApiClient.cs index 8284104..16a7c08 100644 --- a/src/VirgilAgent.BotService/Services/SuggestionsApiClient.cs +++ b/src/VirgilAgent.BotService/Services/SuggestionsApiClient.cs @@ -8,6 +8,12 @@ internal class SuggestionsApiClient(HttpClient httpClient) public async Task> GetSuggestedActionsAsync(string message) { + // Don't send empty messages. + if (string.IsNullOrWhiteSpace(message)) + { + return []; + } + // Encode the message. string encodedMessage = Uri.EscapeDataString(message); string url = $"/suggestions?message={encodedMessage}"; diff --git a/src/VirgilAgent.BotService/VirgilAgent.BotService.csproj b/src/VirgilAgent.BotService/VirgilAgent.BotService.csproj index 5eb0a25..6b89ee6 100644 --- a/src/VirgilAgent.BotService/VirgilAgent.BotService.csproj +++ b/src/VirgilAgent.BotService/VirgilAgent.BotService.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/src/VirgilAgent.ChatService/Program.cs b/src/VirgilAgent.ChatService/Program.cs index e63961b..66ae8ae 100644 --- a/src/VirgilAgent.ChatService/Program.cs +++ b/src/VirgilAgent.ChatService/Program.cs @@ -77,12 +77,15 @@ app.MapDefaultEndpoints(); -app.MapGet("/start", +app.MapPost("/start", async ( [FromServices] ChatService chatService, - [FromQuery] string? locale, - [FromQuery] string? conversationId) => + [FromBody] StartConversationRequest? request) => { + // Get the conversation id and locale from the request. + string? conversationId = request?.ConversationId; + string? locale = request?.Locale; + // Create a new conversation with the initial message. Conversation conversation = await chatService.StartConversationAsync(conversationId, locale); diff --git a/src/VirgilAgent.ChatService/Services/ChatService.cs b/src/VirgilAgent.ChatService/Services/ChatService.cs index e30e923..2e68686 100644 --- a/src/VirgilAgent.ChatService/Services/ChatService.cs +++ b/src/VirgilAgent.ChatService/Services/ChatService.cs @@ -73,9 +73,7 @@ public async Task GetChatResponseAsync( Conversation? conversation = _cache.Get(conversationId, null); if (conversation is null) { - _logger.LogInformation( - "Conversation with id {conversationId} not found in cache, it will be initialized", - conversationId); + _logger.LogInformation("Conversation not found in cache, it will be initialized"); conversation = BuildEmptyConversation(conversationId); } @@ -120,8 +118,7 @@ private void AddMessageToConversation(Conversation conversation, ConversationMes if (conversation.Messages.Count > _chatOptions.MaxSavedMessages) { _logger.LogInformation( - "Conversation with id {conversationId} has reached the max length ({maxLength}), the oldest message will be removed", - conversation.Id, + "Conversation has reached the max length ({maxLength}), the oldest message will be removed", _chatOptions.MaxSavedMessages); conversation.Messages.RemoveAt(0); diff --git a/src/VirgilAgent.ChatService/VirgilAgent.ChatService.csproj b/src/VirgilAgent.ChatService/VirgilAgent.ChatService.csproj index 869c600..f422e62 100644 --- a/src/VirgilAgent.ChatService/VirgilAgent.ChatService.csproj +++ b/src/VirgilAgent.ChatService/VirgilAgent.ChatService.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/src/VirgilAgent.Core/Cache/InMemoryCache.cs b/src/VirgilAgent.Core/Cache/InMemoryCache.cs index 38ccaa0..f19cc46 100644 --- a/src/VirgilAgent.Core/Cache/InMemoryCache.cs +++ b/src/VirgilAgent.Core/Cache/InMemoryCache.cs @@ -60,7 +60,7 @@ public void Set(string key, TValue value, TimeSpan? expiresIn = null) /// public bool ContainsKey(string key) { - return _cache.ContainsKey(key) && !(_cache[key].Expiration.HasValue && _cache[key].Expiration < DateTime.Now); + return _cache.TryGetValue(key, out var value) && !(value.Expiration.HasValue && value.Expiration < DateTime.Now); } /// diff --git a/src/VirgilAgent.Core/Cache/RedisCache.cs b/src/VirgilAgent.Core/Cache/RedisCache.cs index 708b4f0..0eafe95 100644 --- a/src/VirgilAgent.Core/Cache/RedisCache.cs +++ b/src/VirgilAgent.Core/Cache/RedisCache.cs @@ -66,14 +66,10 @@ public void Remove(string key) /// public void Clear() { - foreach (var endPoint in _database.Multiplexer.GetEndPoints()) - { - var server = _database.Multiplexer.GetServer(endPoint); - foreach (var key in server.Keys()) - { - _database.KeyDelete(key); - } - } + _database.Multiplexer.GetEndPoints() + .SelectMany(endPoint => _database.Multiplexer.GetServer(endPoint).Keys()) + .ToList() + .ForEach(key => _database.KeyDelete(key)); } /// diff --git a/src/VirgilAgent.Core/Extensions/HostApplicationBuilderExtensions.cs b/src/VirgilAgent.Core/Extensions/HostApplicationBuilderExtensions.cs index 65db25b..c4391b5 100644 --- a/src/VirgilAgent.Core/Extensions/HostApplicationBuilderExtensions.cs +++ b/src/VirgilAgent.Core/Extensions/HostApplicationBuilderExtensions.cs @@ -29,7 +29,7 @@ public static IHostApplicationBuilder AddCache( builder.Services.AddSingleton(new InMemoryCache(cacheOptions.ExpirationInSeconds)); break; case CacheType.Redis: - builder.AddRedis(cacheOptions.ConnectionString ?? string.Empty); + builder.AddRedisClient(cacheOptions.ConnectionString ?? string.Empty); builder.Services.AddSingleton((services) => { IConnectionMultiplexer redis = services.GetRequiredService(); diff --git a/src/VirgilAgent.Core/StartConversationRequest.cs b/src/VirgilAgent.Core/StartConversationRequest.cs new file mode 100644 index 0000000..9fec1eb --- /dev/null +++ b/src/VirgilAgent.Core/StartConversationRequest.cs @@ -0,0 +1,8 @@ +namespace VirgilAgent.Core; + +/// +/// Data structure for sending a request to start a conversation. +/// +public record StartConversationRequest( + string? Locale, + string? ConversationId); diff --git a/src/VirgilAgent.Core/VirgilAgent.Core.csproj b/src/VirgilAgent.Core/VirgilAgent.Core.csproj index 54a41d3..5fce724 100644 --- a/src/VirgilAgent.Core/VirgilAgent.Core.csproj +++ b/src/VirgilAgent.Core/VirgilAgent.Core.csproj @@ -7,9 +7,9 @@ - + - + diff --git a/src/VirgilAgent.SuggestionsService/Services/OpenAISuggestionsAIClient.cs b/src/VirgilAgent.SuggestionsService/Services/OpenAISuggestionsAIClient.cs index 177c2d9..96f3c1c 100644 --- a/src/VirgilAgent.SuggestionsService/Services/OpenAISuggestionsAIClient.cs +++ b/src/VirgilAgent.SuggestionsService/Services/OpenAISuggestionsAIClient.cs @@ -116,27 +116,32 @@ public async Task> GetSuggestedActionsAsync( /// if the parsing was successful, otherwise . private static bool TryParseLine(string line, out SuggestedAction? suggestedAction) { + suggestedAction = default; + if (string.IsNullOrWhiteSpace(line)) { - suggestedAction = default; + // No suggestion to parse. return false; } - try + string[] parts = line.Split('|'); + if (parts.Length < 2) { - string[] parts = line.Split('|'); - string text = parts[0]; - ActionType actionType = Enum.Parse(parts[1]); - string? actionData = parts.Length > 2 ? parts[2] : null; - - suggestedAction = new(text, actionType, actionData); - return true; + // Invalid suggestion format. + return false; } - catch + + string text = parts[0]; + if (!Enum.TryParse(parts[1], out ActionType actionType)) { - suggestedAction = default; + // Invalid action type. return false; } + + string? actionData = parts.Length > 2 ? parts[2] : null; + + suggestedAction = new SuggestedAction(text, actionType, actionData); + return true; } /// diff --git a/src/VirgilAgent.SuggestionsService/VirgilAgent.SuggestionsService.csproj b/src/VirgilAgent.SuggestionsService/VirgilAgent.SuggestionsService.csproj index f50ec29..042ee3b 100644 --- a/src/VirgilAgent.SuggestionsService/VirgilAgent.SuggestionsService.csproj +++ b/src/VirgilAgent.SuggestionsService/VirgilAgent.SuggestionsService.csproj @@ -10,8 +10,8 @@ - - + +