Skip to content

Commit

Permalink
Add Fody.ConfigureAwait to modules; Fix Disposable pattern in OpenAiC…
Browse files Browse the repository at this point in the history
…lient; Update to 2.7.1
  • Loading branch information
rodion-m committed Jul 13, 2023
1 parent af47f30 commit 6cc79d8
Show file tree
Hide file tree
Showing 18 changed files with 341 additions and 72 deletions.
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.7.1</Version>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
3 changes: 2 additions & 1 deletion OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<PackageId>OpenAI.ChatGPT.AspNetCore</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT integration for .NET with DI</Product>
<Version>2.7.0</Version>
<Description>OpenAI Chat Completions API (ChatGPT) integration with easy DI supporting (Microsoft.Extensions.DependencyInjection). It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand All @@ -24,6 +23,8 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OpenAI.ChatGpt.Modules.StructuredResponse\OpenAI.ChatGpt.Modules.StructuredResponse.csproj" />
<ProjectReference Include="..\OpenAI.ChatGpt.Modules.Translator\OpenAI.ChatGpt.Modules.Translator.csproj" />
<ProjectReference Include="..\OpenAI.ChatGpt\OpenAI.ChatGpt.csproj"/>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<PackageId>OpenAI.ChatGPT.EntityFrameworkCore</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT integration for .NET with EF Core storage</Product>
<Version>2.7.0</Version>
<Description>OpenAI Chat Completions API (ChatGPT) integration with DI and EF Core supporting. It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand Down
3 changes: 3 additions & 0 deletions OpenAI.ChatGpt.Modules.StructuredResponse/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<PackageId>OpenAI.ChatGPT.Modules.StructuredResponse</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT structured response module</Product>
<Version>2.7.0</Version>
<Description>The module for OpenAI ChatGPT that allows to retrive a structured response from ChatGPT.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand All @@ -25,6 +24,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Fody" Version="6.6.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JsonSchema.Net.Generation" Version="3.3.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace OpenAI.ChatGpt.Modules.StructuredResponse;

[Fody.ConfigureAwait(false)]
public static class OpenAiClientExtensions
{
/// <summary>
Expand Down Expand Up @@ -98,6 +99,11 @@ internal static async Task<TObject> GetStructuredResponse<TObject>(
rawResponseGetter,
cancellationToken);

response = response.Trim();
if(response.StartsWith("```") && response.EndsWith("```"))
{
response = response[3..^3];
}
jsonDeserializerOptions ??= new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
Expand All @@ -120,7 +126,8 @@ internal static async Task<TObject> GetStructuredResponse<TObject>(

private static string GetAdditionalJsonResponsePrompt(string responseFormat)
{
return$"\n\nWrite your response in JSON format. The response structure is enclosed within double backticks (JSON Schema) ``{responseFormat}``";
return$"\n\nWrite your response in compact JSON format with escaped strings. " +
$"Here is the response structure, it is enclosed within double backticks (JSON Schema) ``{responseFormat}``";
}

internal static string CreateResponseFormatJson<TObject>()
Expand Down
104 changes: 89 additions & 15 deletions OpenAI.ChatGpt.Modules.Translator/ChatGPTTranslatorService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
using OpenAI.ChatGpt.Models.ChatCompletion;
using System.Text.Json;
using System.Text.Json.Serialization;
using OpenAI.ChatGpt.Models.ChatCompletion;
using OpenAI.ChatGpt.Models.ChatCompletion.Messaging;
using OpenAI.ChatGpt.Modules.StructuredResponse;

namespace OpenAI.ChatGpt.Modules.Translator;

[Fody.ConfigureAwait(false)]
// ReSharper disable once InconsistentNaming
public class ChatGPTTranslatorService : IDisposable
{
private readonly IOpenAiClient _client;
Expand Down Expand Up @@ -45,14 +51,19 @@ public void Dispose()
}
}

public async Task<string> Translate(
public async Task<string> TranslateText(
string text,
string? sourceLanguage = null,
string? targetLanguage = null,
int? maxTokens = null,
string? model = null,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default)
{
if (text == null) throw new ArgumentNullException(nameof(text));
ArgumentNullException.ThrowIfNull(text);
var sourceLanguageOrDefault = sourceLanguage ?? _defaultSourceLanguage;
var targetLanguageOrDefault = targetLanguage ?? _defaultTargetLanguage;
if (sourceLanguageOrDefault is null)
Expand All @@ -63,21 +74,84 @@ public async Task<string> Translate(
{
throw new ArgumentNullException(nameof(targetLanguage), "Target language is not specified");
}
var prompt = GetPrompt(sourceLanguageOrDefault, targetLanguageOrDefault);

var prompt = CreateTextTranslationPrompt(sourceLanguageOrDefault, targetLanguageOrDefault);
var messages = Dialog.StartAsSystem(prompt).ThenUser(text).GetMessages().ToArray();
(model, maxTokens) = ChatCompletionMessage.FindOptimalModelAndMaxToken(messages, model, maxTokens);
var response = await _client.GetChatCompletions(
Dialog.StartAsSystem(prompt).ThenUser(text),
user: null,
requestModifier: requestModifier,
cancellationToken: cancellationToken
);
messages,
maxTokens.Value,
model,
temperature,
user,
requestModifier,
rawResponseGetter,
cancellationToken);
return response;

string CreateTextTranslationPrompt(string sourceLanguage, string targetLanguage)
{
ArgumentNullException.ThrowIfNull(sourceLanguage);
ArgumentNullException.ThrowIfNull(targetLanguage);
return $"I want you to act as a translator from {sourceLanguage} to {targetLanguage}. " +
"The user provides with a sentence and you translate it. " +
"In the response write ONLY translated text." +
(_extraPrompt is not null ? "\n" + _extraPrompt : "");
}
}

private string GetPrompt(string sourceLanguage, string targetLanguage)

public async Task<TObject> TranslateObject<TObject>(
TObject objectToTranslate,
string? sourceLanguage = null,
string? targetLanguage = null,
int? maxTokens = null,
string? model = null,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
JsonSerializerOptions? jsonSerializerOptions = null,
JsonSerializerOptions? jsonDeserializerOptions = null,
CancellationToken cancellationToken = default) where TObject : class
{
return $"I want you to act as a translator from {sourceLanguage} to {targetLanguage}. " +
"I will provide you with an English sentence and you will translate it into Russian. " +
"In the response write ONLY translated text."
+ (_extraPrompt is not null ? "\n" + _extraPrompt : "");
ArgumentNullException.ThrowIfNull(objectToTranslate);
var sourceLanguageOrDefault = sourceLanguage ?? _defaultSourceLanguage;
var targetLanguageOrDefault = targetLanguage ?? _defaultTargetLanguage;
if (sourceLanguageOrDefault is null)
{
throw new ArgumentNullException(nameof(sourceLanguage), "Source language is not specified");
}
if (targetLanguageOrDefault is null)
{
throw new ArgumentNullException(nameof(targetLanguage), "Target language is not specified");
}

var prompt = CreateObjectTranslationPrompt(sourceLanguageOrDefault, targetLanguageOrDefault);
jsonSerializerOptions ??= new JsonSerializerOptions() {DefaultIgnoreCondition = JsonIgnoreCondition.Never};
var objectJson = JsonSerializer.Serialize(objectToTranslate, jsonSerializerOptions);
var dialog = Dialog.StartAsSystem(prompt).ThenUser(objectJson);
var messages = dialog.GetMessages().ToArray();
(model, maxTokens) = ChatCompletionMessage.FindOptimalModelAndMaxToken(messages, model, maxTokens);
var response = await _client.GetStructuredResponse<TObject>(
dialog,
maxTokens.Value,
model,
temperature,
user,
requestModifier,
rawResponseGetter,
jsonDeserializerOptions,
cancellationToken
);
return response;

string CreateObjectTranslationPrompt(string sourceLanguage, string targetLanguage)
{
ArgumentNullException.ThrowIfNull(sourceLanguage);
ArgumentNullException.ThrowIfNull(targetLanguage);
return $"I want you to act as a translator from {sourceLanguage} to {targetLanguage}. " +
"The user provides you with an object in json. You translate only the text fields that need to be translated. " +
(_extraPrompt is not null ? "\n" + _extraPrompt : "");
}
}
}
3 changes: 3 additions & 0 deletions OpenAI.ChatGpt.Modules.Translator/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<PackageId>OpenAI.ChatGPT.Modules.Translator</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT based language translator</Product>
<Version>2.7.0</Version>
<Description>OpenAI ChatGPT based language translator.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand All @@ -26,7 +25,18 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\OpenAI.ChatGpt\OpenAI.ChatGpt.csproj" />
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Fody" Version="6.6.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OpenAI.ChatGpt.Modules.StructuredResponse\OpenAI.ChatGpt.Modules.StructuredResponse.csproj"/>
<ProjectReference Include="..\OpenAI.ChatGpt\OpenAI.ChatGpt.csproj"/>
</ItemGroup>

</Project>
79 changes: 79 additions & 0 deletions OpenAI.ChatGpt.Modules.Translator/OpenAiClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Text.Json;
using OpenAI.ChatGpt.Models.ChatCompletion;

namespace OpenAI.ChatGpt.Modules.Translator;

[Fody.ConfigureAwait(false)]
public static class OpenAiClientExtensions
{
public static Task<string> TranslateText(
this IOpenAiClient client,
string text,
string sourceLanguage,
string targetLanguage,
string? extraPrompt = null,
int? maxTokens = null,
string? model = null,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(text);
ArgumentNullException.ThrowIfNull(sourceLanguage);
ArgumentNullException.ThrowIfNull(targetLanguage);

var translator = new ChatGPTTranslatorService(client, extraPrompt: extraPrompt);
return translator.TranslateText(
text,
sourceLanguage,
targetLanguage,
maxTokens,
model,
temperature,
user,
requestModifier,
rawResponseGetter,
cancellationToken);
}

public static Task<TObject> TranslateObject<TObject>(
this IOpenAiClient client,
TObject objectToTranslate,
string sourceLanguage,
string targetLanguage,
string? extraPrompt = null,
int? maxTokens = null,
string? model = null,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
JsonSerializerOptions? jsonSerializerOptions = null,
JsonSerializerOptions? jsonDeserializerOptions = null,
CancellationToken cancellationToken = default) where TObject: class
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(objectToTranslate);
ArgumentNullException.ThrowIfNull(sourceLanguage);
ArgumentNullException.ThrowIfNull(targetLanguage);

var translator = new ChatGPTTranslatorService(client, extraPrompt: extraPrompt);
return translator.TranslateObject(
objectToTranslate,
sourceLanguage,
targetLanguage,
maxTokens,
model,
temperature,
user,
requestModifier,
rawResponseGetter,
jsonSerializerOptions,
jsonDeserializerOptions,
cancellationToken
);
}
}
1 change: 1 addition & 0 deletions OpenAI.ChatGpt/ChatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace OpenAI.ChatGpt;
/// Used for communication between a user and the assistant (ChatGPT).
/// </summary>
/// <remarks>Not thread-safe. Use one instance per user.</remarks>
[Fody.ConfigureAwait(false)]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class ChatService : IDisposable, IAsyncDisposable
{
Expand Down
3 changes: 3 additions & 0 deletions OpenAI.ChatGpt/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal static async IAsyncEnumerable<TResponse>
[EnumeratorCancellation] CancellationToken cancellationToken = default)
where TRequest : notnull
{
ArgumentNullException.ThrowIfNull(httpClient);
ArgumentNullException.ThrowIfNull(requestUri);
ArgumentNullException.ThrowIfNull(request);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri)
{
Content = JsonContent.Create(request, options: serializerOptions)
Expand Down
1 change: 0 additions & 1 deletion OpenAI.ChatGpt/OpenAI.ChatGpt.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<PackageId>OpenAI.ChatGPT</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT integration for .NET</Product>
<Version>2.7.0</Version>
<Description>.NET integration for ChatGPT with streaming responses supporting (like ChatGPT) via async streams.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
Loading

0 comments on commit 6cc79d8

Please sign in to comment.