Skip to content

[Playground] Refactoring OpenAIApiClient #163 #314

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

Merged
merged 14 commits into from
Oct 6, 2024
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
3 changes: 2 additions & 1 deletion src/AzureOpenAIProxy.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

builder.AddProject<Projects.AzureOpenAIProxy_PlaygroundApp>("playgroundapp")
.WithExternalHttpEndpoints()
.WithReference(apiapp);
.WithReference(apiapp)
.WithEnvironment("ServiceNames__Backend", apiapp.Resource.Name);

await builder.Build().RunAsync();
109 changes: 58 additions & 51 deletions src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
using System.ClientModel;

using Azure.AI.OpenAI;

using OpenAI.Chat;

namespace AzureOpenAIProxy.PlaygroundApp.Clients;

/// <summary>
/// This provides interfaces to the <see cref="OpenAIApiClient"/> class.
/// </summary>
public interface IOpenAIApiClient
{
/// <summary>
/// Send a chat completion request to the OpenAI API.
/// </summary>
/// <param name="clientOptions"><see cref="OpenAIApiClientOptions"/> instance.</param>
/// <returns>Returns the chat completion result.</returns>
Task<string> CompleteChatAsync(OpenAIApiClientOptions clientOptions);
}

/// <summary>
/// This represents the OpenAI API client entity.
/// </summary>
public class OpenAIApiClient : IOpenAIApiClient
{
/// <inheritdoc />
public async Task<string> CompleteChatAsync(OpenAIApiClientOptions clientOptions)
{
var endpoint = new Uri($"{clientOptions.Endpoint!.TrimEnd('/')}/api");
var credential = new ApiKeyCredential(clientOptions.ApiKey!);
var openai = new AzureOpenAIClient(endpoint, credential);
var chat = openai.GetChatClient(clientOptions.DeploymentName);

var messages = new List<ChatMessage>()
{
new SystemChatMessage(clientOptions.SystemPrompt),
new UserChatMessage(clientOptions.UserPrompt),
};
var options = new ChatCompletionOptions
{
MaxOutputTokenCount = clientOptions.MaxOutputTokenCount,
Temperature = clientOptions.Temperature,
};

var result = await chat.CompleteChatAsync(messages, options).ConfigureAwait(false);
var response = result.Value.Content.First().Text;

return response;
}
}
using System.ClientModel;

using Azure.AI.OpenAI;

using AzureOpenAIProxy.PlaygroundApp.Configurations;

using OpenAI.Chat;

namespace AzureOpenAIProxy.PlaygroundApp.Clients;

/// <summary>
/// This provides interfaces to the <see cref="OpenAIApiClient"/> class.
/// </summary>
public interface IOpenAIApiClient
{
/// <summary>
/// Send a chat completion request to the OpenAI API.
/// </summary>
/// <param name="clientOptions"><see cref="OpenAIApiClientOptions"/> instance.</param>
/// <returns>Returns the chat completion result.</returns>
Task<string> CompleteChatAsync(OpenAIApiClientOptions clientOptions);
}

/// <summary>
/// This represents the OpenAI API client entity.
/// </summary>
public class OpenAIApiClient(ServiceNamesSettings names, ServicesSettings settings) : IOpenAIApiClient
{
private readonly ServiceNamesSettings _names = names ?? throw new ArgumentNullException(nameof(names));
private readonly ServicesSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));

/// <inheritdoc />
public async Task<string> CompleteChatAsync(OpenAIApiClientOptions clientOptions)
{
var service = settings[this._names.Backend!];
var endpoint = service.Https.FirstOrDefault() ?? service.Http.First();

Check warning on line 36 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'source' in 'string? Enumerable.FirstOrDefault<string>(IEnumerable<string> source)'.

Check warning on line 36 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'source' in 'string Enumerable.First<string>(IEnumerable<string> source)'.

clientOptions.Endpoint = new Uri($"{endpoint!.TrimEnd('/')}/api");

var credential = new ApiKeyCredential(clientOptions.ApiKey!);
var openai = new AzureOpenAIClient(clientOptions.Endpoint, credential);
var chat = openai.GetChatClient(clientOptions.DeploymentName);

var messages = new List<ChatMessage>()
{
new SystemChatMessage(clientOptions.SystemPrompt), new UserChatMessage(clientOptions.UserPrompt),
};
var options = new ChatCompletionOptions
{
MaxOutputTokenCount = clientOptions.MaxTokens, Temperature = clientOptions.Temperature,

Check warning on line 50 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

View workflow job for this annotation

GitHub Actions / build-test

'OpenAIApiClientOptions.MaxTokens' is obsolete: 'Use MaxOutputTokenCount instead.'
};

var result = await chat.CompleteChatAsync(messages, options).ConfigureAwait(false);
var response = result.Value.Content.First().Text;

return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ public class OpenAIApiClientOptions
/// <summary>
/// Gets or sets the OpenAI API endpoint.
/// </summary>
public string? Endpoint { get; set; }
public Uri? Endpoint { get; set; }

/// <summary>
/// Gets or sets the OpenAI API key.
/// </summary>
public string? ApiKey { get; set; }

/// <summary>
/// Gets or sets the deployment name.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
@using AzureOpenAIProxy.PlaygroundApp.Models
@using AzureOpenAIProxy.PlaygroundApp.Clients
@using AzureOpenAIProxy.PlaygroundApp.Models

@inject IOpenAIApiClient Api

<FluentStack Id="@Id" Style="width: 100%; height: 100%;" Orientation="Orientation.Vertical" VerticalAlignment="VerticalAlignment.Bottom">
<ChatHistoryComponent Id="chat-history" Messages="messages" />
<ChatPromptComponent Id="chat-prompt" OnPromptSent="SendPrompt" OnChatCleared="ClearMessage" />
</FluentStack>

@code {
private string? apiKey = "abcdef";
private string? deploymentName = "model-gpt35turbo16k-0613";
private int? maxTokens = 4096;
private float? temperature = 0.7f;
private string? systemPrompt = "You are an AI assistant that helps people find information.";

private List<ChatMessage>? messages;

[Parameter]
Expand All @@ -21,8 +30,18 @@
private async Task SendPrompt(string prompt)
{
this.messages!.Add(new ChatMessage() { Role = MessageRole.User, Message = prompt });

await Task.CompletedTask;
var options = new OpenAIApiClientOptions()
{
ApiKey = apiKey,
DeploymentName = deploymentName,
MaxTokens = maxTokens,
Temperature = temperature,
SystemPrompt = systemPrompt,
UserPrompt = prompt,
};

var result = await Api.CompleteChatAsync(options);
this.messages!.Add(new ChatMessage() { Role = MessageRole.Assistant, Message = result });
}

private async Task ClearMessage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
</div>

@code {
private string? endpoint = "https://localhost:7001/";
private string? apiKey = "abcdef";
private string? deploymentName = "model-gpt35turbo16k-0613";
private int? maxTokens = 4096;
Expand All @@ -43,7 +42,6 @@
{
var options = new OpenAIApiClientOptions()
{
Endpoint = endpoint,
ApiKey = apiKey,
DeploymentName = deploymentName,
MaxTokens = maxTokens,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace AzureOpenAIProxy.PlaygroundApp.Configurations;

/// <summary>
/// This represents the app settings entity for services.
/// </summary>
public class ServicesSettings : Dictionary<string, ServiceSettings>
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "services";
}

/// <summary>
/// This represents the service settings entity.
/// </summary>
public class ServiceSettings
{
/// <summary>
/// Gets or sets the HTTP endpoints.
/// </summary>
public List<string>? Http { get; set; }

/// <summary>
/// Gets or sets the HTTPS endpoints.
/// </summary>
public List<string>? Https { get; set; }
}

/// <summary>
/// This represents the app settings entity for service names.
/// </summary>
public class ServiceNamesSettings
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "ServiceNames";

/// <summary>
/// Gets or sets the backend service name.
/// </summary>
public string? Backend { get; set; }
}
23 changes: 23 additions & 0 deletions src/AzureOpenAIProxy.PlaygroundApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AzureOpenAIProxy.PlaygroundApp.Clients;
using AzureOpenAIProxy.PlaygroundApp.Configurations;

using Microsoft.FluentUI.AspNetCore.Components;

Expand All @@ -14,6 +15,28 @@

builder.Services.AddFluentUIComponents();

builder.Services.AddSingleton<ServicesSettings>(sp =>
{
var configuration = sp.GetService<IConfiguration>()
?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered.");

var settings = configuration.GetSection(ServicesSettings.Name).Get<ServicesSettings>()
?? throw new InvalidOperationException($"{nameof(ServicesSettings)} could not be retrieved from the configuration.");

return settings!;
});

builder.Services.AddSingleton<ServiceNamesSettings>(sp =>
{
var configuration = sp.GetService<IConfiguration>()
?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered.");

var settings = configuration.GetSection(ServiceNamesSettings.Name).Get<ServiceNamesSettings>()
?? throw new InvalidOperationException($"{nameof(ServiceNamesSettings)} could not be retrieved from the configuration.");

return settings!;
});

builder.Services.AddScoped<IOpenAIApiClient, OpenAIApiClient>();

var app = builder.Build();
Expand Down
Loading