openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.3.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/Custom/Chat/ChatClient.cs

372lines · modecode

1using OpenAI.Telemetry;
2using System;
3using System.ClientModel;
4using System.ClientModel.Primitives;
5using System.Collections.Generic;
6using System.Diagnostics.CodeAnalysis;
7using System.Linq;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace OpenAI.Chat;
12
13// CUSTOM:
14// - Renamed.
15// - Suppressed constructor that takes endpoint parameter; endpoint is now a property in the options class.
16// - Suppressed methods that only take the options parameter.
17/// <summary> The service client for OpenAI chat operations. </summary>
18[CodeGenType("Chat")]
19[CodeGenSuppress("ChatClient", typeof(ClientPipeline), typeof(Uri))]
20[CodeGenSuppress("CompleteChat", typeof(ChatCompletionOptions), typeof(CancellationToken))]
21[CodeGenSuppress("CompleteChatAsync", typeof(ChatCompletionOptions), typeof(CancellationToken))]
22public partial class ChatClient
23{
24 private readonly string _model;
25 private readonly OpenTelemetrySource _telemetry;
26 private static readonly InternalChatCompletionStreamOptions s_includeUsageStreamOptions = new(includeUsage: true, additionalBinaryDataProperties: null);
27
28 // CUSTOM: Added as a convenience.
29 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
30 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
31 /// <param name="apiKey"> The API key to authenticate with the service. </param>
32 /// <exception cref="ArgumentNullException"> <paramref name="model"/> or <paramref name="apiKey"/> is null. </exception>
33 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
34 public ChatClient(string model, string apiKey) : this(model, new ApiKeyCredential(apiKey), new OpenAIClientOptions())
35 {
36 }
37
38 // CUSTOM:
39 // - Added `model` parameter.
40 // - Used a custom pipeline.
41 // - Demoted the endpoint parameter to be a property in the options class.
42 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
43 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
44 /// <param name="credential"> The API key to authenticate with the service. </param>
45 /// <exception cref="ArgumentNullException"> <paramref name="model"/> or <paramref name="credential"/> is null. </exception>
46 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
47 public ChatClient(string model, ApiKeyCredential credential) : this(model, credential, new OpenAIClientOptions())
48 {
49 }
50
51 // CUSTOM:
52 // - Added `model` parameter.
53 // - Used a custom pipeline.
54 // - Demoted the endpoint parameter to be a property in the options class.
55 // - Added telemetry support.
56 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
57 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
58 /// <param name="credential"> The API key to authenticate with the service. </param>
59 /// <param name="options"> The options to configure the client. </param>
60 /// <exception cref="ArgumentNullException"> <paramref name="model"/> or <paramref name="credential"/> is null. </exception>
61 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
62 public ChatClient(string model, ApiKeyCredential credential, OpenAIClientOptions options) : this(model, OpenAIClient.CreateApiKeyAuthenticationPolicy(credential), options)
63 {
64 }
65
66 // CUSTOM: Added as a convenience.
67 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
68 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
69 /// <param name="authenticationPolicy"> The authentication policy used to authenticate with the service. </param>
70 /// <exception cref="ArgumentNullException"> <paramref name="model"/> or <paramref name="authenticationPolicy"/> is null. </exception>
71 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
72 [Experimental("OPENAI001")]
73 public ChatClient(string model, AuthenticationPolicy authenticationPolicy) : this(model, authenticationPolicy, new OpenAIClientOptions())
74 {
75 }
76
77 // CUSTOM: Added as a convenience.
78 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
79 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
80 /// <param name="authenticationPolicy"> The authentication policy used to authenticate with the service. </param>
81 /// <param name="options"> The options to configure the client. </param>
82 /// <exception cref="ArgumentNullException"> <paramref name="model"/> or <paramref name="authenticationPolicy"/> is null. </exception>
83 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
84 [Experimental("OPENAI001")]
85 public ChatClient(string model, AuthenticationPolicy authenticationPolicy, OpenAIClientOptions options)
86 {
87 Argument.AssertNotNullOrEmpty(model, nameof(model));
88 Argument.AssertNotNull(authenticationPolicy, nameof(authenticationPolicy));
89 options ??= new OpenAIClientOptions();
90
91 _model = model;
92 Pipeline = OpenAIClient.CreatePipeline(authenticationPolicy, options);
93 _endpoint = OpenAIClient.GetEndpoint(options);
94 _telemetry = new OpenTelemetrySource(model, _endpoint);
95 }
96
97 // CUSTOM:
98 // - Added `model` parameter.
99 // - Used a custom pipeline.
100 // - Demoted the endpoint parameter to be a property in the options class.
101 // - Added telemetry support.
102 // - Made protected.
103 /// <summary> Initializes a new instance of <see cref="ChatClient"/>. </summary>
104 /// <param name="pipeline"> The HTTP pipeline to send and receive REST requests and responses. </param>
105 /// <param name="model"> The name of the model to use in requests sent to the service. To learn more about the available models, see <see href="https://platform.openai.com/docs/models"/>. </param>
106 /// <param name="options"> The options to configure the client. </param>
107 /// <exception cref="ArgumentNullException"> <paramref name="pipeline"/> or <paramref name="model"/> is null. </exception>
108 /// <exception cref="ArgumentException"> <paramref name="model"/> is an empty string, and was expected to be non-empty. </exception>
109 protected internal ChatClient(ClientPipeline pipeline, string model, OpenAIClientOptions options)
110 {
111 Argument.AssertNotNull(pipeline, nameof(pipeline));
112 Argument.AssertNotNullOrEmpty(model, nameof(model));
113 options ??= new OpenAIClientOptions();
114
115 _model = model;
116 Pipeline = pipeline;
117 _endpoint = OpenAIClient.GetEndpoint(options);
118 _telemetry = new OpenTelemetrySource(model, _endpoint);
119 }
120
121 /// <summary>
122 /// Gets the name of the model used in requests sent to the service.
123 /// </summary>
124 [Experimental("OPENAI001")]
125 public string Model => _model;
126
127 /// <summary> Generates a completion for the given chat. </summary>
128 /// <param name="messages"> The messages comprising the chat so far. </param>
129 /// <param name="options"> The options to configure the chat completion. </param>
130 /// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
131 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
132 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
133 public virtual async Task<ClientResult<ChatCompletion>> CompleteChatAsync(IEnumerable<ChatMessage> messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
134 {
135 Argument.AssertNotNullOrEmpty(messages, nameof(messages));
136
137 options ??= new();
138 CreateChatCompletionOptions(messages, ref options);
139 using OpenTelemetryScope scope = _telemetry.StartChatScope(options);
140
141 try
142 {
143 using BinaryContent content = options.ToBinaryContent();
144
145 ClientResult result = await CompleteChatAsync(content, cancellationToken.ToRequestOptions()).ConfigureAwait(false);
146 ChatCompletion chatCompletion = ChatCompletion.FromClientResult(result);
147 scope?.RecordChatCompletion(chatCompletion);
148 return ClientResult.FromValue(chatCompletion, result.GetRawResponse());
149 }
150 catch (Exception ex)
151 {
152 scope?.RecordException(ex);
153 throw;
154 }
155 }
156
157 /// <summary> Generates a completion for the given chat. </summary>
158 /// <param name="messages"> The messages comprising the chat so far. </param>
159 /// <param name="options"> The options to configure the chat completion. </param>
160 /// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
161 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
162 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
163 public virtual ClientResult<ChatCompletion> CompleteChat(IEnumerable<ChatMessage> messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
164 {
165 Argument.AssertNotNullOrEmpty(messages, nameof(messages));
166
167 options ??= new();
168 CreateChatCompletionOptions(messages, ref options);
169 using OpenTelemetryScope scope = _telemetry.StartChatScope(options);
170
171 try
172 {
173 using BinaryContent content = options.ToBinaryContent();
174 ClientResult result = CompleteChat(content, cancellationToken.ToRequestOptions());
175 ChatCompletion chatCompletion = ChatCompletion.FromClientResult(result);
176
177 scope?.RecordChatCompletion(chatCompletion);
178 return ClientResult.FromValue(chatCompletion, result.GetRawResponse());
179 }
180 catch (Exception ex)
181 {
182 scope?.RecordException(ex);
183 throw;
184 }
185 }
186
187 /// <summary> Generates a completion for the given chat. </summary>
188 /// <param name="messages"> The messages comprising the chat so far. </param>
189 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
190 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
191 public virtual async Task<ClientResult<ChatCompletion>> CompleteChatAsync(params ChatMessage[] messages)
192 => await CompleteChatAsync(messages, default(ChatCompletionOptions)).ConfigureAwait(false);
193
194 /// <summary> Generates a completion for the given chat. </summary>
195 /// <param name="messages"> The messages comprising the chat so far. </param>
196 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
197 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
198 public virtual ClientResult<ChatCompletion> CompleteChat(params ChatMessage[] messages)
199 => CompleteChat(messages, default(ChatCompletionOptions));
200
201 /// <summary>
202 /// Generates a completion for the given chat. The completion is streamed back token by token as it is being
203 /// generated by the model instead of waiting for it to be finished first.
204 /// </summary>
205 /// <remarks>
206 /// <see cref="AsyncCollectionResult{T}"/> implements the <see cref="IAsyncEnumerable{T}"/> interface and can be
207 /// enumerated over using the <c>await foreach</c> pattern.
208 /// </remarks>
209 /// <param name="messages"> The messages comprising the chat so far. </param>
210 /// <param name="options"> The options to configure the chat completion. </param>
211 /// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
212 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
213 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
214 public virtual AsyncCollectionResult<StreamingChatCompletionUpdate> CompleteChatStreamingAsync(IEnumerable<ChatMessage> messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
215 {
216 Argument.AssertNotNull(messages, nameof(messages));
217
218 options ??= new();
219 CreateChatCompletionOptions(messages, ref options, stream: true);
220
221 using BinaryContent content = options.ToBinaryContent();
222 return new AsyncSseUpdateCollection<StreamingChatCompletionUpdate>(
223 async () => await CompleteChatAsync(content, cancellationToken.ToRequestOptions(streaming: true)).ConfigureAwait(false),
224 StreamingChatCompletionUpdate.DeserializeStreamingChatCompletionUpdate,
225 cancellationToken);
226 }
227
228 /// <summary>
229 /// Generates a completion for the given chat. The completion is streamed back token by token as it is being
230 /// generated by the model instead of waiting for it to be finished first.
231 /// </summary>
232 /// <remarks>
233 /// <see cref="CollectionResult{T}"/> implements the <see cref="IEnumerable{T}"/> interface and can be
234 /// enumerated over using the <c>await foreach</c> pattern.
235 /// </remarks>
236 /// <param name="messages"> The messages comprising the chat so far. </param>
237 /// <param name="options"> The options to configure the chat completion. </param>
238 /// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
239 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
240 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
241 public virtual CollectionResult<StreamingChatCompletionUpdate> CompleteChatStreaming(IEnumerable<ChatMessage> messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default)
242 {
243 Argument.AssertNotNull(messages, nameof(messages));
244
245 options ??= new();
246 CreateChatCompletionOptions(messages, ref options, stream: true);
247
248 using BinaryContent content = options.ToBinaryContent();
249 return new SseUpdateCollection<StreamingChatCompletionUpdate>(
250 () => CompleteChat(content, cancellationToken.ToRequestOptions(streaming: true)),
251 StreamingChatCompletionUpdate.DeserializeStreamingChatCompletionUpdate,
252 cancellationToken);
253 }
254
255 /// <summary>
256 /// Generates a completion for the given chat. The completion is streamed back token by token as it is being
257 /// generated by the model instead of waiting for it to be finished first.
258 /// </summary>
259 /// <remarks>
260 /// <see cref="AsyncCollectionResult{T}"/> implements the <see cref="IAsyncEnumerable{T}"/> interface and can be
261 /// enumerated over using the <c>await foreach</c> pattern.
262 /// </remarks>
263 /// <param name="messages"> The messages comprising the chat so far. </param>
264 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
265 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
266 public virtual AsyncCollectionResult<StreamingChatCompletionUpdate> CompleteChatStreamingAsync(params ChatMessage[] messages)
267 => CompleteChatStreamingAsync(messages, default(ChatCompletionOptions));
268
269 /// <summary>
270 /// Generates a completion for the given chat. The completion is streamed back token by token as it is being
271 /// generated by the model instead of waiting for it to be finished first.
272 /// </summary>
273 /// <remarks>
274 /// <see cref="CollectionResult{T}"/> implements the <see cref="IEnumerable{T}"/> interface and can be
275 /// enumerated over using the <c>await foreach</c> pattern.
276 /// </remarks>
277 /// <param name="messages"> The messages comprising the chat so far. </param>
278 /// <exception cref="ArgumentNullException"> <paramref name="messages"/> is null. </exception>
279 /// <exception cref="ArgumentException"> <paramref name="messages"/> is an empty collection, and was expected to be non-empty. </exception>
280 public virtual CollectionResult<StreamingChatCompletionUpdate> CompleteChatStreaming(params ChatMessage[] messages)
281 => CompleteChatStreaming(messages, default(ChatCompletionOptions));
282
283 // CUSTOM:
284 // - Added Experimental attribute.
285 // - Call FromClientResult.
286 [Experimental("OPENAI001")]
287 public virtual async Task<ClientResult<ChatCompletion>> GetChatCompletionAsync(string completionId, CancellationToken cancellationToken = default)
288 {
289 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
290
291 ClientResult result = await GetChatCompletionAsync(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
292 return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse());
293 }
294
295 // CUSTOM:
296 // - Added Experimental attribute.
297 // - Call FromClientResult.
298 [Experimental("OPENAI001")]
299 public virtual ClientResult<ChatCompletion> GetChatCompletion(string completionId, CancellationToken cancellationToken = default)
300 {
301 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
302
303 ClientResult result = GetChatCompletion(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
304 return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse());
305 }
306
307 // CUSTOM:
308 // - Call FromClientResult.
309 [Experimental("OPENAI001")]
310 public virtual ClientResult<ChatCompletion> UpdateChatCompletion(string completionId, IDictionary<string, string> metadata, CancellationToken cancellationToken = default)
311 {
312 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
313 Argument.AssertNotNull(metadata, nameof(metadata));
314
315 InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null);
316 ClientResult result = this.UpdateChatCompletion(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
317 return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse());
318 }
319
320 // CUSTOM:
321 // - Call FromClientResult.
322 [Experimental("OPENAI001")]
323 public virtual async Task<ClientResult<ChatCompletion>> UpdateChatCompletionAsync(string completionId, IDictionary<string, string> metadata, CancellationToken cancellationToken = default)
324 {
325 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
326 Argument.AssertNotNull(metadata, nameof(metadata));
327
328 InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null);
329 ClientResult result = await this.UpdateChatCompletionAsync(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
330 return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse());
331 }
332
333 // CUSTOM:
334 // - Added Experimental attribute.
335 // - Call FromClientResult.
336 [Experimental("OPENAI001")]
337 public virtual async Task<ClientResult<ChatCompletionDeletionResult>> DeleteChatCompletionAsync(string completionId, CancellationToken cancellationToken = default)
338 {
339 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
340
341 ClientResult result = await DeleteChatCompletionAsync(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
342 return ClientResult.FromValue(ChatCompletionDeletionResult.FromClientResult(result), result.GetRawResponse());
343 }
344
345 // CUSTOM:
346 // - Added Experimental attribute.
347 // - Call FromClientResult.
348 [Experimental("OPENAI001")]
349 public virtual ClientResult<ChatCompletionDeletionResult> DeleteChatCompletion(string completionId, CancellationToken cancellationToken = default)
350 {
351 Argument.AssertNotNullOrEmpty(completionId, nameof(completionId));
352
353 ClientResult result = DeleteChatCompletion(completionId, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null);
354 return ClientResult.FromValue(ChatCompletionDeletionResult.FromClientResult(result), result.GetRawResponse());
355 }
356
357 private void CreateChatCompletionOptions(IEnumerable<ChatMessage> messages, ref ChatCompletionOptions options, bool stream = false)
358 {
359 options.Messages = messages.ToList();
360 options.Model = _model;
361 if (stream)
362 {
363 options.Stream = true;
364 options.StreamOptions = s_includeUsageStreamOptions;
365 }
366 else
367 {
368 options.Stream = null;
369 options.StreamOptions = null;
370 }
371 }
372}