build-flow.api/tests/BuildFlow.Rag.Tests/AiModelRuntimeConfigResolverTests.cs
xiangning 54dc3fc26e refactor(rag): 重构AI对话和工作流集成实现
- 移除IWorkflowTriggerService接口及其实现,改用Mediator模式直接发送命令
- 更新AiBotHandler移除workflowTrigger依赖,使用新命令模式触发工作流
- 修改ApproveTaskEndpoint和RejectTaskEndpoint返回bool类型并使用Send.OkAsync
- 更新appsettings.json中AI助手的默认指令配置
- 在BuildFlowHostModule.cs中移除旧的工作流触发服务注册
- 添加SSE流式响应路径前缀配置以支持RAG聊天流
- 重构ChatAgentService使用HTTP客户端工厂和运行时配置解析
- 实现AiChatOutputSanitizer用于清洗AI输出中的模板内容
- 更新EmbeddingService使用运行时配置解析器动态创建客户端
- 调整RAG查询处理器中的系统提示指令内容
- 添加构建流按钮操作的单元测试验证发布和禁用功能
- 扩展即时消息集成测试覆盖文件消息元数据处理场景
- 更新平台权限种子数据添加按钮发布和禁用权限
- 实现AI模型运行时配置解析器支持动态模型切换
2026-07-04 21:29:37 +08:00

134 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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