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);
}
}
}