ChatRAG/Data/ChromaProjectDataRepository.cs
2025-06-21 14:20:07 -03:00

332 lines
12 KiB
C#

using ChatRAG.Models;
using ChatRAG.Services.Contracts;
using ChatRAG.Services.SearchVectors;
using ChatRAG.Settings.ChatRAG.Configuration;
using Microsoft.Extensions.Options;
using System.Text;
using System.Text.Json;
namespace ChatRAG.Data
{
public class ChromaProjectDataRepository : IProjectDataRepository
{
private readonly HttpClient _httpClient;
private readonly string _collectionName;
private readonly ILogger<ChromaProjectDataRepository> _logger;
public ChromaProjectDataRepository(
IOptions<VectorDatabaseSettings> settings,
HttpClient httpClient,
ILogger<ChromaProjectDataRepository> logger)
{
var chromaSettings = settings.Value.Chroma ?? throw new ArgumentNullException("Chroma settings not configured");
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri($"http://{chromaSettings.Host}:{chromaSettings.Port}");
_collectionName = "projects"; // Collection separada para projetos
_logger = logger;
InitializeAsync().GetAwaiter().GetResult();
}
private async Task InitializeAsync()
{
try
{
// Verificar se a collection existe, se não, criar
var collections = await GetCollectionsAsync();
if (!collections.Contains(_collectionName))
{
await CreateProjectsCollection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao inicializar collection de projetos no Chroma");
}
}
public async Task<List<Project>> GetAsync()
{
try
{
var query = new
{
where = new { entity_type = "project" },
include = new[] { "documents", "metadatas" }
};
var json = JsonSerializer.Serialize(query);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"/api/v2/collections/{_collectionName}/get", content);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Erro ao buscar projetos no Chroma");
return new List<Project>();
}
var result = await response.Content.ReadAsStringAsync();
var getResult = JsonSerializer.Deserialize<ChromaGetResult>(result);
var projects = new List<Project>();
if (getResult?.ids?.Length > 0)
{
for (int i = 0; i < getResult.ids.Length; i++)
{
var metadata = getResult.metadatas?[i];
if (metadata != null)
{
projects.Add(new Project
{
Id = metadata.GetValueOrDefault("id")?.ToString() ?? getResult.ids[i],
Nome = metadata.GetValueOrDefault("nome")?.ToString() ?? "",
Descricao = metadata.GetValueOrDefault("descricao")?.ToString() ?? ""
});
}
}
}
return projects;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao recuperar projetos do Chroma");
return new List<Project>();
}
}
public async Task<Project?> GetAsync(string id)
{
try
{
var query = new
{
ids = new[] { id },
include = new[] { "metadatas" }
};
var json = JsonSerializer.Serialize(query);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"/api/v2/collections/{_collectionName}/get", content);
if (!response.IsSuccessStatusCode)
{
return null;
}
var result = await response.Content.ReadAsStringAsync();
var getResult = JsonSerializer.Deserialize<ChromaGetResult>(result);
if (getResult?.ids?.Length > 0 && getResult.metadatas?[0] != null)
{
var metadata = getResult.metadatas[0];
return new Project
{
Id = metadata.GetValueOrDefault("id")?.ToString() ?? id,
Nome = metadata.GetValueOrDefault("nome")?.ToString() ?? "",
Descricao = metadata.GetValueOrDefault("descricao")?.ToString() ?? ""
};
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao buscar projeto {Id} no Chroma", 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 document = new
{
ids = new[] { id },
documents = new[] { $"Projeto: {newProject.Nome}" },
metadatas = new[] { new Dictionary<string, object>
{
["id"] = id,
["nome"] = newProject.Nome,
["descricao"] = newProject.Descricao,
["created_at"] = DateTime.UtcNow.ToString("O"),
["entity_type"] = "project"
}},
embeddings = new[] { new double[384] } // Vector dummy
};
var json = JsonSerializer.Serialize(document);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"/api/v2/collections/{_collectionName}/add", content);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Erro ao criar projeto no Chroma: {error}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao criar projeto no Chroma");
throw;
}
}
public async Task UpdateAsync(string id, Project updatedProject)
{
try
{
// Chroma não tem update direto, então fazemos upsert
updatedProject.Id = id;
var document = new
{
ids = new[] { id },
documents = new[] { $"Projeto: {updatedProject.Nome}" },
metadatas = new[] { new Dictionary<string, object>
{
["id"] = id,
["nome"] = updatedProject.Nome,
["descricao"] = updatedProject.Descricao,
["updated_at"] = DateTime.UtcNow.ToString("O"),
["entity_type"] = "project"
}},
embeddings = new[] { new double[384] }
};
var json = JsonSerializer.Serialize(document);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"/api/v2/collections/{_collectionName}/upsert", content);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Erro ao atualizar projeto no Chroma: {error}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao atualizar projeto {Id} no Chroma", 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 Chroma");
throw;
}
}
public async Task RemoveAsync(string id)
{
try
{
var deleteRequest = new
{
ids = new[] { id }
};
var json = JsonSerializer.Serialize(deleteRequest);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"/api/v2/collections/{_collectionName}/delete", content);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
_logger.LogWarning("Erro ao remover projeto {Id} do Chroma: {Error}", id, error);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao remover projeto {Id} do Chroma", id);
throw;
}
}
private async Task<string[]> GetCollectionsAsync()
{
try
{
var response = await _httpClient.GetAsync("/api/v2/collections");
if (!response.IsSuccessStatusCode)
{
return Array.Empty<string>();
}
var content = await response.Content.ReadAsStringAsync();
var collections = JsonSerializer.Deserialize<string[]>(content);
return collections ?? Array.Empty<string>();
}
catch
{
return Array.Empty<string>();
}
}
private async Task CreateProjectsCollection()
{
var collection = new
{
name = _collectionName,
metadata = new
{
description = "Projects Collection",
created_at = DateTime.UtcNow.ToString("O")
}
};
var json = JsonSerializer.Serialize(collection);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/v2/collections", content);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Erro ao criar collection de projetos: {error}");
}
_logger.LogInformation("Collection de projetos '{CollectionName}' criada no Chroma", _collectionName);
}
}
public class ChromaGetResult
{
public string[] ids { get; set; } = Array.Empty<string>();
public string[] documents { get; set; } = Array.Empty<string>();
public Dictionary<string, object>[]? metadatas { get; set; }
}
}