Skip to content

Commit 2d4906d

Browse files
Merge pull request #271 from Avanade/feat/newcartridges
feat: refactoring OpenAi Adapter.
2 parents 689ab4d + a56469e commit 2d4906d

File tree

10 files changed

+335
-69
lines changed

10 files changed

+335
-69
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# CI & CD workflow
2-
name: CI/CD - Liquid.ChatCompletions.OpenAi component for Liquid Application Framework
2+
name: CI/CD - Liquid.GenAi.OpenAi component for Liquid Application Framework
33

44
on:
55
push:
66
branches: [ main ]
77
paths:
8-
- 'src/Liquid.ChatCompletions.OpenAi/**'
8+
- 'src/Liquid.GenAi.OpenAi/**'
99

1010
pull_request:
1111
branches: [ main, releases/** ]
1212
types: [opened, synchronize, reopened]
1313
paths:
14-
- 'src/Liquid.ChatCompletions.OpenAi/**'
14+
- 'src/Liquid.GenAi.OpenAi/**'
1515

1616
# Allows you to run this workflow manually from the Actions tab
1717
workflow_dispatch:
@@ -20,7 +20,7 @@ jobs:
2020
call-reusable-build-workflow:
2121
uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main
2222
with:
23-
component_name: Liquid.ChatCompletions.OpenAi
23+
component_name: Liquid.GenAi.OpenAi
2424
secrets:
2525
sonar_token: ${{ secrets.SONAR_TOKEN_OPENAI }}
2626
nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }}

Liquid.Application.Framework.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse.Tests", "t
6969
EndProject
7070
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}"
7171
EndProject
72-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.ChatCompletions.OpenAi", "src\Liquid.ChatCompletions.OpenAi\Liquid.ChatCompletions.OpenAi.csproj", "{CB199ED6-3D1D-4E12-A15F-597B6A0BA564}"
72+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.GenAi.OpenAi", "src\Liquid.GenAi.OpenAi\Liquid.GenAi.OpenAi.csproj", "{CB199ED6-3D1D-4E12-A15F-597B6A0BA564}"
7373
EndProject
7474
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.OData", "src\Liquid.Repository.OData\Liquid.Repository.OData.csproj", "{60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}"
7575
EndProject
7676
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}"
7777
EndProject
78+
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}"
79+
EndProject
7880
Global
7981
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8082
Debug|Any CPU = Debug|Any CPU
@@ -205,6 +207,10 @@ Global
205207
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
206208
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
207209
{70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.Build.0 = Release|Any CPU
210+
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
211+
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.Build.0 = Debug|Any CPU
212+
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.ActiveCfg = Release|Any CPU
213+
{ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.Build.0 = Release|Any CPU
208214
EndGlobalSection
209215
GlobalSection(SolutionProperties) = preSolution
210216
HideSolutionNode = FALSE
@@ -225,6 +231,7 @@ Global
225231
{5B0DC38B-5BC9-4DAC-8527-8D1C33E97247} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
226232
{53341B04-6D30-4137-943B-20D8706351E8} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
227233
{70A43D24-4905-4A16-8CE2-165F73243B8D} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
234+
{ECDF1DD3-073D-4708-A20B-1F0F2D663065} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}
228235
EndGlobalSection
229236
GlobalSection(ExtensibilityGlobals) = postSolution
230237
SolutionGuid = {1D003939-9797-4F37-B391-10047A780641}

src/Liquid.ChatCompletions.OpenAi/Extensions/IServiceCollectionExtension.cs renamed to src/Liquid.GenAi.OpenAi/Extensions/IServiceCollectionExtension.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using Liquid.Core.Interfaces;
2-
using Liquid.Core.Settings;
1+
using Liquid.Core.GenAi;
2+
using Liquid.GenAi.OpenAi.Settings;
33
using Microsoft.Extensions.Configuration;
44
using Microsoft.Extensions.DependencyInjection;
55
using System.Diagnostics.CodeAnalysis;
66

7-
namespace Liquid.ChatCompletions.OpenAi.Extensions
7+
namespace Liquid.GenAi.OpenAi.Extensions
88
{
99
/// <summary>
1010
/// Extension methods to register Liquid OpenAi Completions services.
@@ -20,14 +20,14 @@ public static class IServiceCollectionExtension
2020
/// <param name="sectionName">configuration section name</param>
2121
public static IServiceCollection AddLiquidOpenAiCompletions(this IServiceCollection services, string sectionName)
2222
{
23-
services.AddOptions<GenAiOptions>()
23+
services.AddOptions<OpenAiOptions>()
2424
.Configure<IConfiguration>((settings, configuration) =>
2525
{
2626
configuration.GetSection(sectionName).Bind(settings);
2727
});
2828

2929
services.AddSingleton<IOpenAiClientFactory, OpenAiClientFactory>();
30-
services.AddTransient<ILiquidChatCompletions, OpenAiChatCompletions>();
30+
services.AddTransient<ILiquidGenAi, OpenAiAdapter>();
3131

3232
return services;
3333
}

src/Liquid.ChatCompletions.OpenAi/IOpenAiClientFactory.cs renamed to src/Liquid.GenAi.OpenAi/IOpenAiClientFactory.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using Azure.AI.OpenAI;
2-
using OpenAI.Chat;
1+
using OpenAI.Chat;
32

4-
namespace Liquid.ChatCompletions.OpenAi
3+
namespace Liquid.GenAi.OpenAi
54
{
65
/// <summary>
76
/// Provide <see cref="ChatClient"/> generator methods.

src/Liquid.ChatCompletions.OpenAi/Liquid.ChatCompletions.OpenAi.csproj renamed to src/Liquid.GenAi.OpenAi/Liquid.GenAi.OpenAi.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<Copyright>Avanade 2019</Copyright>
1414
<PackageProjectUrl>https://github.com/Avanade/Liquid-Application-Framework</PackageProjectUrl>
1515
<PackageIcon>logo.png</PackageIcon>
16-
<Version>8.0.0-beta-04</Version>
16+
<Version>8.0.0-rc-01</Version>
1717
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1818
<IsPackable>true</IsPackable>
1919
<DebugType>Full</DebugType>
@@ -32,7 +32,7 @@
3232

3333
<ItemGroup>
3434
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
35-
<PackageReference Include="Liquid.Core" Version="8.0.0-beta-09" />
35+
<PackageReference Include="Liquid.Core" Version="8.0.0-rc-01" />
3636
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
3737
</ItemGroup>
3838

src/Liquid.ChatCompletions.OpenAi/OpenAiChatCompletions.cs renamed to src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
using Liquid.Core.Entities;
2-
using Liquid.Core.Interfaces;
3-
using Liquid.Core.Settings;
2+
using Liquid.Core.GenAi;
3+
using Liquid.Core.GenAi.Entities;
4+
using Liquid.Core.GenAi.Enums;
5+
using Liquid.Core.GenAi.Settings;
46
using OpenAI.Chat;
57
using System.Diagnostics.CodeAnalysis;
68

7-
namespace Liquid.ChatCompletions.OpenAi
9+
namespace Liquid.GenAi.OpenAi
810
{
911
///<inheritdoc/>
1012
[ExcludeFromCodeCoverage]
11-
public class OpenAiChatCompletions : ILiquidChatCompletions
13+
public class OpenAiAdapter : ILiquidGenAi
1214
{
1315

1416
private readonly IOpenAiClientFactory _factory;
1517
/// <summary>
16-
/// Initialize a new instance of <see cref="OpenAiChatCompletions"/>
18+
/// Initialize a new instance of <see cref="OpenAiAdapter"/>
1719
/// </summary>
1820
/// <param name="factory">Open IA client Factory.</param>
1921
/// <exception cref="ArgumentNullException"></exception>
20-
public OpenAiChatCompletions(IOpenAiClientFactory factory)
22+
public OpenAiAdapter(IOpenAiClientFactory factory)
2123
{
2224
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
2325
}
2426

2527
///<inheritdoc/>
26-
public async Task<ChatCompletionResult> FunctionCalling(ChatMessages messages, List<FunctionBody> functions, CompletionsSettings settings)
28+
public async Task<ChatCompletionResult> FunctionCalling(LiquidChatMessages messages, List<FunctionBody> functions, CompletionsOptions settings)
2729
{
2830
var client = _factory.GetOpenAIClient(settings.ClientId);
2931

30-
var requestMessages = new List<OpenAI.Chat.ChatMessage>();
32+
var requestMessages = new List<ChatMessage>();
3133

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

34-
var option = MapChatCompletionOptions(requestMessages, settings);
36+
var option = MapChatCompletionOptions(settings);
3537

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

@@ -51,13 +53,13 @@ public async Task<ChatCompletionResult> FunctionCalling(ChatMessages messages, L
5153
}
5254

5355
///<inheritdoc/>
54-
public async Task<ChatCompletionResult> ChatCompletions(string content, string prompt, CompletionsSettings settings, ChatMessages? chatHistory = null)
56+
public async Task<ChatCompletionResult> CompleteChatAsync(string content, string prompt, CompletionsOptions settings, LiquidChatMessages chatHistory = null)
5557
{
5658
var client = _factory.GetOpenAIClient(settings.ClientId);
5759

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

60-
var option = MapChatCompletionOptions(messages, settings);
62+
var option = MapChatCompletionOptions(settings);
6163

6264
var responseWithoutStream = await client.CompleteChatAsync(messages, option);
6365
var response = responseWithoutStream.Value.Content[0].Text;
@@ -75,29 +77,54 @@ public async Task<ChatCompletionResult> ChatCompletions(string content, string p
7577
}
7678

7779
///<inheritdoc/>
78-
public async Task<ReadOnlyMemory<float>> GetEmbeddings(string description, string modelName, string clientId)
80+
public async Task<ChatCompletionResult> CompleteChatAsync(LiquidChatMessages messages, CompletionsOptions settings, List<FunctionBody> functions = null, LiquidChatMessages chatHistory = null)
7981
{
80-
//var client = _factory.GetOpenAIClient(clientId);
82+
var client = _factory.GetOpenAIClient(settings.ClientId);
8183

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

84-
//var embeddings = await client.(embeddingsOptions);
86+
messages.Messages.ForEach(m => requestMessages.Add(MapChatRequestMessage(m)));
8587

86-
//return embeddings.Value.Data[0].Embedding;
88+
var option = MapChatCompletionOptions(settings);
8789

88-
throw new NotImplementedException();
89-
}
90+
var responseWithoutStream = await client.CompleteChatAsync(requestMessages, option);
9091

92+
if (functions != null)
93+
{
94+
functions.ForEach(f => option.Tools.Add(ChatTool.CreateFunctionTool(f.Name, f.Description, f.Parameters)));
95+
}
96+
97+
if (chatHistory?.Messages != null && chatHistory.Messages.Count > 0)
98+
{
99+
foreach (var message in chatHistory.Messages)
100+
{
101+
requestMessages.Add(MapChatRequestMessage(message));
102+
}
103+
}
104+
105+
var response = responseWithoutStream.Value.Content[0].Text;
106+
107+
var result = new ChatCompletionResult()
108+
{
109+
FinishReason = responseWithoutStream.Value.FinishReason.ToString(),
110+
Content = response,
111+
Usage = responseWithoutStream.Value.Usage.TotalTokenCount,
112+
PromptUsage = responseWithoutStream.Value.Usage.InputTokenCount,
113+
CompletionUsage = responseWithoutStream.Value.Usage.OutputTokenCount,
114+
};
115+
116+
return result;
117+
}
91118

92119
/// <summary>
93120
/// get chat messages for a chat completions request.
94121
/// </summary>
95122
/// <param name="content">content of the user message</param>
96123
/// <param name="prompt">prompt message</param>
97124
/// <param name="chatHistory">chat context messages</param>
98-
private List<OpenAI.Chat.ChatMessage> GetChatMessagesAsync(string content, string prompt, ChatMessages? chatHistory = null)
125+
private static List<ChatMessage> GetChatMessagesAsync(string content, string prompt, LiquidChatMessages? chatHistory = null)
99126
{
100-
var messages = new List<OpenAI.Chat.ChatMessage>
127+
var messages = new List<ChatMessage>
101128
{
102129
new SystemChatMessage(prompt)
103130
};
@@ -120,45 +147,57 @@ public async Task<ReadOnlyMemory<float>> GetEmbeddings(string description, strin
120147
/// </summary>
121148
/// <param name="message">chat message</param>
122149
/// <exception cref="ArgumentNullException"></exception>
123-
private OpenAI.Chat.ChatMessage MapChatRequestMessage(Core.Entities.ChatMessage message)
150+
private static ChatMessage MapChatRequestMessage(LiquidChatMessage message)
124151
{
125-
OpenAI.Chat.ChatMessage chatRequestMessage = null;
152+
ChatMessage? chatRequestMessage = null;
126153
switch (message.Role.ToLower())
127154
{
128155
case "system":
129-
chatRequestMessage = new SystemChatMessage(message.Content);
156+
chatRequestMessage = new SystemChatMessage(CreateContent(message));
130157
break;
131158
case "assistant":
132-
chatRequestMessage = new AssistantChatMessage(message.Content);
159+
chatRequestMessage = new AssistantChatMessage(CreateContent(message));
133160
break;
134161
case "user":
135-
chatRequestMessage = new UserChatMessage(message.Content);
162+
chatRequestMessage = new UserChatMessage(CreateContent(message));
136163
break;
137164
default:
138165
break;
139166
}
140167

141168
if (chatRequestMessage == null)
142169
{
143-
throw new ArgumentNullException(nameof(chatRequestMessage));
170+
throw new ApplicationException($"The folowing message is invalid: {message}");
144171
}
145172

146173
return chatRequestMessage;
147174
}
148175

176+
private static IEnumerable<ChatMessageContentPart> CreateContent(LiquidChatMessage message)
177+
{
178+
var messageList = message.Content.ToList();
179+
180+
var content = new List<ChatMessageContentPart>();
181+
182+
messageList.ForEach(x => content.Add(x.Kind == LiquidContentKind.Text ?
183+
ChatMessageContentPart.CreateTextPart(x.Text) :
184+
ChatMessageContentPart.CreateImagePart(x.ImageUri)));
185+
186+
return content;
187+
}
188+
149189
/// <summary>
150190
/// Return a chat completions options based on the chat completions settings.
151191
/// </summary>
152-
/// <param name="messages">Chat messages </param>
153192
/// <param name="settings">Chat completions settings</param>
154193
/// <returns></returns>
155-
private ChatCompletionOptions MapChatCompletionOptions(List<OpenAI.Chat.ChatMessage> messages, CompletionsSettings settings)
194+
private static ChatCompletionOptions MapChatCompletionOptions(CompletionsOptions settings)
156195
{
157196
return new ChatCompletionOptions()
158197
{
159198
Temperature = settings.Temperature,
160199
MaxOutputTokenCount = settings.MaxTokens,
161-
TopP = settings.NucleusSamplingFactor,
200+
TopP = settings.TopP,
162201
FrequencyPenalty = settings.FrequencyPenalty,
163202
PresencePenalty = settings.PresencePenalty
164203

0 commit comments

Comments
 (0)