using ChatApi; using ChatApi.Models; using ChatRAG.Contracts.VectorSearch; using ChatRAG.Data; using ChatRAG.Models; using ChatRAG.Services.Contracts; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; 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. namespace ChatRAG.Services.ResponseService { public class ResponseRAGService : IResponseService { private readonly ChatHistoryService _chatHistoryService; private readonly Kernel _kernel; private readonly TextFilter _textFilter; private readonly TextDataRepository _textDataRepository; private readonly ProjectDataRepository _projectDataRepository; private readonly IChatCompletionService _chatCompletionService; private readonly IVectorSearchService _vectorSearchService; public ResponseRAGService( ChatHistoryService chatHistoryService, Kernel kernel, TextFilter textFilter, TextDataRepository textDataRepository, ProjectDataRepository projectDataRepository, IChatCompletionService chatCompletionService, IVectorSearchService vectorSearchService, ITextDataService textDataService) { this._chatHistoryService = chatHistoryService; this._kernel = kernel; this._textFilter = textFilter; this._textDataRepository = textDataRepository; this._projectDataRepository = projectDataRepository; this._chatCompletionService = chatCompletionService; this._vectorSearchService = vectorSearchService; } public async Task GetResponse(UserData userData, string projectId, string sessionId, string question) { 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 projectData = (await _projectDataRepository.GetAsync()).FirstOrDefault(); 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. 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."; // Usage question = string.Format(basePrompt, project, resposta, question); ChatHistory history = _chatHistoryService.GetSumarizer(sessionId); history.AddUserMessage(question); var response = await _chatCompletionService.GetChatMessageContentAsync(history); history.AddMessage(response.Role, response.Content ?? ""); _chatHistoryService.UpdateHistory(sessionId, history); stopWatch.Stop(); return $"{response.Content ?? ""}\n\nTempo: {stopWatch.ElapsedMilliseconds / 1000}s"; } async Task BuscarTextoRelacionado(string pergunta) { var embeddingService = _kernel.GetRequiredService(); var embeddingPergunta = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(pergunta)); var embeddingArrayPergunta = embeddingPergunta.ToArray().Select(e => (double)e).ToArray(); var textos = await _textDataRepository.GetAsync(); TextoComEmbedding melhorTexto = null; double melhorSimilaridade = -1.0; foreach (var texto in textos) { double similaridade = CalcularSimilaridadeCoseno(embeddingArrayPergunta, texto.Embedding); if (similaridade > melhorSimilaridade) { melhorSimilaridade = similaridade; melhorTexto = texto; } } return melhorTexto != null ? melhorTexto.Conteudo : "Não encontrei uma resposta adequada."; } private async Task BuscarTopTextosRelacionadosComInterface(string pergunta, string projectId) { var embeddingService = _kernel.GetRequiredService(); var embeddingPergunta = await embeddingService.GenerateEmbeddingAsync( _textFilter.ToLowerAndWithoutAccents(pergunta)); var embeddingArray = embeddingPergunta.ToArray().Select(e => (double)e).ToArray(); var resultados = await _vectorSearchService.SearchSimilarDynamicAsync(embeddingArray, projectId, 0.5, 3); 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) { var embeddingService = _kernel.GetRequiredService(); var embeddingPergunta = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(pergunta)); var embeddingArrayPergunta = embeddingPergunta.ToArray().Select(e => (double)e).ToArray(); // Cria instância da classe de busca otimizada var searchTextData = new SearchTextData(_textDataRepository.GetCollection()); // Você precisará expor a collection // Busca dinâmica com threshold adaptativo var resultados = await BuscarSimilaridadeHibridaAdaptativa( searchTextData, embeddingArrayPergunta, projectId, size ); 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.Similaridade:P1}\n" + $"Conteúdo:\n{item.Documento.Conteudo}") .ToList(); return cabecalho + string.Join("\n\n", resultadosFormatados); } // Método auxiliar para busca híbrida adaptativa private async Task> BuscarSimilaridadeHibridaAdaptativa( SearchTextData searchTextData, double[] embeddingArray, string projectId, int quantidadeDesejada) { // Estratégia 1: Busca com threshold alto (0.5) limitado à quantidade desejada var resultados = await searchTextData.BuscarSimilaridadeOtimizada( embeddingArray, projectId, similaridadeMinima: 0.5, limite: quantidadeDesejada ); // Se conseguiu a quantidade desejada com qualidade alta, retorna if (resultados.Count >= quantidadeDesejada) { return resultados.Take(quantidadeDesejada).ToList(); } // Estratégia 2: Se não conseguiu o suficiente, busca com threshold médio if (resultados.Count < quantidadeDesejada) { resultados = await searchTextData.BuscarSimilaridadeOtimizada( embeddingArray, projectId, similaridadeMinima: 0.35, limite: quantidadeDesejada * 2 // Busca mais para ter opções ); if (resultados.Count >= quantidadeDesejada) { return resultados.Take(quantidadeDesejada).ToList(); } } // Estratégia 3: Se ainda não conseguiu, busca com threshold baixo if (resultados.Count < quantidadeDesejada) { resultados = await searchTextData.BuscarSimilaridadeOtimizada( embeddingArray, projectId, similaridadeMinima: 0.2, limite: quantidadeDesejada * 3 ); } // Retorna o que conseguiu encontrar, no máximo a quantidade desejada return resultados.Take(quantidadeDesejada).ToList(); } async Task BuscarTopTextosRelacionados(string pergunta, string projectId, int size = 3) { var embeddingService = _kernel.GetRequiredService(); var embeddingPergunta = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(pergunta)); var embeddingArrayPergunta = embeddingPergunta.ToArray().Select(e => (double)e).ToArray(); var textos = await _textDataRepository.GetByProjectIdAsync(projectId); var melhoresTextos = textos .Select(texto => new { Conteudo = texto.Conteudo, Similaridade = CalcularSimilaridadeCoseno(embeddingArrayPergunta, texto.Embedding) }) .Where(x => x.Similaridade > 0.3) .OrderByDescending(x => x.Similaridade) .Take(3) .ToList(); if (!melhoresTextos.Any()) return "Não encontrei respostas adequadas para a pergunta fornecida."; var cabecalho = $"Contexto encontrado para: '{pergunta}' ({melhoresTextos.Count} resultado(s)):\n\n"; var resultadosFormatados = melhoresTextos .Select((item, index) => $"=== CONTEXTO {index + 1} ===\n" + $"Relevância: {item.Similaridade:P1}\n" + $"Conteúdo:\n{item.Conteudo}") .ToList(); return cabecalho + string.Join("\n\n", resultadosFormatados); } double CalcularSimilaridadeCoseno(double[] embedding1, double[] embedding2) { double dotProduct = 0.0; double normA = 0.0; double normB = 0.0; for (int i = 0; i < embedding1.Length; i++) { dotProduct += embedding1[i] * embedding2[i]; normA += Math.Pow(embedding1[i], 2); normB += Math.Pow(embedding2[i], 2); } return dotProduct / (Math.Sqrt(normA) * Math.Sqrt(normB)); } } } #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.