- 移除IWorkflowTriggerService接口及其实现,改用Mediator模式直接发送命令 - 更新AiBotHandler移除workflowTrigger依赖,使用新命令模式触发工作流 - 修改ApproveTaskEndpoint和RejectTaskEndpoint返回bool类型并使用Send.OkAsync - 更新appsettings.json中AI助手的默认指令配置 - 在BuildFlowHostModule.cs中移除旧的工作流触发服务注册 - 添加SSE流式响应路径前缀配置以支持RAG聊天流 - 重构ChatAgentService使用HTTP客户端工厂和运行时配置解析 - 实现AiChatOutputSanitizer用于清洗AI输出中的模板内容 - 更新EmbeddingService使用运行时配置解析器动态创建客户端 - 调整RAG查询处理器中的系统提示指令内容 - 添加构建流按钮操作的单元测试验证发布和禁用功能 - 扩展即时消息集成测试覆盖文件消息元数据处理场景 - 更新平台权限种子数据添加按钮发布和禁用权限 - 实现AI模型运行时配置解析器支持动态模型切换
134 lines
5.3 KiB
C#
134 lines
5.3 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// AI 运行时模型配置解析测试。确保 Chat/Embedding 调用优先使用模型管理里配置的默认本地模型。
|
||
/// </summary>
|
||
public class AiModelRuntimeConfigResolverTests
|
||
{
|
||
[Fact]
|
||
public async Task ResolveAsyncShouldReplaceLegacyRagOnlyChatInstructions()
|
||
{
|
||
var services = new ServiceCollection();
|
||
var dbName = $"rag-chat-model-{Guid.NewGuid():N}";
|
||
services.AddDbContext<RagDbContext>(options =>
|
||
options.UseInMemoryDatabase(dbName));
|
||
services.AddScoped<AiModelRuntimeConfigResolver>();
|
||
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<RagDbContext>();
|
||
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<AiModelRuntimeConfigResolver>();
|
||
|
||
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<RagDbContext>(options =>
|
||
options.UseInMemoryDatabase(dbName));
|
||
services.AddScoped<AiModelRuntimeConfigResolver>();
|
||
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<RagDbContext>();
|
||
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<AiModelRuntimeConfigResolver>();
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|