openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
achandmsft-patch-1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/Custom/Chat/ChatClient.cs

270lines · modecode

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