Skip to content

Commit

Permalink
v1.4.4
Browse files Browse the repository at this point in the history
Service factory improvements
  • Loading branch information
JZO001 committed Apr 27, 2024
1 parent f3ff8a1 commit 774f84d
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 28 deletions.
3 changes: 2 additions & 1 deletion Forge.OpenAI/Forge.OpenAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/JZO001/Forge.OpenAI</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>1.4.3.0</AssemblyVersion>
<AssemblyVersion>1.4.4.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<Version>$(AssemblyVersion)</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>OpenAI, Azure-OpenAI, Azure-OpenAI-API, ChatGPT, GPT4, GPT-4, GPT-4-API, GPT35, GPT-35, GPT-35-API, GPT3, GPT-3, GPT-3-API, DALLE, DALL-E, DALL-E-API, OpenAi, openAi, azure, assistant, threads, Whisper, AI, ML, dotnet, dotnetcore, machine-learning, sdk, forge, translation, transcription, chat, chatbot, image, image-processing, embedding, embedding-models, moderation, text-completion, fine-tune, dotNet, csharp</PackageTags>
<PackageReleaseNotes>
v1.4.4 - Improved service factory methods and Playground examples
v1.4.3 - Improved service factory methods
v1.4.2 - Fix issues
v1.4.1 - Fix issue in MessageResponseBase, duplicated status field and wrong "incomplete_details" field. Constants updated in Tool class.
Expand Down
43 changes: 43 additions & 0 deletions Forge.OpenAI/Infrastructure/MappingHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Reflection;

namespace Forge.OpenAI.Infrastructure
{

/// <summary>Make a copy about the source instance into the destination one</summary>
public static class MappingHelper
{

public static void Map<TSource, TDestination>(TSource source, TDestination destination, bool throwErrorOnMissingTargetProperty = false)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}

Type sourceType = source.GetType();
Type destinationType = destination.GetType();

foreach (PropertyInfo sourceProperty in sourceType.GetProperties())
{
PropertyInfo destinationProperty = destinationType.GetProperty(sourceProperty.Name);

if (destinationProperty != null)
{
destinationProperty.SetValue(destination, sourceProperty.GetValue(source));
}
else if (throwErrorOnMissingTargetProperty)
{
throw new InvalidOperationException($"Property {sourceProperty.Name} not found in destination object");
}
}
}

}

}
20 changes: 19 additions & 1 deletion Forge.OpenAI/Services/OpenAIService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,24 @@ public class OpenAIService : IOpenAIService
RunStepService = runStepService;
}

/// <summary>
/// Creates a new service instance with individual options.
/// The method gets back the ServiceProvider instance for further use.
/// It is the caller responsibility to dispose it, at the end of the OpenAIService instance lifecycle.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="serviceProvider">The constructed IServiceProvider instance.</param>
/// <returns>
/// IOpenAIService
/// </returns>
public static IOpenAIService CreateService(OpenAIOptions options, out ServiceProvider serviceProvider)
{
return CreateService((OpenAIOptions o) =>
{
MappingHelper.Map(options, o);
}, out serviceProvider);
}

/// <summary>
/// Creates a new service instance with individual options.
/// The method gets back the ServiceProvider instance for further use.
Expand All @@ -149,7 +167,7 @@ public class OpenAIService : IOpenAIService
/// <param name="configure">The configuration handler.</param>
/// <param name="serviceProvider">The constructed IServiceProvider instance.</param>
/// <returns>
/// <br />
/// IOpenAIService
/// </returns>
/// <exception cref="System.ArgumentNullException">configure</exception>
public static IOpenAIService CreateService(Action<OpenAIOptions> configure, out ServiceProvider serviceProvider)
Expand Down
6 changes: 5 additions & 1 deletion Playgrounds/MultipleApiKeyUsage/MultipleApiKeyUsage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
</PropertyGroup>

<ItemGroup>
<None Remove="MultipleApiKeyUsage.csproj.vspscc" />
<None Remove="MultipleApiKeyUsage.csproj.vspscc" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Forge.OpenAI.GPT" Version="1.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
91 changes: 66 additions & 25 deletions Playgrounds/MultipleApiKeyUsage/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
using Forge.OpenAI.Authentication;
using Forge.OpenAI.Interfaces.Services;
using Forge.OpenAI.Models.Common;
using Forge.OpenAI.Models.TextEdits;
using Forge.OpenAI.Models.ChatCompletions;
using Forge.OpenAI.Services;
using Forge.OpenAI.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Forge.OpenAI.GPT;

namespace MultipleApiKeyUsage
{
Expand All @@ -23,64 +23,105 @@ static async Task Main(string[] args)
// Using the loggedIn account, navigate to https://platform.openai.com/account/api-keys
// Here you can create apiKey(s), for example let's create two for this demo.


// Add the created API keys here
const string apiKeyForUserA = "";
const string apiKeyForUserB = "";
const string apiKeyForUserC = "";


// Create the OpenAI service instances for the users with manual DI configuration
// 1, First example: create the OpenAI service instances for the users with manual DI configuration
// AddForgeOpenAI can be replaced with other init methods, see ServiceCollectionExtensions.cs
IOpenAIService openAiInstanceForUserA =
OpenAIService
.CreateService(sc =>
sc.AddForgeOpenAI(options =>
options.AuthenticationInfo = new AuthenticationInfo(apiKeyForUserA)), out ServiceProvider serviceProviderA);

// The same can be done with the OpenAIOptions
OpenAIOptions optionsForUserB = new OpenAIOptions();
optionsForUserB.AuthenticationInfo = new AuthenticationInfo(apiKeyForUserB);

// 2, Second example: the same can be done with an action used to configure OpenAIOptions
IOpenAIService openAiInstanceForUserB = OpenAIService.CreateService((OpenAIOptions options) =>
{
options.AuthenticationInfo = new AuthenticationInfo(apiKeyForUserB);
}, out ServiceProvider serviceProviderB);

// 3, Third example: the same can be done with an own OpenAIOptions instance
OpenAIOptions optionsForUserC = new OpenAIOptions();
optionsForUserC.AuthenticationInfo = new AuthenticationInfo(apiKeyForUserC);

IOpenAIService openAiInstanceForUserC = OpenAIService.CreateService(optionsForUserC, out ServiceProvider serviceProviderC);


// Now lets use the OpenAI instances
using (serviceProviderA)
{
using (serviceProviderB)
{
await TextEditExampleAsync(openAiInstanceForUserA);
await TextEditExampleAsync(openAiInstanceForUserB);

// NOTE: there is an other example in the Playgrounds here, which demonstrates
// how you can use the OpenAPI with multiple users, but with only one ApiKey
// This is useful, if you have multiple users, becuase it is highly recommended
// to differentiate them. If a user against the OpenAPI rules, this user will be
// denied and not your whole apiKey and your other users.
using (serviceProviderC)
{
// make actions with the OpenAI instances
await ChatWithStreamingModeWithCallback(openAiInstanceForUserA);
await ChatWithStreamingModeWithCallback(openAiInstanceForUserB);
await ChatWithStreamingModeWithCallback(openAiInstanceForUserC);

// NOTE: there is an other example in the Playgrounds here, which demonstrates
// how you can use the OpenAPI with multiple users, but with only one ApiKey
// This is useful, if you have multiple users, because it is highly recommended
// to differentiate them. If a user against the OpenAPI rules, this user will be
// denied and not your whole apiKey and your other users.
}
}
}

// NOTE: if you do want to manage the lifecycle of the ServiceProvider instance, you can discard it, like that:
// IOpenAIService openAiInstanceForUserC = OpenAIService.CreateService(optionsForUserC, out _);
//
// This is not a generally recommended way, because you can't dispose the ServiceProvider instance
// and you can't release the resources, which are used by the OpenAI services.
// The recommended way is to use the using statement, like in the example above.
// If you don't want to use the using statement, you can store the ServiceProvider instance
// and dispose it manually, when you don't need it anymore.
// But in this case, you have to be careful, because you have to dispose the ServiceProvider instance
// in the right time, otherwise you can have memory leaks.
//
// The only exception is, when you use the OpenAI services in a long running application, like a web application,
// where you don't want to dispose the ServiceProvider instance, because you want to use it during the whole lifecycle
// of the application. In this case, you have to be careful, because you have to dispose the ServiceProvider instance
// when the application is shutting down, otherwise you can have memory leaks.

}

static async Task TextEditExampleAsync(IOpenAIService openAIService)
static async Task ChatWithStreamingModeWithCallback(IOpenAIService openAi)
{
TextEditRequest request = new TextEditRequest();
request.InputTextForEditing = "Do you happy with your order?";
request.Instruction = "Fix the grammar";
// this method is useful for older .NET where the IAsyncEnumerable is not supported

ChatCompletionRequest request = new ChatCompletionRequest(ChatMessage.CreateFromUser("Write a C# code which demonstrate how to open a text file and read its content"));
request.MaxTokens = 4096 - GPT3Tokenizer.Encode(request.Messages[0].Content).Count; // calculating max token
request.Temperature = 0.1; // lower value means more precise answer

Console.WriteLine(request.InputTextForEditing);
Console.WriteLine(request.Instruction);
Console.WriteLine(request.Messages[0].Content);

HttpOperationResult<TextEditResponse> response =
await openAIService.TextEditService.GetAsync(request, CancellationToken.None);

Action<HttpOperationResult<ChatCompletionStreamedResponse>> receivedDataHandler = (HttpOperationResult<ChatCompletionStreamedResponse> actionResponse) =>
{
if (actionResponse.IsSuccess)
{
Console.Write(actionResponse.Result.Choices[0].Delta.Content);
}
else
{
Console.WriteLine(actionResponse);
}
};

HttpOperationResult response = await openAi.ChatCompletionService.GetStreamAsync(request, receivedDataHandler, CancellationToken.None);
if (response.IsSuccess)
{
response.Result!.Choices.ToList().ForEach(c => Console.WriteLine(c.Text)); // output: Are you happy with your order?
Console.WriteLine();
}
else
{
Console.WriteLine(response);
}

}

}
Expand Down

0 comments on commit 774f84d

Please sign in to comment.