Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# CI & CD workflow
name: CI/CD - Liquid.ChatCompletions.OpenAi component for Liquid Application Framework
name: CI/CD - Liquid.GenAi.OpenAi component for Liquid Application Framework

on:
push:
branches: [ main ]
paths:
- 'src/Liquid.ChatCompletions.OpenAi/**'
- 'src/Liquid.GenAi.OpenAi/**'

pull_request:
branches: [ main, releases/** ]
types: [opened, synchronize, reopened]
paths:
- 'src/Liquid.ChatCompletions.OpenAi/**'
- 'src/Liquid.GenAi.OpenAi/**'

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -20,7 +20,7 @@ jobs:
call-reusable-build-workflow:
uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main
with:
component_name: Liquid.ChatCompletions.OpenAi
component_name: Liquid.GenAi.OpenAi
secrets:
sonar_token: ${{ secrets.SONAR_TOKEN_OPENAI }}
nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }}
9 changes: 8 additions & 1 deletion Liquid.Application.Framework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse.Tests", "t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Storage.AzureStorage.Tests", "test\Liquid.Storage.AzureStorage.Tests\Liquid.Storage.AzureStorage.Tests.csproj", "{53341B04-6D30-4137-943B-20D8706351E8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.ChatCompletions.OpenAi", "src\Liquid.ChatCompletions.OpenAi\Liquid.ChatCompletions.OpenAi.csproj", "{CB199ED6-3D1D-4E12-A15F-597B6A0BA564}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.GenAi.OpenAi", "src\Liquid.GenAi.OpenAi\Liquid.GenAi.OpenAi.csproj", "{CB199ED6-3D1D-4E12-A15F-597B6A0BA564}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.OData", "src\Liquid.Repository.OData\Liquid.Repository.OData.csproj", "{60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Repository.OData.Tests", "test\Liquid.Repository.OData.Tests\Liquid.Repository.OData.Tests.csproj", "{70A43D24-4905-4A16-8CE2-165F73243B8D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.GenAi.OpenAi.Tests", "test\Liquid.GenAi.OpenAi.Tests\Liquid.GenAi.OpenAi.Tests.csproj", "{ECDF1DD3-073D-4708-A20B-1F0F2D663065}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -205,6 +207,10 @@ Global
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.Build.0 = Release|Any CPU
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -225,6 +231,7 @@ Global
{5B0DC38B-5BC9-4DAC-8527-8D1C33E97247} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
{53341B04-6D30-4137-943B-20D8706351E8} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
{70A43D24-4905-4A16-8CE2-165F73243B8D} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
{ECDF1DD3-073D-4708-A20B-1F0F2D663065} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1D003939-9797-4F37-B391-10047A780641}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Liquid.Core.Interfaces;
using Liquid.Core.Settings;
using Liquid.Core.GenAi;
using Liquid.GenAi.OpenAi.Settings;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;

namespace Liquid.ChatCompletions.OpenAi.Extensions
namespace Liquid.GenAi.OpenAi.Extensions
{
/// <summary>
/// Extension methods to register Liquid OpenAi Completions services.
Expand All @@ -20,14 +20,14 @@ public static class IServiceCollectionExtension
/// <param name="sectionName">configuration section name</param>
public static IServiceCollection AddLiquidOpenAiCompletions(this IServiceCollection services, string sectionName)
{
services.AddOptions<GenAiOptions>()
services.AddOptions<OpenAiOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection(sectionName).Bind(settings);
});

services.AddSingleton<IOpenAiClientFactory, OpenAiClientFactory>();
services.AddTransient<ILiquidChatCompletions, OpenAiChatCompletions>();
services.AddTransient<ILiquidGenAi, OpenAiAdapter>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Azure.AI.OpenAI;
using OpenAI.Chat;
using OpenAI.Chat;

namespace Liquid.ChatCompletions.OpenAi
namespace Liquid.GenAi.OpenAi
{
/// <summary>
/// Provide <see cref="ChatClient"/> generator methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<Copyright>Avanade 2019</Copyright>
<PackageProjectUrl>https://github.com/Avanade/Liquid-Application-Framework</PackageProjectUrl>
<PackageIcon>logo.png</PackageIcon>
<Version>8.0.0-beta-04</Version>
<Version>8.0.0-rc-01</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<DebugType>Full</DebugType>
Expand All @@ -32,7 +32,7 @@

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="Liquid.Core" Version="8.0.0-beta-09" />
<PackageReference Include="Liquid.Core" Version="8.0.0-rc-01" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
using Liquid.Core.Entities;
using Liquid.Core.Interfaces;
using Liquid.Core.Settings;
using Liquid.Core.GenAi;
using Liquid.Core.GenAi.Entities;
using Liquid.Core.GenAi.Enums;
using Liquid.Core.GenAi.Settings;
using OpenAI.Chat;
using System.Diagnostics.CodeAnalysis;

namespace Liquid.ChatCompletions.OpenAi
namespace Liquid.GenAi.OpenAi
{
///<inheritdoc/>
[ExcludeFromCodeCoverage]
public class OpenAiChatCompletions : ILiquidChatCompletions
public class OpenAiAdapter : ILiquidGenAi
{

private readonly IOpenAiClientFactory _factory;
/// <summary>
/// Initialize a new instance of <see cref="OpenAiChatCompletions"/>
/// Initialize a new instance of <see cref="OpenAiAdapter"/>
/// </summary>
/// <param name="factory">Open IA client Factory.</param>
/// <exception cref="ArgumentNullException"></exception>
public OpenAiChatCompletions(IOpenAiClientFactory factory)
public OpenAiAdapter(IOpenAiClientFactory factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}

///<inheritdoc/>
public async Task<ChatCompletionResult> FunctionCalling(ChatMessages messages, List<FunctionBody> functions, CompletionsSettings settings)
public async Task<ChatCompletionResult> FunctionCalling(LiquidChatMessages messages, List<FunctionBody> functions, CompletionsOptions settings)
{
var client = _factory.GetOpenAIClient(settings.ClientId);

var requestMessages = new List<OpenAI.Chat.ChatMessage>();
var requestMessages = new List<ChatMessage>();

messages.Messages.ForEach(m => requestMessages.Add(MapChatRequestMessage(m)));

var option = MapChatCompletionOptions(requestMessages, settings);
var option = MapChatCompletionOptions(settings);

functions.ForEach(f => option.Tools.Add(ChatTool.CreateFunctionTool(f.Name, f.Description, f.Parameters)));

Expand All @@ -51,13 +53,13 @@
}

///<inheritdoc/>
public async Task<ChatCompletionResult> ChatCompletions(string content, string prompt, CompletionsSettings settings, ChatMessages? chatHistory = null)
public async Task<ChatCompletionResult> CompleteChatAsync(string content, string prompt, CompletionsOptions settings, LiquidChatMessages chatHistory = null)

Check warning on line 56 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 56 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.
{
var client = _factory.GetOpenAIClient(settings.ClientId);

var messages = GetChatMessagesAsync(content, prompt, chatHistory);

var option = MapChatCompletionOptions(messages, settings);
var option = MapChatCompletionOptions(settings);

var responseWithoutStream = await client.CompleteChatAsync(messages, option);
var response = responseWithoutStream.Value.Content[0].Text;
Expand All @@ -75,29 +77,54 @@
}

///<inheritdoc/>
public async Task<ReadOnlyMemory<float>> GetEmbeddings(string description, string modelName, string clientId)
public async Task<ChatCompletionResult> CompleteChatAsync(LiquidChatMessages messages, CompletionsOptions settings, List<FunctionBody> functions = null, LiquidChatMessages chatHistory = null)

Check warning on line 80 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 80 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 80 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 80 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Cannot convert null literal to non-nullable reference type.
{
//var client = _factory.GetOpenAIClient(clientId);
var client = _factory.GetOpenAIClient(settings.ClientId);

//EmbeddingGenerationOptions embeddingsOptions = new(modelName, new string[] { description });
var requestMessages = new List<ChatMessage>();

//var embeddings = await client.(embeddingsOptions);
messages.Messages.ForEach(m => requestMessages.Add(MapChatRequestMessage(m)));

//return embeddings.Value.Data[0].Embedding;
var option = MapChatCompletionOptions(settings);

throw new NotImplementedException();
}
var responseWithoutStream = await client.CompleteChatAsync(requestMessages, option);

if (functions != null)
{
functions.ForEach(f => option.Tools.Add(ChatTool.CreateFunctionTool(f.Name, f.Description, f.Parameters)));
}

if (chatHistory?.Messages != null && chatHistory.Messages.Count > 0)
{
foreach (var message in chatHistory.Messages)
{
requestMessages.Add(MapChatRequestMessage(message));
}
}

var response = responseWithoutStream.Value.Content[0].Text;

var result = new ChatCompletionResult()
{
FinishReason = responseWithoutStream.Value.FinishReason.ToString(),
Content = response,
Usage = responseWithoutStream.Value.Usage.TotalTokenCount,
PromptUsage = responseWithoutStream.Value.Usage.InputTokenCount,
CompletionUsage = responseWithoutStream.Value.Usage.OutputTokenCount,
};

return result;
}

/// <summary>
/// get chat messages for a chat completions request.
/// </summary>
/// <param name="content">content of the user message</param>
/// <param name="prompt">prompt message</param>
/// <param name="chatHistory">chat context messages</param>
private List<OpenAI.Chat.ChatMessage> GetChatMessagesAsync(string content, string prompt, ChatMessages? chatHistory = null)
private static List<ChatMessage> GetChatMessagesAsync(string content, string prompt, LiquidChatMessages? chatHistory = null)
{
var messages = new List<OpenAI.Chat.ChatMessage>
var messages = new List<ChatMessage>
{
new SystemChatMessage(prompt)
};
Expand All @@ -120,45 +147,57 @@
/// </summary>
/// <param name="message">chat message</param>
/// <exception cref="ArgumentNullException"></exception>
private OpenAI.Chat.ChatMessage MapChatRequestMessage(Core.Entities.ChatMessage message)
private static ChatMessage MapChatRequestMessage(LiquidChatMessage message)
{
OpenAI.Chat.ChatMessage chatRequestMessage = null;
ChatMessage? chatRequestMessage = null;
switch (message.Role.ToLower())
{
case "system":
chatRequestMessage = new SystemChatMessage(message.Content);
chatRequestMessage = new SystemChatMessage(CreateContent(message));
break;
case "assistant":
chatRequestMessage = new AssistantChatMessage(message.Content);
chatRequestMessage = new AssistantChatMessage(CreateContent(message));
break;
case "user":
chatRequestMessage = new UserChatMessage(message.Content);
chatRequestMessage = new UserChatMessage(CreateContent(message));
break;
default:
break;
}

if (chatRequestMessage == null)
{
throw new ArgumentNullException(nameof(chatRequestMessage));
throw new ApplicationException($"The folowing message is invalid: {message}");

Check warning on line 170 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

'System.ApplicationException' should not be thrown by user code. (https://rules.sonarsource.com/csharp/RSPEC-112)

Check warning on line 170 in src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

'System.ApplicationException' should not be thrown by user code. (https://rules.sonarsource.com/csharp/RSPEC-112)
}

return chatRequestMessage;
}

private static IEnumerable<ChatMessageContentPart> CreateContent(LiquidChatMessage message)
{
var messageList = message.Content.ToList();

var content = new List<ChatMessageContentPart>();

messageList.ForEach(x => content.Add(x.Kind == LiquidContentKind.Text ?
ChatMessageContentPart.CreateTextPart(x.Text) :
ChatMessageContentPart.CreateImagePart(x.ImageUri)));

return content;
}

/// <summary>
/// Return a chat completions options based on the chat completions settings.
/// </summary>
/// <param name="messages">Chat messages </param>
/// <param name="settings">Chat completions settings</param>
/// <returns></returns>
private ChatCompletionOptions MapChatCompletionOptions(List<OpenAI.Chat.ChatMessage> messages, CompletionsSettings settings)
private static ChatCompletionOptions MapChatCompletionOptions(CompletionsOptions settings)
{
return new ChatCompletionOptions()
{
Temperature = settings.Temperature,
MaxOutputTokenCount = settings.MaxTokens,
TopP = settings.NucleusSamplingFactor,
TopP = settings.TopP,
FrequencyPenalty = settings.FrequencyPenalty,
PresencePenalty = settings.PresencePenalty

Expand Down
Loading
Loading