Skip to content

Commit

Permalink
Add support for JSON mode and deterministic seed in ChatGPT API
Browse files Browse the repository at this point in the history
This commit introduces the ability to request JSON-formatted responses and deterministic outputs from the ChatGPT API. The jsonMode flag and seed option have been added to all ChatGPT completion methods. The OpenAiClient has been updated to build a request based on these options. The ChatGPTTranslatorService has been adjusted to include these parameters when making requests. The TokensCounterDecorator has been removed as it's deprecated in the API's newer versions. The version number has been updated to reflect these changes.
  • Loading branch information
rodion-m committed Nov 8, 2023
1 parent bfc518c commit 59cee5e
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 153 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.9.3</Version>
<Version>3.0.0</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
Expand Down
37 changes: 36 additions & 1 deletion src/OpenAI.ChatGpt/IOpenAiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ namespace OpenAI.ChatGpt;

public interface IOpenAiClient
{

Task<string> GetChatCompletions(
UserOrSystemMessage dialog,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default);
Expand All @@ -21,6 +24,8 @@ Task<string> GetChatCompletions(
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default);
Expand All @@ -31,6 +36,8 @@ Task<ChatCompletionResponse> GetChatCompletionsRaw(
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default);

Expand All @@ -49,6 +56,18 @@ Task<ChatCompletionResponse> GetChatCompletionsRaw(
/// A unique identifier representing your end-user, which can help OpenAI to monitor
/// and detect abuse.
/// </param>
/// <param name="jsonMode">
/// If true, the response will be returned as a JSON object.
/// When using JSON mode, always instruct the model to produce JSON via some message in the conversation,
/// for example via your system message.
/// See: https://platform.openai.com/docs/guides/text-generation/json-mode
/// </param>
/// <param name="seed">
/// This feature is in Beta.
/// If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.
/// Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.
/// See: https://platform.openai.com/docs/guides/text-generation/reproducible-outputs
/// </param>
/// <param name="requestModifier">A modifier of the raw request. Allows to specify any custom properties.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Chunks of ChatGPT's response, one by one.</returns>
Expand All @@ -58,6 +77,8 @@ IAsyncEnumerable<string> StreamChatCompletions(
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default);

Expand All @@ -69,6 +90,18 @@ IAsyncEnumerable<string> StreamChatCompletions(
/// <param name="model">One of <see cref="ChatCompletionModels"/></param>
/// <param name="temperature"><see cref="ChatCompletionRequest.Temperature"/>></param>
/// <param name="user"><see cref="ChatCompletionRequest.User"/></param>
/// <param name="jsonMode">
/// If true, the response will be returned as a JSON object.
/// When using JSON mode, always instruct the model to produce JSON via some message in the conversation,
/// for example via your system message.
/// See: https://platform.openai.com/docs/guides/text-generation/json-mode
/// </param>
/// <param name="seed">
/// This feature is in Beta.
/// If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.
/// Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.
/// See: https://platform.openai.com/docs/guides/text-generation/reproducible-outputs
/// </param>
/// <param name="requestModifier">Request modifier</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Chunks of ChatGPT's response, one by one</returns>
Expand All @@ -78,11 +111,13 @@ IAsyncEnumerable<string> StreamChatCompletions(
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default);

IAsyncEnumerable<string> StreamChatCompletions(
ChatCompletionRequest request,CancellationToken cancellationToken = default);
ChatCompletionRequest request, CancellationToken cancellationToken = default);

IAsyncEnumerable<ChatCompletionResponse> StreamChatCompletionsRaw(
ChatCompletionRequest request, CancellationToken cancellationToken = default);
Expand Down
29 changes: 29 additions & 0 deletions src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,33 @@ public int MaxTokens
/// </remarks>
[JsonPropertyName("user")]
public string? User { get; set; }

/// <summary>
/// An object specifying the format that the model must output.
/// </summary>
[JsonPropertyName("response_format")]
public ChatCompletionResponseFormat ResponseFormat { get; set; } = new();

/// <summary>
/// This feature is in Beta.
/// If specified, our system will make a best effort to sample deterministically, such that repeated requests with the
/// same `seed` and parameters should return the same result.
/// Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.
/// </summary>
[JsonPropertyName("seed")]
public long? Seed { get; set; }

public class ChatCompletionResponseFormat
{
/// <summary>
/// Setting to `json_object` enables JSON mode. This guarantees that the message the model generates is valid JSON.
/// Note that your system prompt must still instruct the model to produce JSON, and to help ensure you don't forget,
/// the API will throw an error if the string `JSON` does not appear in your system message. Also note that the message
/// content may be partial (i.e. cut off) if `finish_reason="length"`, which indicates the generation exceeded `max_tokens`
/// or the conversation exceeded the max context length.
/// Must be one of `text` or `json_object`.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "text";
}
}
81 changes: 43 additions & 38 deletions src/OpenAI.ChatGpt/OpenAiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,15 @@ private static void ValidateHttpClient(HttpClient httpClient)
}
}

/// <inheritdoc />
public async Task<string> GetChatCompletions(
UserOrSystemMessage dialog,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default)
Expand All @@ -146,6 +149,8 @@ public async Task<string> GetChatCompletions(
model,
temperature,
user,
jsonMode,
seed,
false,
requestModifier
);
Expand All @@ -154,12 +159,15 @@ public async Task<string> GetChatCompletions(
return response.Choices[0].Message!.Content;
}

/// <inheritdoc />
public async Task<string> GetChatCompletions(
IEnumerable<ChatCompletionMessage> messages,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
Action<ChatCompletionResponse>? rawResponseGetter = null,
CancellationToken cancellationToken = default)
Expand All @@ -173,20 +181,25 @@ public async Task<string> GetChatCompletions(
model,
temperature,
user,
false,
requestModifier
jsonMode,
seed,
stream: false,
requestModifier: requestModifier
);
var response = await GetChatCompletionsRaw(request, cancellationToken);
rawResponseGetter?.Invoke(response);
return response.GetMessageContent();
}

/// <inheritdoc />
public async Task<ChatCompletionResponse> GetChatCompletionsRaw(
IEnumerable<ChatCompletionMessage> messages,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default)
{
Expand All @@ -199,8 +212,10 @@ public async Task<ChatCompletionResponse> GetChatCompletionsRaw(
model,
temperature,
user,
false,
requestModifier
jsonMode,
seed,
stream: false,
requestModifier: requestModifier
);
var response = await GetChatCompletionsRaw(request, cancellationToken);
return response;
Expand Down Expand Up @@ -229,30 +244,15 @@ internal async Task<ChatCompletionResponse> GetChatCompletionsRaw(
return jsonResponse;
}

/// <summary>
/// Start streaming chat completions like ChatGPT
/// </summary>
/// <param name="messages">The history of messaging</param>
/// <param name="maxTokens">The length of the response</param>
/// <param name="model">One of <see cref="ChatCompletionModels"/></param>
/// <param name="temperature">
/// What sampling temperature to use, between 0 and 2.
/// Higher values like 0.8 will make the output more random,
/// while lower values like 0.2 will make it more focused and deterministic.
/// </param>
/// <param name="user">
/// A unique identifier representing your end-user, which can help OpenAI to monitor
/// and detect abuse.
/// </param>
/// <param name="requestModifier">A modifier of the raw request. Allows to specify any custom properties.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Chunks of ChatGPT's response, one by one.</returns>
/// <inheritdoc />
public IAsyncEnumerable<string> StreamChatCompletions(
IEnumerable<ChatCompletionMessage> messages,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = 0,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default)
{
Expand All @@ -265,8 +265,10 @@ public IAsyncEnumerable<string> StreamChatCompletions(
model,
temperature,
user,
true,
requestModifier
jsonMode,
seed,
stream: true,
requestModifier: requestModifier
);
return StreamChatCompletions(request, cancellationToken);
}
Expand All @@ -277,6 +279,8 @@ private static ChatCompletionRequest CreateChatCompletionRequest(
string model,
float temperature,
string? user,
bool jsonMode,
long? seed,
bool stream,
Action<ChatCompletionRequest>? requestModifier)
{
Expand All @@ -287,29 +291,26 @@ private static ChatCompletionRequest CreateChatCompletionRequest(
MaxTokens = maxTokens,
Stream = stream,
User = user,
Temperature = temperature
Temperature = temperature,
ResponseFormat = new ChatCompletionRequest.ChatCompletionResponseFormat()
{
Type = jsonMode ? "json_object" : "text"
},
Seed = seed,
};
requestModifier?.Invoke(request);
return request;
}

/// <summary>
/// Start streaming chat completions like ChatGPT
/// </summary>
/// <param name="messages">The history of messaging</param>
/// <param name="maxTokens">The length of the response</param>
/// <param name="model">One of <see cref="ChatCompletionModels"/></param>
/// <param name="temperature"><see cref="ChatCompletionRequest.Temperature"/>></param>
/// <param name="user"><see cref="ChatCompletionRequest.User"/></param>
/// <param name="requestModifier">Request modifier</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Chunks of ChatGPT's response, one by one</returns>
/// <inheritdoc />
public IAsyncEnumerable<string> StreamChatCompletions(
UserOrSystemMessage messages,
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
string model = ChatCompletionModels.Default,
float temperature = ChatCompletionTemperatures.Default,
string? user = null,
bool jsonMode = false,
long? seed = null,
Action<ChatCompletionRequest>? requestModifier = null,
CancellationToken cancellationToken = default)
{
Expand All @@ -321,12 +322,15 @@ public IAsyncEnumerable<string> StreamChatCompletions(
model,
temperature,
user,
true,
requestModifier
jsonMode,
seed,
stream: true,
requestModifier: requestModifier
);
return StreamChatCompletions(request, cancellationToken);
}

/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletions(
ChatCompletionRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
Expand All @@ -342,6 +346,7 @@ public async IAsyncEnumerable<string> StreamChatCompletions(
}
}

/// <inheritdoc />
public IAsyncEnumerable<ChatCompletionResponse> StreamChatCompletionsRaw(
ChatCompletionRequest request, CancellationToken cancellationToken = default)
{
Expand Down
Loading

0 comments on commit 59cee5e

Please sign in to comment.