269 lines
8.6 KiB
C#
269 lines
8.6 KiB
C#
using ChatRAG.Models;
|
|
using ChatRAG.Services.Contracts;
|
|
using ChatRAG.Settings.ChatRAG.Configuration;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Qdrant.Client;
|
|
using Qdrant.Client.Grpc;
|
|
using Google.Protobuf;
|
|
|
|
namespace ChatRAG.Data
|
|
{
|
|
public class QdrantProjectDataRepository : IProjectDataRepository
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly string _collectionName;
|
|
private readonly ILogger<QdrantProjectDataRepository> _logger;
|
|
private readonly QdrantClient _qdrantClient;
|
|
private volatile bool _collectionInitialized = false;
|
|
private readonly SemaphoreSlim _initializationSemaphore = new(1, 1);
|
|
|
|
public QdrantProjectDataRepository(
|
|
IOptions<VectorDatabaseSettings> settings,
|
|
HttpClient httpClient,
|
|
ILogger<QdrantProjectDataRepository> logger)
|
|
{
|
|
var qdrantSettings = settings.Value.Qdrant ?? throw new ArgumentNullException("Qdrant settings not configured");
|
|
_httpClient = httpClient;
|
|
_httpClient.BaseAddress = new Uri($"http://{qdrantSettings.Host}:{qdrantSettings.Port}");
|
|
_collectionName = qdrantSettings.GroupsCollectionName;
|
|
_logger = logger;
|
|
|
|
// Inicializa o QdrantClient - use GRPC (porta 6334) para melhor performance
|
|
_qdrantClient = new QdrantClient(qdrantSettings.Host, port: 6334, https: false);
|
|
|
|
InitializeAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private async Task InitializeAsync()
|
|
{
|
|
try
|
|
{
|
|
if (_collectionInitialized) return;
|
|
|
|
await _initializationSemaphore.WaitAsync();
|
|
|
|
var exists = await _qdrantClient.CollectionExistsAsync(_collectionName);
|
|
if (!exists)
|
|
{
|
|
await CreateProjectsCollection();
|
|
}
|
|
_collectionInitialized = true;
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao inicializar collection de projetos no Qdrant");
|
|
}
|
|
finally
|
|
{
|
|
_initializationSemaphore.Release();
|
|
}
|
|
}
|
|
|
|
public async Task<List<Project>> GetAsync()
|
|
{
|
|
try
|
|
{
|
|
//var scrollRequest = new ScrollPoints
|
|
//{
|
|
// CollectionName = _collectionName,
|
|
// Filter = new Filter(), // Filtro vazio
|
|
// Limit = 1000,
|
|
// WithPayload = true,
|
|
// WithVectors = false
|
|
//};
|
|
|
|
//var result = await _qdrantClient.ScrollAsync(_collectionName, scrollRequest);
|
|
var result = await _qdrantClient.ScrollAsync(_collectionName, new Filter(), 1000, null, true, false);
|
|
|
|
return result.Result.Select(ConvertToProject)
|
|
.Where(p => p != null)
|
|
.ToList()!;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao recuperar projetos do Qdrant");
|
|
return new List<Project>();
|
|
}
|
|
}
|
|
|
|
public async Task<Project?> GetAsync(string id)
|
|
{
|
|
try
|
|
{
|
|
var points = await _qdrantClient.RetrieveAsync(
|
|
_collectionName,
|
|
new[] { new PointId { Uuid = id } },
|
|
withPayload: true,
|
|
withVectors: false
|
|
);
|
|
|
|
var point = points.FirstOrDefault();
|
|
return point != null ? ConvertToProject(point) : null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao buscar projeto {Id} no Qdrant", id);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task CreateAsync(Project newProject)
|
|
{
|
|
try
|
|
{
|
|
var id = string.IsNullOrEmpty(newProject.Id) ? Guid.NewGuid().ToString() : newProject.Id;
|
|
newProject.Id = id;
|
|
|
|
var point = new PointStruct
|
|
{
|
|
Id = new PointId { Uuid= id },
|
|
Vectors = new float[384], // Vector dummy para projetos
|
|
Payload =
|
|
{
|
|
["id"] = newProject.Id,
|
|
["nome"] = newProject.Nome,
|
|
["descricao"] = newProject.Descricao,
|
|
["created_at"] = DateTime.UtcNow.ToString("O"),
|
|
["entity_type"] = "project"
|
|
}
|
|
};
|
|
|
|
await _qdrantClient.UpsertAsync(_collectionName, new[] { point });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao criar projeto no Qdrant");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task UpdateAsync(string id, Project updatedProject)
|
|
{
|
|
try
|
|
{
|
|
updatedProject.Id = id;
|
|
|
|
var point = new PointStruct
|
|
{
|
|
Id = new PointId { Uuid = id },
|
|
Vectors = new float[384], // Vector dummy
|
|
Payload =
|
|
{
|
|
["id"] = updatedProject.Id,
|
|
["nome"] = updatedProject.Nome,
|
|
["descricao"] = updatedProject.Descricao,
|
|
["updated_at"] = DateTime.UtcNow.ToString("O"),
|
|
["entity_type"] = "project"
|
|
}
|
|
};
|
|
|
|
await _qdrantClient.UpsertAsync(_collectionName, new[] { point });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao atualizar projeto {Id} no Qdrant", id);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task SaveAsync(Project project)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(project.Id))
|
|
{
|
|
await CreateAsync(project);
|
|
}
|
|
else
|
|
{
|
|
var existing = await GetAsync(project.Id);
|
|
if (existing == null)
|
|
{
|
|
await CreateAsync(project);
|
|
}
|
|
else
|
|
{
|
|
await UpdateAsync(project.Id, project);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao salvar projeto no Qdrant");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task RemoveAsync(string id)
|
|
{
|
|
try
|
|
{
|
|
await _qdrantClient.DeleteAsync(
|
|
_collectionName,
|
|
new[] { new PointId { Uuid = id }.Num }
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Erro ao remover projeto {Id} do Qdrant", id);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task CreateProjectsCollection()
|
|
{
|
|
var vectorParams = new VectorParams
|
|
{
|
|
Size = 384,
|
|
Distance = Distance.Cosine
|
|
};
|
|
|
|
await _qdrantClient.CreateCollectionAsync(_collectionName, vectorParams);
|
|
|
|
_logger.LogInformation("Collection de projetos '{CollectionName}' criada no Qdrant", _collectionName);
|
|
}
|
|
|
|
private static Project? ConvertToProject(RetrievedPoint point)
|
|
{
|
|
try
|
|
{
|
|
if (point.Payload == null) return null;
|
|
|
|
return new Project
|
|
{
|
|
Id = point.Payload.TryGetValue("id", out var idValue) ? idValue.StringValue : point.Id.ToString(),
|
|
Nome = point.Payload.TryGetValue("nome", out var nomeValue) ? nomeValue.StringValue : "",
|
|
Descricao = point.Payload.TryGetValue("descricao", out var descValue) ? descValue.StringValue : ""
|
|
};
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class QdrantScrollResult
|
|
{
|
|
public QdrantScrollData? result { get; set; }
|
|
}
|
|
|
|
public class QdrantScrollData
|
|
{
|
|
public QdrantPoint[]? points { get; set; }
|
|
}
|
|
|
|
public class QdrantPointResult
|
|
{
|
|
public QdrantPoint? result { get; set; }
|
|
}
|
|
|
|
public class QdrantPoint
|
|
{
|
|
public string? id { get; set; }
|
|
public Dictionary<string, object>? payload { get; set; }
|
|
}
|
|
} |