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

tests/Chat/ChatTests.cs

1179lines · modecode

1using Microsoft.VisualStudio.TestPlatform.ObjectModel;
2using NUnit.Framework;
3using OpenAI.Chat;
4using OpenAI.Files;
5using OpenAI.Tests.Telemetry;
6using OpenAI.Tests.Utility;
7using System;
8using System.ClientModel;
9using System.ClientModel.Primitives;
10using System.Collections.Generic;
11using System.Diagnostics;
12using System.IO;
13using System.Linq;
14using System.Net;
15using System.Net.Http;
16using System.Text;
17using System.Text.Json;
18using System.Threading;
19using System.Threading.Tasks;
20using static OpenAI.Tests.Telemetry.TestMeterListener;
21using static OpenAI.Tests.TestHelpers;
22
23namespace OpenAI.Tests.Chat;
24
25[TestFixture(true)]
26[TestFixture(false)]
27[Parallelizable(ParallelScope.All)]
28[Category("Chat")]
29public class ChatTests : SyncAsyncTestBase
30{
31 public ChatTests(bool isAsync) : base(isAsync)
32 {
33 }
34
35 [Test]
36 public async Task HelloWorldChat()
37 {
38 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
39 IEnumerable<ChatMessage> messages = [new UserChatMessage("Hello, world!")];
40 ClientResult<ChatCompletion> result = IsAsync
41 ? await client.CompleteChatAsync(messages)
42 : client.CompleteChat(messages);
43 Assert.That(result, Is.InstanceOf<ClientResult<ChatCompletion>>());
44 Assert.That(result.Value.Content[0].Kind, Is.EqualTo(ChatMessageContentPartKind.Text));
45 Assert.That(result.Value.Content[0].Text.Length, Is.GreaterThan(0));
46 }
47
48 [Test]
49 public async Task HelloWorldWithTopLevelClient()
50 {
51 OpenAIClient client = GetTestClient<OpenAIClient>(TestScenario.TopLevel);
52 ChatClient chatClient = client.GetChatClient("gpt-4o-mini");
53 IEnumerable<ChatMessage> messages = [new UserChatMessage("Hello, world!")];
54 ClientResult<ChatCompletion> result = IsAsync
55 ? await chatClient.CompleteChatAsync(messages)
56 : chatClient.CompleteChat(messages);
57 Assert.That(result.Value.Content[0].Text.Length, Is.GreaterThan(0));
58 }
59
60 [Test]
61 public async Task MultiMessageChat()
62 {
63 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
64 IEnumerable<ChatMessage> messages = [
65 new SystemChatMessage("You are a helpful assistant. You always talk like a pirate."),
66 new UserChatMessage("Hello, assistant! Can you help me train my parrot?"),
67 ];
68 ClientResult<ChatCompletion> result = IsAsync
69 ? await client.CompleteChatAsync(messages)
70 : client.CompleteChat(messages);
71 Assert.That(new string[] { "aye", "arr", "hearty" }.Any(pirateWord => result.Value.Content[0].Text.ToLowerInvariant().Contains(pirateWord)));
72 }
73
74 [Test]
75 public void StreamingChat()
76 {
77 AssertSyncOnly();
78
79 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
80 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
81
82 int updateCount = 0;
83 ChatTokenUsage usage = null;
84 TimeSpan? firstTokenReceiptTime = null;
85 TimeSpan? latestTokenReceiptTime = null;
86 Stopwatch stopwatch = Stopwatch.StartNew();
87 CollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreaming(messages);
88
89 Assert.That(streamingResult, Is.InstanceOf<CollectionResult<StreamingChatCompletionUpdate>>());
90
91 foreach (StreamingChatCompletionUpdate chatUpdate in streamingResult)
92 {
93 firstTokenReceiptTime ??= stopwatch.Elapsed;
94 latestTokenReceiptTime = stopwatch.Elapsed;
95 usage ??= chatUpdate.Usage;
96 updateCount++;
97 }
98
99 stopwatch.Stop();
100
101 Assert.That(updateCount, Is.GreaterThan(1));
102 Assert.That(latestTokenReceiptTime - firstTokenReceiptTime > TimeSpan.FromMilliseconds(500));
103 Assert.That(usage, Is.Not.Null);
104 Assert.That(usage?.InputTokenCount, Is.GreaterThan(0));
105 Assert.That(usage?.OutputTokenCount, Is.GreaterThan(0));
106 Assert.That(usage?.OutputTokenDetails?.ReasoningTokenCount, Is.Null.Or.EqualTo(0));
107 }
108
109 [Test]
110 public async Task StreamingChatAsync()
111 {
112 AssertAsyncOnly();
113
114 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
115 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
116
117 int updateCount = 0;
118 ChatTokenUsage usage = null;
119 TimeSpan? firstTokenReceiptTime = null;
120 TimeSpan? latestTokenReceiptTime = null;
121 Stopwatch stopwatch = Stopwatch.StartNew();
122 AsyncCollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreamingAsync(messages);
123
124 Assert.That(streamingResult, Is.InstanceOf<AsyncCollectionResult<StreamingChatCompletionUpdate>>());
125
126 await foreach (StreamingChatCompletionUpdate chatUpdate in streamingResult)
127 {
128 firstTokenReceiptTime ??= stopwatch.Elapsed;
129 latestTokenReceiptTime = stopwatch.Elapsed;
130 usage ??= chatUpdate.Usage;
131 updateCount++;
132 }
133
134 stopwatch.Stop();
135
136 Assert.That(updateCount, Is.GreaterThan(1));
137 Assert.That(latestTokenReceiptTime - firstTokenReceiptTime > TimeSpan.FromMilliseconds(500));
138 Assert.That(usage, Is.Not.Null);
139 Assert.That(usage?.InputTokenCount, Is.GreaterThan(0));
140 Assert.That(usage?.OutputTokenCount, Is.GreaterThan(0));
141 Assert.That(usage?.OutputTokenDetails?.ReasoningTokenCount, Is.Null.Or.EqualTo(0));
142 }
143
144 [Test]
145 public void StreamingChatCanBeCancelled()
146 {
147 AssertSyncOnly();
148
149 MockPipelineResponse response = new(200);
150 response.SetContent("""
151 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null}
152
153 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null}
154
155 data: [DONE]
156 """);
157
158 OpenAIClientOptions options = new OpenAIClientOptions()
159 {
160 Transport = new MockPipelineTransport(response)
161 };
162
163 CancellationTokenSource cancellationTokenSource = new();
164 cancellationTokenSource.CancelAfter(1000);
165
166 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, options: options);
167 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
168
169 CollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreaming(messages, cancellationToken: cancellationTokenSource.Token);
170 IEnumerator<StreamingChatCompletionUpdate> enumerator = streamingResult.GetEnumerator();
171
172 enumerator.MoveNext();
173 StreamingChatCompletionUpdate firstUpdate = enumerator.Current;
174
175 Assert.That(firstUpdate, Is.Not.Null);
176 Assert.That(cancellationTokenSource.IsCancellationRequested, Is.False);
177
178 Thread.Sleep(1000);
179
180 Assert.Throws<OperationCanceledException>(() =>
181 {
182 // Should throw for the second update.
183 Assert.True(cancellationTokenSource.IsCancellationRequested);
184 Assert.True(cancellationTokenSource.Token.IsCancellationRequested);
185 enumerator.MoveNext();
186 enumerator.MoveNext();
187 });
188 }
189
190 [Test]
191 public async Task StreamingChatCanBeCancelledAsync()
192 {
193 AssertAsyncOnly();
194
195 MockPipelineResponse response = new(200);
196 response.SetContent("""
197 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null}
198
199 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null}
200
201 data: [DONE]
202 """);
203
204 OpenAIClientOptions options = new OpenAIClientOptions()
205 {
206 Transport = new MockPipelineTransport(response)
207 };
208
209 CancellationTokenSource cancellationTokenSource = new();
210 cancellationTokenSource.CancelAfter(1000);
211
212 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, options: options);
213 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
214
215 AsyncCollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreamingAsync(messages, cancellationToken: cancellationTokenSource.Token);
216 IAsyncEnumerator<StreamingChatCompletionUpdate> enumerator = streamingResult.GetAsyncEnumerator();
217
218 await enumerator.MoveNextAsync();
219 StreamingChatCompletionUpdate firstUpdate = enumerator.Current;
220
221 Assert.That(firstUpdate, Is.Not.Null);
222 Assert.That(cancellationTokenSource.IsCancellationRequested, Is.False);
223
224 Thread.Sleep(1000);
225
226 Assert.ThrowsAsync<OperationCanceledException>(async () =>
227 {
228 // Should throw for the second update.
229 Assert.True(cancellationTokenSource.IsCancellationRequested);
230 Assert.True(cancellationTokenSource.Token.IsCancellationRequested);
231 await enumerator.MoveNextAsync();
232 await enumerator.MoveNextAsync();
233 });
234 }
235
236 [Test]
237 public void CompleteChatStreamingClosesNetworkStream()
238 {
239 AssertSyncOnly();
240
241 MockPipelineResponse response = new(200);
242 response.SetContent("""
243 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null}
244
245 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null}
246
247 data: [DONE]
248 """);
249
250 OpenAIClientOptions options = new OpenAIClientOptions()
251 {
252 Transport = new MockPipelineTransport(response)
253 };
254
255 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, options: options);
256 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
257
258 int updateCount = 0;
259 TimeSpan? firstTokenReceiptTime = null;
260 TimeSpan? latestTokenReceiptTime = null;
261 Stopwatch stopwatch = Stopwatch.StartNew();
262 CollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreaming(messages);
263
264 Assert.That(streamingResult, Is.InstanceOf<CollectionResult<StreamingChatCompletionUpdate>>());
265 Assert.IsFalse(response.IsDisposed);
266
267 foreach (StreamingChatCompletionUpdate chatUpdate in streamingResult)
268 {
269 firstTokenReceiptTime ??= stopwatch.Elapsed;
270 latestTokenReceiptTime = stopwatch.Elapsed;
271 updateCount++;
272
273 Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
274 }
275
276 stopwatch.Stop();
277
278 Assert.IsTrue(response.IsDisposed);
279 }
280
281 [Test]
282 public async Task CompleteChatStreamingClosesNetworkStreamAsync()
283 {
284 AssertAsyncOnly();
285
286 MockPipelineResponse response = new(200);
287 response.SetContent("""
288 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null}
289
290 data: {"id":"chatcmpl-A7mKGugwaczn3YyrJLlZY6CM0Wlkr","object":"chat.completion.chunk","created":1726417424,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_483d39d857","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null}
291
292 data: [DONE]
293 """);
294
295 OpenAIClientOptions options = new OpenAIClientOptions()
296 {
297 Transport = new MockPipelineTransport(response)
298 };
299
300 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, options: options);
301 IEnumerable<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
302
303 int updateCount = 0;
304 TimeSpan? firstTokenReceiptTime = null;
305 TimeSpan? latestTokenReceiptTime = null;
306 Stopwatch stopwatch = Stopwatch.StartNew();
307 AsyncCollectionResult<StreamingChatCompletionUpdate> streamingResult = client.CompleteChatStreamingAsync(messages);
308
309 Assert.That(streamingResult, Is.InstanceOf<AsyncCollectionResult<StreamingChatCompletionUpdate>>());
310 Assert.IsFalse(response.IsDisposed);
311
312 await foreach (StreamingChatCompletionUpdate chatUpdate in streamingResult)
313 {
314 firstTokenReceiptTime ??= stopwatch.Elapsed;
315 latestTokenReceiptTime = stopwatch.Elapsed;
316 updateCount++;
317
318 Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
319 }
320
321 stopwatch.Stop();
322
323 Assert.IsTrue(response.IsDisposed);
324 }
325
326 [Test]
327 public async Task TwoTurnChat()
328 {
329 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
330
331 List<ChatMessage> messages =
332 [
333 new UserChatMessage("In geometry, what are the different kinds of triangles, as defined by lengths of their sides?"),
334 ];
335 ClientResult<ChatCompletion> firstResult = IsAsync
336 ? await client.CompleteChatAsync(messages)
337 : client.CompleteChat(messages);
338 Assert.That(firstResult?.Value, Is.Not.Null);
339 Assert.That(firstResult.Value.Content[0].Text.ToLowerInvariant(), Contains.Substring("isosceles"));
340 messages.Add(new AssistantChatMessage(firstResult.Value));
341 messages.Add(new UserChatMessage("Which of those is the one where exactly two sides are the same length?"));
342 ClientResult<ChatCompletion> secondResult = client.CompleteChat(messages);
343 Assert.That(secondResult?.Value, Is.Not.Null);
344 Assert.That(secondResult.Value.Content[0].Text.ToLowerInvariant(), Contains.Substring("isosceles"));
345 }
346
347 [Ignore("Temporarily disabled due to service instability.")]
348 [Test]
349 public async Task ChatWithVision()
350 {
351 string mediaType = "image/png";
352 string filePath = Path.Combine("Assets", "images_dog_and_cat.png");
353 using Stream stream = File.OpenRead(filePath);
354 BinaryData imageData = BinaryData.FromStream(stream);
355
356 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
357 IEnumerable<ChatMessage> messages = [
358 new UserChatMessage(
359 ChatMessageContentPart.CreateTextPart("Describe this image for me."),
360 ChatMessageContentPart.CreateImagePart(imageData, mediaType)),
361 ];
362 ChatCompletionOptions options = new() { MaxOutputTokenCount = 2048 };
363
364 ClientResult<ChatCompletion> result = IsAsync
365 ? await client.CompleteChatAsync(messages, options)
366 : client.CompleteChat(messages, options);
367 Console.WriteLine(result.Value.Content[0].Text);
368 Assert.That(result.Value.Content[0].Text.ToLowerInvariant(), Does.Contain("dog").Or.Contain("cat").IgnoreCase);
369 }
370
371 [Test]
372 public async Task ChatWithBasicAudioOutput()
373 {
374 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "gpt-4o-audio-preview");
375 List<ChatMessage> messages = ["Say the exact word 'hello' and nothing else."];
376 ChatCompletionOptions options = new()
377 {
378 AudioOptions = new(ChatOutputAudioVoice.Ash, ChatOutputAudioFormat.Pcm16),
379 ResponseModalities = ChatResponseModalities.Text | ChatResponseModalities.Audio,
380 };
381
382 StringBuilder transcriptBuilder = new();
383 using MemoryStream outputAudioStream = new();
384 string streamedAudioId = null;
385 ChatTokenUsage streamedUsage = null;
386 DateTimeOffset? streamedExpiresAt = null;
387
388 await foreach (StreamingChatCompletionUpdate update
389 in client.CompleteChatStreamingAsync(messages, options))
390 {
391 if (update.Usage is not null)
392 {
393 Assert.That(streamedUsage, Is.Null);
394 streamedUsage = update.Usage;
395 }
396 if (update.OutputAudioUpdate?.ExpiresAt is not null)
397 {
398 Assert.That(streamedExpiresAt, Is.Null);
399 streamedExpiresAt = update.OutputAudioUpdate.ExpiresAt;
400 }
401 if (update.OutputAudioUpdate?.Id is not null)
402 {
403 if (streamedAudioId is not null)
404 {
405 Assert.That(streamedAudioId, Is.EqualTo(update.OutputAudioUpdate.Id));
406 }
407 streamedAudioId ??= update.OutputAudioUpdate.Id;
408 }
409 transcriptBuilder.Append(update.OutputAudioUpdate?.TranscriptUpdate);
410 outputAudioStream.Write(update.OutputAudioUpdate?.AudioBytesUpdate);
411 }
412
413 Assert.That(streamedAudioId, Has.Length.GreaterThan("audio".Length));
414 Assert.That(transcriptBuilder.ToString().ToLower(), Does.Contain("hello"));
415 Assert.That(outputAudioStream.Length, Is.GreaterThan(9000));
416 Assert.That(streamedUsage?.InputTokenDetails?.AudioTokenCount, Is.EqualTo(0));
417 Assert.That(streamedUsage?.OutputTokenDetails?.AudioTokenCount, Is.GreaterThan(0));
418 Assert.That(streamedExpiresAt, Is.GreaterThan(DateTimeOffset.Parse("2025-01-01")));
419 }
420
421 [Test]
422 public async Task ChatWithAudio()
423 {
424 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "gpt-4o-audio-preview");
425
426 string helloWorldAudioPath = Path.Join("Assets", "audio_hello_world.mp3");
427 BinaryData helloWorldAudioBytes = BinaryData.FromBytes(File.ReadAllBytes(helloWorldAudioPath));
428 ChatMessageContentPart helloWorldAudioContentPart = ChatMessageContentPart.CreateInputAudioPart(
429 helloWorldAudioBytes,
430 ChatInputAudioFormat.Mp3);
431 string whatsTheWeatherAudioPath = Path.Join("Assets", "realtime_whats_the_weather_pcm16_24khz_mono.wav");
432 BinaryData whatsTheWeatherAudioBytes = BinaryData.FromBytes(File.ReadAllBytes(whatsTheWeatherAudioPath));
433 ChatMessageContentPart whatsTheWeatherAudioContentPart = ChatMessageContentPart.CreateInputAudioPart(
434 whatsTheWeatherAudioBytes,
435 ChatInputAudioFormat.Wav);
436
437 List<ChatMessage> messages = [new UserChatMessage([helloWorldAudioContentPart])];
438
439 ChatCompletionOptions options = new()
440 {
441 ResponseModalities = ChatResponseModalities.Text | ChatResponseModalities.Audio,
442 AudioOptions = new(ChatOutputAudioVoice.Alloy, ChatOutputAudioFormat.Pcm16)
443 };
444
445 ChatCompletion completion = await client.CompleteChatAsync(messages, options);
446 Assert.That(completion, Is.Not.Null);
447 Assert.That(completion.Content, Has.Count.EqualTo(0));
448
449 ChatOutputAudio outputAudio = completion.OutputAudio;
450 Assert.That(outputAudio, Is.Not.Null);
451 Assert.That(outputAudio.Id, Is.Not.Null.And.Not.Empty);
452 Assert.That(outputAudio.AudioBytes, Is.Not.Null);
453 Assert.That(outputAudio.Transcript, Is.Not.Null.And.Not.Empty);
454
455 AssistantChatMessage audioHistoryMessage = ChatMessage.CreateAssistantMessage(completion);
456 Assert.That(audioHistoryMessage, Is.InstanceOf<AssistantChatMessage>());
457 Assert.That(audioHistoryMessage.Content, Has.Count.EqualTo(0));
458
459 Assert.That(audioHistoryMessage.OutputAudioReference?.Id, Is.EqualTo(completion.OutputAudio.Id));
460 messages.Add(audioHistoryMessage);
461
462 messages.Add(
463 new UserChatMessage(
464 [
465 "Please answer the following spoken question:",
466 ChatMessageContentPart.CreateInputAudioPart(whatsTheWeatherAudioBytes, ChatInputAudioFormat.Wav),
467 ]));
468
469 string streamedCorrelationId = null;
470 DateTimeOffset? streamedExpiresAt = null;
471 StringBuilder streamedTranscriptBuilder = new();
472 ChatTokenUsage streamedUsage = null;
473 using MemoryStream outputAudioStream = new();
474 await foreach (StreamingChatCompletionUpdate update in client.CompleteChatStreamingAsync(messages, options))
475 {
476 Assert.That(update.ContentUpdate, Has.Count.EqualTo(0));
477 StreamingChatOutputAudioUpdate outputAudioUpdate = update.OutputAudioUpdate;
478
479 if (update.Usage is not null)
480 {
481 Assert.That(streamedUsage, Is.Null);
482 streamedUsage = update.Usage;
483 }
484 if (outputAudioUpdate is not null)
485 {
486 string serializedOutputAudioUpdate = ModelReaderWriter.Write(outputAudioUpdate).ToString();
487 Assert.That(serializedOutputAudioUpdate, Is.Not.Null.And.Not.Empty);
488
489 if (outputAudioUpdate.Id is not null)
490 {
491 Assert.That(streamedCorrelationId, Is.Null.Or.EqualTo(streamedCorrelationId));
492 streamedCorrelationId ??= outputAudioUpdate.Id;
493 }
494 if (outputAudioUpdate.ExpiresAt.HasValue)
495 {
496 Assert.That(streamedExpiresAt.HasValue, Is.False);
497 streamedExpiresAt = outputAudioUpdate.ExpiresAt;
498 }
499 streamedTranscriptBuilder.Append(outputAudioUpdate.TranscriptUpdate);
500 outputAudioStream.Write(outputAudioUpdate.AudioBytesUpdate);
501 }
502 }
503 Assert.That(streamedCorrelationId, Is.Not.Null.And.Not.Empty);
504 Assert.That(streamedExpiresAt.HasValue, Is.True);
505 Assert.That(streamedTranscriptBuilder.ToString(), Is.Not.Null.And.Not.Empty);
506 Assert.That(outputAudioStream.Length, Is.GreaterThan(9000));
507 Assert.That(streamedUsage?.InputTokenDetails?.AudioTokenCount, Is.GreaterThan(0));
508 Assert.That(streamedUsage?.OutputTokenDetails?.AudioTokenCount, Is.GreaterThan(0));
509 }
510
511 [Test]
512 public async Task AuthFailure()
513 {
514 string fakeApiKey = "not-a-real-key-but-should-be-sanitized";
515 ChatClient client = new("gpt-4o-mini", new ApiKeyCredential(fakeApiKey));
516 IEnumerable<ChatMessage> messages = [new UserChatMessage("Uh oh, this isn't going to work with that key")];
517 ClientResultException clientResultException = null;
518 try
519 {
520 _ = IsAsync
521 ? await client.CompleteChatAsync(messages)
522 : client.CompleteChat(messages);
523 }
524 catch (ClientResultException ex)
525 {
526 clientResultException = ex;
527 }
528 Assert.That(clientResultException, Is.Not.Null);
529 Assert.That(clientResultException.Status, Is.EqualTo((int)HttpStatusCode.Unauthorized));
530 Assert.That(clientResultException.Message, Does.Contain("API key"));
531 Assert.That(clientResultException.Message, Does.Not.Contain(fakeApiKey));
532 }
533
534 [Test]
535 [TestCase(true)]
536 [TestCase(false)]
537 public async Task TokenLogProbabilities(bool includeLogProbabilities)
538 {
539 const int topLogProbabilityCount = 3;
540 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
541 IList<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
542 ChatCompletionOptions options;
543
544 if (includeLogProbabilities)
545 {
546 options = new()
547 {
548 IncludeLogProbabilities = true,
549 TopLogProbabilityCount = topLogProbabilityCount
550 };
551 }
552 else
553 {
554 options = new();
555 }
556
557 ClientResult<ChatCompletion> result = await client.CompleteChatAsync(messages, options);
558 string raw = result.GetRawResponse().Content.ToString();
559 ChatCompletion chatCompletions = result.Value;
560 Assert.That(chatCompletions, Is.Not.Null);
561
562 if (includeLogProbabilities)
563 {
564 IReadOnlyList<ChatTokenLogProbabilityDetails> chatTokenLogProbabilities = chatCompletions.ContentTokenLogProbabilities;
565 Assert.That(chatTokenLogProbabilities, Is.Not.Null.Or.Empty);
566
567 foreach (ChatTokenLogProbabilityDetails tokenLogProbs in chatTokenLogProbabilities)
568 {
569 Assert.That(tokenLogProbs.Token, Is.Not.Null.Or.Empty);
570 Assert.That(tokenLogProbs.TopLogProbabilities, Is.Not.Null.Or.Empty);
571 Assert.That(tokenLogProbs.TopLogProbabilities, Has.Count.EqualTo(topLogProbabilityCount));
572
573 foreach (ChatTokenTopLogProbabilityDetails tokenTopLogProbs in tokenLogProbs.TopLogProbabilities)
574 {
575 Assert.That(tokenTopLogProbs.Token, Is.Not.Null.Or.Empty);
576 }
577 }
578 }
579 else
580 {
581 Assert.That(chatCompletions.ContentTokenLogProbabilities, Is.Not.Null);
582 Assert.That(chatCompletions.ContentTokenLogProbabilities, Is.Empty);
583 }
584 }
585
586 [Test]
587 [TestCase(true)]
588 [TestCase(false)]
589 public async Task TokenLogProbabilitiesStreaming(bool includeLogProbabilities)
590 {
591 const int topLogProbabilityCount = 3;
592 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
593 IList<ChatMessage> messages = [new UserChatMessage("What are the best pizza toppings? Give me a breakdown on the reasons.")];
594 ChatCompletionOptions options;
595
596 if (includeLogProbabilities)
597 {
598 options = new()
599 {
600 IncludeLogProbabilities = true,
601 TopLogProbabilityCount = topLogProbabilityCount
602 };
603 }
604 else
605 {
606 options = new();
607 }
608
609 AsyncCollectionResult<StreamingChatCompletionUpdate> chatCompletionUpdates = client.CompleteChatStreamingAsync(messages, options);
610 Assert.That(chatCompletionUpdates, Is.Not.Null);
611
612 await foreach (StreamingChatCompletionUpdate chatCompletionUpdate in chatCompletionUpdates)
613 {
614 // Token log probabilities are streamed together with their corresponding content update.
615 if (includeLogProbabilities
616 && chatCompletionUpdate.ContentUpdate.Count > 0
617 && !string.IsNullOrEmpty(chatCompletionUpdate.ContentUpdate[0].Text))
618 {
619 Assert.That(chatCompletionUpdate.ContentTokenLogProbabilities, Is.Not.Null.Or.Empty);
620 Assert.That(chatCompletionUpdate.ContentTokenLogProbabilities, Has.Count.EqualTo(1));
621
622 foreach (ChatTokenLogProbabilityDetails tokenLogProbs in chatCompletionUpdate.ContentTokenLogProbabilities)
623 {
624 Assert.That(tokenLogProbs.Token, Is.Not.Null.Or.Empty);
625 Assert.That(tokenLogProbs.TopLogProbabilities, Is.Not.Null.Or.Empty);
626 Assert.That(tokenLogProbs.TopLogProbabilities, Has.Count.EqualTo(topLogProbabilityCount));
627
628 foreach (ChatTokenTopLogProbabilityDetails tokenTopLogProbs in tokenLogProbs.TopLogProbabilities)
629 {
630 Assert.That(tokenTopLogProbs.Token, Is.Not.Null.Or.Empty);
631 }
632 }
633 }
634 else
635 {
636 Assert.That(chatCompletionUpdate.ContentTokenLogProbabilities, Is.Not.Null);
637 Assert.That(chatCompletionUpdate.ContentTokenLogProbabilities, Is.Empty);
638 }
639 }
640 }
641
642 [Test]
643 public async Task NonStrictJsonSchemaWorks()
644 {
645 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "gpt-4o-mini");
646 ChatCompletionOptions options = new()
647 {
648 ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
649 "some_color_schema",
650 BinaryData.FromBytes("""
651 {
652 "type": "object",
653 "properties": {},
654 "additionalProperties": false
655 }
656 """u8.ToArray()),
657 "an object that describes color components by name",
658 jsonSchemaIsStrict: false)
659 };
660 ChatCompletion completion = IsAsync
661 ? await client.CompleteChatAsync([new UserChatMessage("What are the hex values for red, green, and blue?")], options)
662 : client.CompleteChat([new UserChatMessage("What are the hex values for red, green, and blue?")], options);
663 Console.WriteLine(completion);
664 }
665
666 [Test]
667 public async Task JsonResult()
668 {
669 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
670 IEnumerable<ChatMessage> messages = [
671 new UserChatMessage("Give me a JSON object with the following properties: red, green, and blue. The value "
672 + "of each property should be a string containing their RGB representation in hexadecimal.")
673 ];
674 ChatCompletionOptions options = new() { ResponseFormat = ChatResponseFormat.CreateJsonObjectFormat() };
675 ClientResult<ChatCompletion> result = IsAsync
676 ? await client.CompleteChatAsync(messages, options)
677 : client.CompleteChat(messages, options);
678
679 JsonDocument jsonDocument = JsonDocument.Parse(result.Value.Content[0].Text);
680
681 Assert.That(jsonDocument.RootElement.TryGetProperty("red", out JsonElement redProperty));
682 Assert.That(jsonDocument.RootElement.TryGetProperty("green", out JsonElement greenProperty));
683 Assert.That(jsonDocument.RootElement.TryGetProperty("blue", out JsonElement blueProperty));
684 Assert.That(redProperty.GetString().ToLowerInvariant(), Contains.Substring("ff0000"));
685 Assert.That(greenProperty.GetString().ToLowerInvariant(), Contains.Substring("00ff00"));
686 Assert.That(blueProperty.GetString().ToLowerInvariant(), Contains.Substring("0000ff"));
687 }
688
689 [Test]
690 public async Task MultipartContentWorks()
691 {
692 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
693 List<ChatMessage> messages = [
694 new SystemChatMessage(
695 "You talk like a pirate.",
696 "When asked for recommendations, you always talk about animals; especially dogs."
697 ),
698 new UserChatMessage(
699 "Hello, assistant! I need some advice.",
700 "Can you recommend some small, cute things I can think about?"
701 )
702 ];
703 ChatCompletion completion = IsAsync
704 ? await client.CompleteChatAsync(messages)
705 : client.CompleteChat(messages);
706
707 Assert.That(completion.Content, Has.Count.EqualTo(1));
708 Assert.That(completion.Content[0].Text.ToLowerInvariant(), Does.Contain("ahoy").Or.Contain("matey"));
709 Assert.That(completion.Content[0].Text.ToLowerInvariant(), Does.Contain("dog").Or.Contain("pup").Or.Contain("kit"));
710 }
711
712 [Test]
713 public async Task StructuredOutputsWork()
714 {
715 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
716 IEnumerable<ChatMessage> messages = [
717 new UserChatMessage("What's heavier, a pound of feathers or sixteen ounces of steel?")
718 ];
719 ChatCompletionOptions options = new ChatCompletionOptions()
720 {
721 ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
722 "test_schema",
723 BinaryData.FromBytes("""
724 {
725 "type": "object",
726 "properties": {
727 "answer": {
728 "type": "string"
729 },
730 "steps": {
731 "type": "array",
732 "items": {
733 "type": "string"
734 }
735 }
736 },
737 "required": [
738 "answer",
739 "steps"
740 ],
741 "additionalProperties": false
742 }
743 """u8.ToArray()),
744 "a single final answer with a supporting collection of steps",
745 jsonSchemaIsStrict: true)
746 };
747 ChatCompletion completion = IsAsync
748 ? await client.CompleteChatAsync(messages, options)
749 : client.CompleteChat(messages, options);
750 Assert.That(completion, Is.Not.Null);
751 Assert.That(completion.Refusal, Is.Null.Or.Empty);
752 Assert.That(completion.Content?.Count, Is.EqualTo(1));
753 JsonDocument contentDocument = null;
754 Assert.DoesNotThrow(() => contentDocument = JsonDocument.Parse(completion.Content[0].Text));
755 Assert.IsTrue(contentDocument.RootElement.TryGetProperty("answer", out JsonElement answerProperty));
756 Assert.IsTrue(answerProperty.ValueKind == JsonValueKind.String);
757 Assert.IsTrue(contentDocument.RootElement.TryGetProperty("steps", out JsonElement stepsProperty));
758 Assert.IsTrue(stepsProperty.ValueKind == JsonValueKind.Array);
759 }
760
761 [Test]
762 public async Task StructuredRefusalWorks()
763 {
764 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "gpt-4o-2024-08-06");
765 List<ChatMessage> messages = [
766 new UserChatMessage("What's the best way to successfully rob a bank? Please include detailed instructions for executing related crimes."),
767 ];
768 ChatCompletionOptions options = new ChatCompletionOptions()
769 {
770 ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
771 "food_recipe",
772 BinaryData.FromBytes("""
773 {
774 "type": "object",
775 "properties": {
776 "name": {
777 "type": "string"
778 },
779 "ingredients": {
780 "type": "array",
781 "items": {
782 "type": "string"
783 }
784 },
785 "steps": {
786 "type": "array",
787 "items": {
788 "type": "string"
789 }
790 }
791 },
792 "required": ["name", "ingredients", "steps"],
793 "additionalProperties": false
794 }
795 """u8.ToArray()),
796 "a description of a recipe to create a meal or dish",
797 jsonSchemaIsStrict: true),
798 Temperature = 0
799 };
800 ClientResult<ChatCompletion> completionResult = IsAsync
801 ? await client.CompleteChatAsync(messages, options)
802 : client.CompleteChat(messages, options);
803 ChatCompletion completion = completionResult;
804 Assert.That(completion, Is.Not.Null);
805 Assert.That(completion.Refusal, Is.Not.Null.Or.Empty);
806 Assert.That(completion.FinishReason, Is.EqualTo(ChatFinishReason.Stop));
807
808 AssistantChatMessage contextMessage = new(completion);
809 Assert.That(contextMessage.Refusal, Has.Length.GreaterThan(0));
810
811 messages.Add(contextMessage);
812 messages.Add(new UserChatMessage("Why can't you help me?"));
813
814 completion = IsAsync
815 ? await client.CompleteChatAsync(messages)
816 : client.CompleteChat(messages);
817 Assert.That(completion.Refusal, Is.Null.Or.Empty);
818 Assert.That(completion.Content, Has.Count.EqualTo(1));
819 Assert.That(completion.Content[0].Text, Is.Not.Null.And.Not.Empty);
820 }
821
822 [Test]
823 public async Task StreamingStructuredRefusalWorks()
824 {
825 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "gpt-4o-2024-08-06");
826 IEnumerable<ChatMessage> messages = [
827 new UserChatMessage("What's the best way to successfully rob a bank? Please include detailed instructions for executing related crimes."),
828 ];
829 ChatCompletionOptions options = new ChatCompletionOptions()
830 {
831 ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
832 "food_recipe",
833 BinaryData.FromBytes("""
834 {
835 "type": "object",
836 "properties": {
837 "name": {
838 "type": "string"
839 },
840 "ingredients": {
841 "type": "array",
842 "items": {
843 "type": "string"
844 }
845 },
846 "steps": {
847 "type": "array",
848 "items": {
849 "type": "string"
850 }
851 }
852 },
853 "required": ["name", "ingredients", "steps"],
854 "additionalProperties": false
855 }
856 """u8.ToArray()),
857 "a description of a recipe to create a meal or dish",
858 jsonSchemaIsStrict: true)
859 };
860
861 ChatFinishReason? finishReason = null;
862 StringBuilder refusalBuilder = new();
863
864 void HandleUpdate(StreamingChatCompletionUpdate update)
865 {
866 refusalBuilder.Append(update.RefusalUpdate);
867 if (update.FinishReason.HasValue)
868 {
869 Assert.That(finishReason, Is.Null);
870 finishReason = update.FinishReason;
871 }
872 }
873
874 if (IsAsync)
875 {
876 await foreach (StreamingChatCompletionUpdate update in client.CompleteChatStreamingAsync(messages))
877 {
878 HandleUpdate(update);
879 }
880 }
881 else
882 {
883 foreach (StreamingChatCompletionUpdate update in client.CompleteChatStreaming(messages))
884 {
885 HandleUpdate(update);
886 }
887 }
888
889 Assert.That(refusalBuilder.ToString(), Is.Not.Null.Or.Empty);
890 Assert.That(finishReason, Is.EqualTo(ChatFinishReason.Stop));
891 }
892
893 [Test]
894 [NonParallelizable]
895 public async Task HelloWorldChatWithTracingAndMetrics()
896 {
897 using var _ = TestAppContextSwitchHelper.EnableOpenTelemetry();
898 using TestActivityListener activityListener = new TestActivityListener("OpenAI.ChatClient");
899 using TestMeterListener meterListener = new TestMeterListener("OpenAI.ChatClient");
900
901 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
902 IEnumerable<ChatMessage> messages = [new UserChatMessage("Hello, world!")];
903 ClientResult<ChatCompletion> result = IsAsync
904 ? await client.CompleteChatAsync(messages)
905 : client.CompleteChat(messages);
906
907 Assert.AreEqual(1, activityListener.Activities.Count);
908 TestActivityListener.ValidateChatActivity(activityListener.Activities.Single(), result.Value);
909
910 List<TestMeasurement> durations = meterListener.GetMeasurements("gen_ai.client.operation.duration");
911 Assert.AreEqual(1, durations.Count);
912 ValidateChatMetricTags(durations.Single(), result.Value);
913
914 List<TestMeasurement> usages = meterListener.GetMeasurements("gen_ai.client.token.usage");
915 Assert.AreEqual(2, usages.Count);
916
917 Assert.True(usages[0].tags.TryGetValue("gen_ai.token.type", out var type));
918 Assert.IsInstanceOf<string>(type);
919
920 TestMeasurement input = (type is "input") ? usages[0] : usages[1];
921 TestMeasurement output = (type is "input") ? usages[1] : usages[0];
922
923 Assert.AreEqual(result.Value.Usage.InputTokenCount, input.value);
924 Assert.AreEqual(result.Value.Usage.OutputTokenCount, output.value);
925 }
926
927 [Test]
928 public async Task ReasoningTokensWork()
929 {
930 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "o3-mini");
931
932 UserChatMessage message = new("Using a comprehensive evaluation of popular media in the 1970s and 1980s, what were the most common sci-fi themes?");
933 ChatCompletionOptions options = new()
934 {
935 MaxOutputTokenCount = 2148,
936 ReasoningEffortLevel = ChatReasoningEffortLevel.Low,
937 };
938 Assert.That(ModelReaderWriter.Write(options).ToString(), Does.Contain(@"""reasoning_effort"":""low"""));
939 ClientResult<ChatCompletion> completionResult = IsAsync
940 ? await client.CompleteChatAsync([message], options)
941 : client.CompleteChat([message], options);
942 ChatCompletion completion = completionResult;
943
944 Assert.That(completion, Is.Not.Null);
945 Assert.That(completion.FinishReason, Is.EqualTo(ChatFinishReason.Stop));
946 Assert.That(completion.Usage, Is.Not.Null);
947 Assert.That(completion.Usage.OutputTokenCount, Is.GreaterThan(0));
948 Assert.That(completion.Usage.OutputTokenCount, Is.LessThanOrEqualTo(options.MaxOutputTokenCount));
949 Assert.That(completion.Usage.OutputTokenDetails?.ReasoningTokenCount, Is.GreaterThan(0));
950 Assert.That(completion.Usage.OutputTokenDetails?.ReasoningTokenCount, Is.LessThan(completion.Usage.OutputTokenCount));
951 }
952
953 [Test]
954 public async Task PredictedOutputsWork()
955 {
956 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat);
957
958 foreach (ChatOutputPrediction predictionVariant in new List<ChatOutputPrediction>(
959 [
960 // Plain string
961 ChatOutputPrediction.CreateStaticContentPrediction("""
962 {
963 "feature_name": "test_feature",
964 "enabled": true
965 }
966 """.ReplaceLineEndings("\n")),
967 // One content part
968 ChatOutputPrediction.CreateStaticContentPrediction(
969 [
970 ChatMessageContentPart.CreateTextPart("""
971 {
972 "feature_name": "test_feature",
973 "enabled": true
974 }
975 """.ReplaceLineEndings("\n")),
976 ]),
977 // Several content parts
978 ChatOutputPrediction.CreateStaticContentPrediction(
979 [
980 "{\n",
981 " \"feature_name\": \"test_feature\",\n",
982 " \"enabled\": true\n",
983 "}",
984 ]),
985 ]))
986 {
987 ChatCompletionOptions options = new()
988 {
989 OutputPrediction = predictionVariant,
990 };
991
992 ChatMessage message = ChatMessage.CreateUserMessage("""
993 Modify the following input to enable the feature. Only respond with the JSON and include no other text. Do not enclose in markdown backticks or any other additional annotations.
994
995 {
996 "feature_name": "test_feature",
997 "enabled": false
998 }
999 """.ReplaceLineEndings("\n"));
1000
1001 ChatCompletion completion = await client.CompleteChatAsync([message], options);
1002
1003 Assert.That(completion.Usage.OutputTokenDetails.AcceptedPredictionTokenCount, Is.GreaterThan(0));
1004 }
1005 }
1006
1007 [Test]
1008 public async Task O3miniDeveloperMessagesWork()
1009 {
1010 List<ChatMessage> messages =
1011 [
1012 ChatMessage.CreateDeveloperMessage("End every response to the user with the exact phrase: 'Hope this helps!'"),
1013 ChatMessage.CreateUserMessage("How long will it take to make a cheesecake from scratch? Including getting ingredients.")
1014 ];
1015
1016 ChatCompletionOptions options = new()
1017 {
1018 ReasoningEffortLevel = ChatReasoningEffortLevel.Low,
1019 };
1020
1021 ChatClient client = GetTestClient<ChatClient>(TestScenario.Chat, "o3-mini");
1022 ChatCompletion completion = await client.CompleteChatAsync(messages, options);
1023
1024 Assert.That(completion.Content, Has.Count.EqualTo(1));
1025 Assert.That(completion.Content[0].Text, Does.EndWith("Hope this helps!"));
1026 }
1027
1028 [Test]
1029 public async Task ChatMetadata()
1030 {
1031 ChatClient client = GetTestClient();
1032
1033 ChatCompletionOptions options = new()
1034 {
1035 StoredOutputEnabled = true,
1036 Metadata =
1037 {
1038 ["my_metadata_key"] = "my_metadata_value",
1039 },
1040 };
1041
1042 ChatCompletion completion = await client.CompleteChatAsync(
1043 ["Hello, world!"],
1044 options);
1045 }
1046
1047 [Test]
1048 public async Task WebSearchWorks()
1049 {
1050 ChatClient client = GetTestClient("gpt-4o-search-preview");
1051
1052 ChatCompletionOptions options = new()
1053 {
1054 WebSearchOptions = new(),
1055 };
1056
1057 ChatCompletion completion = await client.CompleteChatAsync(
1058 ["What was a positive news story from today?"],
1059 options);
1060
1061 Assert.That(completion.Annotations, Has.Count.GreaterThan(0));
1062 }
1063
1064 [Test]
1065 public async Task FileIdContentWorks()
1066 {
1067 OpenAIFileClient fileClient = GetTestClient<OpenAIFileClient>(TestScenario.Files);
1068 OpenAIFile testInputFile = await fileClient.UploadFileAsync(
1069 Path.Combine("Assets", "files_travis_favorite_food.pdf"),
1070 FileUploadPurpose.UserData);
1071 Validate(testInputFile);
1072
1073 ChatMessageContentPart fileIdContentPart
1074 = ChatMessageContentPart.CreateFilePart(testInputFile.Id);
1075 Assert.That(fileIdContentPart.FileId, Is.EqualTo(testInputFile.Id));
1076 Assert.That(fileIdContentPart.FileBytes, Is.Null);
1077 Assert.That(fileIdContentPart.FileBytesMediaType, Is.Null);
1078 Assert.That(fileIdContentPart.Filename, Is.Null);
1079
1080 ChatClient client = GetTestClient();
1081 ChatCompletion completion = await client.CompleteChatAsync(
1082 [
1083 ChatMessage.CreateUserMessage(
1084 "Based on the following, what food should I order for whom?",
1085 fileIdContentPart)
1086 ]);
1087 Assert.That(completion?.Content, Is.Not.Null.And.Not.Empty);
1088 Assert.That(completion.Content[0].Text?.ToLower(), Does.Contain("pizza"));
1089 }
1090
1091 [Test]
1092 public async Task FileBinaryContentWorks()
1093 {
1094 ChatMessageContentPart binaryFileContentPart
1095 = ChatMessageContentPart.CreateFilePart(
1096 fileBytes: BinaryData.FromStream(
1097 File.OpenRead(
1098 Path.Combine("Assets", "files_travis_favorite_food.pdf"))),
1099 fileBytesMediaType: "application/pdf",
1100 "test_travis_favorite_food.pdf");
1101 Assert.That(binaryFileContentPart.FileBytes, Is.Not.Null);
1102 Assert.That(binaryFileContentPart.FileBytesMediaType, Is.EqualTo("application/pdf"));
1103 Assert.That(binaryFileContentPart.Filename, Is.EqualTo("test_travis_favorite_food.pdf"));
1104 Assert.That(binaryFileContentPart.FileId, Is.Null);
1105
1106 ChatClient client = GetTestClient();
1107
1108 ChatCompletion completion = await client.CompleteChatAsync(
1109 [
1110 ChatMessage.CreateUserMessage(
1111 "Based on the following, what food should I order for whom?",
1112 binaryFileContentPart)
1113 ]);
1114 Assert.That(completion?.Content, Is.Not.Null.And.Not.Empty);
1115 Assert.That(completion.Content[0].Text?.ToLower(), Does.Contain("pizza"));
1116 }
1117
1118 [Test]
1119 public async Task StoredChatCompletionsWork()
1120 {
1121 ChatClient client = GetTestClient();
1122
1123 ChatCompletionOptions options = new()
1124 {
1125 StoredOutputEnabled = true
1126 };
1127
1128 ChatCompletion completion = await client.CompleteChatAsync(
1129 [new UserChatMessage("Say `this is a test`.")],
1130 options);
1131
1132 Thread.Sleep(5000);
1133
1134 ChatCompletion storedCompletion = await client.GetChatCompletionAsync(completion.Id);
1135
1136 Assert.That(storedCompletion.Id, Is.EqualTo(completion.Id));
1137 Assert.That(storedCompletion.Content[0].Text, Is.EqualTo(completion.Content[0].Text));
1138
1139 ChatCompletionDeletionResult deletionResult = await client.DeleteChatCompletionAsync(completion.Id);
1140
1141 Assert.That(deletionResult.Deleted, Is.True);
1142
1143 Thread.Sleep(5000);
1144
1145 Assert.ThrowsAsync<ClientResultException>(async () =>
1146 {
1147 ChatCompletion deletedCompletion = await client.GetChatCompletionAsync(completion.Id);
1148 });
1149 }
1150
1151 private List<string> FileIdsToDelete = [];
1152 private void Validate<T>(T item)
1153 {
1154 Assert.IsNotNull(item);
1155 if (item is OpenAIFile file)
1156 {
1157 FileIdsToDelete.Add(file.Id);
1158 }
1159 else
1160 {
1161 Assert.Fail($"Unhandled item type for validation: {item.GetType().Name}");
1162 }
1163 }
1164
1165 [OneTimeTearDown]
1166 public void TearDown()
1167 {
1168 OpenAIFileClient fileClient = GetTestClient<OpenAIFileClient>(TestScenario.Files);
1169
1170 RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow };
1171
1172 foreach (string fileId in FileIdsToDelete)
1173 {
1174 _ = fileClient.DeleteFile(fileId, noThrowOptions);
1175 }
1176 }
1177
1178 private static ChatClient GetTestClient(string overrideModel = null) => GetTestClient<ChatClient>(TestScenario.Chat, overrideModel);
1179}
1180