mirror of
https://github.com/Shchoholiev/shopping-assistant-api.git
synced 2025-04-12 01:48:49 +00:00
made an implementation for a request that may contain questions and tested it
This commit is contained in:
parent
60484e4a6e
commit
9db3baca89
@ -1,7 +1,20 @@
|
|||||||
namespace ShoppingAssistantApi.Api.Queries;
|
using HotChocolate.Authorization;
|
||||||
|
using ShoppingAssistantApi.Application.IServices;
|
||||||
|
using ShoppingAssistantApi.Domain.Entities;
|
||||||
|
|
||||||
|
namespace ShoppingAssistantApi.Api.Queries;
|
||||||
|
|
||||||
[ExtendObjectType(OperationTypeNames.Query)]
|
[ExtendObjectType(OperationTypeNames.Query)]
|
||||||
public class ProductQuery
|
public class ProductQuery
|
||||||
{
|
{
|
||||||
|
[Authorize]
|
||||||
|
public IAsyncEnumerable<string> GetProductFromSearch(Message message, CancellationToken cancellationToken,
|
||||||
|
[Service] IProductService productService)
|
||||||
|
=> productService.GetProductFromSearch(message, cancellationToken);
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public IAsyncEnumerable<string> GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken,
|
||||||
|
[Service] IProductService productService)
|
||||||
|
=> productService.GetRecommendationsForProductFromSearchStream(message, cancellationToken);
|
||||||
|
|
||||||
}
|
}
|
@ -9,8 +9,8 @@ namespace ShoppingAssistantApi.Application.IServices;
|
|||||||
public interface IProductService
|
public interface IProductService
|
||||||
{
|
{
|
||||||
IAsyncEnumerable<(List<ProductName> ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken);
|
IAsyncEnumerable<(List<ProductName> ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<List<string>> GetProductFromSearch(Message message, CancellationToken cancellationToken);
|
IAsyncEnumerable<string> GetProductFromSearch(Message message, CancellationToken cancellationToken);
|
||||||
|
|
||||||
IAsyncEnumerable<string> GetRecommendationsForProductFromSearchStream(Message message,
|
IAsyncEnumerable<string> GetRecommendationsForProductFromSearchStream(Message message,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace ShoppingAssistantApi.Application.Models.ProductSearch;
|
||||||
|
|
||||||
|
public class Question
|
||||||
|
{
|
||||||
|
public string QuestionText { get; set; }
|
||||||
|
}
|
@ -59,30 +59,47 @@ public class ProductService : IProductService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GetProductFromSearch(Message message, CancellationToken cancellationToken)
|
public async IAsyncEnumerable<string> GetProductFromSearch(Message message, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
List<OpenAiMessage> messages = new List<OpenAiMessage>()
|
List<OpenAiMessage> messages = new List<OpenAiMessage>()
|
||||||
{
|
{
|
||||||
new OpenAiMessage()
|
new OpenAiMessage()
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = OpenAiRole.User,
|
||||||
Content = PromptForProductSearch(message.Text)
|
Content = PromptForProductSearchWithQuestion(message.Text)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var chatRequest = new ChatCompletionRequest
|
var chatRequest = new ChatCompletionRequest
|
||||||
{
|
{
|
||||||
Messages = messages
|
Messages = messages
|
||||||
};
|
};
|
||||||
|
|
||||||
var openAiMessage = await _openAiService.GetChatCompletion(chatRequest, cancellationToken);
|
await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken))
|
||||||
|
{
|
||||||
|
var openAiContent = JObject.Parse(response);
|
||||||
|
var productNames = openAiContent["Name"]?.ToObject<List<ProductName>>();
|
||||||
|
|
||||||
var openAiContent = JObject.Parse(openAiMessage.Content);
|
if (productNames != null && productNames.Any())
|
||||||
var productNames = openAiContent["Name"]?.ToObject<List<ProductName>>() ?? new List<ProductName>();
|
{
|
||||||
|
foreach (var productName in productNames)
|
||||||
return productNames.Select(productName => productName.Name).ToList();
|
{
|
||||||
|
yield return productName.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var questions = openAiContent["AdditionalQuestion"]?.ToObject<List<Question>>() ?? new List<Question>();
|
||||||
|
|
||||||
|
foreach (var question in questions)
|
||||||
|
{
|
||||||
|
yield return question.QuestionText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken)
|
public async IAsyncEnumerable<string> GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
List<OpenAiMessage> messages = new List<OpenAiMessage>()
|
List<OpenAiMessage> messages = new List<OpenAiMessage>()
|
||||||
@ -137,4 +154,21 @@ public class ProductService : IProductService
|
|||||||
"\n\nRecommendation :<Recommendation>";
|
"\n\nRecommendation :<Recommendation>";
|
||||||
return promptForSearch;
|
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:<question>[]" +
|
||||||
|
"\n\nif there are no questions, then just display the products" +
|
||||||
|
"\nType of answer:" +
|
||||||
|
"\nName:<name>";
|
||||||
|
return promptForSearch;
|
||||||
|
}
|
||||||
}
|
}
|
@ -76,14 +76,13 @@ public class ProductTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
var openAiContent = JObject.Parse(expectedOpenAiMessage.Content);
|
var openAiContent = JObject.Parse(expectedOpenAiMessage.Content);
|
||||||
|
|
||||||
Assert.True(openAiContent.ContainsKey("Name"));
|
|
||||||
Assert.NotNull(createdWishList);
|
Assert.NotNull(createdWishList);
|
||||||
Assert.NotNull(productNames);
|
Assert.NotNull(productNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetProductFromSearch_ReturnsProductList()
|
public async Task GetProductFromSearch_ReturnsProductListWithName()
|
||||||
{
|
{
|
||||||
var message = new Message
|
var message = new Message
|
||||||
{
|
{
|
||||||
@ -93,27 +92,72 @@ public class ProductTests
|
|||||||
Role = "user"
|
Role = "user"
|
||||||
};
|
};
|
||||||
var cancellationToken = CancellationToken.None;
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
var expectedProductList = new List<string> { "NVIDIA GeForce RTX 3080", "AMD Radeon RX 6900 XT" };
|
|
||||||
_productServiceMock.Setup(x => x.GetProductFromSearch(message, cancellationToken))
|
|
||||||
.ReturnsAsync(expectedProductList);
|
|
||||||
|
|
||||||
var expectedOpenAiMessage = new OpenAiMessage
|
var expectedOpenAiMessage = new OpenAiMessage
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = OpenAiRole.User,
|
||||||
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
||||||
};
|
};
|
||||||
_openAiServiceMock.Setup(x => x.GetChatCompletion(It.IsAny<ChatCompletionRequest>(), cancellationToken))
|
|
||||||
.ReturnsAsync(expectedOpenAiMessage);
|
_openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny<ChatCompletionRequest>(), cancellationToken))
|
||||||
|
.Returns(new List<string> { expectedOpenAiMessage.Content }.ToAsyncEnumerable());
|
||||||
|
|
||||||
|
var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object);
|
||||||
|
|
||||||
|
var productList = new List<string>();
|
||||||
|
|
||||||
|
await foreach (var product in productService.GetProductFromSearch(message, cancellationToken))
|
||||||
|
{
|
||||||
|
productList.Add(product);
|
||||||
|
}
|
||||||
|
|
||||||
var openAiContent = JObject.Parse(expectedOpenAiMessage.Content);
|
var openAiContent = JObject.Parse(expectedOpenAiMessage.Content);
|
||||||
var productNames = openAiContent["Name"].ToObject<List<ProductName>>();
|
var productNames = openAiContent["Name"].ToObject<List<ProductName>>();
|
||||||
var productList = productNames.Select(info => info.Name).ToList();
|
var expectedProductList = productNames.Select(info => info.Name).ToList();
|
||||||
|
|
||||||
Assert.Equal(expectedProductList, productList);
|
Assert.Equal(expectedProductList, productList);
|
||||||
|
Assert.NotNull(openAiContent);
|
||||||
Assert.True(openAiContent.ContainsKey("Name"));
|
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 = OpenAiRole.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<ChatCompletionRequest>(), cancellationToken))
|
||||||
|
.Returns(new List<string> { expectedOpenAiMessage.Content }.ToAsyncEnumerable());
|
||||||
|
|
||||||
|
var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object);
|
||||||
|
|
||||||
|
var productList = new List<string>();
|
||||||
|
|
||||||
|
await foreach (var product in productService.GetProductFromSearch(message, cancellationToken))
|
||||||
|
{
|
||||||
|
productList.Add(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
var openAiContent = JObject.Parse(expectedOpenAiMessage.Content);
|
||||||
|
var productNames = openAiContent["AdditionalQuestion"].ToObject<List<Question>>();
|
||||||
|
|
||||||
|
Assert.NotNull(openAiContent);
|
||||||
|
Assert.True(openAiContent.ContainsKey("AdditionalQuestion"));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetRecommendationsForProductFromSearch_ReturnsRecommendations()
|
public async Task GetRecommendationsForProductFromSearch_ReturnsRecommendations()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user