mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-06-06 16:14:40 +08:00
feat: Support multiple vector database selection, add InMemory and Qdrant adapters, introduce admin dashboard
This commit is contained in:
18
Services/VectorDb/IVectorDbService.cs
Normal file
18
Services/VectorDb/IVectorDbService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Foxel.Models.Vector;
|
||||
|
||||
namespace Foxel.Services.VectorDB;
|
||||
|
||||
public interface IVectorDbService
|
||||
{
|
||||
Task BuildUserPictureVectorsAsync();
|
||||
Task<List<PictureVector>> SearchAsync(ReadOnlyMemory<float> query, int? userId, int topK = 10);
|
||||
Task AddPictureToUserCollectionAsync(int userId, PictureVector pictureVector);
|
||||
Task RemovePictureFromUserCollectionAsync(int userId, int pictureId);
|
||||
Task ClearVectorsAsync();
|
||||
}
|
||||
|
||||
public enum VectorDbType
|
||||
{
|
||||
InMemory,
|
||||
Qdrant
|
||||
}
|
||||
@@ -5,19 +5,18 @@ using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
|
||||
namespace Foxel.Services.VectorDB;
|
||||
|
||||
public class VectorDbService
|
||||
public class InMemoryVectorDbService : IVectorDbService
|
||||
{
|
||||
private readonly VectorStore _vectorStore;
|
||||
private readonly IDbContextFactory<MyDbContext> _contextFactory;
|
||||
|
||||
public VectorDbService(IDbContextFactory<MyDbContext> contextFactory)
|
||||
public InMemoryVectorDbService(IDbContextFactory<MyDbContext> contextFactory)
|
||||
{
|
||||
_vectorStore = new InMemoryVectorStore();
|
||||
_contextFactory = contextFactory;
|
||||
_ = InitData();
|
||||
}
|
||||
|
||||
private async Task InitData()
|
||||
public async Task BuildUserPictureVectorsAsync()
|
||||
{
|
||||
await using var dbContext = await _contextFactory.CreateDbContextAsync();
|
||||
var userPictures = dbContext.Pictures
|
||||
@@ -30,12 +29,12 @@ public class VectorDbService
|
||||
{
|
||||
int userId = group.Key;
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = _vectorStore.GetCollection<int, PictureVector>(collectionName);
|
||||
var collection = _vectorStore.GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
|
||||
var picVectors = group.Select(p => new PictureVector
|
||||
{
|
||||
Id = p.Id,
|
||||
Id = (ulong)p.Id,
|
||||
Name = p.Name,
|
||||
Embedding = p.Embedding
|
||||
}).ToList();
|
||||
@@ -50,7 +49,7 @@ public class VectorDbService
|
||||
public async Task<List<PictureVector>> SearchAsync(ReadOnlyMemory<float> query, int? userId, int topK = 10)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = _vectorStore.GetCollection<int, PictureVector>(collectionName);
|
||||
var collection = _vectorStore.GetCollection<ulong, PictureVector>(collectionName);
|
||||
var results = collection.SearchAsync(query, topK);
|
||||
var res = new List<PictureVector>();
|
||||
await foreach (var record in results)
|
||||
@@ -64,7 +63,7 @@ public class VectorDbService
|
||||
public async Task AddPictureToUserCollectionAsync(int userId, PictureVector pictureVector)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = _vectorStore.GetCollection<int, PictureVector>(collectionName);
|
||||
var collection = _vectorStore.GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.UpsertAsync(pictureVector);
|
||||
}
|
||||
@@ -72,8 +71,18 @@ public class VectorDbService
|
||||
public async Task RemovePictureFromUserCollectionAsync(int userId, int pictureId)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = _vectorStore.GetCollection<int, PictureVector>(collectionName);
|
||||
var collection = _vectorStore.GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.DeleteAsync(pictureId);
|
||||
await collection.DeleteAsync((ulong)pictureId);
|
||||
}
|
||||
|
||||
public async Task ClearVectorsAsync()
|
||||
{
|
||||
var collections = _vectorStore.ListCollectionNamesAsync();
|
||||
await foreach (var name in collections)
|
||||
{
|
||||
var collection = _vectorStore.GetCollection<ulong, PictureVector>(name);
|
||||
await collection.EnsureCollectionDeletedAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Services/VectorDb/QdrantVectorDbService.cs
Normal file
112
Services/VectorDb/QdrantVectorDbService.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using Foxel.Models.Vector;
|
||||
using Foxel.Services.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.Qdrant;
|
||||
using Qdrant.Client;
|
||||
|
||||
namespace Foxel.Services.VectorDB;
|
||||
|
||||
public class QdrantVectorDbService : IVectorDbService
|
||||
{
|
||||
private readonly IDbContextFactory<MyDbContext> _contextFactory;
|
||||
private readonly IConfigService _configService;
|
||||
private VectorStore? _vectorStore;
|
||||
private string? _currentHost;
|
||||
private string? _currentApiKey;
|
||||
|
||||
public QdrantVectorDbService(IDbContextFactory<MyDbContext> contextFactory, IConfigService configService)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
private VectorStore GetVectorStore()
|
||||
{
|
||||
string host = _configService["VectorDb:QdrantHost"] ??
|
||||
"b63da3b8-c126-4546-95ab-176f907fb1ef.eu-central-1-0.aws.cloud.qdrant.io";
|
||||
|
||||
string apiKey = _configService["VectorDb:QdrantApiKey"] ??
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.QzQN4cyo5mldCi9ohe0Aqap4fpTMuSEMGkXtkgBTNQI";
|
||||
|
||||
if (_vectorStore == null || _currentHost != host || _currentApiKey != apiKey)
|
||||
{
|
||||
var qdrantClient = new QdrantClient(host, https: true, apiKey: apiKey);
|
||||
_vectorStore = new QdrantVectorStore(qdrantClient, true);
|
||||
|
||||
_currentHost = host;
|
||||
_currentApiKey = apiKey;
|
||||
}
|
||||
|
||||
return _vectorStore;
|
||||
}
|
||||
|
||||
public async Task BuildUserPictureVectorsAsync()
|
||||
{
|
||||
await using var dbContext = await _contextFactory.CreateDbContextAsync();
|
||||
var userPictures = dbContext.Pictures
|
||||
.Where(p => p.UserId != null && p.Embedding != null)
|
||||
.Select(p => new { p.Id, p.Name, p.Embedding, p.UserId })
|
||||
.GroupBy(p => p.UserId!.Value)
|
||||
.ToList();
|
||||
|
||||
foreach (var group in userPictures)
|
||||
{
|
||||
int userId = group.Key;
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = GetVectorStore().GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
var picVectors = group.Select(p => new PictureVector
|
||||
{
|
||||
Id = (ulong)p.Id,
|
||||
Name = p.Name,
|
||||
Embedding = p.Embedding
|
||||
}).ToList();
|
||||
|
||||
foreach (var picVector in picVectors)
|
||||
{
|
||||
await collection.UpsertAsync(picVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<PictureVector>> SearchAsync(ReadOnlyMemory<float> query, int? userId, int topK = 10)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = GetVectorStore().GetCollection<ulong, PictureVector>(collectionName);
|
||||
var results = collection.SearchAsync(query, topK);
|
||||
var res = new List<PictureVector>();
|
||||
await foreach (var record in results)
|
||||
{
|
||||
res.Add(record.Record);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public async Task AddPictureToUserCollectionAsync(int userId, PictureVector pictureVector)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = GetVectorStore().GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.UpsertAsync(pictureVector);
|
||||
}
|
||||
|
||||
public async Task RemovePictureFromUserCollectionAsync(int userId, int pictureId)
|
||||
{
|
||||
var collectionName = $"picture_{userId}";
|
||||
var collection = GetVectorStore().GetCollection<ulong, PictureVector>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.DeleteAsync((ulong)pictureId);
|
||||
}
|
||||
|
||||
public async Task ClearVectorsAsync()
|
||||
{
|
||||
var collections = GetVectorStore().ListCollectionNamesAsync();
|
||||
await foreach (var name in collections)
|
||||
{
|
||||
var collection = GetVectorStore().GetCollection<ulong, PictureVector>(name);
|
||||
await collection.EnsureCollectionDeletedAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Services/VectorDb/VectorDbInitializer.cs
Normal file
30
Services/VectorDb/VectorDbInitializer.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Foxel.Services.Configuration;
|
||||
|
||||
namespace Foxel.Services.VectorDB;
|
||||
|
||||
public class VectorDbInitializer : IHostedService
|
||||
{
|
||||
private readonly IVectorDbService _vectorDbService;
|
||||
private readonly IConfigService _configService;
|
||||
private const string VectorDbTypeConfigKey = "VectorDb:Type";
|
||||
|
||||
public VectorDbInitializer(IVectorDbService vectorDbService, IConfigService configService)
|
||||
{
|
||||
_vectorDbService = vectorDbService;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var dbTypeStr = await _configService.GetValueAsync(VectorDbTypeConfigKey) ?? "InMemory";
|
||||
if (string.Equals(dbTypeStr, "InMemory", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await _vectorDbService.BuildUserPictureVectorsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
95
Services/VectorDb/VectorDbManager.cs
Normal file
95
Services/VectorDb/VectorDbManager.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Foxel.Models.Vector;
|
||||
using Foxel.Services.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Foxel.Services.VectorDB;
|
||||
|
||||
public class VectorDbManager(IServiceProvider serviceProvider, IConfigService configService)
|
||||
: IVectorDbService
|
||||
{
|
||||
private IVectorDbService? _currentService;
|
||||
private readonly Lock _lock = new();
|
||||
private const string VectorDbTypeConfigKey = "VectorDb:Type";
|
||||
|
||||
private IVectorDbService GetCurrentService()
|
||||
{
|
||||
if (_currentService == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_currentService == null)
|
||||
{
|
||||
var dbTypeStr = configService[VectorDbTypeConfigKey] ?? "InMemory";
|
||||
if (Enum.TryParse<VectorDbType>(dbTypeStr, true, out var dbType))
|
||||
{
|
||||
_currentService = CreateVectorDbService(dbType);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentService = CreateVectorDbService(VectorDbType.InMemory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _currentService;
|
||||
}
|
||||
|
||||
public async Task SwitchVectorDbAsync(VectorDbType type)
|
||||
{
|
||||
IVectorDbService oldService = null;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var currentType = GetCurrentVectorDbType();
|
||||
if (currentType == type && _currentService != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
oldService = _currentService;
|
||||
_currentService = CreateVectorDbService(type);
|
||||
}
|
||||
|
||||
await configService.SetConfigAsync(VectorDbTypeConfigKey, type.ToString(), "向量数据库类型");
|
||||
if (type == VectorDbType.InMemory)
|
||||
{
|
||||
await BuildUserPictureVectorsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public VectorDbType GetCurrentVectorDbType()
|
||||
{
|
||||
var currentService = GetCurrentService();
|
||||
if (currentService is InMemoryVectorDbService)
|
||||
return VectorDbType.InMemory;
|
||||
if (currentService is QdrantVectorDbService)
|
||||
return VectorDbType.Qdrant;
|
||||
return VectorDbType.InMemory;
|
||||
}
|
||||
|
||||
private IVectorDbService CreateVectorDbService(VectorDbType type)
|
||||
{
|
||||
var dbContextFactory = serviceProvider.GetRequiredService<IDbContextFactory<MyDbContext>>();
|
||||
|
||||
return type switch
|
||||
{
|
||||
VectorDbType.InMemory => new InMemoryVectorDbService(dbContextFactory),
|
||||
VectorDbType.Qdrant => new QdrantVectorDbService(dbContextFactory, configService),
|
||||
_ => new InMemoryVectorDbService(dbContextFactory)
|
||||
};
|
||||
}
|
||||
|
||||
public Task BuildUserPictureVectorsAsync() => GetCurrentService().BuildUserPictureVectorsAsync();
|
||||
|
||||
public Task<List<PictureVector>> SearchAsync(ReadOnlyMemory<float> query, int? userId, int topK = 10)
|
||||
=> GetCurrentService().SearchAsync(query, userId, topK);
|
||||
|
||||
public Task AddPictureToUserCollectionAsync(int userId, PictureVector pictureVector)
|
||||
=> GetCurrentService().AddPictureToUserCollectionAsync(userId, pictureVector);
|
||||
|
||||
public Task RemovePictureFromUserCollectionAsync(int userId, int pictureId)
|
||||
=> GetCurrentService().RemovePictureFromUserCollectionAsync(userId, pictureId);
|
||||
|
||||
public Task ClearVectorsAsync() => GetCurrentService().ClearVectorsAsync();
|
||||
}
|
||||
Reference in New Issue
Block a user