Busca mais eficiente

This commit is contained in:
Ricardo Carneiro 2025-06-10 18:57:51 -03:00
parent fdb4fc6996
commit 8f4c8ad1af
5 changed files with 214 additions and 4 deletions

View File

@ -58,7 +58,7 @@ namespace ChatApi.Controllers
[HttpPost] [HttpPost]
[Route("saveproject")] [Route("savegroup")]
public async Task SaveSingleProject([FromBody] ProjectRequest project) public async Task SaveSingleProject([FromBody] ProjectRequest project)
{ {
var projectSave = project.MapTo<ProjectRequest, Project>(); var projectSave = project.MapTo<ProjectRequest, Project>();
@ -66,7 +66,7 @@ namespace ChatApi.Controllers
} }
[HttpGet] [HttpGet]
[Route("projects")] [Route("groups")]
public async Task<IEnumerable<ProjectRequest>> GetProjects() public async Task<IEnumerable<ProjectRequest>> GetProjects()
{ {
var projects = await _projectDataRepository.GetAsync(); var projects = await _projectDataRepository.GetAsync();

118
Data/SearchTextData.cs Normal file
View File

@ -0,0 +1,118 @@
using ChatRAG.Models;
using MongoDB.Driver;
using System.ComponentModel;
namespace ChatRAG.Data
{
public class SearchTextData
{
private readonly IMongoCollection<TextoComEmbedding> _textsCollection;
public SearchTextData(IMongoCollection<TextoComEmbedding> textsCollection)
{
_textsCollection = textsCollection;
}
// Busca otimizada com filtros pré-aplicados
public async Task<List<ResultadoSimilaridade>> 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<TextoComEmbedding>.Filter;
var filters = new List<FilterDefinition<TextoComEmbedding>>();
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<List<ResultadoSimilaridade>> 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<ResultadoSimilaridade> Membros { get; set; }
public int TotalMembros => Membros.Count;
}
}

View File

@ -23,6 +23,11 @@ namespace ChatRAG.Data
bookStoreDatabaseSettings.Value.TextCollectionName); bookStoreDatabaseSettings.Value.TextCollectionName);
} }
public IMongoCollection<TextoComEmbedding> GetCollection()
{
return _textsCollection;
}
public async Task<List<TextoComEmbedding>> GetAsync() => public async Task<List<TextoComEmbedding>> GetAsync() =>
await _textsCollection.Find(_ => true).ToListAsync(); await _textsCollection.Find(_ => true).ToListAsync();

View File

@ -90,7 +90,7 @@ builder.Services.AddSingleton<CryptUtil>();
//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin"); //var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin");
//Olllama //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("tinydolphin", new Uri("http://localhost:11435"));
//builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435")); //builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435"));

View File

@ -42,7 +42,8 @@ namespace ChatRAG.Services.ResponseService
stopWatch.Start(); stopWatch.Start();
//var resposta = await BuscarTextoRelacionado(question); //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(); 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."; return melhorTexto != null ? melhorTexto.Conteudo : "Não encontrei uma resposta adequada.";
} }
// Adicione esta nova rotina no seu ResponseRAGService
async Task<string> BuscarTopTextosRelacionadosDinamico(string pergunta, string projectId, int size = 3)
{
var embeddingService = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();
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<List<ResultadoSimilaridade>> 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<string> BuscarTopTextosRelacionados(string pergunta, string projectId, int size = 3) async Task<string> BuscarTopTextosRelacionados(string pergunta, string projectId, int size = 3)
{ {
var embeddingService = _kernel.GetRequiredService<ITextEmbeddingGenerationService>(); var embeddingService = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();