diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs index 87ba158..d3e76d7 100644 --- a/Controllers/ChatController.cs +++ b/Controllers/ChatController.cs @@ -58,7 +58,7 @@ namespace ChatApi.Controllers [HttpPost] - [Route("saveproject")] + [Route("savegroup")] public async Task SaveSingleProject([FromBody] ProjectRequest project) { var projectSave = project.MapTo(); @@ -66,7 +66,7 @@ namespace ChatApi.Controllers } [HttpGet] - [Route("projects")] + [Route("groups")] public async Task> GetProjects() { var projects = await _projectDataRepository.GetAsync(); diff --git a/Data/SearchTextData.cs b/Data/SearchTextData.cs new file mode 100644 index 0000000..6ac8aa1 --- /dev/null +++ b/Data/SearchTextData.cs @@ -0,0 +1,118 @@ +using ChatRAG.Models; +using MongoDB.Driver; +using System.ComponentModel; + +namespace ChatRAG.Data +{ + public class SearchTextData + { + private readonly IMongoCollection _textsCollection; + + public SearchTextData(IMongoCollection textsCollection) + { + _textsCollection = textsCollection; + } + + // Busca otimizada com filtros pré-aplicados + public async Task> BuscarSimilaridadeOtimizada( + double[] queryEmbedding, + string projectId = null, + string tipoDocumento = null, + double similaridadeMinima = 0.3, + int? limite = 3) + { + // 1. Aplica filtros primeiro para reduzir dataset + var filterBuilder = Builders.Filter; + var filters = new List>(); + + if (!string.IsNullOrEmpty(projectId)) + filters.Add(filterBuilder.Eq(x => x.ProjetoId, projectId)); + + if (!string.IsNullOrEmpty(tipoDocumento)) + filters.Add(filterBuilder.Eq(x => x.TipoDocumento, tipoDocumento)); + + var finalFilter = filters.Any() + ? filterBuilder.And(filters) + : filterBuilder.Empty; + + // 2. Busca apenas os documentos filtrados + var documentosFiltrados = await _textsCollection + .Find(finalFilter) + .ToListAsync(); + + // 3. Calcula similaridade apenas nos documentos filtrados + var resultados = documentosFiltrados + .AsParallel() // Paralelização para performance + .Select(doc => new ResultadoSimilaridade + { + Documento = doc, + Similaridade = CalcularSimilaridadeCoseno(queryEmbedding, doc.Embedding) + }) + .Where(r => r.Similaridade >= similaridadeMinima) + .OrderByDescending(r => r.Similaridade) + .ToList(); + + // 4. Aplica limite se especificado + if (limite.HasValue) + resultados = resultados.Take(limite.Value).ToList(); + + return resultados; + } + + // Busca dinâmica baseada em threshold de similaridade + public async Task> BuscarPorSimilaridadeDinamica( + double[] queryEmbedding, + string projectId, + double similaridadeMinima = 0.5, + int limiteSuperior = 50) // Limite máximo para evitar sobrecarga + { + var resultados = await BuscarSimilaridadeOtimizada( + queryEmbedding, + projectId, + similaridadeMinima: similaridadeMinima, + limite: limiteSuperior + ); + + // Se não encontrar resultados suficientes, relaxa o threshold + if (resultados.Count < 3) + { + resultados = await BuscarSimilaridadeOtimizada( + queryEmbedding, + projectId, + similaridadeMinima: similaridadeMinima * 0.7, // Reduz 30% + limite: limiteSuperior + ); + } + + return resultados; + } + + 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)); + } + + } + + public class ResultadoSimilaridade + { + public TextoComEmbedding Documento { get; set; } + public double Similaridade { get; set; } + } + + public class GrupoSimilaridade + { + public ResultadoSimilaridade Representante { get; set; } + public List Membros { get; set; } + public int TotalMembros => Membros.Count; + } +} diff --git a/Data/TextDataRepository.cs b/Data/TextDataRepository.cs index 536da5a..ebac305 100644 --- a/Data/TextDataRepository.cs +++ b/Data/TextDataRepository.cs @@ -23,6 +23,11 @@ namespace ChatRAG.Data bookStoreDatabaseSettings.Value.TextCollectionName); } + public IMongoCollection GetCollection() + { + return _textsCollection; + } + public async Task> GetAsync() => await _textsCollection.Find(_ => true).ToListAsync(); diff --git a/Program.cs b/Program.cs index 39fd3d3..60c9f4f 100644 --- a/Program.cs +++ b/Program.cs @@ -90,7 +90,7 @@ builder.Services.AddSingleton(); //var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin"); //Olllama -builder.Services.AddOllamaChatCompletion("llama3.1", new Uri("http://localhost:11434")); +builder.Services.AddOllamaChatCompletion("llama3.2", new Uri("http://localhost:11434")); //builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); //builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435")); diff --git a/Services/ResponseService/ResponseCompanyService.cs b/Services/ResponseService/ResponseCompanyService.cs index 583035c..4795e19 100644 --- a/Services/ResponseService/ResponseCompanyService.cs +++ b/Services/ResponseService/ResponseCompanyService.cs @@ -42,7 +42,8 @@ namespace ChatRAG.Services.ResponseService stopWatch.Start(); //var resposta = await BuscarTextoRelacionado(question); - var resposta = await BuscarTopTextosRelacionados(question, projectId); + //var resposta = await BuscarTopTextosRelacionados(question, projectId); + var resposta = await BuscarTopTextosRelacionadosDinamico(question, projectId); var projectData = (await _projectDataRepository.GetAsync()).FirstOrDefault(); @@ -87,6 +88,92 @@ namespace ChatRAG.Services.ResponseService return melhorTexto != null ? melhorTexto.Conteudo : "Não encontrei uma resposta adequada."; } + // Adicione esta nova rotina no seu ResponseRAGService + + 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();