Browse Source

Merge pull request #23895 from abpframework/10.0-ai-tests

Initial tests for Volo.Abp.AI package
pull/23973/head
Halil İbrahim Kalkan 4 months ago
committed by GitHub
parent
commit
f7e6480d7f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      framework/Volo.Abp.slnx
  2. 15
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs
  3. 6
      framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs
  4. 4
      framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs
  5. 3
      framework/test/Volo.Abp.AI.Tests/Volo.Abp.AI.Tests.abppkg
  6. 18
      framework/test/Volo.Abp.AI.Tests/Volo.Abp.AI.Tests.csproj
  7. 40
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/AbpAITestModule.cs
  8. 47
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/ChatClientAccessor_Tests.cs
  9. 89
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/ChatClient_Tests.cs
  10. 62
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/KernelAccessor_Tests.cs
  11. 61
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Mocks/MockChatClient.cs
  12. 4
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Mocks/MockDefaultChatClient.cs
  13. 7
      framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Workspaces/WordCounter.cs

5
framework/Volo.Abp.slnx

@ -1,5 +1,7 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.csproj" />
<Project Path="src/Volo.Abp.AI/Volo.Abp.AI.csproj" />
<Project Path="src/Volo.Abp.ApiVersioning.Abstractions/Volo.Abp.ApiVersioning.Abstractions.csproj" />
<Project Path="src/Volo.Abp.AspNetCore.Abstractions/Volo.Abp.AspNetCore.Abstractions.csproj" />
<Project Path="src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" />
@ -164,12 +166,11 @@
<Project Path="src/Volo.Abp.Validation/Volo.Abp.Validation.csproj" />
<Project Path="src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj" />
<Project Path="src/Volo.Abp/Volo.Abp.csproj" />
<Project Path="src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.csproj" />
<Project Path="src/Volo.Abp.AI/Volo.Abp.AI.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="test/AbpTestBase/AbpTestBase.csproj" />
<Project Path="test/SimpleConsoleDemo/SimpleConsoleDemo.csproj" />
<Project Path="test/Volo.Abp.AI.Tests/Volo.Abp.AI.Tests.csproj" />
<Project Path="test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj" />
<Project Path="test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj" />
<Project Path="test/Volo.Abp.AspNetCore.Mvc.PlugIn/Volo.Abp.AspNetCore.Mvc.PlugIn.csproj" />

15
framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs

@ -34,6 +34,7 @@ public class AbpAIModule : AbpModule
}
context.Services.TryAddTransient(typeof(IChatClient<>), typeof(TypedChatClient<>));
context.Services.TryAddTransient(typeof(IChatClientAccessor<>), typeof(ChatClientAccessor<>));
context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(KernelAccessor<>));
}
@ -99,5 +100,19 @@ public class AbpAIModule : AbpModule
sp => sp.GetRequiredKeyedService<IChatClient>(serviceName)
);
}
if (workspaceConfig.Kernel.Builder is null)
{
context.Services.AddKeyedTransient<Kernel>(
AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name),
(sp, _) =>
{
var chatClient = sp.GetRequiredKeyedService<IChatClient>(serviceName);
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<IChatClient>(chatClient);
return builder.Build();
}
);
}
}
}

6
framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs

@ -5,9 +5,9 @@ using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AI;
[Dependency(ReplaceServices = true, TryRegister = true)]
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IChatClientAccessor))]
public class ChatClientAccessor : IChatClientAccessor
public class ChatClientAccessor : IChatClientAccessor, ITransientDependency
{
public IChatClient? ChatClient { get; }
@ -19,8 +19,6 @@ public class ChatClientAccessor : IChatClientAccessor
}
}
[Dependency(ReplaceServices = true, TryRegister = true)]
[ExposeServices(typeof(IChatClientAccessor))]
public class ChatClientAccessor<TWorkSpace> : IChatClientAccessor<TWorkSpace>
where TWorkSpace : class
{

4
framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs

@ -13,6 +13,8 @@ public class DefaultKernelAccessor : IKernelAccessor, ITransientDependency
public DefaultKernelAccessor(IServiceProvider serviceProvider)
{
Kernel = serviceProvider.GetKeyedService<Kernel>(
AbpAIModule.DefaultWorkspaceName);
AbpAIWorkspaceOptions.GetKernelServiceKeyName(
AbpAIModule.DefaultWorkspaceName
));
}
}

3
framework/test/Volo.Abp.AI.Tests/Volo.Abp.AI.Tests.abppkg

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

18
framework/test/Volo.Abp.AI.Tests/Volo.Abp.AI.Tests.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Volo.Abp.AI.Tests</AssemblyName>
<PackageId>Volo.Abp.AI.Tests</PackageId>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.AI\Volo.Abp.AI.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
</Project>

40
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/AbpAITestModule.cs

@ -0,0 +1,40 @@
using Microsoft.Extensions.AI;
using Volo.Abp.AI;
using Volo.Abp.AI.Mocks;
using Volo.Abp.AI.Tests.Workspaces;
using Volo.Abp.Modularity;
namespace Volo.Abp.AutoMapper;
[DependsOn(
typeof(AbpTestBaseModule),
typeof(AbpAIModule)
)]
public class AbpAITestModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAIWorkspaceOptions>(options =>
{
options.Workspaces.ConfigureDefault(options =>
{
options.ConfigureChatClient(clientOptions =>
{
clientOptions.Builder = new ChatClientBuilder(new MockDefaultChatClient());
});
});
options.Workspaces.Configure<WordCounter>(workspaceOptions =>
{
workspaceOptions.ConfigureChatClient(clientOptions =>
{
clientOptions.Builder = new ChatClientBuilder(new MockChatClient());
});
});
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
}

47
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/ChatClientAccessor_Tests.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
using Shouldly;
using Volo.Abp.AI.Tests.Workspaces;
using Volo.Abp.AutoMapper;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AI;
public class ChatClientAccessor_Tests : AbpIntegratedTest<AbpAITestModule>
{
[Fact]
public void Should_Resolve_DefaultChatClientAccessor()
{
// Arrange & Act
var chatClientAccessor = GetRequiredService<IChatClientAccessor>();
// Assert
chatClientAccessor.ShouldNotBeNull();
chatClientAccessor.ChatClient.ShouldNotBeNull();
}
[Fact]
public void Should_Resolve_ChatClientAccessor_For_Workspace()
{
// Arrange & Act
var chatClientAccessor = GetRequiredService<IChatClientAccessor<WordCounter>>();
// Assert
chatClientAccessor.ShouldNotBeNull();
chatClientAccessor.ChatClient.ShouldNotBeNull();
}
[Fact]
public void Should_Resolve_ChatClientAccessor_For_NonConfigured_Workspace()
{
// Arrange & Act
var chatClientAccessor = GetRequiredService<IChatClientAccessor<NonConfiguredWorkspace>>();
// Assert
chatClientAccessor.ShouldNotBeNull();
chatClientAccessor.ChatClient.ShouldBeNull();
}
public class NonConfiguredWorkspace
{
}
}

89
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/ChatClient_Tests.cs

@ -0,0 +1,89 @@
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Shouldly;
using Volo.Abp.AI.Mocks;
using Volo.Abp.AI.Tests.Workspaces;
using Volo.Abp.AutoMapper;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AI.Tests;
public class ChatClient_Tests : AbpIntegratedTest<AbpAITestModule>
{
[Fact]
public void Should_Resolve_ChatClient_For_Workspace()
{
// Arrange & Act
var chatClient = GetRequiredService<IChatClient<WordCounter>>();
// Assert
chatClient.ShouldNotBeNull();
chatClient.ShouldNotBeOfType<MockDefaultChatClient>();
}
[Fact]
public void Should_Resolve_Keyed_ChatClient_For_Workspace()
{
// Arrange
var workspaceName = WorkspaceNameAttribute.GetWorkspaceName<WordCounter>();
var serviceName = AbpAIWorkspaceOptions.GetChatClientServiceKeyName(workspaceName);
// Act
var chatClient = GetRequiredKeyedService<IChatClient>(
serviceName
);
// Assert
chatClient.ShouldNotBeNull();
}
[Fact]
public void Should_Resolve_Default_ChatClient()
{
// Arrange & Act
var chatClient = GetRequiredService<IChatClient>();
// Assert
chatClient.ShouldNotBeNull();
chatClient.ShouldBeOfType<MockDefaultChatClient>();
}
[Fact]
public async Task Should_Get_Response_For_Workspace()
{
// Arrange
var chatClient = GetRequiredService<IChatClient<WordCounter>>();
// Act
var response = await chatClient.GetResponseAsync(new[]
{
new ChatMessage(ChatRole.User, "Hello, how are you?")
});
// Assert
response.ShouldNotBeNull();
response.Messages.ShouldNotBeEmpty();
}
[Fact]
public async Task Should_Get_Streaming_Response_For_Workspace()
{
// Arrange
var chatClient = GetRequiredService<IChatClient<WordCounter>>();
var messagesInput = new[]
{
new ChatMessage(ChatRole.User, "Hello, how are you?")
};
// Act
var responseParts = 0;
await foreach (var response in chatClient.GetStreamingResponseAsync(messagesInput))
{
responseParts++;
}
// Assert
responseParts.ShouldBe(MockChatClient.StreamingResponseParts);
}
}

62
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/KernelAccessor_Tests.cs

@ -0,0 +1,62 @@
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Shouldly;
using Volo.Abp.AI.Mocks;
using Volo.Abp.AI.Tests.Workspaces;
using Volo.Abp.AutoMapper;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AI;
public class KernelAccessor_Tests : AbpIntegratedTest<AbpAITestModule>
{
[Fact]
public void Should_Resolve_DefaultKernelAccessor()
{
// Arrange & Act
var kernelAccessor = GetRequiredService<IKernelAccessor>();
// Assert
kernelAccessor.ShouldNotBeNull();
kernelAccessor.Kernel.ShouldNotBeNull();
}
[Fact]
public async Task Should_Get_Response_From_DefaultKernel()
{
// Arrange
var kernelAccessor = GetRequiredService<IKernelAccessor>();
var kernel = kernelAccessor.Kernel;
// Act
var result = await kernel.GetRequiredService<IChatClient>()
.GetResponseAsync("Hello, World!");
// Assert
result.ShouldNotBeNull();
result.RawRepresentation.ShouldBe(MockChatClient.MockResponse);
}
[Fact]
public void Should_Resolve_KernelAccessor_For_Workspace()
{
// Arrange & Act
var kernelAccessor = GetRequiredService<IKernelAccessor<WordCounter>>();
// Assert
kernelAccessor.ShouldNotBeNull();
kernelAccessor.Kernel.ShouldNotBeNull();
}
[Fact]
public async Task Should_Get_Response_From_Kernel_For_Workspace()
{
// Arrange
var kernelAccessor = GetRequiredService<IKernelAccessor<WordCounter>>();
var kernel = kernelAccessor.Kernel;
// Act
var result = await kernel.GetRequiredService<IChatClient>()
.GetResponseAsync("Hello, World!");
// Assert
result.ShouldNotBeNull();
result.RawRepresentation.ShouldBe(MockChatClient.MockResponse);
}
}

61
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Mocks/MockChatClient.cs

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.AI;
namespace Volo.Abp.AI.Mocks;
public class MockChatClient : IChatClient
{
public const int StreamingResponseParts = 4;
public const string MockResponse = "This is a mock response.";
public void Dispose()
{
}
public Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions options = null,
CancellationToken cancellationToken = default)
{
var responseMessages = messages.ToList();
responseMessages.Add(new ChatMessage(ChatRole.Assistant, MockResponse));
return Task.FromResult(new ChatResponse
{
Messages = responseMessages,
RawRepresentation = MockResponse
});
}
public object GetService(Type serviceType, object serviceKey = null)
{
return null;
}
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
for (var i = 0; i < StreamingResponseParts; i++)
{
await Task.Delay(25, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
break;
}
yield return new ChatResponseUpdate
{
Role = ChatRole.Assistant,
RawRepresentation = MockResponse + " " + (i + 1),
};
}
}
}

4
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Mocks/MockDefaultChatClient.cs

@ -0,0 +1,4 @@
namespace Volo.Abp.AI.Mocks;
public class MockDefaultChatClient : MockChatClient
{
}

7
framework/test/Volo.Abp.AI.Tests/Volo/Abp/AI/Workspaces/WordCounter.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.AI.Tests.Workspaces;
[WorkspaceName("WordCounter")]
public class WordCounter
{
}
Loading…
Cancel
Save