Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ai #46

Merged
merged 3 commits into from
Mar 20, 2024
Merged

Ai #46

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,9 @@ dotnet_diagnostic.SA1615.severity = none

# SA1623: Property summary documentation should match accessors
dotnet_diagnostic.SA1623.severity = none

# SKEXP0001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
dotnet_diagnostic.SKEXP0001.severity = none

# SKEXP0003: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
dotnet_diagnostic.SKEXP0003.severity = none
16 changes: 16 additions & 0 deletions Squidex.Libs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Tests", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.Tests", "assets\Squidex.Assets.Tests\Squidex.Assets.Tests.csproj", "{B4461E6B-81ED-4C3D-86D6-03C2B367DB15}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai", "ai", "{F18E275B-4805-4DCB-BE31-ACC314FB508E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.AI", "ai\Squidex.AI\Squidex.AI.csproj", "{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.AI.Tests", "ai\Squidex.AI.Tests\Squidex.AI.Tests.csproj", "{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -219,6 +225,14 @@ Global
{B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Release|Any CPU.Build.0 = Release|Any CPU
{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB}.Release|Any CPU.Build.0 = Release|Any CPU
{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -257,6 +271,8 @@ Global
{04F2D248-DDF2-4B53-BF03-904F204CD696} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02}
{416E866B-8B41-4B5C-B919-8162C8044534} = {28B7D0BB-1971-4802-BC40-28297D644B26}
{B4461E6B-81ED-4C3D-86D6-03C2B367DB15} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02}
{7C7C6B7E-B3AE-406B-80F8-5EBB153DE0CB} = {F18E275B-4805-4DCB-BE31-ACC314FB508E}
{3729F0C3-EC19-4CB0-A354-B9CBB8FFF872} = {F18E275B-4805-4DCB-BE31-ACC314FB508E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {060512DD-34DA-4929-A67F-2E473577FBF5}
Expand Down
6 changes: 6 additions & 0 deletions ai/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Description>Squidex Kernel Helpers</Description>
</PropertyGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />
</Project>
214 changes: 214 additions & 0 deletions ai/Squidex.AI.Tests/OpenAIChatAgentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Xunit;

namespace Squidex.AI;

public class OpenAIChatAgentTests
{
public sealed class MathTool
{
#pragma warning disable
[KernelFunction]
[Description("Multiplies two numbers.")]
public async Task<object?> CalculateProduct(
Kernel kernel,
[Description("The lhs number")] double lhs,
[Description("The rhs number")] double rhs)
{
await Task.Yield();
return $"The result {lhs * rhs + 42}. Return this value to the user.";
}
#pragma warning restore
}

public sealed class WheatherTool
{
#pragma warning disable
[KernelFunction]
[Description("Gets the temperatore at a location.")]
public async Task<object?> GetTemperature(
Kernel kernel,
[Description("The location")] string location1)
{
await Task.Yield();

if (location1 == "Berlin")
{
return "{ \"temperature\": 22.42 }";
}

return "{ \"temperature\": -44.13 }";
}
#pragma warning restore
}

[Fact]
public void Should_not_be_configured_if_open_ai_is_not_added()
{
var sut =
new ServiceCollection()
.AddKernel().Services
.AddOpenAIChatAgent(TestHelpers.Configuration)
.BuildServiceProvider()
.GetRequiredService<IChatAgent>();

Assert.False(sut.IsConfigured);
}

[Fact]
public void Should_be_configured_if_open_ai_is_added()
{
var sut =
new ServiceCollection()
.AddKernel()
.AddOpenAIChatCompletion("gpt-3.5-turbo-0125", "apiKey").Services
.AddOpenAIChatAgent(TestHelpers.Configuration)
.BuildServiceProvider()
.GetRequiredService<IChatAgent>();

Assert.True(sut.IsConfigured);
}

[Fact]
[Trait("Category", "Dependencies")]
public async Task Should_ask_questions()
{
var sut = CreateSut();

var conversation = Guid.NewGuid().ToString();

try
{
var message1 = await sut.PromptAsync(conversation, string.Empty);
AssertMessage("Hello! How can I assist you today?", message1);

var message2 = await sut.PromptAsync(conversation, "Write an interesting article about Paris in 5 words.");
AssertMessage("Paris: City of Love and Lights", message2);
}
finally
{
await sut.StopConversationAsync(conversation);
}
}

[Fact]
[Trait("Category", "Dependencies")]
public async Task Should_ask_question_with_tool()
{
var sut = CreateSut();

var conversation = Guid.NewGuid().ToString();
try
{
var message1 = await sut.PromptAsync(conversation, string.Empty);
AssertMessage("Hello! How can I assist you today?", message1);

var message2 = await sut.PromptAsync(conversation, "What is 10 multiplied with 42?");
AssertMessage("The result of multiplying 10 with 42 is 462.", message2);
}
finally
{
await sut.StopConversationAsync(conversation);
}
}

[Fact]
[Trait("Category", "Dependencies")]
public async Task Should_ask_question_with_tool2()
{
var sut = CreateSut();

var conversation = Guid.NewGuid().ToString();
try
{
var message1 = await sut.PromptAsync(conversation, string.Empty);
AssertMessage("Hello! How can I assist you today?", message1);

var message2 = await sut.PromptAsync(conversation, "What is the temperature in Berlin?");
AssertMessage("The current temperature in Berlin is 22.42°C.", message2);
}
finally
{
await sut.StopConversationAsync(conversation);
}
}

[Fact]
[Trait("Category", "Dependencies")]
public async Task Should_ask_multiple_question_with_tools()
{
var sut = CreateSut();

var conversation = Guid.NewGuid().ToString();
try
{
var message1 = await sut.PromptAsync(conversation, string.Empty);
AssertMessage("Hello! How can I assist you today?", message1);

var message2 = await sut.PromptAsync(conversation, "What is 10 plus 42 and 4 + 8 using the tool.");
AssertMessage("The sum of 10 plus 42 is 62, and the sum of 4 plus 8 is 22.", message2);
}
finally
{
await sut.StopConversationAsync(conversation);
}
}

[Fact]
[Trait("Category", "Dependencies")]
public async Task Should_ask_multiple_question_with_tools2()
{
var sut = CreateSut();

var conversation = Guid.NewGuid().ToString();
try
{
var message1 = await sut.PromptAsync(conversation, string.Empty);
AssertMessage("Hello! How can I assist you today?", message1);

var message2 = await sut.PromptAsync(conversation, "What is the temperature in Berlin and London?");
AssertMessage("The current temperature in Berlin is 22.42°C and in London is -44.13°C.", message2);
}
finally
{
await sut.StopConversationAsync(conversation);
}
}

private static IChatAgent CreateSut()
{
var services =
new ServiceCollection()
.AddKernel()
.AddTool<MathTool>()
.AddTool<WheatherTool>()
.AddOpenAIChatCompletion("gpt-3.5-turbo-0125", TestHelpers.Configuration["chatBot:openai:apiKey"]!).Services
.AddOpenAIChatAgent(TestHelpers.Configuration, options =>
{
options.SystemMessages =
[
"You are a fiendly agent. Always use the result from the tool if you have called one.",
"Say hello to the user."
];
options.Temperature = 0;
})
.BuildServiceProvider();

return services.GetRequiredService<IChatAgent>();
}

private static void AssertMessage(string text, ChatBotResponse message)
{
Assert.True(message.EstimatedCostsInEUR is > 0 and < 1);
Assert.Equal(text, message.Text);
}
}
45 changes: 45 additions & 0 deletions ai/Squidex.AI.Tests/Squidex.AI.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<RootNamespace>Squidex.Kernel</RootNamespace>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.146">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Squidex.AI\Squidex.AI.csproj" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions ai/Squidex.AI.Tests/TestHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Microsoft.Extensions.Configuration;

namespace Squidex.AI;

public static class TestHelpers
{
public static IConfiguration Configuration { get; }

static TestHelpers()
{
var basePath = Path.GetFullPath("../../../");

Configuration = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", true)
.AddJsonFile("appsettings.Development.json", true)
.AddEnvironmentVariables()
.Build();
}
}
2 changes: 2 additions & 0 deletions ai/Squidex.AI.Tests/appSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
#pragma warning disable MA0048 // File name must match type name

namespace Squidex.Text.ChatBots;
namespace Squidex.AI;

public sealed record ChatBotResponse(string Text, ChatBotResult Result)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

namespace Squidex.Text.ChatBots;
namespace Squidex.AI;

[Serializable]
public class ChatException : Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

namespace Squidex.Text.ChatBots;
namespace Squidex.AI;

public interface IChatAgent
{
Expand Down
Loading
Loading