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/ChatStoreTests.cs

1012lines · modecode

1using NUnit.Framework;
2using OpenAI.Chat;
3using OpenAI.Tests.Utility;
4using System;
5using System.ClientModel;
6using System.Collections.Generic;
7using System.Threading;
8using System.Threading.Tasks;
9using static OpenAI.Tests.TestHelpers;
10
11namespace OpenAI.Tests.Chat;
12
13[TestFixture(true)]
14[TestFixture(false)]
15[Parallelizable(ParallelScope.All)]
16[Category("StoredChat")]
17public class ChatStoreToolTests : SyncAsyncTestBase
18{
19 private const int s_delayInMilliseconds = 5000;
20
21 public ChatStoreToolTests(bool isAsync) : base(isAsync)
22 {
23 }
24
25 [Test]
26 public async Task ChatMetadata()
27 {
28 ChatClient client = GetTestClient();
29
30 ChatCompletionOptions options = new()
31 {
32 StoredOutputEnabled = true,
33 Metadata =
34 {
35 ["my_metadata_key"] = "my_metadata_value",
36 },
37 };
38
39 ChatCompletion completion = await client.CompleteChatAsync(
40 ["Hello, world!"],
41 options);
42
43 int count = 0;
44 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync())
45 {
46 count++;
47 }
48 Assert.That(count, Is.GreaterThan(0));
49 }
50
51 [Test]
52 public async Task GetChatCompletionsWithPagination()
53 {
54 ChatClient client = GetTestClient();
55
56 // Create multiple completions with stored output enabled
57 var completionIds = new List<string>();
58 for (int i = 0; i < 3; i++)
59 {
60 ChatCompletionOptions options = new()
61 {
62 StoredOutputEnabled = true,
63 Metadata = { ["test_key"] = $"test_value_{i}" }
64 };
65
66 ChatCompletion completion = await client.CompleteChatAsync(
67 [$"Test message {i}: Say 'Hello World {i}'"],
68 options);
69
70 completionIds.Add(completion.Id);
71 }
72
73 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
74
75 // Test pagination with limit
76 ChatCompletionCollectionOptions paginationOptions = new()
77 {
78 PageSizeLimit = 2
79 };
80
81 int totalCount = 0;
82 string lastId = null;
83
84 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(paginationOptions))
85 {
86 totalCount++;
87 lastId = fetchedCompletion.Id;
88 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
89 Assert.That(fetchedCompletion.Content, Is.Not.Null);
90
91 if (totalCount >= 2) break; // Stop after getting 2 items
92 }
93
94 Assert.That(totalCount, Is.EqualTo(2));
95 Assert.That(lastId, Is.Not.Null);
96
97 // Clean up
98 foreach (var id in completionIds)
99 {
100 try
101 {
102 await client.DeleteChatCompletionAsync(id);
103 }
104 catch { /* Ignore cleanup errors */ }
105 }
106 }
107
108 [Test]
109 public async Task GetChatCompletionsWithAfterIdPagination()
110 {
111 ChatClient client = GetTestClient();
112
113 // Create multiple completions
114 var completionIds = new List<string>();
115 for (int i = 0; i < 3; i++)
116 {
117 ChatCompletionOptions createOptions = new()
118 {
119 StoredOutputEnabled = true
120 };
121
122 ChatCompletion completion = await client.CompleteChatAsync(
123 [$"Pagination test {i}: Say 'Test {i}'"],
124 createOptions);
125
126 completionIds.Add(completion.Id);
127 }
128
129 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
130
131 // Get first completion to use as afterId
132 string afterId = null;
133 await foreach (var firstCompletion in client.GetChatCompletionsAsync())
134 {
135 afterId = firstCompletion.Id;
136 break;
137 }
138
139 Assert.That(afterId, Is.Not.Null);
140
141 // Test pagination starting after the first ID
142 ChatCompletionCollectionOptions paginationOptions = new()
143 {
144 AfterId = afterId,
145 PageSizeLimit = 2
146 };
147
148 int count = 0;
149 await foreach (var completion in client.GetChatCompletionsAsync(paginationOptions))
150 {
151 count++;
152 // Ensure we don't get the afterId completion
153 Assert.That(completion.Id, Is.Not.EqualTo(afterId));
154 if (count >= 2) break;
155 }
156
157 // Clean up
158 foreach (var id in completionIds)
159 {
160 try
161 {
162 await client.DeleteChatCompletionAsync(id);
163 }
164 catch { /* Ignore cleanup errors */ }
165 }
166 }
167
168 [Test]
169 public async Task GetChatCompletionsWithOrderFiltering()
170 {
171 ChatClient client = GetTestClient();
172
173 // Create completions with timestamps
174 var completionIds = new List<string>();
175 for (int i = 0; i < 2; i++)
176 {
177 ChatCompletionOptions createOptions = new()
178 {
179 StoredOutputEnabled = true,
180 Metadata = { ["sequence"] = i.ToString() }
181 };
182
183 ChatCompletion completion = await client.CompleteChatAsync(
184 [$"Order test {i}: Say 'Order {i}'"],
185 createOptions);
186
187 completionIds.Add(completion.Id);
188 await Task.Delay(1000); // Ensure different timestamps
189 }
190
191 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
192
193 // Test ascending order
194 ChatCompletionCollectionOptions ascOptions = new()
195 {
196 Order = ChatCompletionCollectionOrder.Ascending,
197 PageSizeLimit = 5
198 };
199
200 var ascResults = new List<ChatCompletion>();
201 await foreach (var completion in client.GetChatCompletionsAsync(ascOptions))
202 {
203 ascResults.Add(completion);
204 if (ascResults.Count >= 2) break;
205 }
206
207 // Test descending order
208 ChatCompletionCollectionOptions descOptions = new()
209 {
210 Order = ChatCompletionCollectionOrder.Descending,
211 PageSizeLimit = 5
212 };
213
214 var descResults = new List<ChatCompletion>();
215 await foreach (var completion in client.GetChatCompletionsAsync(descOptions))
216 {
217 descResults.Add(completion);
218 if (descResults.Count >= 2) break;
219 }
220
221 // Verify we get results in both cases
222 Assert.That(ascResults, Has.Count.GreaterThan(0));
223 Assert.That(descResults, Has.Count.GreaterThan(0));
224
225 // The first result in descending order should be the most recent
226 // (though exact ordering validation is tricky due to timing)
227 Assert.That(descResults[0].Id, Is.Not.Null.And.Not.Empty);
228
229 // Clean up
230 foreach (var id in completionIds)
231 {
232 try
233 {
234 await client.DeleteChatCompletionAsync(id);
235 }
236 catch { /* Ignore cleanup errors */ }
237 }
238 }
239
240 [Test]
241 public async Task GetChatCompletionsWithMetadataFiltering()
242 {
243 ChatClient client = GetTestClient();
244
245 // Create completions with different metadata
246 var testMetadataKey = $"test_scenario_{Guid.NewGuid():N}";
247 var completionIds = new List<string>();
248
249 // Create completion with specific metadata
250 ChatCompletionOptions options1 = new()
251 {
252 StoredOutputEnabled = true,
253 Metadata = { [testMetadataKey] = "target_value" }
254 };
255
256 ChatCompletion targetCompletion = await client.CompleteChatAsync(
257 ["Metadata test: Say 'Target completion'"],
258 options1);
259 completionIds.Add(targetCompletion.Id);
260
261 // Create completion with different metadata
262 ChatCompletionOptions options2 = new()
263 {
264 StoredOutputEnabled = true,
265 Metadata = { [testMetadataKey] = "other_value" }
266 };
267
268 ChatCompletion otherCompletion = await client.CompleteChatAsync(
269 ["Metadata test: Say 'Other completion'"],
270 options2);
271 completionIds.Add(otherCompletion.Id);
272
273 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
274
275 // Filter by specific metadata
276 ChatCompletionCollectionOptions filterOptions = new()
277 {
278 Metadata = { [testMetadataKey] = "target_value" },
279 PageSizeLimit = 10
280 };
281
282 int totalFound = 0;
283
284 await foreach (var completion in client.GetChatCompletionsAsync(filterOptions))
285 {
286 totalFound++;
287 Assert.That(completion.Id, Is.Not.Null.And.Not.Empty);
288 if (totalFound >= 20) break; // Prevent infinite loop
289 }
290
291 // Should find completions (filtering behavior may vary by implementation)
292 Assert.That(totalFound, Is.GreaterThan(0));
293
294 // Clean up
295 foreach (var id in completionIds)
296 {
297 try
298 {
299 await client.DeleteChatCompletionAsync(id);
300 }
301 catch { /* Ignore cleanup errors */ }
302 }
303 }
304
305 [Test]
306 public async Task GetChatCompletionsWithModelFiltering()
307 {
308 ChatClient client = GetTestClient();
309
310 // Create completion with default model
311 ChatCompletionOptions createOptions = new()
312 {
313 StoredOutputEnabled = true,
314 Metadata = { ["model_test"] = "true" }
315 };
316
317 ChatCompletion completion = await client.CompleteChatAsync(
318 ["Model filter test: Say 'Hello'"],
319 createOptions);
320
321 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
322
323 // Filter by the model used by the test client
324 ChatCompletionCollectionOptions filterOptions = new()
325 {
326 Model = "gpt-4o-mini-2024-07-18", // Common test model
327 PageSizeLimit = 10
328 };
329
330 int count = 0;
331 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(filterOptions))
332 {
333 count++;
334 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
335 Assert.That(fetchedCompletion.Model, Is.EqualTo(filterOptions.Model));
336 if (count >= 5) break; // Limit results for test performance
337 }
338
339 Assert.That(count, Is.GreaterThan(0));
340
341 // Clean up
342 try
343 {
344 await client.DeleteChatCompletionAsync(completion.Id);
345 }
346 catch { /* Ignore cleanup errors */ }
347 }
348
349 [Test]
350 public async Task GetChatCompletionsWithEmptyOptions()
351 {
352 ChatClient client = GetTestClient();
353
354 // Create a completion to ensure we have something to fetch
355 ChatCompletionOptions createOptions = new()
356 {
357 StoredOutputEnabled = true
358 };
359
360 ChatCompletion completion = await client.CompleteChatAsync(
361 ["Empty options test: Say 'Hello'"],
362 createOptions);
363
364 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
365
366 // Test with default/empty options
367 int count = 0;
368 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync())
369 {
370 count++;
371 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
372 Assert.That(fetchedCompletion.Content, Is.Not.Null);
373 if (count >= 3) break; // Limit for test performance
374 }
375
376 Assert.That(count, Is.GreaterThan(0));
377
378 // Clean up
379 try
380 {
381 await client.DeleteChatCompletionAsync(completion.Id);
382 }
383 catch { /* Ignore cleanup errors */ }
384 }
385
386 [Test]
387 public async Task GetChatCompletionsWithCombinedFilters()
388 {
389 ChatClient client = GetTestClient();
390
391 // Create completion with combined metadata for filtering
392 var testKey = $"combined_test_{Guid.NewGuid():N}";
393 ChatCompletionOptions createOptions = new()
394 {
395 StoredOutputEnabled = true,
396 Metadata =
397 {
398 [testKey] = "combined_value",
399 ["test_type"] = "integration"
400 }
401 };
402
403 ChatCompletion completion = await client.CompleteChatAsync(
404 ["Combined filters test: Say 'Combined test'"],
405 createOptions);
406
407 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
408
409 // Test with combined filters
410 ChatCompletionCollectionOptions combinedOptions = new()
411 {
412 PageSizeLimit = 5,
413 Order = ChatCompletionCollectionOrder.Descending,
414 Metadata = { [testKey] = "combined_value" }
415 };
416
417 int count = 0;
418
419 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(combinedOptions))
420 {
421 count++;
422 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
423
424 if (count >= 10) break; // Prevent excessive iterations
425 }
426
427 Assert.That(count, Is.GreaterThan(0));
428
429 // Clean up
430 try
431 {
432 await client.DeleteChatCompletionAsync(completion.Id);
433 }
434 catch { /* Ignore cleanup errors */ }
435 }
436
437 [Test]
438 public async Task StoredChatCompletionsWork()
439 {
440 ChatClient client = GetTestClient();
441
442 ChatCompletionOptions options = new()
443 {
444 StoredOutputEnabled = true
445 };
446
447 ChatCompletion completion = await client.CompleteChatAsync(
448 [new UserChatMessage("Say `this is a test`.")],
449 options);
450
451 await RetryWithExponentialBackoffAsync(async () =>
452 {
453
454 ChatCompletion storedCompletion = await client.GetChatCompletionAsync(completion.Id);
455
456 Assert.That(storedCompletion.Id, Is.EqualTo(completion.Id));
457 Assert.That(storedCompletion.Content[0].Text, Is.EqualTo(completion.Content[0].Text));
458
459 ChatCompletionDeletionResult deletionResult = await client.DeleteChatCompletionAsync(completion.Id);
460
461 Assert.That(deletionResult.Deleted, Is.True);
462 });
463
464 await Task.Delay(s_delayInMilliseconds);
465
466 Assert.ThrowsAsync<ClientResultException>(async () =>
467 {
468 ChatCompletion deletedCompletion = await client.GetChatCompletionAsync(completion.Id);
469 });
470 }
471
472 [Test]
473 public async Task UpdateChatCompletionWorks()
474 {
475 ChatClient client = GetTestClient();
476
477 var testMetadataKey = $"test_key_{Guid.NewGuid():N}";
478 var initialOptions = new ChatCompletionOptions
479 {
480 StoredOutputEnabled = true,
481 Metadata = { [testMetadataKey] = "initial_value" }
482 };
483
484 ChatCompletion chatCompletion = await client.CompleteChatAsync(
485 [new UserChatMessage("Say `this is a test`.")],
486 initialOptions);
487
488 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
489
490 var newMetadata = new Dictionary<string, string>
491 {
492 [testMetadataKey] = "updated_value",
493 ["updated_by"] = "unit_test"
494 };
495
496 ChatCompletion updated = await client.UpdateChatCompletionAsync(chatCompletion.Id, newMetadata);
497
498 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be updated
499
500 Assert.That(updated, Is.Not.Null);
501 Assert.That(updated.Id, Is.EqualTo(chatCompletion.Id));
502
503 ChatCompletionDeletionResult deletionResult = await client.DeleteChatCompletionAsync(chatCompletion.Id);
504 Assert.That(deletionResult.Deleted, Is.True);
505
506 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be deleted
507
508 Assert.ThrowsAsync<ClientResultException>(async () =>
509 {
510 _ = await client.GetChatCompletionAsync(chatCompletion.Id);
511 });
512 }
513
514 [Test]
515 public async Task GetChatCompletionsValidatesCollectionEnumeration()
516 {
517 ChatClient client = GetTestClient();
518
519 // Create a completion to ensure we have data
520 ChatCompletionOptions createOptions = new()
521 {
522 StoredOutputEnabled = true,
523 Metadata = { ["enumeration_test"] = "true" }
524 };
525
526 ChatCompletion completion = await client.CompleteChatAsync(
527 ["Enumeration test: Say 'Test enumeration'"],
528 createOptions);
529
530 await Task.Delay(5000); // Wait for completion to be stored
531
532 // Test that we can enumerate multiple times
533 ChatCompletionCollectionOptions collectionOptions = new()
534 {
535 PageSizeLimit = 2
536 };
537
538 var collection = client.GetChatCompletionsAsync(collectionOptions);
539
540 // First enumeration
541 int firstCount = 0;
542 await foreach (var item in collection)
543 {
544 firstCount++;
545 Assert.That(item.Id, Is.Not.Null.And.Not.Empty);
546 if (firstCount >= 2) break;
547 }
548
549 // Second enumeration (should work independently)
550 int secondCount = 0;
551 await foreach (var item in collection)
552 {
553 secondCount++;
554 Assert.That(item.Id, Is.Not.Null.And.Not.Empty);
555 if (secondCount >= 2) break;
556 }
557
558 Assert.That(firstCount, Is.GreaterThan(0));
559 Assert.That(secondCount, Is.GreaterThan(0));
560
561 // Clean up
562 try
563 {
564 await client.DeleteChatCompletionAsync(completion.Id);
565 }
566 catch { /* Ignore cleanup errors */ }
567 }
568
569 [Test]
570 public async Task GetChatCompletionsHandlesLargeLimits()
571 {
572 ChatClient client = GetTestClient();
573
574 // Create a completion for testing
575 ChatCompletionOptions createOptions = new()
576 {
577 StoredOutputEnabled = true
578 };
579
580 ChatCompletion completion = await client.CompleteChatAsync(
581 ["Large limit test: Say 'Testing large limits'"],
582 createOptions);
583
584 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
585
586 // Test with a large page size limit
587 ChatCompletionCollectionOptions largeOptions = new()
588 {
589 PageSizeLimit = 100
590 };
591
592 int count = 0;
593 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(largeOptions))
594 {
595 count++;
596 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
597 if (count >= 20) break; // Prevent excessive test time
598 }
599
600 Assert.That(count, Is.GreaterThan(0));
601
602 // Clean up
603 try
604 {
605 await client.DeleteChatCompletionAsync(completion.Id);
606 }
607 catch { /* Ignore cleanup errors */ }
608 }
609
610 [Test]
611 public async Task GetChatCompletionsWithMinimalLimits()
612 {
613 ChatClient client = GetTestClient();
614
615 // Create a completion for testing
616 ChatCompletionOptions createOptions = new()
617 {
618 StoredOutputEnabled = true
619 };
620
621 ChatCompletion completion = await client.CompleteChatAsync(
622 ["Minimal limit test: Say 'Testing minimal limits'"],
623 createOptions);
624
625 await Task.Delay(s_delayInMilliseconds); // Wait for completions to be stored
626
627 // Test with minimal page size
628 ChatCompletionCollectionOptions minimalOptions = new()
629 {
630 PageSizeLimit = 1
631 };
632
633 int count = 0;
634 await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(minimalOptions))
635 {
636 count++;
637 Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty);
638 if (count >= 3) break; // Get a few items to verify pagination works
639 }
640
641 Assert.That(count, Is.GreaterThan(0));
642
643 // Clean up
644 try
645 {
646 await client.DeleteChatCompletionAsync(completion.Id);
647 }
648 catch { /* Ignore cleanup errors */ }
649 }
650
651 [Test]
652 public async Task GetChatCompletionMessagesWithBasicUsage()
653 {
654 ChatClient client = GetTestClient();
655
656 // Create a completion with stored output enabled to have messages
657 ChatCompletionOptions createOptions = new()
658 {
659 StoredOutputEnabled = true,
660 Metadata = { ["test_scenario"] = "basic_messages" }
661 };
662
663 ChatCompletion completion = await client.CompleteChatAsync(
664 ["Basic messages test: Say 'Hello, this is a test message.'"],
665 createOptions);
666
667 await RetryWithExponentialBackoffAsync(async () =>
668 {
669 // Test basic enumeration of messages
670 int messageCount = 0;
671 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id))
672 {
673 messageCount++;
674 Assert.That(message.Id, Is.Not.Null.And.Not.Empty);
675 Assert.AreEqual("Basic messages test: Say 'Hello, this is a test message.'", message.Content);
676
677 if (messageCount >= 5) break; // Prevent infinite loop
678 }
679
680 Assert.That(messageCount, Is.GreaterThan(0));
681 });
682
683 // Clean up
684 try
685 {
686 await client.DeleteChatCompletionAsync(completion.Id);
687 }
688 catch { /* Ignore cleanup errors */ }
689 }
690
691 [Test]
692 public async Task GetChatCompletionMessagesWithPagination()
693 {
694 ChatClient client = GetTestClient();
695
696 // Create completion with multiple messages (conversation with tool calls)
697 List<ChatMessage> conversationMessages = new()
698 {
699 new UserChatMessage("What's the weather like today? Use the weather tool."),
700 new UserChatMessage("Name something I could do outside in this weather."),
701 new UserChatMessage("Name something else I could do outside in this weather."),
702 new UserChatMessage("Name something yet another thing I could do outside in this weather.")
703 };
704
705 // Add function definition to trigger more back-and-forth
706 ChatTool weatherTool = ChatTool.CreateFunctionTool(
707 "get_weather",
708 "Get current weather information",
709 BinaryData.FromString("""
710 {
711 "type": "object",
712 "properties": {
713 "location": {
714 "type": "string",
715 "description": "The city and state, e.g. San Francisco, CA"
716 }
717 },
718 "required": ["location"]
719 }
720 """));
721
722 ChatCompletionOptions createOptions = new()
723 {
724 StoredOutputEnabled = true,
725 Tools = { weatherTool },
726 Metadata = { ["test_scenario"] = "pagination_messages" }
727 };
728
729 ChatCompletion completion = await client.CompleteChatAsync(
730 conversationMessages,
731 createOptions);
732
733 await RetryWithExponentialBackoffAsync(async () =>
734 {
735 // Test pagination with limit
736 int totalMessages = 0;
737 string lastMessageId = null;
738
739 var options = new ChatCompletionMessageCollectionOptions()
740 {
741 PageSizeLimit = 2
742 };
743
744 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id, options))
745 {
746 totalMessages++;
747 lastMessageId = message.Id;
748 Assert.That(message.Id, Is.Not.Null.And.Not.Empty);
749
750 if (totalMessages >= 4) break; // Get a few pages worth
751 }
752
753 Assert.That(totalMessages, Is.GreaterThan(3));
754 Assert.That(lastMessageId, Is.Not.Null);
755 });
756
757 // Clean up
758 try
759 {
760 await client.DeleteChatCompletionAsync(completion.Id);
761 }
762 catch { /* Ignore cleanup errors */ }
763 }
764
765 [Test]
766 public async Task GetChatCompletionMessagesWithAfterIdPagination()
767 {
768 ChatClient client = GetTestClient();
769
770 // Create completion
771 ChatCompletionOptions createOptions = new()
772 {
773 StoredOutputEnabled = true,
774 Metadata = { ["test_scenario"] = "after_id_pagination" }
775 };
776
777 ChatCompletion completion = await client.CompleteChatAsync(
778 ["After ID pagination test: Please provide a detailed response with multiple sentences."],
779 createOptions);
780
781 await RetryWithExponentialBackoffAsync(async () =>
782 {
783 // Get first message to use as afterId
784 string afterId = null;
785 await foreach (var firstMessage in client.GetChatCompletionMessagesAsync(completion.Id))
786 {
787 afterId = firstMessage.Id;
788 break;
789 }
790
791 if (afterId != null)
792 {
793 // Test pagination starting after the first message
794 int count = 0;
795 var options = new ChatCompletionMessageCollectionOptions()
796 {
797 AfterId = afterId,
798 PageSizeLimit = 3
799 };
800
801 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id, options))
802 {
803 count++;
804 // Ensure we don't get the afterId message
805 Assert.That(message.Id, Is.Not.EqualTo(afterId));
806
807 if (count >= 3) break;
808 }
809
810 // We might not have messages after the first one, so just verify the method works
811 Assert.That(count, Is.GreaterThanOrEqualTo(0));
812 }
813 });
814 // Clean up
815 try
816 {
817 await client.DeleteChatCompletionAsync(completion.Id);
818 }
819 catch { /* Ignore cleanup errors */ }
820 }
821
822 [Test]
823 public async Task GetChatCompletionMessagesWithOrderFiltering()
824 {
825 ChatClient client = GetTestClient();
826
827 // Create completion with detailed conversation
828 ChatCompletionOptions createOptions = new()
829 {
830 StoredOutputEnabled = true,
831 Metadata = { ["test_scenario"] = "order_filtering" }
832 };
833
834 ChatCompletion completion = await client.CompleteChatAsync(
835 ["Order filtering test: Please provide a comprehensive response about machine learning."],
836 createOptions);
837
838 await RetryWithExponentialBackoffAsync(async () =>
839 {
840 // Test ascending order
841 List<ChatCompletionMessageListDatum> ascMessages = new();
842 var ascOptions = new ChatCompletionMessageCollectionOptions()
843 {
844 Order = ChatCompletionMessageCollectionOrder.Ascending,
845 PageSizeLimit = 5
846 };
847
848 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id, ascOptions))
849 {
850 ascMessages.Add(message);
851 if (ascMessages.Count >= 3) break;
852 }
853
854 // Test descending order
855 List<ChatCompletionMessageListDatum> descMessages = new();
856 var descOptions = new ChatCompletionMessageCollectionOptions()
857 {
858 Order = ChatCompletionMessageCollectionOrder.Descending,
859 PageSizeLimit = 5
860 };
861
862 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id, descOptions))
863 {
864 descMessages.Add(message);
865 if (descMessages.Count >= 3) break;
866 }
867
868 // Verify we get results in both cases
869 Assert.That(ascMessages, Has.Count.GreaterThan(0));
870 Assert.That(descMessages, Has.Count.GreaterThan(0));
871 });
872
873 // Clean up
874 try
875 {
876 await client.DeleteChatCompletionAsync(completion.Id);
877 }
878 catch { /* Ignore cleanup errors */ }
879 }
880
881 [Test]
882 public async Task GetChatCompletionMessagesWithCancellationToken()
883 {
884 ChatClient client = GetTestClient();
885
886 // Create completion
887 ChatCompletionOptions createOptions = new()
888 {
889 StoredOutputEnabled = true,
890 Metadata = { ["test_scenario"] = "cancellation_token" }
891 };
892
893 ChatCompletion completion = await client.CompleteChatAsync(
894 ["Cancellation test: Say 'Hello World'"],
895 createOptions);
896
897 // Test with cancellation token
898 using var cts = new CancellationTokenSource();
899
900 await RetryWithExponentialBackoffAsync(async () =>
901 {
902 try
903 {
904 int count = 0;
905 await foreach (var message in client.GetChatCompletionMessagesAsync(completion.Id, cancellationToken: cts.Token))
906 {
907 count++;
908 Assert.That(message.Id, Is.Not.Null.And.Not.Empty);
909
910 if (count >= 2)
911 {
912 cts.Cancel(); // Cancel after getting some messages
913 break;
914 }
915 }
916
917 Assert.That(count, Is.GreaterThanOrEqualTo(1));
918 }
919 catch (OperationCanceledException)
920 {
921 // This is expected if cancellation happens during enumeration
922 }
923 });
924
925 // Clean up
926 try
927 {
928 await client.DeleteChatCompletionAsync(completion.Id);
929 }
930 catch { /* Ignore cleanup errors */ }
931 }
932
933 [Test]
934 public async Task GetChatCompletionMessagesWithCombinedOptions()
935 {
936 ChatClient client = GetTestClient();
937
938 // Create completion with comprehensive options
939 ChatCompletionOptions createOptions = new()
940 {
941 StoredOutputEnabled = true,
942 Metadata = { ["test_scenario"] = "combined_options" }
943 };
944
945 ChatCompletion completion = await client.CompleteChatAsync(
946 ["Combined options test: Provide a detailed explanation of artificial intelligence."],
947 createOptions);
948
949 await RetryWithExponentialBackoffAsync(async () =>
950 {
951 // Test combined options: limit + order + cancellation token
952 using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
953
954 List<ChatCompletionMessageListDatum> messages = new();
955
956 await foreach (var message in client.GetChatCompletionMessagesAsync(
957 completion.Id,
958 new ChatCompletionMessageCollectionOptions()
959 {
960 PageSizeLimit = 3,
961 Order = ChatCompletionMessageCollectionOrder.Descending
962 },
963 cancellationToken: cts.Token))
964 {
965 messages.Add(message);
966
967 // Validate message structure
968 Assert.That(message.Id, Is.Not.Null.And.Not.Empty);
969
970 if (messages.Count >= 3) break;
971 }
972
973 Assert.That(messages, Has.Count.GreaterThan(0));
974 });
975
976 // Clean up
977 try
978 {
979 await client.DeleteChatCompletionAsync(completion.Id);
980 }
981 catch { /* Ignore cleanup errors */ }
982 }
983
984 private static async Task RetryWithExponentialBackoffAsync(Func<Task> action, int maxRetries = 5, int initialWaitMs = 750)
985 {
986 int waitDuration = initialWaitMs;
987 int retryCount = 0;
988 bool successful = false;
989
990 while (retryCount < maxRetries && !successful)
991 {
992 try
993 {
994 await action();
995 successful = true;
996 }
997 catch (ClientResultException ex) when (ex.Status == 404)
998 {
999 // If we get a 404, it means the resource is not yet available
1000 await Task.Delay(waitDuration);
1001 waitDuration *= 2; // Exponential backoff
1002 retryCount++;
1003 if (retryCount >= maxRetries)
1004 {
1005 throw; // Re-throw the exception if we've exhausted all retries
1006 }
1007 }
1008 }
1009 }
1010
1011 private static ChatClient GetTestClient(string overrideModel = null) => GetTestClient<ChatClient>(TestScenario.Chat, overrideModel);
1012}
1013