openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.6.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

tests/Chat/ChatSmokeTests.cs

1056lines · modecode

1using Microsoft.ClientModel.TestFramework;
2using Microsoft.ClientModel.TestFramework.Mocks;
3using Microsoft.VisualStudio.TestPlatform.ObjectModel;
4using NUnit.Framework;
5using OpenAI.Chat;
6using System;
7using System.ClientModel;
8using System.ClientModel.Primitives;
9using System.Collections.Generic;
10using System.IO;
11using System.Net;
12using System.Text;
13using System.Text.Json;
14using System.Threading.Tasks;
15
16namespace OpenAI.Tests.Chat;
17
18[Parallelizable(ParallelScope.All)]
19[Category("Chat")]
20[Category("Smoke")]
21public class ChatSmokeTests : ClientTestBase
22{
23 public ChatSmokeTests(bool isAsync) : base(isAsync)
24 {
25 }
26
27 [Test]
28 public async Task SmokeTest()
29 {
30 string mockResponseId = Guid.NewGuid().ToString();
31 long mockCreated = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
32
33 BinaryData mockResponse = BinaryData.FromString($$"""
34 {
35 "id": "{{mockResponseId}}",
36 "created": {{mockCreated}},
37 "choices": [
38 {
39 "finish_reason": "stop",
40 "message": { "role": "assistant", "content": "Hi there, user!" }
41 }
42 ],
43 "additional_property": "hello, additional world!"
44 }
45 """);
46 MockPipelineTransport mockTransport = new(_ => new MockPipelineResponse(200).WithContent(BinaryContent.Create(mockResponse)))
47 {
48 ExpectSyncPipeline = !IsAsync,
49 };
50
51 OpenAIClientOptions options = new()
52 {
53 Transport = mockTransport
54 };
55 ChatClient client = CreateProxyFromClient(new ChatClient("model_name_replaced", new ApiKeyCredential("sk-not-a-real-key"), options));
56
57 ClientResult<ChatCompletion> completionResult = await client.CompleteChatAsync([new UserChatMessage("Mock me!")]);
58 Assert.That(completionResult?.GetRawResponse(), Is.Not.Null);
59 Assert.That(completionResult.GetRawResponse().Content?.ToString(), Does.Contain("additional world"));
60
61 ChatCompletion completion = completionResult;
62
63 Assert.That(completion.Id, Is.EqualTo(mockResponseId));
64 Assert.That(completion.CreatedAt.ToUnixTimeSeconds, Is.EqualTo(mockCreated));
65 Assert.That(completion.Role, Is.EqualTo(ChatMessageRole.Assistant));
66 Assert.That(completion.Content[0].Text, Is.EqualTo("Hi there, user!"));
67 }
68
69 [Test]
70 public void CanCreateClients()
71 {
72 Uri fakeUri = new("https://127.0.0.1");
73 ApiKeyCredential fakeCredential = new("sk-not-a-real-credential");
74
75 {
76 OpenAIClient topLevelClient = new(fakeCredential);
77 Assert.That(topLevelClient, Is.Not.Null);
78 ChatClient chatClient = topLevelClient.GetChatClient("model");
79 Assert.That(chatClient, Is.Not.Null);
80 }
81 {
82 OpenAIClient topLevelClient = new(fakeCredential, new OpenAIClientOptions()
83 {
84 Endpoint = fakeUri
85 });
86 Assert.That(topLevelClient, Is.Not.Null);
87 ChatClient chatClient = topLevelClient.GetChatClient("model");
88 Assert.That(chatClient, Is.Not.Null);
89 }
90 {
91 ChatClient chatClient = new("model", fakeCredential);
92 Assert.That(chatClient, Is.Not.Null);
93 }
94 {
95 ChatClient chatClient = new("model", fakeCredential, new OpenAIClientOptions()
96 {
97 Endpoint = fakeUri
98 });
99 Assert.That(chatClient, Is.Not.Null);
100 }
101 }
102
103 [Test]
104 public async Task AuthFailureStreaming()
105 {
106 string fakeApiKey = "not-a-real-key-but-should-be-sanitized";
107 ChatClient client = CreateProxyFromClient(new ChatClient("gpt-4o-mini", new ApiKeyCredential(fakeApiKey)));
108 Exception caughtException = null;
109 try
110 {
111 await foreach (var _ in client.CompleteChatStreamingAsync(
112 [new UserChatMessage("Uh oh, this isn't going to work with that key")]))
113 { }
114 }
115 catch (Exception ex)
116 {
117 caughtException = ex;
118 }
119 var clientResultException = caughtException as ClientResultException;
120 Assert.That(clientResultException, Is.Not.Null);
121 Assert.That(clientResultException.Status, Is.EqualTo((int)HttpStatusCode.Unauthorized));
122 Assert.That(clientResultException.Message, Does.Contain("API key"));
123 Assert.That(clientResultException.Message, Does.Not.Contain(fakeApiKey));
124 }
125
126 [Test]
127 [TestCase(true)]
128 [TestCase(false)]
129 public void SerializeChatToolChoiceAsString(bool fromRawJson)
130 {
131 ChatToolChoice choice;
132
133 if (fromRawJson)
134 {
135 BinaryData data = BinaryData.FromString($"\"auto\"");
136
137 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
138 choice = ModelReaderWriter.Read<ChatToolChoice>(data);
139 }
140 else
141 {
142 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
143 choice = ChatToolChoice.CreateAutoChoice();
144 }
145
146 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
147 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
148 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
149 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.String));
150 Assert.That(choiceAsJson.RootElement.ToString(), Is.EqualTo("auto"));
151 }
152
153 [Test]
154 [TestCase(true)]
155 [TestCase(false)]
156 public void SerializeChatToolChoiceAsObject(bool fromRawJson)
157 {
158 const string functionName = "my_function_name";
159 ChatToolChoice choice;
160
161 if (fromRawJson)
162 {
163 BinaryData data = BinaryData.FromString($$"""
164 {
165 "type": "function",
166 "function": {
167 "name": "{{functionName}}"
168 },
169 "additional_property": true
170 }
171 """);
172
173 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
174 choice = ModelReaderWriter.Read<ChatToolChoice>(data);
175 }
176 else
177 {
178 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
179 choice = ChatToolChoice.CreateFunctionChoice(functionName);
180 }
181
182 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
183 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
184 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
185 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
186
187 Assert.That(choiceAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
188 Assert.That(typeProperty, Is.Not.Null);
189 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
190 Assert.That(typeProperty.ToString(), Is.EqualTo("function"));
191
192 Assert.That(choiceAsJson.RootElement.TryGetProperty("function", out JsonElement functionProperty), Is.True);
193 Assert.That(functionProperty, Is.Not.Null);
194 Assert.That(functionProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
195
196 Assert.That(functionProperty.TryGetProperty("name", out JsonElement functionNameProperty), Is.True);
197 Assert.That(functionNameProperty, Is.Not.Null);
198 Assert.That(functionNameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
199 Assert.That(functionNameProperty.ToString(), Is.EqualTo(functionName));
200
201 if (fromRawJson)
202 {
203 // Confirm that we also have the additional data.
204 Assert.That(choiceAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
205 Assert.That(additionalPropertyProperty, Is.Not.Null);
206 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
207 }
208 }
209
210#pragma warning disable CS0618
211 [Test]
212 [TestCase(true)]
213 [TestCase(false)]
214 public void SerializeChatFunctionChoiceAsString(bool fromRawJson)
215 {
216 ChatFunctionChoice choice;
217
218 if (fromRawJson)
219 {
220 BinaryData data = BinaryData.FromString($"\"auto\"");
221
222 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
223 choice = ModelReaderWriter.Read<ChatFunctionChoice>(data);
224 }
225 else
226 {
227 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
228 choice = ChatFunctionChoice.CreateAutoChoice();
229 }
230
231 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
232 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
233 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
234 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.String));
235 Assert.That(choiceAsJson.RootElement.ToString(), Is.EqualTo("auto"));
236 }
237#pragma warning restore CS0618
238
239#pragma warning disable CS0618
240 [Test]
241 [TestCase(true)]
242 [TestCase(false)]
243 public void SerializeChatFunctionChoiceAsObject(bool fromRawJson)
244 {
245 const string functionName = "my_function_name";
246 ChatFunctionChoice choice;
247
248 if (fromRawJson)
249 {
250 BinaryData data = BinaryData.FromString($$"""
251 {
252 "name": "{{functionName}}",
253 "additional_property": true
254 }
255 """);
256
257 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
258 choice = ModelReaderWriter.Read<ChatFunctionChoice>(data);
259 }
260 else
261 {
262 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
263
264 choice = ChatFunctionChoice.CreateNamedChoice(functionName);
265 }
266
267 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
268 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
269 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
270 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
271
272 Assert.That(choiceAsJson.RootElement.TryGetProperty("name", out JsonElement nameProperty), Is.True);
273 Assert.That(nameProperty, Is.Not.Null);
274 Assert.That(nameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
275 Assert.That(nameProperty.ToString(), Is.EqualTo(functionName));
276
277 if (fromRawJson)
278 {
279 // Confirm that we also have the additional data.
280 Assert.That(choiceAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
281 Assert.That(additionalPropertyProperty, Is.Not.Null);
282 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
283 }
284 }
285#pragma warning restore CS0618
286
287 [Test]
288 [TestCase(true)]
289 [TestCase(false)]
290 public void SerializeChatMessageContentPartAsText(bool fromRawJson)
291 {
292 const string text = "Hello, world!";
293 ChatMessageContentPart part;
294
295 if (fromRawJson)
296 {
297 BinaryData data = BinaryData.FromString($$"""
298 {
299 "type": "text",
300 "text": "{{text}}",
301 "additional_property": true
302 }
303 """);
304
305 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
306 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
307 }
308 else
309 {
310 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
311 part = ChatMessageContentPart.CreateTextPart(text);
312 }
313
314 BinaryData serializedPart = ModelReaderWriter.Write(part);
315 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
316 Assert.That(partAsJson.RootElement, Is.Not.Null);
317 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
318
319 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
320 Assert.That(typeProperty, Is.Not.Null);
321 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
322 Assert.That(typeProperty.ToString(), Is.EqualTo("text"));
323
324 Assert.That(partAsJson.RootElement.TryGetProperty("text", out JsonElement textProperty), Is.True);
325 Assert.That(textProperty, Is.Not.Null);
326 Assert.That(textProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
327 Assert.That(textProperty.ToString(), Is.EqualTo(text));
328
329 if (fromRawJson)
330 {
331 // Confirm that we also have the additional data.
332 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
333 Assert.That(additionalPropertyProperty, Is.Not.Null);
334 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
335 }
336 }
337
338 [Test]
339 [TestCase(true)]
340 [TestCase(false)]
341 public void SerializeChatMessageContentPartAsImageUri(bool fromRawJson)
342 {
343 const string uri = "https://avatars.githubusercontent.com/u/14957082";
344 ChatMessageContentPart part;
345
346 if (fromRawJson)
347 {
348 BinaryData data = BinaryData.FromString($$"""
349 {
350 "type": "image_url",
351 "image_url": {
352 "url": "{{uri}}",
353 "detail": "high"
354 },
355 "additional_property": true
356 }
357 """);
358
359 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
360 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
361 }
362 else
363 {
364 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
365 part = ChatMessageContentPart.CreateImagePart(new Uri(uri), ChatImageDetailLevel.High);
366 }
367
368 BinaryData serializedPart = ModelReaderWriter.Write(part);
369 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
370 Assert.That(partAsJson.RootElement, Is.Not.Null);
371 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
372
373 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
374 Assert.That(typeProperty, Is.Not.Null);
375 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
376 Assert.That(typeProperty.ToString(), Is.EqualTo("image_url"));
377
378 Assert.That(partAsJson.RootElement.TryGetProperty("image_url", out JsonElement imageUrlProperty), Is.True);
379 Assert.That(imageUrlProperty, Is.Not.Null);
380 Assert.That(imageUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
381
382 Assert.That(imageUrlProperty.TryGetProperty("url", out JsonElement imageUrlUrlProperty), Is.True);
383 Assert.That(imageUrlUrlProperty, Is.Not.Null);
384 Assert.That(imageUrlUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
385 Assert.That(imageUrlUrlProperty.ToString(), Is.EqualTo(uri));
386
387 Assert.That(imageUrlProperty.TryGetProperty("detail", out JsonElement imageUrlDetailProperty), Is.True);
388 Assert.That(imageUrlDetailProperty, Is.Not.Null);
389 Assert.That(imageUrlDetailProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
390 Assert.That(imageUrlDetailProperty.ToString(), Is.EqualTo("high"));
391
392 if (fromRawJson)
393 {
394 // Confirm that we also have the additional data.
395 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
396 Assert.That(additionalPropertyProperty, Is.Not.Null);
397 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
398 }
399 }
400
401 [Test]
402 [TestCase(true)]
403 [TestCase(false)]
404 public void SerializeChatMessageContentPartAsImageBytes(bool fromRawJson)
405 {
406 string imageMediaType = "image/png";
407 string imageFilename = "images_dog_and_cat.png";
408 string imagePath = Path.Combine("Assets", imageFilename);
409 using Stream image = File.OpenRead(imagePath);
410
411 BinaryData imageData = BinaryData.FromStream(image);
412 string base64EncodedData = Convert.ToBase64String(imageData.ToArray());
413 string dataUri = $"data:{imageMediaType};base64,{base64EncodedData}";
414
415 ChatMessageContentPart part;
416
417 if (fromRawJson)
418 {
419 BinaryData data = BinaryData.FromString($$"""
420 {
421 "type": "image_url",
422 "image_url": {
423 "url": "{{dataUri}}",
424 "detail": "auto"
425 },
426 "additional_property": true
427 }
428 """);
429
430 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
431 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
432
433 // Confirm that we parsed the data URI correctly.
434 Assert.That(part.ImageBytesMediaType, Is.EqualTo(imageMediaType));
435 Assert.That(part.ImageBytes.ToArray(), Is.EqualTo(imageData.ToArray()));
436 }
437 else
438 {
439 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
440 part = ChatMessageContentPart.CreateImagePart(imageData, imageMediaType, ChatImageDetailLevel.Auto);
441 }
442
443 BinaryData serializedPart = ModelReaderWriter.Write(part);
444 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
445 Assert.That(partAsJson.RootElement, Is.Not.Null);
446 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
447
448 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
449 Assert.That(typeProperty, Is.Not.Null);
450 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
451 Assert.That(typeProperty.ToString(), Is.EqualTo("image_url"));
452
453 Assert.That(partAsJson.RootElement.TryGetProperty("image_url", out JsonElement imageUrlProperty), Is.True);
454 Assert.That(imageUrlProperty, Is.Not.Null);
455 Assert.That(imageUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
456
457 Assert.That(imageUrlProperty.TryGetProperty("url", out JsonElement imageUrlUrlProperty), Is.True);
458 Assert.That(imageUrlUrlProperty, Is.Not.Null);
459 Assert.That(imageUrlUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
460 Assert.That(imageUrlUrlProperty.ToString(), Is.EqualTo(dataUri));
461
462 Assert.That(imageUrlProperty.TryGetProperty("detail", out JsonElement imageUrlDetailProperty), Is.True);
463 Assert.That(imageUrlDetailProperty, Is.Not.Null);
464 Assert.That(imageUrlDetailProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
465 Assert.That(imageUrlDetailProperty.ToString(), Is.EqualTo("auto"));
466
467 if (fromRawJson)
468 {
469 // Confirm that we also have the additional data.
470 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
471 Assert.That(additionalPropertyProperty, Is.Not.Null);
472 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
473 }
474 }
475
476 [Test]
477 public void SerializeCompoundContent()
478 {
479 UserChatMessage message = new(
480 ChatMessageContentPart.CreateTextPart("Describe this image for me:"),
481 ChatMessageContentPart.CreateImagePart(new Uri("https://api.openai.com/test")));
482 string serializedMessage = ModelReaderWriter.Write(message).ToString();
483 Assert.That(serializedMessage, Does.Contain("this image"));
484 Assert.That(serializedMessage, Does.Contain("openai.com/test"));
485 }
486
487 [Test]
488 public void CanSerializeChatMessage()
489 {
490 var text = "Hello, world!";
491 ChatMessage message = new UserChatMessage(text);
492 message.Patch.Set("$.custom_property"u8, "custom_property");
493
494 BinaryData serialized = ModelReaderWriter.Write(message);
495 using JsonDocument doc = JsonDocument.Parse(serialized.ToString());
496 JsonElement root = doc.RootElement;
497
498 Assert.That(root, Is.Not.Null);
499 Assert.That(root.ValueKind, Is.EqualTo(JsonValueKind.Object));
500
501 Assert.That(root.TryGetProperty("content", out JsonElement contentProperty), Is.True);
502 Assert.That(contentProperty, Is.Not.Null);
503 Assert.That(contentProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
504 Assert.That(contentProperty.ToString(), Is.EqualTo(text));
505
506 Assert.That(root.TryGetProperty("role", out JsonElement roleProperty), Is.True);
507 Assert.That(roleProperty, Is.Not.Null);
508 Assert.That(roleProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
509 Assert.That(roleProperty.ToString(), Is.EqualTo("user"));
510
511 Assert.That(root.TryGetProperty("custom_property", out JsonElement customProperty), Is.True);
512 Assert.That(customProperty, Is.Not.Null);
513 Assert.That(customProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
514 Assert.That(customProperty.ToString(), Is.EqualTo("custom_property"));
515 }
516
517 [Test]
518 public void SerializeRefusalMessages()
519 {
520 AssistantChatMessage message = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
521 {
522 "role": "assistant",
523 "content": [
524 {
525 "type": "refusal",
526 "refusal": "I'm telling you 'no' from a content part."
527 }
528 ],
529 "refusal": "I'm telling you 'no' from the message refusal."
530 }
531 """));
532 Assert.That(message.Content, Has.Count.EqualTo(1));
533 Assert.That(message.Content[0].Refusal, Is.EqualTo("I'm telling you 'no' from a content part."));
534 Assert.That(message.Refusal, Is.EqualTo("I'm telling you 'no' from the message refusal."));
535 string reserialized = ModelReaderWriter.Write(message).ToString();
536 Assert.That(reserialized, Does.Contain("from a content part"));
537 Assert.That(reserialized, Does.Contain("from the message refusal"));
538
539 AssistantChatMessage manufacturedMessage = new([
540 ChatToolCall.CreateFunctionToolCall("fake_tool_call_id", "fake_function_name", BinaryData.FromBytes("{}"u8.ToArray()))
541 ]);
542 manufacturedMessage.Refusal = "No!";
543 string serialized = ModelReaderWriter.Write(manufacturedMessage).ToString();
544 Assert.That(serialized, Does.Contain("refusal"));
545 Assert.That(serialized, Does.Contain("No!"));
546 Assert.That(serialized, Does.Contain("tool_calls"));
547 Assert.That(serialized, Does.Not.Contain("content"));
548 }
549
550 [Test]
551 public void SerializeAudioThings()
552 {
553 // User audio input: wire-correlated ("real") content parts should cleanly serialize/deserialize
554 ChatMessageContentPart inputAudioContentPart = ChatMessageContentPart.CreateInputAudioPart(
555 BinaryData.FromBytes([0x4, 0x2]),
556 ChatInputAudioFormat.Mp3);
557 Assert.That(inputAudioContentPart, Is.Not.Null);
558 BinaryData serializedInputAudioContentPart = ModelReaderWriter.Write(inputAudioContentPart);
559 Assert.That(serializedInputAudioContentPart.ToString(), Does.Contain(@"""format"":""mp3"""));
560 ChatMessageContentPart deserializedInputAudioContentPart = ModelReaderWriter.Read<ChatMessageContentPart>(serializedInputAudioContentPart);
561 Assert.That(deserializedInputAudioContentPart.InputAudioBytes.ToArray()[1], Is.EqualTo(0x2));
562
563 AssistantChatMessage message = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromBytes("""
564 {
565 "role": "assistant",
566 "audio": {
567 "id": "audio_correlated_id_1234"
568 }
569 }
570 """u8.ToArray()));
571 Assert.That(message.Content, Has.Count.EqualTo(0));
572 Assert.That(message.OutputAudioReference, Is.Not.Null);
573 Assert.That(message.OutputAudioReference.Id, Is.EqualTo("audio_correlated_id_1234"));
574 string serializedMessage = ModelReaderWriter.Write(message).ToString();
575 Assert.That(serializedMessage, Does.Contain(@"""audio"":{""id"":""audio_correlated_id_1234""}"));
576
577 AssistantChatMessage ordinaryTextAssistantMessage = new(["This was a message from the assistant"]);
578 ordinaryTextAssistantMessage.OutputAudioReference = new("extra-audio-id");
579 BinaryData serializedLateAudioMessage = ModelReaderWriter.Write(ordinaryTextAssistantMessage);
580 Assert.That(serializedLateAudioMessage.ToString(), Does.Contain("was a message"));
581 Assert.That(serializedLateAudioMessage.ToString(), Does.Contain("extra-audio-id"));
582
583 BinaryData rawAudioResponse = BinaryData.FromBytes("""
584 {
585 "id": "chatcmpl-AOqyHuhjVDeGVbCZXJZ8mCLyl5nBq",
586 "object": "chat.completion",
587 "created": 1730486857,
588 "model": "gpt-4o-audio-preview-2024-10-01",
589 "choices": [
590 {
591 "index": 0,
592 "message": {
593 "role": "assistant",
594 "content": null,
595 "refusal": null,
596 "audio": {
597 "id": "audio_6725224ac62481908ab55dc283289d87",
598 "data": "dHJ1bmNhdGVk",
599 "expires_at": 1730490458,
600 "transcript": "Hello there! How can I assist you with your test today?"
601 }
602 },
603 "finish_reason": "stop"
604 }
605 ],
606 "usage": {
607 "prompt_tokens": 28,
608 "completion_tokens": 97,
609 "total_tokens": 125,
610 "prompt_tokens_details": {
611 "cached_tokens": 0,
612 "text_tokens": 11,
613 "image_tokens": 0,
614 "audio_tokens": 17
615 },
616 "completion_tokens_details": {
617 "reasoning_tokens": 0,
618 "text_tokens": 23,
619 "audio_tokens": 74,
620 "accepted_prediction_tokens": 0,
621 "rejected_prediction_tokens": 0
622 }
623 },
624 "system_fingerprint": "fp_49254d0e9b"
625 }
626 """u8.ToArray());
627 ChatCompletion audioCompletion = ModelReaderWriter.Read<ChatCompletion>(rawAudioResponse);
628 Assert.That(audioCompletion, Is.Not.Null);
629 Assert.That(audioCompletion.Content, Has.Count.EqualTo(0));
630 Assert.That(audioCompletion.OutputAudio, Is.Not.Null);
631 Assert.That(audioCompletion.OutputAudio.Id, Is.EqualTo("audio_6725224ac62481908ab55dc283289d87"));
632 Assert.That(audioCompletion.OutputAudio.AudioBytes, Is.Not.Null);
633 Assert.That(audioCompletion.OutputAudio.Transcript, Is.Not.Null.And.Not.Empty);
634
635 AssistantChatMessage audioHistoryMessage = new(audioCompletion);
636 Assert.That(audioHistoryMessage.OutputAudioReference?.Id, Is.EqualTo(audioCompletion.OutputAudio.Id));
637
638 foreach (KeyValuePair<ChatResponseModalities, (bool, bool, bool)> modalitiesValueToKeyTextAndAudioPresenceItem
639 in new List<KeyValuePair<ChatResponseModalities, (bool, bool, bool)>>()
640 {
641 new(ChatResponseModalities.Default, (false, false, false)),
642 new(ChatResponseModalities.Default | ChatResponseModalities.Text, (true, true, false)),
643 new(ChatResponseModalities.Default | ChatResponseModalities.Audio, (true, false, true)),
644 new(ChatResponseModalities.Default | ChatResponseModalities.Text | ChatResponseModalities.Audio, (true, true, true)),
645 new(ChatResponseModalities.Text, (true, true, false)),
646 new(ChatResponseModalities.Audio, (true, false, true)),
647 new(ChatResponseModalities.Text | ChatResponseModalities.Audio, (true, true, true)),
648 })
649 {
650 ChatResponseModalities modalitiesValue = modalitiesValueToKeyTextAndAudioPresenceItem.Key;
651 (bool keyExpected, bool textExpected, bool audioExpected) = modalitiesValueToKeyTextAndAudioPresenceItem.Value;
652 ChatCompletionOptions testOptions = new()
653 {
654 ResponseModalities = modalitiesValue,
655 };
656 string serializedOptions = ModelReaderWriter.Write(testOptions).ToString().ToLower();
657 Assert.That(serializedOptions.Contains("modalities"), Is.EqualTo(keyExpected));
658 Assert.That(serializedOptions.Contains("text"), Is.EqualTo(textExpected));
659 Assert.That(serializedOptions.Contains("audio"), Is.EqualTo(audioExpected));
660 }
661 }
662
663 [Test]
664 [TestCase(true)]
665 [TestCase(false)]
666 public void SerializeChatMessageWithSingleStringContent(bool fromRawJson)
667 {
668 const string text = "Hello, world!";
669 AssistantChatMessage message;
670
671 if (fromRawJson)
672 {
673 BinaryData data = BinaryData.FromString($$"""
674 {
675 "role": "assistant",
676 "content": "{{text}}",
677 "additional_property": true
678 }
679 """);
680
681 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
682 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
683 }
684 else
685 {
686 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
687 message = new AssistantChatMessage([
688 ChatMessageContentPart.CreateTextPart(text),
689 ]);
690 }
691
692 BinaryData serializedMessage = ModelReaderWriter.Write(message);
693 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
694 Assert.That(messageAsJson.RootElement, Is.Not.Null);
695 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
696
697 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.True);
698 Assert.That(contentProperty, Is.Not.Null);
699 Assert.That(contentProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
700 Assert.That(contentProperty.ToString(), Is.EqualTo(text));
701
702 if (fromRawJson)
703 {
704 // Confirm that we also have the additional data.
705 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
706 Assert.That(additionalPropertyProperty, Is.Not.Null);
707 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
708 }
709 }
710
711
712 [Test]
713 [TestCase(true)]
714 [TestCase(false)]
715 public void SerializeChatMessageWithEmptyStringContent(bool fromRawJson)
716 {
717 const string text = "";
718 AssistantChatMessage message;
719
720 if (fromRawJson)
721 {
722 BinaryData data = BinaryData.FromString($$"""
723 {
724 "role": "assistant",
725 "content": "{{text}}",
726 "additional_property": true
727 }
728 """);
729
730 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
731 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
732 }
733 else
734 {
735 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
736 message = new AssistantChatMessage([
737 ChatMessageContentPart.CreateTextPart(text),
738 ]);
739 }
740
741 BinaryData serializedMessage = ModelReaderWriter.Write(message);
742 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
743 Assert.That(messageAsJson.RootElement, Is.Not.Null);
744 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
745
746 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.True);
747 Assert.That(contentProperty, Is.Not.Null);
748 Assert.That(contentProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
749 Assert.That(contentProperty.ToString(), Is.EqualTo(text));
750
751 if (fromRawJson)
752 {
753 // Confirm that we also have the additional data.
754 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
755 Assert.That(additionalPropertyProperty, Is.Not.Null);
756 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
757 }
758 }
759
760 [Test]
761 [TestCase(true)]
762 [TestCase(false)]
763 public void SerializeChatMessageWithNoContent(bool fromRawJson)
764 {
765 string toolCallId = "fake_tool_call_id";
766 string toolCallType = "function";
767 string toolCallFunctionName = "fake_function_name";
768 string toolCallFunctionArguments = "{}";
769 AssistantChatMessage message;
770
771 if (fromRawJson)
772 {
773 BinaryData data = BinaryData.FromString($$"""
774 {
775 "role": "assistant",
776 "tool_calls": [{
777 "id": "{{toolCallId}}",
778 "type": "{{toolCallType}}",
779 "function": {
780 "name": "{{toolCallFunctionName}}",
781 "arguments": "{{toolCallFunctionArguments}}"
782 }
783 }],
784 "additional_property": true
785 }
786 """);
787
788 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
789 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
790 }
791 else
792 {
793 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
794 message = new AssistantChatMessage([
795 ChatToolCall.CreateFunctionToolCall(toolCallId, toolCallFunctionName, BinaryData.FromBytes("{}"u8.ToArray()))
796 ]);
797 }
798
799 BinaryData serializedMessage = ModelReaderWriter.Write(message);
800 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
801 Assert.That(messageAsJson.RootElement, Is.Not.Null);
802 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
803
804 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.False);
805
806 Assert.That(messageAsJson.RootElement.TryGetProperty("tool_calls", out JsonElement toolCallsProperty), Is.True);
807 Assert.That(toolCallsProperty, Is.Not.Null);
808 Assert.That(toolCallsProperty.ValueKind, Is.EqualTo(JsonValueKind.Array));
809
810 foreach (JsonElement toolCall in toolCallsProperty.EnumerateArray())
811 {
812 Assert.That(toolCall.TryGetProperty("id", out JsonElement toolCallIdProperty), Is.True);
813 Assert.That(toolCallIdProperty, Is.Not.Null);
814 Assert.That(toolCallIdProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
815 Assert.That(toolCallIdProperty.ToString(), Is.EqualTo(toolCallId));
816
817 Assert.That(toolCall.TryGetProperty("type", out JsonElement toolCallTypeProperty), Is.True);
818 Assert.That(toolCallTypeProperty, Is.Not.Null);
819 Assert.That(toolCallTypeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
820 Assert.That(toolCallTypeProperty.ToString(), Is.EqualTo(toolCallType));
821
822 Assert.That(toolCall.TryGetProperty("function", out JsonElement toolCallFunctionProperty), Is.True);
823 Assert.That(toolCallFunctionProperty, Is.Not.Null);
824 Assert.That(toolCallFunctionProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
825
826 Assert.That(toolCallFunctionProperty.TryGetProperty("name", out JsonElement toolCallFunctionNameProperty), Is.True);
827 Assert.That(toolCallFunctionNameProperty, Is.Not.Null);
828 Assert.That(toolCallFunctionNameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
829 Assert.That(toolCallFunctionNameProperty.ToString(), Is.EqualTo(toolCallFunctionName));
830
831 Assert.That(toolCallFunctionProperty.TryGetProperty("arguments", out JsonElement toolCallFunctionArgumentsProperty), Is.True);
832 Assert.That(toolCallFunctionArgumentsProperty, Is.Not.Null);
833 Assert.That(toolCallFunctionArgumentsProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
834 Assert.That(toolCallFunctionArgumentsProperty.ToString(), Is.EqualTo(toolCallFunctionArguments));
835 }
836
837 if (fromRawJson)
838 {
839 // Confirm that we also have the additional data.
840 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
841 Assert.That(additionalPropertyProperty, Is.Not.Null);
842 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
843 }
844 }
845
846#pragma warning disable CS0618
847 [Test]
848 public void AssistantAndFunctionMessagesHandleNoContentCorrectly()
849 {
850 // AssistantChatMessage and FunctionChatMessage can both exist without content, but follow different rules:
851 // - AssistantChatMessage treats content as optional, as valid assistant message variants (e.g. for tool calls)
852 // - FunctionChatMessage meanwhile treats content as required and nullable.
853 // This test validates that no-content assistant messages just don't serialize content, while no-content
854 // function messages serialize content with an explicit null value.
855
856 ChatToolCall fakeToolCall = ChatToolCall.CreateFunctionToolCall("call_abcd1234", "function_name", functionArguments: BinaryData.FromString("{}"));
857 AssistantChatMessage assistantChatMessage = new([fakeToolCall]);
858 string serializedAssistantChatMessage = ModelReaderWriter.Write(assistantChatMessage).ToString();
859 Assert.That(serializedAssistantChatMessage, Does.Not.Contain("content"));
860
861 FunctionChatMessage functionChatMessage = new("function_name", null);
862 string serializedFunctionChatMessage = ModelReaderWriter.Write(functionChatMessage).ToString();
863 Assert.That(serializedFunctionChatMessage, Does.Contain(@"""content"":null"));
864 }
865#pragma warning restore CS0618
866
867#pragma warning disable CS0618
868 [Test]
869 public void SerializeMessagesWithNullProperties()
870 {
871 AssistantChatMessage assistantMessage = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
872 {
873 "role": "assistant",
874 "content": null,
875 "refusal": null,
876 "function_call": null
877 }
878 """));
879 Assert.That(assistantMessage.Content, Has.Count.EqualTo(0));
880 Assert.That(assistantMessage.Refusal, Is.Null);
881 Assert.That(assistantMessage.FunctionCall, Is.Null);
882
883 foreach ((string role, Type messageType) in new List<(string, Type)>()
884 {
885 ("assistant", typeof(AssistantChatMessage)),
886 ("function", typeof(FunctionChatMessage)),
887 ("tool", typeof(ToolChatMessage)),
888 ("system", typeof(SystemChatMessage)),
889 ("user", typeof(UserChatMessage))
890 })
891 {
892 ChatMessage message = (ChatMessage)((object)ModelReaderWriter.Read(
893 BinaryData.FromString($$"""
894 {
895 "role": "{{role}}",
896 "content": [null]
897 }
898 """),
899 messageType));
900 Assert.That(message, Is.Not.Null);
901 Assert.That(message.Content, Has.Count.EqualTo(1));
902 Assert.That(message.Content[0], Is.Null);
903 }
904
905 assistantMessage = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
906 {
907 "role": "assistant",
908 "content": [null]
909 }
910 """));
911 Assert.That(assistantMessage.Content, Has.Count.EqualTo(1));
912 Assert.That(assistantMessage.Content[0], Is.Null);
913 FunctionChatMessage functionMessage = new("my_function", null);
914 BinaryData serializedMessage = ModelReaderWriter.Write(functionMessage);
915 Console.WriteLine(serializedMessage.ToString());
916
917 FunctionChatMessage deserializedMessage = ModelReaderWriter.Read<FunctionChatMessage>(serializedMessage);
918 }
919#pragma warning restore CS0618
920
921 [Test]
922 public async Task TopLevelClientOptionsPersistence()
923 {
924 MockPipelineTransport mockTransport = new(_ => new MockPipelineResponse(200).WithContent(BinaryContent.Create(BinaryData.FromString("{}"))))
925 {
926 ExpectSyncPipeline = !IsAsync
927 };
928 OpenAIClientOptions options = new()
929 {
930 Transport = mockTransport,
931 Endpoint = new Uri("https://my.custom.com/expected/test/endpoint"),
932 };
933 Uri observedEndpoint = null;
934 options.AddPolicy(new TestPipelinePolicy(message =>
935 {
936 observedEndpoint = message?.Request?.Uri;
937 }),
938 PipelinePosition.PerCall);
939
940 OpenAIClient topLevelClient = CreateProxyFromClient(new OpenAIClient(new ApiKeyCredential("mock-credential"), options));
941 ChatClient firstClient = CreateProxyFromClient(topLevelClient.GetChatClient("mock-model"));
942 ClientResult first = await firstClient.CompleteChatAsync(new UserChatMessage("Hello, world"));
943
944 Assert.That(observedEndpoint, Is.Not.Null);
945 Assert.That(observedEndpoint.AbsoluteUri, Does.Contain("my.custom.com/expected/test/endpoint"));
946 }
947
948 [Test]
949 public void CanUseCollections()
950 {
951 ChatCompletionOptions options = new();
952 Assert.That(options.Tools.Count, Is.EqualTo(0));
953 Assert.That(options.Metadata.Count, Is.EqualTo(0));
954 Assert.That(options.StopSequences.Count, Is.EqualTo(0));
955 }
956
957 [Test]
958 public void IdempotentOptionsSerialization()
959 {
960 ChatCompletionOptions emptyOptions = new();
961 BinaryData serializedEmptyOptions = ModelReaderWriter.Write(emptyOptions);
962 Assert.That(serializedEmptyOptions.ToString(), Is.EqualTo("{}"));
963 ChatCompletionOptions deserializedEmptyOptions = ModelReaderWriter.Read<ChatCompletionOptions>(serializedEmptyOptions);
964 BinaryData reserializedEmptyOptions = ModelReaderWriter.Write(deserializedEmptyOptions);
965 Assert.That(reserializedEmptyOptions.ToString(), Is.EqualTo("{}"));
966
967 ChatCompletionOptions originalOptions = new()
968 {
969 IncludeLogProbabilities = true,
970 FrequencyPenalty = 0.4f,
971 };
972
973 BinaryData serializedOptions = ModelReaderWriter.Write(originalOptions);
974
975 string serializedOptionsText = serializedOptions.ToString();
976 Assert.That(serializedOptionsText, Does.Contain("frequency_penalty"));
977 Assert.That(serializedOptionsText, Does.Not.Contain("presence_penalty"));
978 Assert.That(serializedOptionsText, Does.Not.Contain("stream_options"));
979
980 ChatCompletionOptions deserializedOptions = ModelReaderWriter.Read<ChatCompletionOptions>(serializedOptions);
981 BinaryData reserializedOptions = ModelReaderWriter.Write(deserializedOptions);
982
983 string reserializedOptionsText = reserializedOptions.ToString();
984 Assert.That(serializedOptions.ToString(), Is.EqualTo(reserializedOptionsText));
985 }
986
987 [Test]
988 public void StableImageContentPartSerialization()
989 {
990 string base64HelloWorld = Convert.ToBase64String(Encoding.UTF8.GetBytes("hello world"));
991
992 void AssertExpectedImagePart(ChatMessageContentPart imagePart)
993 {
994 Assert.That(imagePart.Kind, Is.EqualTo(ChatMessageContentPartKind.Image));
995 Assert.That(imagePart.ImageBytesMediaType, Is.EqualTo("image/png"));
996 Assert.That(imagePart.ImageDetailLevel, Is.EqualTo(ChatImageDetailLevel.High));
997 Assert.That(Convert.FromBase64String(Convert.ToBase64String(imagePart.ImageBytes.ToArray())), Is.EqualTo("hello world"));
998 }
999
1000 ChatMessageContentPart imagePart = ChatMessageContentPart.CreateImagePart(
1001 BinaryData.FromBytes(Encoding.UTF8.GetBytes("hello world")),
1002 "image/png",
1003 ChatImageDetailLevel.High);
1004
1005 AssertExpectedImagePart(imagePart);
1006
1007 BinaryData serializedImagePart = ModelReaderWriter.Write(imagePart);
1008 Assert.That(serializedImagePart, Is.Not.Null);
1009
1010 ChatMessageContentPart deserializedImagePart = ModelReaderWriter.Read<ChatMessageContentPart>(serializedImagePart);
1011
1012 AssertExpectedImagePart(deserializedImagePart);
1013
1014 ChatMessageContentPart nonDataImagePart = ChatMessageContentPart.CreateImagePart(
1015 new Uri("https://test.openai.com/image.png"),
1016 ChatImageDetailLevel.High);
1017
1018 Assert.That(nonDataImagePart.Kind, Is.EqualTo(ChatMessageContentPartKind.Image));
1019 Assert.That(nonDataImagePart.ImageUri?.AbsoluteUri, Is.EqualTo("https://test.openai.com/image.png"));
1020
1021 serializedImagePart = ModelReaderWriter.Write(nonDataImagePart);
1022 Assert.That(serializedImagePart, Is.Not.Null);
1023
1024 deserializedImagePart = ModelReaderWriter.Read<ChatMessageContentPart>(serializedImagePart);
1025 Assert.That(deserializedImagePart.Kind, Is.EqualTo(ChatMessageContentPartKind.Image));
1026 Assert.That(deserializedImagePart.ImageUri?.AbsoluteUri, Is.EqualTo("https://test.openai.com/image.png"));
1027 }
1028
1029 [Test]
1030 public void StableFileContentPartSerialization()
1031 {
1032 string base64HelloWorld = Convert.ToBase64String(Encoding.UTF8.GetBytes("hello world"));
1033
1034 void AssertExpectedFilePart(ChatMessageContentPart filePart)
1035 {
1036 Assert.That(filePart.Kind, Is.EqualTo(ChatMessageContentPartKind.File));
1037 Assert.That(filePart.FileBytesMediaType, Is.EqualTo("text/plain"));
1038 Assert.That(filePart.Filename, Is.EqualTo("test_content_part.txt"));
1039 Assert.That(Convert.FromBase64String(Convert.ToBase64String(filePart.FileBytes.ToArray())), Is.EqualTo("hello world"));
1040 }
1041
1042 ChatMessageContentPart filePart = ChatMessageContentPart.CreateFilePart(
1043 BinaryData.FromBytes(Encoding.UTF8.GetBytes("hello world")),
1044 "text/plain",
1045 "test_content_part.txt");
1046
1047 AssertExpectedFilePart(filePart);
1048
1049 BinaryData serializedFilePart = ModelReaderWriter.Write(filePart);
1050 Assert.That(serializedFilePart, Is.Not.Null);
1051
1052 ChatMessageContentPart deserializedFilePart = ModelReaderWriter.Read<ChatMessageContentPart>(serializedFilePart);
1053
1054 AssertExpectedFilePart(deserializedFilePart);
1055 }
1056}
1057