using BuildFlow.Domain.Entities; using BuildFlow.Domain.Enums; using BuildFlow.Infrastructure.AI; using BuildFlow.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Xunit; namespace BuildFlow.Rag.Tests; /// /// AI 运行时模型配置解析测试。确保 Chat/Embedding 调用优先使用模型管理里配置的默认本地模型。 /// public class AiModelRuntimeConfigResolverTests { [Fact] public async Task ResolveAsyncShouldReplaceLegacyRagOnlyChatInstructions() { var services = new ServiceCollection(); var dbName = $"rag-chat-model-{Guid.NewGuid():N}"; services.AddDbContext(options => options.UseInMemoryDatabase(dbName)); services.AddScoped(); services.AddSingleton(Options.Create(new AiOptions { BaseUrl = "http://localhost:1234/v1", ApiKey = "lm-studio", ChatModel = "fallback-chat", EmbeddingModel = "fallback-embedding", DefaultInstructions = "你是 BuildFlow AI 助手。没有知识库上下文时按普通对话直接回答。", })); await using var provider = services.BuildServiceProvider(); await using (var scope = provider.CreateAsyncScope()) { var db = scope.ServiceProvider.GetRequiredService(); await db.AiModels.AddAsync(new AiModel { Id = Guid.NewGuid(), Name = "旧对话模型", Provider = ModelProvider.LMStudio, ModelId = "qwen/qwen3.6-27b", BaseUrl = "http://localhost:1234/v1", ApiKey = "lm-studio", ModelType = ModelType.Chat, IsActive = true, IsDefault = true, DefaultInstructions = "你是一个RAG知识库助手,基于提供的上下文准确回答用户问题。如果上下文中没有相关信息,请如实说明。", CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }); await db.SaveChangesAsync(); } await using (var scope = provider.CreateAsyncScope()) { var resolver = scope.ServiceProvider.GetRequiredService(); var config = await resolver.ResolveAsync(ModelType.Chat, CancellationToken.None); Assert.Equal("你是 BuildFlow AI 助手。没有知识库上下文时按普通对话直接回答。", config.DefaultInstructions); } } [Fact] public async Task ResolveAsyncShouldPreferDefaultEmbeddingModelFromDatabase() { var services = new ServiceCollection(); var dbName = $"rag-ai-model-{Guid.NewGuid():N}"; services.AddDbContext(options => options.UseInMemoryDatabase(dbName)); services.AddScoped(); services.AddSingleton(Options.Create(new AiOptions { BaseUrl = "https://fallback.example/v1", ApiKey = "fallback-key", ChatModel = "fallback-chat", EmbeddingModel = "fallback-embedding", DefaultInstructions = "fallback instructions", })); await using var provider = services.BuildServiceProvider(); await using (var scope = provider.CreateAsyncScope()) { var db = scope.ServiceProvider.GetRequiredService(); await db.AiModels.AddRangeAsync( new AiModel { Id = Guid.NewGuid(), Name = "旧嵌入模型", Provider = ModelProvider.OpenAI, ModelId = "remote-embedding", BaseUrl = "https://remote.example/v1", ApiKey = "remote-key", ModelType = ModelType.Embedding, IsActive = true, IsDefault = false, CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }, new AiModel { Id = Guid.NewGuid(), Name = "本地嵌入模型", Provider = ModelProvider.LMStudio, ModelId = "text-embedding-embeddinggemma-300m", BaseUrl = "http://localhost:1234/v1", ApiKey = "lm-studio", ModelType = ModelType.Embedding, IsActive = true, IsDefault = true, CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }); await db.SaveChangesAsync(); } await using (var scope = provider.CreateAsyncScope()) { var resolver = scope.ServiceProvider.GetRequiredService(); var config = await resolver.ResolveAsync(ModelType.Embedding, CancellationToken.None); Assert.Equal("text-embedding-embeddinggemma-300m", config.ModelId); Assert.Equal("http://localhost:1234/v1", config.BaseUrl); Assert.Equal("lm-studio", config.ApiKey); } } }