openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.2.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/Custom/Chat/ChatClient.cs

324lines · modecode

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