using Microsoft.TypeSpec.Generator.Customizations;
using OpenAI.Telemetry;
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenAI.Chat;
// CUSTOM:
// - Renamed.
// - Suppressed constructor that takes endpoint parameter; endpoint is now a property in the options class.
// - Suppressed methods that only take the options parameter.
/// The service client for OpenAI chat operations.
[CodeGenType("Chat")]
[CodeGenSuppress("ChatClient", typeof(ClientPipeline), typeof(Uri))]
[CodeGenSuppress("CompleteChat", typeof(ChatCompletionOptions), typeof(CancellationToken))]
[CodeGenSuppress("CompleteChatAsync", typeof(ChatCompletionOptions), typeof(CancellationToken))]
public partial class ChatClient
{
private readonly string _model;
private readonly OpenTelemetrySource _telemetry;
private static readonly InternalChatCompletionStreamOptions s_includeUsageStreamOptions = new(includeUsage: true, patch: default);
// CUSTOM: Added as a convenience.
/// Initializes a new instance of .
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The API key to authenticate with the service.
/// or is null.
/// is an empty string, and was expected to be non-empty.
public ChatClient(string model, string apiKey) : this(model, new ApiKeyCredential(apiKey), new OpenAIClientOptions())
{
}
// CUSTOM:
// - Added `model` parameter.
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
/// Initializes a new instance of .
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The to authenticate with the service.
/// or is null.
/// is an empty string, and was expected to be non-empty.
public ChatClient(string model, ApiKeyCredential credential) : this(model, credential, new OpenAIClientOptions())
{
}
// CUSTOM:
// - Added `model` parameter.
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
// - Added telemetry support.
/// Initializes a new instance of .
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The to authenticate with the service.
/// The options to configure the client.
/// or is null.
/// is an empty string, and was expected to be non-empty.
public ChatClient(string model, ApiKeyCredential credential, OpenAIClientOptions options) : this(model, OpenAIClient.CreateApiKeyAuthenticationPolicy(credential), options)
{
}
// CUSTOM: Added as a convenience.
/// Initializes a new instance of .
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The authentication policy used to authenticate with the service.
/// or is null.
/// is an empty string, and was expected to be non-empty.
[Experimental("OPENAI001")]
public ChatClient(string model, AuthenticationPolicy authenticationPolicy) : this(model, authenticationPolicy, new OpenAIClientOptions())
{
}
// CUSTOM: Added as a convenience.
/// Initializes a new instance of .
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The authentication policy used to authenticate with the service.
/// The options to configure the client.
/// or is null.
/// is an empty string, and was expected to be non-empty.
[Experimental("OPENAI001")]
public ChatClient(string model, AuthenticationPolicy authenticationPolicy, OpenAIClientOptions options)
{
Argument.AssertNotNullOrEmpty(model, nameof(model));
Argument.AssertNotNull(authenticationPolicy, nameof(authenticationPolicy));
options ??= new OpenAIClientOptions();
_model = model;
Pipeline = OpenAIClient.CreatePipeline(authenticationPolicy, options);
_endpoint = OpenAIClient.GetEndpoint(options);
_telemetry = new OpenTelemetrySource(model, _endpoint);
}
// CUSTOM:
// - Added `model` parameter.
// - Used a custom pipeline.
// - Demoted the endpoint parameter to be a property in the options class.
// - Added telemetry support.
// - Made protected.
/// Initializes a new instance of .
/// The HTTP pipeline to send and receive REST requests and responses.
/// The name of the model to use in requests sent to the service. To learn more about the available models, see .
/// The options to configure the client.
/// or is null.
/// is an empty string, and was expected to be non-empty.
protected internal ChatClient(ClientPipeline pipeline, string model, OpenAIClientOptions options)
{
Argument.AssertNotNull(pipeline, nameof(pipeline));
Argument.AssertNotNullOrEmpty(model, nameof(model));
options ??= new OpenAIClientOptions();
_model = model;
Pipeline = pipeline;
_endpoint = OpenAIClient.GetEndpoint(options);
_telemetry = new OpenTelemetrySource(model, _endpoint);
}
[Experimental("SCME0002")]
public ChatClient(ChatClientSettings settings)
: this(settings?.Model, AuthenticationPolicy.Create(settings), settings?.Options)
{
}
///
/// Gets the name of the model used in requests sent to the service.
///
[Experimental("OPENAI001")]
public string Model => _model;
///
/// Gets the endpoint URI for the service.
///
[Experimental("OPENAI001")]
public Uri Endpoint => _endpoint;
/// Generates a completion for the given chat.
/// The messages comprising the chat so far.
/// The options to configure the chat completion.
/// A token that can be used to cancel this method call.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual Task> CompleteChatAsync(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
{
return CompleteChatAsync(messages, options, cancellationToken.ToRequestOptions() ?? new RequestOptions());
}
internal async Task> CompleteChatAsync(IEnumerable messages, ChatCompletionOptions options, RequestOptions requestOptions)
{
Argument.AssertNotNullOrEmpty(messages, nameof(messages));
Argument.AssertNotNull(requestOptions, nameof(requestOptions));
if (requestOptions.BufferResponse is false)
{
throw new InvalidOperationException("'requestOptions.BufferResponse' must be 'true' when calling 'CompleteChatAsync'.");
}
options ??= new();
var clonedOptions = CreateChatCompletionOptions(messages, options);
using OpenTelemetryScope scope = _telemetry.StartChatScope(clonedOptions);
try
{
using BinaryContent content = clonedOptions.ToBinaryContent();
ClientResult result = await CompleteChatAsync(content, requestOptions).ConfigureAwait(false);
ChatCompletion chatCompletion = (ChatCompletion)result;
scope?.RecordChatCompletion(chatCompletion);
return ClientResult.FromValue(chatCompletion, result.GetRawResponse());
}
catch (Exception ex)
{
scope?.RecordException(ex);
throw;
}
}
/// Generates a completion for the given chat.
/// The messages comprising the chat so far.
/// The options to configure the chat completion.
/// A token that can be used to cancel this method call.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual ClientResult CompleteChat(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(messages, nameof(messages));
options ??= new();
var clonedOptions = CreateChatCompletionOptions(messages, options);
using OpenTelemetryScope scope = _telemetry.StartChatScope(clonedOptions);
try
{
using BinaryContent content = clonedOptions.ToBinaryContent();
ClientResult result = CompleteChat(content, cancellationToken.ToRequestOptions());
ChatCompletion chatCompletion = (ChatCompletion)result;
scope?.RecordChatCompletion(chatCompletion);
return ClientResult.FromValue(chatCompletion, result.GetRawResponse());
}
catch (Exception ex)
{
scope?.RecordException(ex);
throw;
}
}
/// Generates a completion for the given chat.
/// The messages comprising the chat so far.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual async Task> CompleteChatAsync(params ChatMessage[] messages)
=> await CompleteChatAsync(messages, default(ChatCompletionOptions)).ConfigureAwait(false);
/// Generates a completion for the given chat.
/// The messages comprising the chat so far.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual ClientResult CompleteChat(params ChatMessage[] messages)
=> CompleteChat(messages, default(ChatCompletionOptions));
///
/// Generates a completion for the given chat. The completion is streamed back token by token as it is being
/// generated by the model instead of waiting for it to be finished first.
///
///
/// implements the interface and can be
/// enumerated over using the await foreach pattern.
///
/// The messages comprising the chat so far.
/// The options to configure the chat completion.
/// A token that can be used to cancel this method call.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual AsyncCollectionResult CompleteChatStreamingAsync(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
{
return CompleteChatStreamingAsync(messages, options, cancellationToken.ToRequestOptions(streaming: true));
}
internal AsyncCollectionResult CompleteChatStreamingAsync(IEnumerable messages, ChatCompletionOptions options, RequestOptions requestOptions)
{
Argument.AssertNotNull(messages, nameof(messages));
Argument.AssertNotNull(requestOptions, nameof(requestOptions));
if (requestOptions.BufferResponse is true)
{
throw new InvalidOperationException("'requestOptions.BufferResponse' must be 'false' when calling 'CompleteChatStreamingAsync'.");
}
options ??= new();
var clonedOptions = CreateChatCompletionOptions(messages, options, stream: true);
using BinaryContent content = clonedOptions.ToBinaryContent();
return new AsyncSseUpdateCollection(
async () => await CompleteChatAsync(content, requestOptions).ConfigureAwait(false),
StreamingChatCompletionUpdate.DeserializeStreamingChatCompletionUpdate,
requestOptions.CancellationToken);
}
///
/// Generates a completion for the given chat. The completion is streamed back token by token as it is being
/// generated by the model instead of waiting for it to be finished first.
///
///
/// implements the interface and can be
/// enumerated over using the await foreach pattern.
///
/// The messages comprising the chat so far.
/// The options to configure the chat completion.
/// A token that can be used to cancel this method call.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual CollectionResult CompleteChatStreaming(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNull(messages, nameof(messages));
options ??= new();
var clonedOptions = CreateChatCompletionOptions(messages, options, stream: true);
using BinaryContent content = clonedOptions.ToBinaryContent();
return new SseUpdateCollection(
() => CompleteChat(content, cancellationToken.ToRequestOptions(streaming: true)),
StreamingChatCompletionUpdate.DeserializeStreamingChatCompletionUpdate,
cancellationToken);
}
///
/// Generates a completion for the given chat. The completion is streamed back token by token as it is being
/// generated by the model instead of waiting for it to be finished first.
///
///
/// implements the interface and can be
/// enumerated over using the await foreach pattern.
///
/// The messages comprising the chat so far.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual AsyncCollectionResult CompleteChatStreamingAsync(params ChatMessage[] messages)
=> CompleteChatStreamingAsync(messages, default(ChatCompletionOptions));
///
/// Generates a completion for the given chat. The completion is streamed back token by token as it is being
/// generated by the model instead of waiting for it to be finished first.
///
///
/// implements the interface and can be
/// enumerated over using the await foreach pattern.
///
/// The messages comprising the chat so far.
/// is null.
/// is an empty collection, and was expected to be non-empty.
public virtual CollectionResult CompleteChatStreaming(params ChatMessage[] messages)
=> CompleteChatStreaming(messages, default(ChatCompletionOptions));
// CUSTOM:
// - Added Experimental attribute.
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual async Task> GetChatCompletionAsync(string completionId, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
ClientResult result = await GetChatCompletionAsync(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
return ClientResult.FromValue((ChatCompletion)result, result.GetRawResponse());
}
// CUSTOM:
// - Added Experimental attribute.
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual ClientResult GetChatCompletion(string completionId, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
ClientResult result = GetChatCompletion(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
return ClientResult.FromValue((ChatCompletion)result, result.GetRawResponse());
}
// CUSTOM:
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual ClientResult UpdateChatCompletion(string completionId, IDictionary metadata, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
Argument.AssertNotNull(metadata, nameof(metadata));
InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null);
ClientResult result = this.UpdateChatCompletion(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
return ClientResult.FromValue((ChatCompletion)result, result.GetRawResponse());
}
// CUSTOM:
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual async Task> UpdateChatCompletionAsync(string completionId, IDictionary metadata, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
Argument.AssertNotNull(metadata, nameof(metadata));
InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null);
ClientResult result = await this.UpdateChatCompletionAsync(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
return ClientResult.FromValue((ChatCompletion)result, result.GetRawResponse());
}
// CUSTOM:
// - Added Experimental attribute.
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual async Task> DeleteChatCompletionAsync(string completionId, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
ClientResult result = await DeleteChatCompletionAsync(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
return ClientResult.FromValue((ChatCompletionDeletionResult)result, result.GetRawResponse());
}
// CUSTOM:
// - Added Experimental attribute.
// - Call FromClientResult.
[Experimental("OPENAI001")]
public virtual ClientResult DeleteChatCompletion(string completionId, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
ClientResult result = DeleteChatCompletion(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
return ClientResult.FromValue((ChatCompletionDeletionResult)result, result.GetRawResponse());
}
private ChatCompletionOptions CreateChatCompletionOptions(IEnumerable messages, ChatCompletionOptions options, bool stream = false)
{
var clonedOptions = options.Clone();
foreach (var message in messages)
{
clonedOptions.Messages.Add(message);
}
clonedOptions.Model ??= _model;
if (stream)
{
clonedOptions.Stream = true;
clonedOptions.StreamOptions = s_includeUsageStreamOptions;
}
else
{
clonedOptions.Stream = null;
clonedOptions.StreamOptions = null;
}
return clonedOptions;
}
}