diff --git a/ShoppingAssistantApi.Application/IServices/IProductService.cs b/ShoppingAssistantApi.Application/IServices/IProductService.cs index 7bd64d6..c2fddd2 100644 --- a/ShoppingAssistantApi.Application/IServices/IProductService.cs +++ b/ShoppingAssistantApi.Application/IServices/IProductService.cs @@ -11,6 +11,7 @@ public interface IProductService IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken); Task> GetProductFromSearch(Message message, CancellationToken cancellationToken); - - Task> GetRecommendationsForProductFromSearch(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 3f230ff..5a11d68 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs @@ -83,7 +83,7 @@ public class ProductService : IProductService return productNames.Select(productName => productName.Name).ToList(); } - public async Task> GetRecommendationsForProductFromSearch(Message message, CancellationToken cancellationToken) + public async IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken) { List messages = new List() { @@ -93,18 +93,22 @@ public class ProductService : IProductService Content = PromptForRecommendationsForProductSearch(message.Text) } }; - + var chatRequest = new ChatCompletionRequest { Messages = messages }; - var openAiMessage = await _openAiService.GetChatCompletion(chatRequest, cancellationToken); + await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) + { + var openAiContent = JObject.Parse(response); + var recommendations = openAiContent["Recommendation"]?.ToObject>() ?? new List(); - var openAiContent = JObject.Parse(openAiMessage.Content); - var recommendations = openAiContent["Recommendation"]?.ToObject>() ?? new List(); - - return recommendations; + foreach (var recommendation in recommendations) + { + yield return recommendation; + } + } } public string PromptForProductSearch(string message) diff --git a/ShoppingAssistantApi.UnitTests/ProductTests.cs b/ShoppingAssistantApi.UnitTests/ProductTests.cs index 57fa61b..86fe6da 100644 --- a/ShoppingAssistantApi.UnitTests/ProductTests.cs +++ b/ShoppingAssistantApi.UnitTests/ProductTests.cs @@ -46,17 +46,14 @@ public class ProductTests Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }" }; - var openAiServiceMock = new Mock(); - var wishlistsServiceMock = new Mock(); - - openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) + _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) .Returns((ChatCompletionRequest request, CancellationToken token) => { var asyncEnumerable = new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable(); return asyncEnumerable; }); - wishlistsServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), cancellationToken)) + _wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), cancellationToken)) .ReturnsAsync(new WishlistDto { Id = "someID", @@ -65,7 +62,7 @@ public class ProductTests CreatedById = "someId" }); - var productService = new ProductService(openAiServiceMock.Object, wishlistsServiceMock.Object); + var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); List productNames = null; WishlistDto createdWishList = null; @@ -128,20 +125,31 @@ public class ProductTests Role = "user" }; var cancellationToken = CancellationToken.None; - + var expectedOpenAiMessage = new OpenAiMessage { Role = OpenAiRole.User, Content = "{ \"Recommendation\": [\"Recommendation 1\", \"Recommendation 2\"] }" }; - _openAiServiceMock.Setup(x => x.GetChatCompletion(It.IsAny(), cancellationToken)) - .ReturnsAsync(expectedOpenAiMessage); - var recommendations = await _productServiceMock.Object.GetRecommendationsForProductFromSearch(message, cancellationToken); + _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