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; } }