From 13083ffb5ddc2276cf949a9f0635a68f8deb7d93 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Fri, 20 Jun 2025 22:21:54 -0300 Subject: [PATCH] feat:qdrant --- ChatHistoryService.cs | 21 ++- Controllers/ChatController.cs | 6 +- Program.cs | 8 +- Services/Contracts/IResponseService.cs | 1 + .../ResponseService/MongoResponseService.cs | 7 +- .../ResponseService/QdrantResponseService.cs | 8 +- .../ResponseService/ResponseCompanyService.cs | 152 ++++++++++++++++-- appsettings.json | 11 +- 8 files changed, 180 insertions(+), 34 deletions(-) diff --git a/ChatHistoryService.cs b/ChatHistoryService.cs index c40630a..7b4f255 100644 --- a/ChatHistoryService.cs +++ b/ChatHistoryService.cs @@ -29,7 +29,7 @@ namespace ChatApi } } - public ChatHistory GetSumarizer(string sessionId) + public ChatHistory GetSumarizer(string sessionId, string language = "en") { if (_keyValues.ContainsKey(sessionId)) { @@ -39,7 +39,12 @@ namespace ChatApi else { var msg = new List(); - PromptEn(msg); + + if (language == "pt") + PromptPt(msg); + else + PromptEn(msg); + string json = JsonSerializer.Serialize(msg); var history = new ChatHistory(JsonSerializer.Deserialize>(json)); _keyValues[sessionId] = history; @@ -62,13 +67,21 @@ namespace ChatApi public void PromptEn(List msg) { msg.Add(new ChatMessageContent(AuthorRole.System, "You are an expert software analyst and QA professional.")); - msg.Add(new ChatMessageContent(AuthorRole.System, "Please provide a comprehensive response in English. Consider the project context and requirements above to generate accurate and relevant information.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Please provide a comprehensive response in English (US). Consider the project context and requirements above to generate accurate and relevant information.")); msg.Add(new ChatMessageContent(AuthorRole.System, "If you have test case requests: Use Gherkin format (Given-When-Then) with realistic scenarios covering happy path, edge cases, and error handling.")); msg.Add(new ChatMessageContent(AuthorRole.System, "If you have project summaries: Include objectives, key features, technologies, and main challenges.")); msg.Add(new ChatMessageContent(AuthorRole.System, "If you have a task list request for one developer: Organize tasks by priority and estimated effort for a single developer, including technical dependencies.")); - msg.Add(new ChatMessageContent(AuthorRole.System, "If you have a task list request for more than one developer: Organize tasks by priority and estimated effort for a every developer, including technical dependencies.")); //msg.Add(new ChatMessageContent(AuthorRole.User, "Use sempre portugues do Brasil.")); } + public void PromptPt(List msg) + { + msg.Add(new ChatMessageContent(AuthorRole.System, "Você é um analista de software especialista e profissional de QA.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Por favor, forneça uma resposta abrangente em português do Brasil. Considere o contexto do projeto e os requisitos acima para gerar informações precisas e relevantes.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Se forem solicitados casos de teste: Use o formato Gherkin (Dado-Quando-Então) com cenários realistas cobrindo caminho feliz, casos extremos e tratamento de erros.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Se for solicitado um resumo do projeto: Inclua objetivos, principais funcionalidades, tecnologias e principais desafios.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Se for uma solicitação de lista de tarefas para um(ou mais) desenvolvedor(es): Organize as tarefas por prioridade e esforço estimado para um único desenvolvedor, incluindo dependências técnicas.")); + //msg.Add(new ChatMessageContent(AuthorRole.User, "Use sempre português do Brasil.")); + } } } diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs index 8e18666..c6aa742 100644 --- a/Controllers/ChatController.cs +++ b/Controllers/ChatController.cs @@ -62,7 +62,7 @@ namespace ChatApi.Controllers [Route("savegroup")] public async Task SaveSingleProject([FromBody] ProjectRequest project) { - var projectSave = project.MapTo(); + var projectSave = project.MapTo(); await _projectDataRepository.SaveAsync(projectSave); } @@ -122,9 +122,9 @@ namespace ChatApi.Controllers [HttpGet] [Route("texts")] - public async Task> GetTexts() + public async Task> GetTexts(string groupId) { - var texts = await _textDataService.GetAll(); + var texts = await _textDataService.GetByPorjectId(groupId); return texts.Select(t => { return new TextResponse { diff --git a/Program.cs b/Program.cs index 83cbca2..f061ca4 100644 --- a/Program.cs +++ b/Program.cs @@ -21,6 +21,7 @@ using Microsoft.SemanticKernel; using System.Text; using static OllamaSharp.OllamaApiClient; using static System.Net.Mime.MediaTypeNames; +using static System.Net.WebRequestMethods; #pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -106,7 +107,12 @@ builder.Services.AddSingleton(); //Desktop //builder.Services.AddOllamaChatCompletion("llama3.2", new Uri("http://localhost:11434")); //Notebook -builder.Services.AddOllamaChatCompletion("llama3.2:3b", new Uri("http://localhost:11435")); +var model = "meta-llama/Llama-3.2-3B-Instruct"; +var url = "https://api.deepinfra.com/v1/openai"; // Adicione o /v1/openai +builder.Services.AddOpenAIChatCompletion(model, new Uri(url), "HedaR4yPrp9N2XSHfwdZjpZvPIxejPFK"); + +//builder.Services.AddOllamaChatCompletion("llama3.2:3b", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("llama3.2:1b", new Uri("http://localhost:11435")); //builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); diff --git a/Services/Contracts/IResponseService.cs b/Services/Contracts/IResponseService.cs index 029e887..cdee0db 100644 --- a/Services/Contracts/IResponseService.cs +++ b/Services/Contracts/IResponseService.cs @@ -5,5 +5,6 @@ namespace ChatRAG.Services.Contracts public interface IResponseService { Task GetResponse(UserData userData, string projectId, string sessionId, string question); + Task GetResponse(UserData userData, string projectId, string sessionId, string question, string language = "pt"); } } diff --git a/Services/ResponseService/MongoResponseService.cs b/Services/ResponseService/MongoResponseService.cs index 8bc3d4a..fd6cd27 100644 --- a/Services/ResponseService/MongoResponseService.cs +++ b/Services/ResponseService/MongoResponseService.cs @@ -31,7 +31,7 @@ namespace ChatRAG.Services.ResponseService // ======================================== // MÉTODO ORIGINAL - Delega para ResponseRAGService // ======================================== - public async Task GetResponse(UserData userData, string projectId, string sessionId, string question) + public async Task GetResponse(UserData userData, string projectId, string sessionId, string question, string language="pt") { return await _originalService.GetResponse(userData, projectId, sessionId, question); } @@ -108,6 +108,11 @@ namespace ChatRAG.Services.ResponseService LastRequest = DateTime.UtcNow }; } + + public Task GetResponse(UserData userData, string projectId, string sessionId, string question) + { + return this.GetResponse(userData, projectId, sessionId, question, "pt"); + } } } #pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/Services/ResponseService/QdrantResponseService.cs b/Services/ResponseService/QdrantResponseService.cs index e291b09..385aa6f 100644 --- a/Services/ResponseService/QdrantResponseService.cs +++ b/Services/ResponseService/QdrantResponseService.cs @@ -35,7 +35,8 @@ namespace ChatRAG.Services.ResponseService UserData userData, string projectId, string sessionId, - string userMessage) + string userMessage, + string language = "pt") { try { @@ -200,6 +201,11 @@ namespace ChatRAG.Services.ResponseService return false; } } + + public Task GetResponse(UserData userData, string projectId, string sessionId, string question) + { + return this.GetResponse(userData, projectId, sessionId, question, "pt"); + } } } diff --git a/Services/ResponseService/ResponseCompanyService.cs b/Services/ResponseService/ResponseCompanyService.cs index 21fd4c6..943c3e4 100644 --- a/Services/ResponseService/ResponseCompanyService.cs +++ b/Services/ResponseService/ResponseCompanyService.cs @@ -7,6 +7,7 @@ using ChatRAG.Models; using ChatRAG.Services.Contracts; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Embeddings; #pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -42,32 +43,52 @@ namespace ChatRAG.Services.ResponseService this._vectorSearchService = vectorSearchService; } - public async Task GetResponse(UserData userData, string projectId, string sessionId, string question) + public async Task GetResponse(UserData userData, string projectId, string sessionId, string question, string language = "pt") { var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); - //var resposta = await BuscarTextoRelacionado(question); - //var resposta = await BuscarTopTextosRelacionados(question, projectId); - //var resposta = await BuscarTopTextosRelacionadosDinamico(question, projectId); - var resposta = await BuscarTopTextosRelacionadosComInterface(question, projectId); - + var searchStrategy = await ClassificarEstrategiaDeBusca(question, language); - var projectData = (await _projectDataRepository.GetAsync()).FirstOrDefault(); + string resposta; + switch (searchStrategy) + { + case SearchStrategy.TodosProjeto: + resposta = await BuscarTodosRequisitosDoProjeto(question, projectId); + break; + case SearchStrategy.SimilaridadeComFiltro: + resposta = await BuscarTopTextosRelacionadosComInterface(question, projectId); + break; + case SearchStrategy.SimilaridadeGlobal: + resposta = await BuscarTopTextosRelacionados(question, projectId); + break; + default: + resposta = await BuscarTopTextosRelacionadosComInterface(question, projectId); + break; + } + + var projectData = await _projectDataRepository.GetAsync(projectId); var project = $"Nome: {projectData.Nome} \n\n Descrição:{projectData.Descricao}"; //question = $"Para responder à solicitação/pergunta: \"{question}\" por favor, considere o projeto: \"{project}\" e os requisitos: \"{resposta}\""; // Base prompt template - string basePrompt = @"You are an expert software analyst and QA professional. + string basePrompt = @"You are a QA professional. Generate ONLY what the user requests. + Project Context: {0} + Requirements: {1} + User Request: ""{2}"" - Project Context: {0} - Requirements: {1} - - User Request: ""{2}"" - - Please answer the question above from User Request with a comprehensive response in English. Consider the project context and requirements above to generate accurate and relevant information."; + Focus exclusively on the user's request. Do not add summaries, explanations, or additional content unless specifically asked."; + if (language == "pt") + { + basePrompt = @"Você é um profissional de QA. Gere APENAS o que o usuário solicitar. + Contexto do Projeto: {0} + Requisitos: {1} + Solicitação do Usuário: ""{2}"" + + Foque exclusivamente na solicitação do usuário. Não adicione resumos, explicações ou conteúdo adicional, a menos que especificamente solicitado."; + } // Usage question = string.Format(basePrompt, project, resposta, question); @@ -75,7 +96,15 @@ namespace ChatRAG.Services.ResponseService history.AddUserMessage(question); - var response = await _chatCompletionService.GetChatMessageContentAsync(history); + var executionSettings = new OpenAIPromptExecutionSettings + { + Temperature = 0.8, + TopP = 1.0, + FrequencyPenalty = 0, + PresencePenalty = 0 + }; + + var response = await _chatCompletionService.GetChatMessageContentAsync(history, executionSettings); history.AddMessage(response.Role, response.Content ?? ""); _chatHistoryService.UpdateHistory(sessionId, history); @@ -85,6 +114,68 @@ namespace ChatRAG.Services.ResponseService } + private async Task ClassificarEstrategiaDeBusca(string question, string language) + { + string prompt = language == "pt" ? + @"TAREFA: Classificar estratégia de busca + ENTRADA: ""{0}"" + + REGRAS OBRIGATÓRIAS: + - Se menciona ""projeto"" sem especificar módulos/aspectos → TODOS_PROJETO + - Se menciona ""todo"", ""todos"", ""completo"", ""geral"" → TODOS_PROJETO + - Se menciona aspectos específicos como ""usuário"", ""login"", ""pagamento"" → SIMILARIDADE_FILTRADA + - Se pergunta ""como funciona"" algo específico → SIMILARIDADE_GLOBAL + + EXEMPLOS OBRIGATÓRIOS: + ""gere casos de teste para o projeto"" → TODOS_PROJETO + ""gere resumo do projeto"" → TODOS_PROJETO + ""gere lista de tarefas para este projeto"" → TODOS_PROJETO + ""casos de teste para usuários"" → SIMILARIDADE_FILTRADA + ""como funciona validação CPF"" → SIMILARIDADE_GLOBAL + + RESPOSTA OBRIGATÓRIA (copie exatamente): TODOS_PROJETO, SIMILARIDADE_FILTRADA ou SIMILARIDADE_GLOBAL" : + + @"TASK: Classify search strategy + INPUT: ""{0}"" + + MANDATORY RULES: + - If mentions ""project"" without specifying modules/aspects → ALL_PROJECT + - If mentions ""all"", ""entire"", ""complete"", ""overview"" → ALL_PROJECT + - If mentions specific aspects like ""user"", ""login"", ""payment"" → FILTERED_SIMILARITY + - If asks ""how does"" something specific work → GLOBAL_SIMILARITY + + MANDATORY EXAMPLES: + ""generate test cases for the project"" → ALL_PROJECT + ""generate project summary"" → ALL_PROJECT + ""generate task list for this project"" → ALL_PROJECT + ""test cases for users"" → FILTERED_SIMILARITY + ""how does CPF validation work"" → GLOBAL_SIMILARITY + + MANDATORY RESPONSE (copy exactly): ALL_PROJECT, FILTERED_SIMILARITY or GLOBAL_SIMILARITY"; + + var classificationPrompt = string.Format(prompt, question); + + var executionSettings = new OpenAIPromptExecutionSettings + { + Temperature = 0.0, // Mais determinístico + MaxTokens = 50, // Resposta curta + TopP = 1.0, + FrequencyPenalty = 0, + PresencePenalty = 0 + }; + + // Aqui você faria a chamada para o Ollama + var resp = await _chatCompletionService.GetChatMessageContentAsync(classificationPrompt, executionSettings); + //var classification = await _ollamaService.GetResponse(classificationPrompt); + var classification = resp.Content ?? ""; + + return classification.ToUpper().Contains("TODOS") || classification.ToUpper().Contains("ALL") ? + SearchStrategy.TodosProjeto : + classification.ToUpper().Contains("FILTRADA") || classification.ToUpper().Contains("FILTERED") ? + SearchStrategy.SimilaridadeComFiltro : + SearchStrategy.SimilaridadeGlobal; + } + async Task BuscarTextoRelacionado(string pergunta) { var embeddingService = _kernel.GetRequiredService(); @@ -133,6 +224,25 @@ namespace ChatRAG.Services.ResponseService return cabecalho + string.Join("\n\n", resultadosFormatados); } + private async Task BuscarTodosRequisitosDoProjeto(string pergunta, string projectId) + { + var resultados = await _vectorSearchService.GetDocumentsByProjectAsync(projectId); + + if (!resultados.Any()) + return "Não encontrei respostas adequadas para a pergunta fornecida."; + + var cabecalho = $"Contexto encontrado para: '{pergunta}' ({resultados.Count} resultado(s)):\n\n"; + + var resultadosFormatados = resultados + .Select((item, index) => + $"=== CONTEXTO {index + 1} ===\n" + + $"Relevância: {item.Score:P1}\n" + + $"Conteúdo:\n{item.Content}") + .ToList(); + + return cabecalho + string.Join("\n\n", resultadosFormatados); + } + async Task BuscarTopTextosRelacionadosDinamico(string pergunta, string projectId, int size = 3) { @@ -265,6 +375,18 @@ namespace ChatRAG.Services.ResponseService } return dotProduct / (Math.Sqrt(normA) * Math.Sqrt(normB)); } + + public Task GetResponse(UserData userData, string projectId, string sessionId, string question) + { + return this.GetResponse(userData, projectId, sessionId, question, "pt"); + } + } + + public enum SearchStrategy + { + TodosProjeto, + SimilaridadeComFiltro, + SimilaridadeGlobal } } diff --git a/appsettings.json b/appsettings.json index c3bfa6d..b4ae3c3 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,11 +1,4 @@ { - "DomvsDatabase": { - "ConnectionString": "mongodb://admin:c4rn31r0@k3sw2:27017,k3ss1:27017/?authSource=admin", - "DatabaseName": "RAGProjects-dev-en", - "TextCollectionName": "Texts", - "ProjectCollectionName": "Groups", - "UserDataName": "UserData" - }, "Logging": { "LogLevel": { "Default": "Information", @@ -14,10 +7,10 @@ } }, "VectorDatabase": { - "Provider": "Qdrant", // 👈 Mude para "Qdrant" quando quiser testar + "Provider": "Qdrant", "MongoDB": { "ConnectionString": "mongodb://admin:c4rn31r0@k3sw2:27017,k3ss1:27017/?authSource=admin", - "DatabaseName": "RAGProjects-dev-en", + "DatabaseName": "RAGProjects-dev-pt", "TextCollectionName": "Texts", "ProjectCollectionName": "Groups", "UserDataName": "UserData"