From dc4826dacc4d8a9430e0e34d279b761124950164 Mon Sep 17 00:00:00 2001 From: stasex Date: Tue, 24 Oct 2023 02:01:46 +0300 Subject: [PATCH] added changes to the search method and removed unnecessary code --- .../Mutations/ProductMutation.cs | 3 - .../Queries/ProductQuery.cs | 9 - .../IServices/IProductService.cs | 9 +- .../Services/ProductService.cs | 216 ++-------------- .../ProductTests.cs | 244 ++---------------- 5 files changed, 48 insertions(+), 433 deletions(-) diff --git a/ShoppingAssistantApi.Api/Mutations/ProductMutation.cs b/ShoppingAssistantApi.Api/Mutations/ProductMutation.cs index 24ad5e3..f4598a5 100644 --- a/ShoppingAssistantApi.Api/Mutations/ProductMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/ProductMutation.cs @@ -9,7 +9,4 @@ namespace ShoppingAssistantApi.Api.Mutations; [ExtendObjectType(OperationTypeNames.Mutation)] public class ProductMutation { - public IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist( - Message message, CancellationToken cancellationToken, [Service] IProductService productService) - => productService.StartNewSearchAndReturnWishlist(message, cancellationToken); } \ No newline at end of file diff --git a/ShoppingAssistantApi.Api/Queries/ProductQuery.cs b/ShoppingAssistantApi.Api/Queries/ProductQuery.cs index cbaa775..b76586b 100644 --- a/ShoppingAssistantApi.Api/Queries/ProductQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/ProductQuery.cs @@ -7,14 +7,5 @@ namespace ShoppingAssistantApi.Api.Queries; [ExtendObjectType(OperationTypeNames.Query)] public class ProductQuery { - [Authorize] - public IAsyncEnumerable GetProductFromSearch(Message message, CancellationToken cancellationToken, - [Service] IProductService productService) - => productService.GetProductFromSearch(message, cancellationToken); - [Authorize] - public IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken, - [Service] IProductService productService) - => productService.GetRecommendationsForProductFromSearchStream(message, cancellationToken); - } \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/IServices/IProductService.cs b/ShoppingAssistantApi.Application/IServices/IProductService.cs index 03a8545..3cf6d42 100644 --- a/ShoppingAssistantApi.Application/IServices/IProductService.cs +++ b/ShoppingAssistantApi.Application/IServices/IProductService.cs @@ -8,12 +8,5 @@ namespace ShoppingAssistantApi.Application.IServices; public interface IProductService { IAsyncEnumerable SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken); - - // TODO remove all methods below - IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken); - - IAsyncEnumerable GetProductFromSearch(Message message, CancellationToken cancellationToken); - - IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, - CancellationToken cancellationToken); + } \ No newline at end of file diff --git a/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs b/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs index 6da34ab..a429da6 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs @@ -1,9 +1,4 @@ -using System.Collections.ObjectModel; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using Newtonsoft.Json.Linq; -using ShoppingAssistantApi.Application.IRepositories; -using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; using ShoppingAssistantApi.Application.Models.OpenAi; @@ -19,7 +14,6 @@ public class ProductService : IProductService private readonly IWishlistsService _wishlistsService; private readonly IOpenAiService _openAiService; - public ProductService(IOpenAiService openAiService, IWishlistsService wishlistsService) { @@ -36,19 +30,29 @@ public class ProductService : IProductService new OpenAiMessage { Role = "User", - Content = PromptForProductSearch(message.Text) + Content = "" } }, Stream = true }; - + + var suggestionBuffer = new Suggestion(); + var messageBuffer = new MessagePart(); var currentDataType = SearchEventType.Wishlist; var dataTypeHolder = string.Empty; + var dataBuffer = string.Empty; await foreach (var data in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) { if (data.Contains("[")) { + if (dataTypeHolder=="[Message]" && messageBuffer.Text!=null) + { + _wishlistsService.AddMessageToPersonalWishlistAsync(wishlistId, new MessageCreateDto() + { + Text = messageBuffer.Text, + }, cancellationToken); + } dataTypeHolder = string.Empty; dataTypeHolder += data; } @@ -66,6 +70,8 @@ public class ProductService : IProductService else { + dataBuffer += data; + switch (currentDataType) { case SearchEventType.Message: @@ -74,16 +80,21 @@ public class ProductService : IProductService Event = SearchEventType.Message, Data = data }; + messageBuffer.Text += data; break; case SearchEventType.Suggestion: - yield return new ServerSentEvent + suggestionBuffer.Text += data; + if (data.Contains(";")) { - Event = SearchEventType.Suggestion, - Data = data - }; - break; - + yield return new ServerSentEvent + { + Event = SearchEventType.Suggestion, + Data = suggestionBuffer.Text + }; + suggestionBuffer.Text = string.Empty; + } + break; case SearchEventType.Product: yield return new ServerSentEvent { @@ -91,17 +102,8 @@ public class ProductService : IProductService Data = data }; break; - - case SearchEventType.Wishlist: - yield return new ServerSentEvent - { - Event = SearchEventType.Wishlist, - Data = data - }; - break; } - dataTypeHolder = string.Empty; } } } @@ -129,170 +131,4 @@ public class ProductService : IProductService return SearchEventType.Wishlist; } } - - - - - - - - - - - - - - - - - - - - // TODO: remove all methods below - public async IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken) - { - List messages = new List() - { - new OpenAiMessage() - { - Role = "User", - Content = PromptForProductSearch(message.Text) - } - }; - - var chatRequest = new ChatCompletionRequest - { - Messages = messages - }; - - await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) - { - var openAiContent = JObject.Parse(response); - var productNames = openAiContent["Name"]?.ToObject>() ?? new List(); - - WishlistCreateDto newWishlist = new WishlistCreateDto() - { - Type = "Product", - FirstMessageText = message.Text - }; - - var resultWishlistTask = _wishlistsService.StartPersonalWishlistAsync(newWishlist, cancellationToken); - var resultWishlist = await resultWishlistTask; - - yield return (productNames, resultWishlist); - } - } - - public async IAsyncEnumerable GetProductFromSearch(Message message, [EnumeratorCancellation] CancellationToken cancellationToken) - { - List messages = new List() - { - new OpenAiMessage() - { - Role = "User", - Content = PromptForProductSearchWithQuestion(message.Text) - } - }; - - var chatRequest = new ChatCompletionRequest - { - Messages = messages - }; - - await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) - { - var openAiContent = JObject.Parse(response); - var productNames = openAiContent["Name"]?.ToObject>(); - - if (productNames != null && productNames.Any()) - { - foreach (var productName in productNames) - { - yield return productName.Name; - } - } - else - { - var questions = openAiContent["AdditionalQuestion"]?.ToObject>() ?? new List(); - - foreach (var question in questions) - { - yield return question.QuestionText; - } - } - } - } - - - public async IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken) - { - List messages = new List() - { - new OpenAiMessage() - { - Role = "User", - Content = PromptForRecommendationsForProductSearch(message.Text) - } - }; - - var chatRequest = new ChatCompletionRequest - { - Messages = messages - }; - - await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) - { - var openAiContent = JObject.Parse(response); - var recommendations = openAiContent["Recommendation"]?.ToObject>() ?? new List(); - - foreach (var recommendation in recommendations) - { - yield return recommendation; - } - } - } - - public string PromptForProductSearch(string message) - { - string promptForSearch = "Return information in JSON. " + - "\nProvide information, only that indicated in the type of answer, namely only the name. " + - "\nAsk additional questions to the user if there is not enough information. " + - "\nIf there are several answer options, list them. " + - "\nYou don't need to display questions and products together! " + - "\nDo not output any text other than JSON!!! " + - $"\n\nQuestion: {message} " + - $"\nType of answer: Question:[] " + - $"\n\nif there are no questions, then just display the products " + - $"\nType of answer: Name:"; - return promptForSearch; - } - - public string PromptForRecommendationsForProductSearch(string message) - { - string promptForSearch = "Return information in JSON. " + - "\nProvide only information indicated in the type of answer, namely only the recommendation. " + - "\nIf there are several answer options, list them. " + - "\nDo not output any text other than JSON." + - $"\n\nGive recommendations for this question: {message} " + - "\nType of answer: " + - "\n\nRecommendation :"; - return promptForSearch; - } - - public string PromptForProductSearchWithQuestion(string message) - { - string promptForSearch = "Return information in JSON. " + - "\nAsk additional questions to the user if there is not enough information." + - "\nIf there are several answer options, list them. " + - "\nYou don't need to display questions and products together!" + - "\nDo not output any text other than JSON!!!" + - $"\n\nQuestion: {message}" + - "\n\nif you can ask questions to clarify the choice, then ask them" + - "\nType of answer:" + - "\nAdditionalQuestion:[]" + - "\n\nif there are no questions, then just display the products" + - "\nType of answer:" + - "\nName:"; - return promptForSearch; - } } \ No newline at end of file diff --git a/ShoppingAssistantApi.UnitTests/ProductTests.cs b/ShoppingAssistantApi.UnitTests/ProductTests.cs index caf6e09..e4b6a01 100644 --- a/ShoppingAssistantApi.UnitTests/ProductTests.cs +++ b/ShoppingAssistantApi.UnitTests/ProductTests.cs @@ -29,56 +29,6 @@ public class ProductTests _productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); } - - - /*[Fact] - public async Task SearchProductAsync_WhenWishlistIdIsEmpty_CreatesWishlistAndReturnsEvent() - { - // Arrange - string wishlistId = string.Empty; // Simulating an empty wishlist ID - var message = new MessageCreateDto - { - Text = "Your message text here" - }; - var cancellationToken = CancellationToken.None; - - // Define your expected new wishlist and event data - var newWishlistId = "123"; // Example wishlist ID - var expectedEvent = new ServerSentEvent - { - Event = SearchEventType.Wishlist, - Data = newWishlistId - }; - - // Mock the StartPersonalWishlistAsync method to return the expected wishlist - _wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), CancellationToken.None)) - .ReturnsAsync(new WishlistDto - { - Id = "123", - Name = "MacBook", - Type = WishlistTypes.Product.ToString(), // Use enum - CreatedById = "someId" - }); - - // Mock the GetChatCompletionStream method to provide SSE data - var sseData = new List { "[Question] What is your question?" }; - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) - .Returns(sseData.ToAsyncEnumerable()); - - // Act - var result = await _productService.SearchProductAsync(wishlistId, message, cancellationToken).ToListAsync(); - - // Assert - // Check if the first item in the result is the expected wishlist creation event - var firstEvent = result.FirstOrDefault(); - Assert.NotNull(firstEvent); - Assert.Equal(expectedEvent.Event, firstEvent.Event); - Assert.Equal(expectedEvent.Data, firstEvent.Data); - - // You can add more assertions to verify the other SSE events as needed. - }*/ - - [Fact] public async Task SearchProductAsync_WhenWishlistExists_ReturnsExpectedEvents() { @@ -94,19 +44,34 @@ public class ProductTests var expectedSseData = new List { "[", - "Question", + "Message", "]", " What", - " features", - " are", - " you", - " looking", + " u", + " want", + " ?", + "[", + "Options", + "]", + " USB-C", + " ;", + " Keyboard", + " ultra", + " ;", "?\n", "[", "Options", "]", " USB", - "-C" + "-C", + " ;", + "[", + "Message", + "]", + " What", + " u", + " want", + " ?" }; // Mock the GetChatCompletionStream method to provide the expected SSE data @@ -123,171 +88,4 @@ public class ProductTests // Check if the actual SSE events match the expected SSE events Assert.Equal(8, actualSseEvents.Count); } - - [Fact] - public async Task StartNewSearchAndReturnWishlist_CreatesWishlistObject() - { - // Arrange - var expectedOpenAiMessage = new OpenAiMessage - { - Role = "User", - Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }" - }; - - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), CancellationToken.None)) - .Returns((ChatCompletionRequest request, CancellationToken token) => - { - var asyncEnumerable = new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable(); - return asyncEnumerable; - }); - - _wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), CancellationToken.None)) - .ReturnsAsync(new WishlistDto - { - Id = "someID", - Name = "MacBook", - Type = "Product", // Use enum - CreatedById = "someId" - }); - - var message = new Message - { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Text = "what are the best graphics cards you know?", - CreatedById = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Role = "user" - }; - - List productNames = null; - WishlistDto createdWishList = null; - - // Act - var result = _productService.StartNewSearchAndReturnWishlist(message, CancellationToken.None); - - await foreach (var (productList, wishlist) in result) - { - productNames = productList; - createdWishList = wishlist; - } - - // Assert - Assert.NotNull(createdWishList); - Assert.NotNull(productNames); - } - - [Fact] - public async Task GetProductFromSearch_ReturnsProductListWithName() - { - var message = new Message - { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Text = "what are the best graphics cards you know?", - CreatedById = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Role = "user" - }; - var cancellationToken = CancellationToken.None; - - var expectedOpenAiMessage = new OpenAiMessage - { - Role = "User", - Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }" - }; - - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) - .Returns(new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable()); - - var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); - - var productList = new List(); - - await foreach (var product in productService.GetProductFromSearch(message, cancellationToken)) - { - productList.Add(product); - } - - var openAiContent = JObject.Parse(expectedOpenAiMessage.Content); - var productNames = openAiContent["Name"].ToObject>(); - var expectedProductList = productNames.Select(info => info.Name).ToList(); - - Assert.Equal(expectedProductList, productList); - Assert.NotNull(openAiContent); - Assert.True(openAiContent.ContainsKey("Name")); - } - - [Fact] - public async Task GetProductFromSearch_ReturnsProductListWithQuestion() - { - var message = new Message - { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Text = "what are the best graphics cards you know?", - CreatedById = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Role = "user" - }; - var cancellationToken = CancellationToken.None; - - var expectedOpenAiMessage = new OpenAiMessage - { - Role = "User", - Content = "{ \"AdditionalQuestion\": [{ \"QuestionText\": \"What specific MacBook model are you using?\" }," + - " { \"QuestionText\": \"Do you have any preferences for brand or capacity?\" }] }" - }; - - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) - .Returns(new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable()); - - var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); - - var productList = new List(); - - await foreach (var product in productService.GetProductFromSearch(message, cancellationToken)) - { - productList.Add(product); - } - - var openAiContent = JObject.Parse(expectedOpenAiMessage.Content); - var productNames = openAiContent["AdditionalQuestion"].ToObject>(); - - Assert.NotNull(openAiContent); - Assert.True(openAiContent.ContainsKey("AdditionalQuestion")); - } - - [Fact] - public async Task GetRecommendationsForProductFromSearch_ReturnsRecommendations() - { - var message = new Message - { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Text = "get recommendations for this product", - CreatedById = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Role = "user" - }; - var cancellationToken = CancellationToken.None; - - var expectedOpenAiMessage = new OpenAiMessage - { - Role = "User", - Content = "{ \"Recommendation\": [\"Recommendation 1\", \"Recommendation 2\"] }" - }; - - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) - .Returns((ChatCompletionRequest request, CancellationToken token) => - { - var asyncEnumerable = new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable(); - return asyncEnumerable; - }); - - var recommendations = new List(); - var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); - - await foreach (var recommendation in productService.GetRecommendationsForProductFromSearchStream(message, cancellationToken)) - { - recommendations.Add(recommendation); - } - - var openAiContent = JObject.Parse(expectedOpenAiMessage.Content); - Assert.NotNull(openAiContent); - Assert.True(openAiContent.ContainsKey("Recommendation")); - Assert.Equal(new List { "Recommendation 1", "Recommendation 2" }, recommendations); - } } \ No newline at end of file