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

tests/Chat/ChatTests.cs

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