Skip to content

Commit

Permalink
Added models
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisIvanov committed Jun 5, 2024
1 parent 8d89e2a commit 8069c6d
Show file tree
Hide file tree
Showing 18 changed files with 361 additions and 63 deletions.
1 change: 1 addition & 0 deletions src/server/CookingApp/Common/ExceptionMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public static class ExceptionMessages
{
public const string NullOrEmptyInputValues = "The provided input contains either null or an empty value";
public const string SubscriptionCreationFail = "Failed to create a subscription. {0}";
public const string ResponseRequestFailed = "The ChatGPT API failed to respond. Please try again.";
}
}
54 changes: 54 additions & 0 deletions src/server/CookingApp/Controllers/ChatGPTController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace CookingApp.Controllers
{
using CookingApp.Common;
using CookingApp.Services.OpenAI.Completions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenAI.ObjectModels.ResponseModels;

[ApiController]
[AllowAnonymous]
public class GPTController : ControllerBase
{
private readonly ICompletionService _completionService;
private readonly ILogger<GPTController> _logger;

public GPTController(ICompletionService completionService, ILogger<GPTController> logger)
{
_completionService = completionService;
_logger = logger;
}

[HttpPost("chat-request")]
[AllowAnonymous]
public async Task<IActionResult> SendQuery([FromBody] string message, [FromHeader] string? chatId = null)
{
try
{
_logger.LogInformation("Attempting ChatGPT API connection.");

var result =
String.IsNullOrEmpty(chatId)
? await _completionService.CreateCompletion(message)
: await _completionService.UpdateCompletion(message, chatId);

if (result == null)
{
return BadRequest(ExceptionMessages.ResponseRequestFailed);
}

_logger.LogInformation("Successfully connected to ChatGPT API.");
_logger.LogInformation("Response received.");
// To display the message you need to get into result.Choices[0].Message.Content.
// The chat id is also contained inside the result

return Ok(result);
}
catch (Exception ex)
{
_logger.LogError("Failed attempt to contact ChatGPT API.");
return BadRequest(ex.Message);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CookingApp.Infrastructure.Configurations.OpenAI
{
public class OpenAIOptions
{
public string APIKey { get; set; }

public string APIUrl { get; set; }

public string Model { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
using CookingApp.Services.Stripe;
using Stripe;
using CookingApp.Infrastructure.Configurations.Stripe;
using CookingApp.Services.OpenAI.Completions;
using OpenAI.Extensions;
using OpenAI;
using Microsoft.AspNetCore.DataProtection.KeyManagement;

namespace CookingApp.Infrastructure.Extensions
{
Expand Down Expand Up @@ -118,7 +122,7 @@ public static IHostApplicationBuilder AddMongoDatabase(this WebApplicationBuilde
return builder;
}
public static IHostApplicationBuilder AddStripeIntegration(this WebApplicationBuilder builder)
{
{
builder.Services.AddScoped<IStripeService, StripeService>();
builder.Services.AddScoped<CustomerService>();
builder.Services.AddScoped<PriceService>();
Expand All @@ -134,5 +138,19 @@ public static IHostApplicationBuilder AddStripeIntegration(this WebApplicationBu

return builder;
}
}

public static IHostApplicationBuilder AddOpenAIIntegration(this WebApplicationBuilder builder)
{
builder.Services.AddOpenAIService();
builder.Services.Configure<OpenAiOptions>(options =>
{
options.ApiKey = builder.Configuration.GetValue<string>("OpenAPIOptions:APIKey") ?? string.Empty;
options.DefaultModelId = builder.Configuration.GetValue<string>("OpenAPIOptions:Model") ?? string.Empty;
});

builder.Services.AddScoped<ICompletionService, CompletionService>();

return builder;
}
}
}
11 changes: 11 additions & 0 deletions src/server/CookingApp/Models/Allergy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CookingApp.Infrastructure.Common;
using MongoDB.Bson.Serialization.Attributes;

public class Allergy : MongoEntity
{
[BsonElement("name")]
public string Name { get; set; }

[BsonElement("severity")]
public AllergySeverity Severity { get; set; }
}
6 changes: 6 additions & 0 deletions src/server/CookingApp/Models/AllergySeverity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public enum AllergySeverity
{
Mild,
Moderate,
Severe
}
14 changes: 14 additions & 0 deletions src/server/CookingApp/Models/Chat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using CookingApp.Infrastructure.Common;
using MongoDB.Bson.Serialization.Attributes;

public class Chat : MongoEntity
{
[BsonElement("createdTime")]
public DateTime CreatedTime { get; set; }

[BsonElement("requests")]
public List<Request> Requests { get; set; } = new List<Request>();

[BsonElement("responses")]
public List<Response> Responses { get; set; } = new List<Response>();
}
6 changes: 6 additions & 0 deletions src/server/CookingApp/Models/DietaryPreference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public enum DietaryPreference
{
None,
Vegan,
Vegetarian
}
11 changes: 11 additions & 0 deletions src/server/CookingApp/Models/Food.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CookingApp.Infrastructure.Common;
using MongoDB.Bson.Serialization.Attributes;

public class Food : MongoEntity
{
[BsonElement("name")]
public string Name { get; set; }

[BsonElement("type")]
public FoodType Type { get; set; }
}
9 changes: 9 additions & 0 deletions src/server/CookingApp/Models/FoodType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public enum FoodType
{
Vegetable,
Fruit,
Grain,
Protein,
Dairy,
Other
}
11 changes: 11 additions & 0 deletions src/server/CookingApp/Models/Request.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CookingApp.Infrastructure.Common;
using MongoDB.Bson.Serialization.Attributes;

public class Request : MongoEntity
{
[BsonElement("message")]
public string Message { get; set; }

[BsonElement("timestamp")]
public DateTime Timestamp { get; set; }
}
11 changes: 11 additions & 0 deletions src/server/CookingApp/Models/Response.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CookingApp.Infrastructure.Common;
using MongoDB.Bson.Serialization.Attributes;

public class Response : MongoEntity
{
[BsonElement("message")]
public string Message { get; set; }

[BsonElement("timestamp")]
public DateTime Timestamp { get; set; }
}
26 changes: 26 additions & 0 deletions src/server/CookingApp/Models/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using System;
using CookingApp.Infrastructure.Common;

public class User : MongoEntity
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }

[BsonElement("name")]
public string Name { get; set; }

[BsonElement("dietaryPreference")]
public DietaryPreference DietaryPreference { get; set; }

[BsonElement("allergies")]
public List<Allergy> Allergies { get; set; } = new List<Allergy>();

[BsonElement("avoidedFoods")]
public List<Food> AvoidedFoods { get; set; } = new List<Food>();

[BsonElement("chats")]
public List<Chat> Chats { get; set; } = new List<Chat>();
}
3 changes: 1 addition & 2 deletions src/server/CookingApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@
p.WithIgnoreIfNullConvention(true);
});
builder.AddStripeIntegration();

builder.Services.AddOpenAIService();
builder.AddOpenAIIntegration();

builder.Host.UseLogging(p =>
{
Expand Down
128 changes: 128 additions & 0 deletions src/server/CookingApp/Services/OpenAI/Completions/CompletionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
namespace CookingApp.Services.OpenAI.Completions
{
using global::OpenAI.Interfaces;
using global::OpenAI.ObjectModels.ResponseModels;
using global::OpenAI.ObjectModels.RequestModels;
using global::OpenAI.ObjectModels;
using CookingApp.Common.CompletionConstants;
using CookingApp.Infrastructure.Interfaces;
using System.Linq;

/// <summary>
/// This class it to assist with the personal needs of the user.
/// After defining their dietary/allergic needs the chat completion
/// will fill them in for the chatbot to take into account.
/// </summary>
public class CompletionService : ICompletionService
{
private readonly IRepository<User> _userRepo;
private readonly IRepository<Chat> _chatRepo;
private readonly IOpenAIService _openAIService;

public CompletionService(IOpenAIService openAIService, IRepository<User> userRepo, IRepository<Chat> chatRepo)
{
_openAIService = openAIService;
_userRepo = userRepo;
_chatRepo = chatRepo;
}

public async Task<ChatCompletionCreateResponse> CreateCompletion(string request)
{
//TODO: get the userId through JWT Bearer
var user = await _userRepo.GetByIdAsync("userId");

// Get the user allergies
var userAllergies = user.Allergies;

// Case if the converstaion is new and the chat doesn't exist
var completionResult = await _openAIService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
{
Messages = new List<ChatMessage>
{
// Creating a prompt for the chatboot to answer a question about cooking/diatery needs.
ChatMessage.FromSystem(Completions.Instructions
+ userAllergies + "."
+ Completions.PromptEngineeringPrevention),
ChatMessage.FromUser(Completions.Suggestion),
ChatMessage.FromAssistant(Completions.ExampleResponse),
ChatMessage.FromUser(request)
},
Model = Models.Gpt_4o
});

var userChat = CreateNewChat(completionResult.Id);

if (completionResult.Successful)
{
var response = completionResult.Choices[0].Message.Content;
UpdateUserChat(userChat, request, response);
return completionResult;
}

return null;

}

public async Task<ChatCompletionCreateResponse> UpdateCompletion(string request, string? chatId = null)
{
var userChat = await _chatRepo.GetByIdAsync(chatId);

var completionResult = await _openAIService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
{
Messages = new List<ChatMessage>
{
ChatMessage.FromUser(request)
},
Model = Models.Gpt_4o
});

if (completionResult.Successful)
{
var response = completionResult.Choices[0].Message.Content;
UpdateUserChat(userChat, request, response);
return completionResult;
}

return null;
}

private static bool ChatExists(string chatID, User? user)
=> user.Chats.Any(x => x.Id == chatID);

private Request CreateNewRequest(string message)
=> new Request()
{
Message = message,
Timestamp = DateTime.UtcNow,
};

private Response CreateNewResponse(string message)
=> new Response()
{
Message = message,
Timestamp = DateTime.UtcNow,
};

// Creates a new chat using the ID originating from the ChatGPT API
private Chat CreateNewChat(string id)
{
var chat = new Chat()
{
Id = id,
Requests = new List<Request>(),
Responses = new List<Response>()
};

_chatRepo.InsertAsync(chat);

return chat;
}

private async void UpdateUserChat(Chat? userChat, string? request, string? response)
{
userChat.Requests.Add(CreateNewRequest(request));
userChat.Responses.Add(CreateNewResponse(response));
await _chatRepo.UpdateAsync(userChat);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CookingApp.Services.OpenAI.Completions
{
using global::OpenAI.ObjectModels.ResponseModels;

public interface ICompletionService
{
Task<ChatCompletionCreateResponse> CreateCompletion(string message);

Task<ChatCompletionCreateResponse> UpdateCompletion(string request, string? chatId = null);
}
}
Loading

0 comments on commit 8069c6d

Please sign in to comment.