From 5cec2a7210e5ba0e9d1afbb4bf05d2edf9ecaaec Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro <71648276+ricarneiro@users.noreply.github.com> Date: Tue, 20 May 2025 21:51:05 -0300 Subject: [PATCH] Add project files. --- ChatGemini/ChatGemini.csproj | 17 +++ ChatGemini/ChatGemini.http | 6 + ChatGemini/Controllers/ChatController.cs | 45 ++++++ ChatGemini/Models/ChatMessage.cs | 18 +++ ChatGemini/Models/ChatRequest.cs | 8 ++ ChatGemini/Models/ChatResponse.cs | 8 ++ ChatGemini/Program.cs | 30 ++++ ChatGemini/Properties/launchSettings.json | 41 ++++++ ChatGemini/Services/GeminiChatService.cs | 108 +++++++++++++++ ChatGemini/Services/IChatHistoryService.cs | 10 ++ ChatGemini/Services/IChatService.cs | 9 ++ .../Services/MongoDbChatHistoryService.cs | 36 +++++ ChatGemini/Services/ServerSpaceChatService.cs | 120 ++++++++++++++++ ChatGemini/appsettings.Development.json | 8 ++ ChatGemini/appsettings.json | 21 +++ ChatServerSpace.sln | 24 ++++ ChatServerSpace/ChatDeepInfra.csproj | 18 +++ ChatServerSpace/ChatGemini.http | 6 + ChatServerSpace/Controllers/ChatController.cs | 47 +++++++ ChatServerSpace/Models/ChatMessage.cs | 18 +++ ChatServerSpace/Models/ChatRequest.cs | 9 ++ ChatServerSpace/Models/ChatResponse.cs | 8 ++ ChatServerSpace/Program.cs | 40 ++++++ .../Properties/launchSettings.json | 41 ++++++ .../Services/ChatServiceFactory.cs | 29 ++++ .../Services/DeepInfraChatService.cs | 130 ++++++++++++++++++ .../Services/IChatHistoryService.cs | 10 ++ ChatServerSpace/Services/IChatService.cs | 9 ++ .../Services/IChatServiceFactory.cs | 7 + .../Services/MongoDbChatHistoryService.cs | 36 +++++ .../Services/ServerSpaceChatService.cs | 126 +++++++++++++++++ ChatServerSpace/appsettings.Development.json | 8 ++ ChatServerSpace/appsettings.json | 23 ++++ 33 files changed, 1074 insertions(+) create mode 100644 ChatGemini/ChatGemini.csproj create mode 100644 ChatGemini/ChatGemini.http create mode 100644 ChatGemini/Controllers/ChatController.cs create mode 100644 ChatGemini/Models/ChatMessage.cs create mode 100644 ChatGemini/Models/ChatRequest.cs create mode 100644 ChatGemini/Models/ChatResponse.cs create mode 100644 ChatGemini/Program.cs create mode 100644 ChatGemini/Properties/launchSettings.json create mode 100644 ChatGemini/Services/GeminiChatService.cs create mode 100644 ChatGemini/Services/IChatHistoryService.cs create mode 100644 ChatGemini/Services/IChatService.cs create mode 100644 ChatGemini/Services/MongoDbChatHistoryService.cs create mode 100644 ChatGemini/Services/ServerSpaceChatService.cs create mode 100644 ChatGemini/appsettings.Development.json create mode 100644 ChatGemini/appsettings.json create mode 100644 ChatServerSpace.sln create mode 100644 ChatServerSpace/ChatDeepInfra.csproj create mode 100644 ChatServerSpace/ChatGemini.http create mode 100644 ChatServerSpace/Controllers/ChatController.cs create mode 100644 ChatServerSpace/Models/ChatMessage.cs create mode 100644 ChatServerSpace/Models/ChatRequest.cs create mode 100644 ChatServerSpace/Models/ChatResponse.cs create mode 100644 ChatServerSpace/Program.cs create mode 100644 ChatServerSpace/Properties/launchSettings.json create mode 100644 ChatServerSpace/Services/ChatServiceFactory.cs create mode 100644 ChatServerSpace/Services/DeepInfraChatService.cs create mode 100644 ChatServerSpace/Services/IChatHistoryService.cs create mode 100644 ChatServerSpace/Services/IChatService.cs create mode 100644 ChatServerSpace/Services/IChatServiceFactory.cs create mode 100644 ChatServerSpace/Services/MongoDbChatHistoryService.cs create mode 100644 ChatServerSpace/Services/ServerSpaceChatService.cs create mode 100644 ChatServerSpace/appsettings.Development.json create mode 100644 ChatServerSpace/appsettings.json diff --git a/ChatGemini/ChatGemini.csproj b/ChatGemini/ChatGemini.csproj new file mode 100644 index 0000000..e5252f5 --- /dev/null +++ b/ChatGemini/ChatGemini.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + ChatServerSpace + ChatServerSpace + + + + + + + + + diff --git a/ChatGemini/ChatGemini.http b/ChatGemini/ChatGemini.http new file mode 100644 index 0000000..29dacad --- /dev/null +++ b/ChatGemini/ChatGemini.http @@ -0,0 +1,6 @@ +@ChatGemini_HostAddress = http://localhost:5004 + +GET {{ChatGemini_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/ChatGemini/Controllers/ChatController.cs b/ChatGemini/Controllers/ChatController.cs new file mode 100644 index 0000000..dfb0919 --- /dev/null +++ b/ChatGemini/Controllers/ChatController.cs @@ -0,0 +1,45 @@ +using ChatServerSpace.Models; +using ChatServerSpace.Services; +using Microsoft.AspNetCore.Mvc; + +namespace ChatServerSpace.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ChatController : ControllerBase + { + private readonly IChatService _chatService; + private readonly ILogger _logger; + + public ChatController(IChatService chatService, ILogger logger) + { + _chatService = chatService; + _logger = logger; + } + + [HttpPost] + public async Task> Chat([FromBody] ChatRequest request) + { + if (string.IsNullOrWhiteSpace(request.SessionId)) + { + return BadRequest("SessionId is required"); + } + + if (string.IsNullOrWhiteSpace(request.Message)) + { + return BadRequest("Message is required"); + } + + try + { + var response = await _chatService.ProcessMessageAsync(request); + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing chat message"); + return StatusCode(500, "An error occurred while processing your message"); + } + } + } +} \ No newline at end of file diff --git a/ChatGemini/Models/ChatMessage.cs b/ChatGemini/Models/ChatMessage.cs new file mode 100644 index 0000000..cb45bf9 --- /dev/null +++ b/ChatGemini/Models/ChatMessage.cs @@ -0,0 +1,18 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; +using ThirdParty.Json.LitJson; + +namespace ChatServerSpace.Models +{ + public class ChatMessage + { + [BsonElement("_id")] + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } + public string SessionId { get; set; } = string.Empty; + public string Role { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + } +} diff --git a/ChatGemini/Models/ChatRequest.cs b/ChatGemini/Models/ChatRequest.cs new file mode 100644 index 0000000..13fe482 --- /dev/null +++ b/ChatGemini/Models/ChatRequest.cs @@ -0,0 +1,8 @@ +namespace ChatServerSpace.Models +{ + public class ChatRequest + { + public required string SessionId { get; set; } + public required string Message { get; set; } + } +} diff --git a/ChatGemini/Models/ChatResponse.cs b/ChatGemini/Models/ChatResponse.cs new file mode 100644 index 0000000..c9c470d --- /dev/null +++ b/ChatGemini/Models/ChatResponse.cs @@ -0,0 +1,8 @@ +namespace ChatServerSpace.Models +{ + public class ChatResponse + { + public required string SessionId { get; set; } + public required string Response { get; set; } + } +} diff --git a/ChatGemini/Program.cs b/ChatGemini/Program.cs new file mode 100644 index 0000000..6c526b9 --- /dev/null +++ b/ChatGemini/Program.cs @@ -0,0 +1,30 @@ +using ChatServerSpace.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddHttpClient(); + +// Register services +builder.Services.AddSingleton(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/ChatGemini/Properties/launchSettings.json b/ChatGemini/Properties/launchSettings.json new file mode 100644 index 0000000..c3e5f44 --- /dev/null +++ b/ChatGemini/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:21434", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7238;http://localhost:5004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ChatGemini/Services/GeminiChatService.cs b/ChatGemini/Services/GeminiChatService.cs new file mode 100644 index 0000000..e25e7b3 --- /dev/null +++ b/ChatGemini/Services/GeminiChatService.cs @@ -0,0 +1,108 @@ +using ChatServerSpace.Models; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.Google; + +#pragma warning disable SKEXP0070 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +namespace ChatServerSpace.Services +{ + public class GeminiChatService : IChatService + { + private readonly IChatHistoryService _historyService; + private readonly Kernel _kernel; + private readonly ILogger _logger; + + public GeminiChatService(IChatHistoryService historyService, IConfiguration config, ILogger logger) + { + _historyService = historyService; + _logger = logger; + + var apiKey = config["Google:GeminiApiKey"] ?? throw new ArgumentNullException("Google:GeminiApiKey configuration is missing"); + var modelId = config["Google:ModelId"] ?? "gemini-1.5-pro"; + + _kernel = Kernel.CreateBuilder() + .AddGoogleAIGeminiChatCompletion(modelId, apiKey) + .Build(); + } + + public async Task ProcessMessageAsync(ChatRequest request) + { + try + { + // Save user message to history + var userMessage = new ChatMessage + { + + SessionId = request.SessionId, + Role = "user", + Content = request.Message + }; + await _historyService.SaveMessageAsync(userMessage); + + // Get chat history + var history = await _historyService.GetSessionHistoryAsync(request.SessionId); + + // Create chat history for Semantic Kernel + var chatHistory = new Microsoft.SemanticKernel.ChatCompletion.ChatHistory(); + + foreach (var message in history) + { + if (message.Role.Equals("user", StringComparison.OrdinalIgnoreCase)) + { + chatHistory.AddUserMessage(message.Content); + } + else if (message.Role.Equals("assistant", StringComparison.OrdinalIgnoreCase)) + { + chatHistory.AddAssistantMessage(message.Content); + } + } + + try + { + // Get response from Gemini + var chatCompletionService = _kernel.GetRequiredService(); + + var exec = new PromptExecutionSettings + { + ExtensionData = new Dictionary + { + { "maxOutputTokens", 8192 }, + { "temperature", 0.7 }, + { "topP", 0.95 }, + { "stopSequences", new List() } + } + }; + var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, exec); + var responseText = result.ToString(); + + // Save assistant message to history + var assistantMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "assistant", + Content = responseText + }; + await _historyService.SaveMessageAsync(assistantMessage); + + await Task.Delay(2000); + + return new ChatResponse + { + SessionId = request.SessionId, + Response = responseText + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting response from Gemini"); + throw; + } + } + catch (Exception ex) + { + throw new Exception("Erro na parada!", ex); + } + } + } +} +#pragma warning restore SKEXP0070 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/ChatGemini/Services/IChatHistoryService.cs b/ChatGemini/Services/IChatHistoryService.cs new file mode 100644 index 0000000..5d7ee7a --- /dev/null +++ b/ChatGemini/Services/IChatHistoryService.cs @@ -0,0 +1,10 @@ +using ChatServerSpace.Models; + +namespace ChatServerSpace.Services +{ + public interface IChatHistoryService + { + Task SaveMessageAsync(ChatMessage message); + Task> GetSessionHistoryAsync(string sessionId); + } +} diff --git a/ChatGemini/Services/IChatService.cs b/ChatGemini/Services/IChatService.cs new file mode 100644 index 0000000..7299938 --- /dev/null +++ b/ChatGemini/Services/IChatService.cs @@ -0,0 +1,9 @@ +using ChatServerSpace.Models; + +namespace ChatServerSpace.Services +{ + public interface IChatService + { + Task ProcessMessageAsync(ChatRequest request); + } +} diff --git a/ChatGemini/Services/MongoDbChatHistoryService.cs b/ChatGemini/Services/MongoDbChatHistoryService.cs new file mode 100644 index 0000000..f2e02fd --- /dev/null +++ b/ChatGemini/Services/MongoDbChatHistoryService.cs @@ -0,0 +1,36 @@ +using ChatServerSpace.Models; +using MongoDB.Driver; + +namespace ChatServerSpace.Services +{ + public class MongoDbChatHistoryService : IChatHistoryService + { + private readonly IMongoCollection _messages; + + public MongoDbChatHistoryService(IConfiguration config) + { + var connectionString = config["MongoDB:ConnectionString"]; + var databaseName = config["MongoDB:DatabaseName"]; + var collectionName = config["MongoDB:CollectionName"]; + + var client = new MongoClient(connectionString); + var database = client.GetDatabase(databaseName); + _messages = database.GetCollection(collectionName); + } + + public async Task SaveMessageAsync(ChatMessage message) + { + await _messages.InsertOneAsync(message); + } + + public async Task> GetSessionHistoryAsync(string sessionId) + { + var filter = Builders.Filter.Eq(m => m.SessionId, sessionId); + var sort = Builders.Sort.Ascending(m => m.Timestamp); + + return await _messages.Find(filter) + .Sort(sort) + .ToListAsync(); + } + } +} diff --git a/ChatGemini/Services/ServerSpaceChatService.cs b/ChatGemini/Services/ServerSpaceChatService.cs new file mode 100644 index 0000000..c6263d6 --- /dev/null +++ b/ChatGemini/Services/ServerSpaceChatService.cs @@ -0,0 +1,120 @@ +using ChatServerSpace.Models; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace ChatServerSpace.Services +{ + public class ServerSpaceChatService : IChatService + { + private readonly IChatHistoryService _historyService; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _apiKey; + private readonly JsonSerializerOptions _jsonOptions; + + public ServerSpaceChatService(IChatHistoryService historyService, IConfiguration config, ILogger logger, HttpClient httpClient) + { + _historyService = historyService; + _logger = logger; + _httpClient = httpClient; + + _apiKey = config["ServerSpace:ApiKey"] ?? throw new ArgumentNullException("ServerSpace:ApiKey configuration is missing"); + _httpClient.BaseAddress = new Uri("https://gpt.serverspace.com.br/"); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + + _jsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + } + + public async Task ProcessMessageAsync(ChatRequest request) + { + try + { + // Save user message to history + var userMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "user", + Content = request.Message + }; + await _historyService.SaveMessageAsync(userMessage); + + // Get chat history + var history = await _historyService.GetSessionHistoryAsync(request.SessionId); + + // Create messages array for API request + var messages = history.Select(m => new + { + role = m.Role, + content = m.Content + }).ToList(); + + // Create API request body + var requestBody = new + { + model = "anthropic/claude-3.5-haiku", + max_tokens = 1024, + top_p = 0.1, + temperature = 0.6, + messages + }; + + // Send request to ServerSpace API + var jsonContent = JsonSerializer.Serialize(requestBody, _jsonOptions); + var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync("v1/chat/completions", content); + response.EnsureSuccessStatusCode(); + + var responseJson = await response.Content.ReadAsStringAsync(); + var responseObj = JsonSerializer.Deserialize(responseJson, _jsonOptions); + + if (responseObj == null || string.IsNullOrEmpty(responseObj.Choices?[0]?.Message?.Content)) + { + throw new Exception("Invalid response from ServerSpace API"); + } + + var responseText = responseObj.Choices[0].Message.Content; + + // Save assistant message to history + var assistantMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "assistant", + Content = responseText + }; + await _historyService.SaveMessageAsync(assistantMessage); + + return new ChatResponse + { + SessionId = request.SessionId, + Response = responseText + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing message with ServerSpace API"); + throw; + } + } + + private class ServerSpaceResponse + { + public List? Choices { get; set; } + } + + private class ServerSpaceChoice + { + public ServerSpaceMessage? Message { get; set; } + } + + private class ServerSpaceMessage + { + public string? Content { get; set; } + } + } +} \ No newline at end of file diff --git a/ChatGemini/appsettings.Development.json b/ChatGemini/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/ChatGemini/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ChatGemini/appsettings.json b/ChatGemini/appsettings.json new file mode 100644 index 0000000..2436180 --- /dev/null +++ b/ChatGemini/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Google": { + "GeminiApiKey": "AIzaSyBnea0la9S-rEVBv8qUqbrv37Zl9-0kHpQ", + "ModelId": "gemini-1.5-pro" + }, + "ServerSpace": { + "ApiKey": "tIAXVf3AkCkkpSX+PjFvktfEeSPyA1ZYam50UO3ye/qmxVZX6PIXstmJsLZXkQ39C33onFD/81mdxvhbGHm7tQ==" + }, + "MongoDB": { + "ConnectionString": "mongodb://admin:c4rn31r0@192.168.0.82:27017,192.168.0.81:27017/?authSource=admin", + "DatabaseName": "ChatServerSpaceDb", + "CollectionName": "ChatMessages" + } +} diff --git a/ChatServerSpace.sln b/ChatServerSpace.sln new file mode 100644 index 0000000..12c06ba --- /dev/null +++ b/ChatServerSpace.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34511.84 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatDeepInfra", "ChatServerSpace\ChatDeepInfra.csproj", "{9CC710C3-37FE-4B1A-9C6A-D01EA7A74AB7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9CC710C3-37FE-4B1A-9C6A-D01EA7A74AB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CC710C3-37FE-4B1A-9C6A-D01EA7A74AB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CC710C3-37FE-4B1A-9C6A-D01EA7A74AB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CC710C3-37FE-4B1A-9C6A-D01EA7A74AB7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5AF01BE8-CB55-4F4F-90FC-DDF2A1C86A09} + EndGlobalSection +EndGlobal diff --git a/ChatServerSpace/ChatDeepInfra.csproj b/ChatServerSpace/ChatDeepInfra.csproj new file mode 100644 index 0000000..d748ec8 --- /dev/null +++ b/ChatServerSpace/ChatDeepInfra.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + ChatServerSpace + ChatServerSpace + + + + + + + + + + \ No newline at end of file diff --git a/ChatServerSpace/ChatGemini.http b/ChatServerSpace/ChatGemini.http new file mode 100644 index 0000000..29dacad --- /dev/null +++ b/ChatServerSpace/ChatGemini.http @@ -0,0 +1,6 @@ +@ChatGemini_HostAddress = http://localhost:5004 + +GET {{ChatGemini_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/ChatServerSpace/Controllers/ChatController.cs b/ChatServerSpace/Controllers/ChatController.cs new file mode 100644 index 0000000..8d50e2a --- /dev/null +++ b/ChatServerSpace/Controllers/ChatController.cs @@ -0,0 +1,47 @@ +using ChatServerSpace.Models; +using ChatServerSpace.Services; +using Microsoft.AspNetCore.Mvc; + +namespace ChatServerSpace.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ChatController : ControllerBase + { + private readonly IChatServiceFactory _chatServiceFactory; + private readonly IChatService _chatService; + private readonly ILogger _logger; + + public ChatController(IChatServiceFactory chatServiceFactory, ILogger logger) + { + _chatServiceFactory = chatServiceFactory; + _logger = logger; + } + + [HttpPost] + public async Task> Chat([FromBody] ChatRequest request) + { + if (string.IsNullOrWhiteSpace(request.SessionId)) + { + return BadRequest("SessionId is required"); + } + + if (string.IsNullOrWhiteSpace(request.Message)) + { + return BadRequest("Message is required"); + } + + try + { + var chatService = _chatServiceFactory.CreateChatService(); + var response = await chatService.ProcessMessageAsync(request); + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing chat message"); + return StatusCode(500, "An error occurred while processing your message"); + } + } + } +} \ No newline at end of file diff --git a/ChatServerSpace/Models/ChatMessage.cs b/ChatServerSpace/Models/ChatMessage.cs new file mode 100644 index 0000000..cb45bf9 --- /dev/null +++ b/ChatServerSpace/Models/ChatMessage.cs @@ -0,0 +1,18 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; +using ThirdParty.Json.LitJson; + +namespace ChatServerSpace.Models +{ + public class ChatMessage + { + [BsonElement("_id")] + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } + public string SessionId { get; set; } = string.Empty; + public string Role { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + } +} diff --git a/ChatServerSpace/Models/ChatRequest.cs b/ChatServerSpace/Models/ChatRequest.cs new file mode 100644 index 0000000..175d9f3 --- /dev/null +++ b/ChatServerSpace/Models/ChatRequest.cs @@ -0,0 +1,9 @@ +namespace ChatServerSpace.Models +{ + public class ChatRequest + { + public required string SessionId { get; set; } + public required string Message { get; set; } + public required string Criativity { get; set; } + } +} diff --git a/ChatServerSpace/Models/ChatResponse.cs b/ChatServerSpace/Models/ChatResponse.cs new file mode 100644 index 0000000..c9c470d --- /dev/null +++ b/ChatServerSpace/Models/ChatResponse.cs @@ -0,0 +1,8 @@ +namespace ChatServerSpace.Models +{ + public class ChatResponse + { + public required string SessionId { get; set; } + public required string Response { get; set; } + } +} diff --git a/ChatServerSpace/Program.cs b/ChatServerSpace/Program.cs new file mode 100644 index 0000000..98b62a2 --- /dev/null +++ b/ChatServerSpace/Program.cs @@ -0,0 +1,40 @@ +using ChatServerSpace.Services; +using Microsoft.SemanticKernel; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddHttpClient(); + +builder.Services.AddHttpClient() + .ConfigureHttpClient(client => + { + // Aumentar o timeout para 10 minutos (600 segundos) + client.Timeout = TimeSpan.FromSeconds(600); + }); + +// Register services +builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/ChatServerSpace/Properties/launchSettings.json b/ChatServerSpace/Properties/launchSettings.json new file mode 100644 index 0000000..c3e5f44 --- /dev/null +++ b/ChatServerSpace/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:21434", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7238;http://localhost:5004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ChatServerSpace/Services/ChatServiceFactory.cs b/ChatServerSpace/Services/ChatServiceFactory.cs new file mode 100644 index 0000000..9f56fa8 --- /dev/null +++ b/ChatServerSpace/Services/ChatServiceFactory.cs @@ -0,0 +1,29 @@ +namespace ChatServerSpace.Services +{ + public class ChatServiceFactory : IChatServiceFactory + { + private readonly IServiceProvider _serviceProvider; + private readonly IConfiguration _configuration; + + public ChatServiceFactory(IServiceProvider serviceProvider, IConfiguration configuration) + { + _serviceProvider = serviceProvider; + _configuration = configuration; + } + + public IChatService CreateChatService() + { + // Determine qual implementação usar com base nas configurações + var useDirectHttpClient = _configuration.GetValue("ChatServer:UseServer"); + + if (useDirectHttpClient== "DeepInfra") + { + return _serviceProvider.GetRequiredService(); + } + else + { + return _serviceProvider.GetRequiredService(); + } + } + } +} diff --git a/ChatServerSpace/Services/DeepInfraChatService.cs b/ChatServerSpace/Services/DeepInfraChatService.cs new file mode 100644 index 0000000..fc8da67 --- /dev/null +++ b/ChatServerSpace/Services/DeepInfraChatService.cs @@ -0,0 +1,130 @@ +using ChatServerSpace.Models; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace ChatServerSpace.Services +{ + public class DeepInfraChatService : IChatService + { + private readonly IChatHistoryService _historyService; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _apiKey; + private readonly string _modelId; + private readonly string _endpoint; + + public DeepInfraChatService( + IChatHistoryService historyService, + IConfiguration config, + ILogger logger, + HttpClient httpClient) + { + _historyService = historyService; + _logger = logger; + _httpClient = httpClient; + + _apiKey = config["ChatServer:ApiKey"] ?? throw new ArgumentNullException("ServeSpace:ApiKey configuration is missing"); + _modelId = config["ChatServer:ModelId"] ?? "meta-llama/Meta-Llama-3.1-70B-Instruct"; + _endpoint = config["ChatServer:Url"] ?? "https://api.deepinfra.com/v1/openai/chat/completions"; + + // Configurar o cliente HTTP + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + public async Task ProcessMessageAsync(ChatRequest request) + { + try + { + // Salvar mensagem do usuário no histórico + var userMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "user", + Content = request.Message + }; + await _historyService.SaveMessageAsync(userMessage); + + // Obter histórico de chat + var history = await _historyService.GetSessionHistoryAsync(request.SessionId); + + // Criar mensagens para a API + var messages = history.Select(m => new + { + role = m.Role.ToLowerInvariant(), + content = m.Content + }).ToList(); + + // Criando o payload para a requisição + var payload = new + { + model = _modelId, + messages, + max_tokens = 8192, + temperature = 0.8, + top_p = 0.95 + }; + + _logger.LogInformation("Enviando solicitação para a API DeepInfra: {0}", JsonSerializer.Serialize(payload)); + + // Enviando requisição para a API + var content = new StringContent( + JsonSerializer.Serialize(payload), + Encoding.UTF8, + "application/json"); + + _httpClient.Timeout = TimeSpan.FromSeconds(600); + var response = await _httpClient.PostAsync(_endpoint, content); + + // Garantir que a resposta foi bem-sucedida + response.EnsureSuccessStatusCode(); + + // Processar resposta + var responseContent = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("Resposta recebida da API: {0}", responseContent); + + var apiResponse = JsonSerializer.Deserialize(responseContent, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + var responseText = apiResponse?.Choices?.FirstOrDefault()?.Message?.Content ?? "Sem resposta"; + + // Salvar resposta do assistente no histórico + var assistantMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "assistant", + Content = responseText + }; + await _historyService.SaveMessageAsync(assistantMessage); + + return new ChatResponse + { + SessionId = request.SessionId, + Response = responseText + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Erro ao processar mensagem com DeepInfra API"); + throw new Exception("Erro ao se comunicar com a API DeepInfra", ex); + } + } + + // Classes para deserialização da resposta da API + private class DeepInfraResponse + { + public List? Choices { get; set; } + } + + private class DeepInfraChoice + { + public DeepInfraMessage? Message { get; set; } + } + + private class DeepInfraMessage + { + public string? Content { get; set; } + } + } +} \ No newline at end of file diff --git a/ChatServerSpace/Services/IChatHistoryService.cs b/ChatServerSpace/Services/IChatHistoryService.cs new file mode 100644 index 0000000..5d7ee7a --- /dev/null +++ b/ChatServerSpace/Services/IChatHistoryService.cs @@ -0,0 +1,10 @@ +using ChatServerSpace.Models; + +namespace ChatServerSpace.Services +{ + public interface IChatHistoryService + { + Task SaveMessageAsync(ChatMessage message); + Task> GetSessionHistoryAsync(string sessionId); + } +} diff --git a/ChatServerSpace/Services/IChatService.cs b/ChatServerSpace/Services/IChatService.cs new file mode 100644 index 0000000..7299938 --- /dev/null +++ b/ChatServerSpace/Services/IChatService.cs @@ -0,0 +1,9 @@ +using ChatServerSpace.Models; + +namespace ChatServerSpace.Services +{ + public interface IChatService + { + Task ProcessMessageAsync(ChatRequest request); + } +} diff --git a/ChatServerSpace/Services/IChatServiceFactory.cs b/ChatServerSpace/Services/IChatServiceFactory.cs new file mode 100644 index 0000000..cc4ed65 --- /dev/null +++ b/ChatServerSpace/Services/IChatServiceFactory.cs @@ -0,0 +1,7 @@ +namespace ChatServerSpace.Services +{ + public interface IChatServiceFactory + { + IChatService CreateChatService(); + } +} diff --git a/ChatServerSpace/Services/MongoDbChatHistoryService.cs b/ChatServerSpace/Services/MongoDbChatHistoryService.cs new file mode 100644 index 0000000..f2e02fd --- /dev/null +++ b/ChatServerSpace/Services/MongoDbChatHistoryService.cs @@ -0,0 +1,36 @@ +using ChatServerSpace.Models; +using MongoDB.Driver; + +namespace ChatServerSpace.Services +{ + public class MongoDbChatHistoryService : IChatHistoryService + { + private readonly IMongoCollection _messages; + + public MongoDbChatHistoryService(IConfiguration config) + { + var connectionString = config["MongoDB:ConnectionString"]; + var databaseName = config["MongoDB:DatabaseName"]; + var collectionName = config["MongoDB:CollectionName"]; + + var client = new MongoClient(connectionString); + var database = client.GetDatabase(databaseName); + _messages = database.GetCollection(collectionName); + } + + public async Task SaveMessageAsync(ChatMessage message) + { + await _messages.InsertOneAsync(message); + } + + public async Task> GetSessionHistoryAsync(string sessionId) + { + var filter = Builders.Filter.Eq(m => m.SessionId, sessionId); + var sort = Builders.Sort.Ascending(m => m.Timestamp); + + return await _messages.Find(filter) + .Sort(sort) + .ToListAsync(); + } + } +} diff --git a/ChatServerSpace/Services/ServerSpaceChatService.cs b/ChatServerSpace/Services/ServerSpaceChatService.cs new file mode 100644 index 0000000..5db3e93 --- /dev/null +++ b/ChatServerSpace/Services/ServerSpaceChatService.cs @@ -0,0 +1,126 @@ +using ChatServerSpace.Models; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace ChatServerSpace.Services +{ + public class ServerSpaceChatService : IChatService + { + private readonly IChatHistoryService _historyService; + private readonly Kernel _kernel; + private readonly ILogger _logger; + + public ServerSpaceChatService(IChatHistoryService historyService, IConfiguration config, ILogger logger, HttpClient httpClient) + { + _historyService = historyService; + _logger = logger; + + var apiKey = config["ChatServer:ApiKey"] ?? throw new ArgumentNullException("ServeSpace:ApiKey configuration is missing"); + var modelId = config["ChatServer:ModelId"] ?? "anthropic/claude-3.7-sonnet"; + var endpoint = config["ChatServer:Url"] ?? "https://api.deepinfra.com/v1/openai/chat/completions"; + + + _kernel = Kernel.CreateBuilder() + .AddOpenAIChatCompletion(modelId, new Uri(endpoint), apiKey) + .Build(); + } + + public async Task ProcessMessageAsync(ChatRequest request) + { + try + { + // Save user message to history + var userMessage = new ChatMessage + { + + SessionId = request.SessionId, + Role = "user", + Content = request.Message + }; + await _historyService.SaveMessageAsync(userMessage); + + // Get chat history + var history = await _historyService.GetSessionHistoryAsync(request.SessionId); + + // Create chat history for Semantic Kernel + var chatHistory = new Microsoft.SemanticKernel.ChatCompletion.ChatHistory(); + + foreach (var message in history) + { + if (message.Role.Equals("user", StringComparison.OrdinalIgnoreCase)) + { + chatHistory.AddUserMessage(message.Content); + } + else if (message.Role.Equals("assistant", StringComparison.OrdinalIgnoreCase)) + { + chatHistory.AddAssistantMessage(message.Content); + } + } + + try + { + // Get response from Gemini + var chatCompletionService = _kernel.GetRequiredService(); + + var exec = new PromptExecutionSettings + { + ExtensionData = new Dictionary + { + { "maxOutputTokens", 8192 }, + { "temperature", 0.8 }, + { "topP", 0.95 }, + //{ "stopSequences", new List() } + } + }; + var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, exec); + var responseText = result.ToString(); + + // Save assistant message to history + var assistantMessage = new ChatMessage + { + SessionId = request.SessionId, + Role = "assistant", + Content = responseText + }; + await _historyService.SaveMessageAsync(assistantMessage); + + await Task.Delay(2000); + + return new ChatResponse + { + SessionId = request.SessionId, + Response = responseText + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting response from Gemini"); + throw; + } + } + catch (Exception ex) + { + throw new Exception("Erro na parada!", ex); + } + } + + private class ServerSpaceResponse + { + public List? Choices { get; set; } + } + + private class ServerSpaceChoice + { + public ServerSpaceMessage? Message { get; set; } + } + + private class ServerSpaceMessage + { + public string? Content { get; set; } + } + } +} \ No newline at end of file diff --git a/ChatServerSpace/appsettings.Development.json b/ChatServerSpace/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/ChatServerSpace/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ChatServerSpace/appsettings.json b/ChatServerSpace/appsettings.json new file mode 100644 index 0000000..e801f3f --- /dev/null +++ b/ChatServerSpace/appsettings.json @@ -0,0 +1,23 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ChatServer": { + //"ApiKey": "tIAXVf3AkCkkpSX+PjFvktfEeSPyA1ZYam50UO3ye/qmxVZX6PIXstmJsLZXkQ39C33onFD/81mdxvhbGHm7tQ==", + //"ModelId": "anthropic/claude-3.5-haiku", + //"Url": "https://gpt.serverspace.com.br/v1/chat/completions" + "ApiKey": "HedaR4yPrp9N2XSHfwdZjpZvPIxejPFK", + "ModelId": "meta-llama/Meta-Llama-3.1-70B-Instruct", + "Url": "https://api.deepinfra.com/v1/openai/chat/completions", + "UseServer": "DeepInfra" + }, + "MongoDB": { + "ConnectionString": "mongodb://admin:c4rn31r0@192.168.0.82:27017,192.168.0.81:27017/?authSource=admin", + "DatabaseName": "ChatServerSpaceDb", + "CollectionName": "ChatMessages" + } +}