openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.0.0-beta.13

Branches

Tags

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

Clone

HTTPS

Download ZIP

tests/Chat/ChatSmokeTests.cs

796lines · modecode

1using Microsoft.VisualStudio.TestPlatform.ObjectModel;
2using NUnit.Framework;
3using OpenAI.Chat;
4using OpenAI.Tests.Utility;
5using System;
6using System.ClientModel;
7using System.ClientModel.Primitives;
8using System.Collections.Generic;
9using System.IO;
10using System.Net;
11using System.Reflection;
12using System.Text.Json;
13using System.Threading.Tasks;
14using static System.Net.Mime.MediaTypeNames;
15
16namespace OpenAI.Tests.Chat;
17
18[TestFixture(true)]
19[TestFixture(false)]
20[Parallelizable(ParallelScope.All)]
21[Category("Chat")]
22[Category("Smoke")]
23public class ChatSmokeTests : SyncAsyncTestBase
24{
25 public ChatSmokeTests(bool isAsync) : base(isAsync)
26 {
27 }
28
29 [Test]
30 public async Task SmokeTest()
31 {
32 string mockResponseId = Guid.NewGuid().ToString();
33 long mockCreated = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
34
35 BinaryData mockRequest = BinaryData.FromString($$"""
36 {
37 "model": "gpt-4o-mini",
38 "messages": [
39 { "role": "user", "content": "Hello, assistant!" }
40 ]
41 }
42 """);
43 BinaryData mockResponse = BinaryData.FromString($$"""
44 {
45 "id": "{{mockResponseId}}",
46 "created": {{mockCreated}},
47 "choices": [
48 {
49 "finish_reason": "stop",
50 "message": { "role": "assistant", "content": "Hi there, user!" }
51 }
52 ],
53 "additional_property": "hello, additional world!"
54 }
55 """);
56 MockPipelineTransport mockTransport = new(mockRequest, mockResponse);
57
58 OpenAIClientOptions options = new()
59 {
60 Transport = mockTransport
61 };
62 ChatClient client = new("model_name_replaced", new ApiKeyCredential("sk-not-a-real-key"), options);
63
64 ClientResult<ChatCompletion> completionResult = IsAsync
65 ? await client.CompleteChatAsync([ new UserChatMessage("Mock me!") ])
66 : client.CompleteChat([ new UserChatMessage("Mock me!") ]);
67 Assert.That(completionResult?.GetRawResponse(), Is.Not.Null);
68 Assert.That(completionResult.GetRawResponse().Content?.ToString(), Does.Contain("additional world"));
69
70 ChatCompletion completion = completionResult;
71
72 Assert.That(completion.Id, Is.EqualTo(mockResponseId));
73 Assert.That(completion.CreatedAt.ToUnixTimeSeconds, Is.EqualTo(mockCreated));
74 Assert.That(completion.Role, Is.EqualTo(ChatMessageRole.Assistant));
75 Assert.That(completion.Content[0].Text, Is.EqualTo("Hi there, user!"));
76
77 var data = (IDictionary<string, BinaryData>)
78 typeof(ChatCompletion)
79 .GetProperty("SerializedAdditionalRawData", BindingFlags.Instance | BindingFlags.NonPublic)
80 .GetValue(completion);
81 Assert.That(data, Is.Not.Null);
82 Assert.That(data.Count, Is.GreaterThan(0));
83 }
84
85 [Test]
86 public void CanCreateClients()
87 {
88 Uri fakeUri = new("https://127.0.0.1");
89 ApiKeyCredential fakeCredential = new("sk-not-a-real-credential");
90
91 {
92 OpenAIClient topLevelClient = new(fakeCredential);
93 Assert.That(topLevelClient, Is.Not.Null);
94 ChatClient chatClient = topLevelClient.GetChatClient("model");
95 Assert.That(chatClient, Is.Not.Null);
96 }
97 {
98 OpenAIClient topLevelClient = new(fakeCredential, new OpenAIClientOptions()
99 {
100 Endpoint = fakeUri
101 });
102 Assert.That(topLevelClient, Is.Not.Null);
103 ChatClient chatClient = topLevelClient.GetChatClient("model");
104 Assert.That(chatClient, Is.Not.Null);
105 }
106 {
107 ChatClient chatClient = new("model", fakeCredential);
108 Assert.That(chatClient, Is.Not.Null);
109 }
110 {
111 ChatClient chatClient = new("model", fakeCredential, new OpenAIClientOptions()
112 {
113 Endpoint = fakeUri
114 });
115 Assert.That(chatClient, Is.Not.Null);
116 }
117 }
118
119 [Test]
120 public void AuthFailureStreaming()
121 {
122 string fakeApiKey = "not-a-real-key-but-should-be-sanitized";
123 ChatClient client = new("gpt-4o-mini", new ApiKeyCredential(fakeApiKey));
124 Exception caughtException = null;
125 try
126 {
127 foreach (var _ in client.CompleteChatStreaming(
128 [new UserChatMessage("Uh oh, this isn't going to work with that key")]))
129 { }
130 }
131 catch (Exception ex)
132 {
133 caughtException = ex;
134 }
135 var clientResultException = caughtException as ClientResultException;
136 Assert.That(clientResultException, Is.Not.Null);
137 Assert.That(clientResultException.Status, Is.EqualTo((int)HttpStatusCode.Unauthorized));
138 Assert.That(clientResultException.Message, Does.Contain("API key"));
139 Assert.That(clientResultException.Message, Does.Not.Contain(fakeApiKey));
140 }
141
142 [Test]
143 [TestCase(true)]
144 [TestCase(false)]
145 public void SerializeChatToolChoiceAsString(bool fromRawJson)
146 {
147 ChatToolChoice choice;
148
149 if (fromRawJson)
150 {
151 BinaryData data = BinaryData.FromString($"\"auto\"");
152
153 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
154 choice = ModelReaderWriter.Read<ChatToolChoice>(data);
155 }
156 else
157 {
158 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
159 choice = ChatToolChoice.CreateAutoChoice();
160 }
161
162 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
163 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
164 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
165 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.String));
166 Assert.That(choiceAsJson.RootElement.ToString(), Is.EqualTo("auto"));
167 }
168
169 [Test]
170 [TestCase(true)]
171 [TestCase(false)]
172 public void SerializeChatToolChoiceAsObject(bool fromRawJson)
173 {
174 const string functionName = "my_function_name";
175 ChatToolChoice choice;
176
177 if (fromRawJson)
178 {
179 BinaryData data = BinaryData.FromString($$"""
180 {
181 "type": "function",
182 "function": {
183 "name": "{{functionName}}"
184 },
185 "additional_property": true
186 }
187 """);
188
189 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
190 choice = ModelReaderWriter.Read<ChatToolChoice>(data);
191 }
192 else
193 {
194 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
195 choice = ChatToolChoice.CreateFunctionChoice(functionName);
196 }
197
198 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
199 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
200 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
201 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
202
203 Assert.That(choiceAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
204 Assert.That(typeProperty, Is.Not.Null);
205 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
206 Assert.That(typeProperty.ToString(), Is.EqualTo("function"));
207
208 Assert.That(choiceAsJson.RootElement.TryGetProperty("function", out JsonElement functionProperty), Is.True);
209 Assert.That(functionProperty, Is.Not.Null);
210 Assert.That(functionProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
211
212 Assert.That(functionProperty.TryGetProperty("name", out JsonElement functionNameProperty), Is.True);
213 Assert.That(functionNameProperty, Is.Not.Null);
214 Assert.That(functionNameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
215 Assert.That(functionNameProperty.ToString(), Is.EqualTo(functionName));
216
217 if (fromRawJson)
218 {
219 // Confirm that we also have the additional data.
220 Assert.That(choiceAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
221 Assert.That(additionalPropertyProperty, Is.Not.Null);
222 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
223 }
224 }
225
226#pragma warning disable CS0618
227 [Test]
228 [TestCase(true)]
229 [TestCase(false)]
230 public void SerializeChatFunctionChoiceAsString(bool fromRawJson)
231 {
232 ChatFunctionChoice choice;
233
234 if (fromRawJson)
235 {
236 BinaryData data = BinaryData.FromString($"\"auto\"");
237
238 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
239 choice = ModelReaderWriter.Read<ChatFunctionChoice>(data);
240 }
241 else
242 {
243 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
244 choice = ChatFunctionChoice.Auto;
245 }
246
247 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
248 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
249 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
250 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.String));
251 Assert.That(choiceAsJson.RootElement.ToString(), Is.EqualTo("auto"));
252 }
253#pragma warning restore CS0618
254
255#pragma warning disable CS0618
256 [Test]
257 [TestCase(true)]
258 [TestCase(false)]
259 public void SerializeChatFunctionChoiceAsObject(bool fromRawJson)
260 {
261 const string functionName = "my_function_name";
262 ChatFunctionChoice choice;
263
264 if (fromRawJson)
265 {
266 BinaryData data = BinaryData.FromString($$"""
267 {
268 "name": "{{functionName}}",
269 "additional_property": true
270 }
271 """);
272
273 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
274 choice = ModelReaderWriter.Read<ChatFunctionChoice>(data);
275 }
276 else
277 {
278 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
279
280 choice = new ChatFunctionChoice(new ChatFunction(functionName));
281 }
282
283 BinaryData serializedChoice = ModelReaderWriter.Write(choice);
284 using JsonDocument choiceAsJson = JsonDocument.Parse(serializedChoice);
285 Assert.That(choiceAsJson.RootElement, Is.Not.Null);
286 Assert.That(choiceAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
287
288 Assert.That(choiceAsJson.RootElement.TryGetProperty("name", out JsonElement nameProperty), Is.True);
289 Assert.That(nameProperty, Is.Not.Null);
290 Assert.That(nameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
291 Assert.That(nameProperty.ToString(), Is.EqualTo(functionName));
292
293 if (fromRawJson)
294 {
295 // Confirm that we also have the additional data.
296 Assert.That(choiceAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
297 Assert.That(additionalPropertyProperty, Is.Not.Null);
298 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
299 }
300 }
301#pragma warning restore CS0618
302
303 [Test]
304 [TestCase(true)]
305 [TestCase(false)]
306 public void SerializeChatMessageContentPartAsText(bool fromRawJson)
307 {
308 const string text = "Hello, world!";
309 ChatMessageContentPart part;
310
311 if (fromRawJson)
312 {
313 BinaryData data = BinaryData.FromString($$"""
314 {
315 "type": "text",
316 "text": "{{text}}",
317 "additional_property": true
318 }
319 """);
320
321 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
322 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
323 }
324 else
325 {
326 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
327 part = ChatMessageContentPart.CreateTextPart(text);
328 }
329
330 BinaryData serializedPart = ModelReaderWriter.Write(part);
331 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
332 Assert.That(partAsJson.RootElement, Is.Not.Null);
333 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
334
335 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
336 Assert.That(typeProperty, Is.Not.Null);
337 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
338 Assert.That(typeProperty.ToString(), Is.EqualTo("text"));
339
340 Assert.That(partAsJson.RootElement.TryGetProperty("text", out JsonElement textProperty), Is.True);
341 Assert.That(textProperty, Is.Not.Null);
342 Assert.That(textProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
343 Assert.That(textProperty.ToString(), Is.EqualTo(text));
344
345 if (fromRawJson)
346 {
347 // Confirm that we also have the additional data.
348 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
349 Assert.That(additionalPropertyProperty, Is.Not.Null);
350 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
351 }
352 }
353
354 [Test]
355 [TestCase(true)]
356 [TestCase(false)]
357 public void SerializeChatMessageContentPartAsImageUri(bool fromRawJson)
358 {
359 const string uri = "https://avatars.githubusercontent.com/u/14957082";
360 ChatMessageContentPart part;
361
362 if (fromRawJson)
363 {
364 BinaryData data = BinaryData.FromString($$"""
365 {
366 "type": "image_url",
367 "image_url": {
368 "url": "{{uri}}",
369 "detail": "high"
370 },
371 "additional_property": true
372 }
373 """);
374
375 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
376 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
377 }
378 else
379 {
380 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
381 part = ChatMessageContentPart.CreateImagePart(new Uri(uri), ChatImageDetailLevel.High);
382 }
383
384 BinaryData serializedPart = ModelReaderWriter.Write(part);
385 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
386 Assert.That(partAsJson.RootElement, Is.Not.Null);
387 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
388
389 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
390 Assert.That(typeProperty, Is.Not.Null);
391 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
392 Assert.That(typeProperty.ToString(), Is.EqualTo("image_url"));
393
394 Assert.That(partAsJson.RootElement.TryGetProperty("image_url", out JsonElement imageUrlProperty), Is.True);
395 Assert.That(imageUrlProperty, Is.Not.Null);
396 Assert.That(imageUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
397
398 Assert.That(imageUrlProperty.TryGetProperty("url", out JsonElement imageUrlUrlProperty), Is.True);
399 Assert.That(imageUrlUrlProperty, Is.Not.Null);
400 Assert.That(imageUrlUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
401 Assert.That(imageUrlUrlProperty.ToString(), Is.EqualTo(uri));
402
403 Assert.That(imageUrlProperty.TryGetProperty("detail", out JsonElement imageUrlDetailProperty), Is.True);
404 Assert.That(imageUrlDetailProperty, Is.Not.Null);
405 Assert.That(imageUrlDetailProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
406 Assert.That(imageUrlDetailProperty.ToString(), Is.EqualTo("high"));
407
408 if (fromRawJson)
409 {
410 // Confirm that we also have the additional data.
411 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
412 Assert.That(additionalPropertyProperty, Is.Not.Null);
413 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
414 }
415 }
416
417 [Test]
418 [TestCase(true)]
419 [TestCase(false)]
420 public void SerializeChatMessageContentPartAsImageBytes(bool fromRawJson)
421 {
422 string imageMediaType = "image/png";
423 string imageFilename = "images_dog_and_cat.png";
424 string imagePath = Path.Combine("Assets", imageFilename);
425 using Stream image = File.OpenRead(imagePath);
426
427 BinaryData imageData = BinaryData.FromStream(image);
428 string base64EncodedData = Convert.ToBase64String(imageData.ToArray());
429 string dataUri = $"data:{imageMediaType};base64,{base64EncodedData}";
430
431 ChatMessageContentPart part;
432
433 if (fromRawJson)
434 {
435 BinaryData data = BinaryData.FromString($$"""
436 {
437 "type": "image_url",
438 "image_url": {
439 "url": "{{dataUri}}",
440 "detail": "auto"
441 },
442 "additional_property": true
443 }
444 """);
445
446 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
447 part = ModelReaderWriter.Read<ChatMessageContentPart>(data);
448
449 // Confirm that we parsed the data URI correctly.
450 Assert.That(part.ImageBytesMediaType, Is.EqualTo(imageMediaType));
451 Assert.That(part.ImageBytes.ToArray(), Is.EqualTo(imageData.ToArray()));
452 }
453 else
454 {
455 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
456 part = ChatMessageContentPart.CreateImagePart(imageData, imageMediaType, ChatImageDetailLevel.Auto);
457 }
458
459 BinaryData serializedPart = ModelReaderWriter.Write(part);
460 using JsonDocument partAsJson = JsonDocument.Parse(serializedPart);
461 Assert.That(partAsJson.RootElement, Is.Not.Null);
462 Assert.That(partAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
463
464 Assert.That(partAsJson.RootElement.TryGetProperty("type", out JsonElement typeProperty), Is.True);
465 Assert.That(typeProperty, Is.Not.Null);
466 Assert.That(typeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
467 Assert.That(typeProperty.ToString(), Is.EqualTo("image_url"));
468
469 Assert.That(partAsJson.RootElement.TryGetProperty("image_url", out JsonElement imageUrlProperty), Is.True);
470 Assert.That(imageUrlProperty, Is.Not.Null);
471 Assert.That(imageUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
472
473 Assert.That(imageUrlProperty.TryGetProperty("url", out JsonElement imageUrlUrlProperty), Is.True);
474 Assert.That(imageUrlUrlProperty, Is.Not.Null);
475 Assert.That(imageUrlUrlProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
476 Assert.That(imageUrlUrlProperty.ToString(), Is.EqualTo(dataUri));
477
478 Assert.That(imageUrlProperty.TryGetProperty("detail", out JsonElement imageUrlDetailProperty), Is.True);
479 Assert.That(imageUrlDetailProperty, Is.Not.Null);
480 Assert.That(imageUrlDetailProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
481 Assert.That(imageUrlDetailProperty.ToString(), Is.EqualTo("auto"));
482
483 if (fromRawJson)
484 {
485 // Confirm that we also have the additional data.
486 Assert.That(partAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
487 Assert.That(additionalPropertyProperty, Is.Not.Null);
488 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
489 }
490 }
491
492 [Test]
493 public void SerializeCompoundContent()
494 {
495 UserChatMessage message = new(
496 ChatMessageContentPart.CreateTextPart("Describe this image for me:"),
497 ChatMessageContentPart.CreateImagePart(new Uri("https://api.openai.com/test")));
498 string serializedMessage = ModelReaderWriter.Write(message).ToString();
499 Assert.That(serializedMessage, Does.Contain("this image"));
500 Assert.That(serializedMessage, Does.Contain("openai.com/test"));
501 }
502
503 [Test]
504 public void SerializeRefusalMessages()
505 {
506 AssistantChatMessage message = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
507 {
508 "role": "assistant",
509 "content": [
510 {
511 "type": "refusal",
512 "refusal": "I'm telling you 'no' from a content part."
513 }
514 ],
515 "refusal": "I'm telling you 'no' from the message refusal."
516 }
517 """));
518 Assert.That(message.Content, Has.Count.EqualTo(1));
519 Assert.That(message.Content[0].Refusal, Is.EqualTo("I'm telling you 'no' from a content part."));
520 Assert.That(message.Refusal, Is.EqualTo("I'm telling you 'no' from the message refusal."));
521 string reserialized = ModelReaderWriter.Write(message).ToString();
522 Assert.That(reserialized, Does.Contain("from a content part"));
523 Assert.That(reserialized, Does.Contain("from the message refusal"));
524
525 AssistantChatMessage manufacturedMessage = new([
526 ChatToolCall.CreateFunctionToolCall("fake_tool_call_id", "fake_function_name", "{}")
527 ]);
528 manufacturedMessage.Refusal = "No!";
529 string serialized = ModelReaderWriter.Write(manufacturedMessage).ToString();
530 Assert.That(serialized, Does.Contain("refusal"));
531 Assert.That(serialized, Does.Contain("No!"));
532 Assert.That(serialized, Does.Contain("tool_calls"));
533 Assert.That(serialized, Does.Not.Contain("content"));
534 }
535
536 [Test]
537 [TestCase(true)]
538 [TestCase(false)]
539 public void SerializeChatMessageWithSingleStringContent(bool fromRawJson)
540 {
541 const string text = "Hello, world!";
542 AssistantChatMessage message;
543
544 if (fromRawJson)
545 {
546 BinaryData data = BinaryData.FromString($$"""
547 {
548 "role": "assistant",
549 "content": "{{text}}",
550 "additional_property": true
551 }
552 """);
553
554 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
555 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
556 }
557 else
558 {
559 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
560 message = new AssistantChatMessage([
561 ChatMessageContentPart.CreateTextPart(text),
562 ]);
563 }
564
565 BinaryData serializedMessage = ModelReaderWriter.Write(message);
566 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
567 Assert.That(messageAsJson.RootElement, Is.Not.Null);
568 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
569
570 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.True);
571 Assert.That(contentProperty, Is.Not.Null);
572 Assert.That(contentProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
573 Assert.That(contentProperty.ToString(), Is.EqualTo(text));
574
575 if (fromRawJson)
576 {
577 // Confirm that we also have the additional data.
578 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
579 Assert.That(additionalPropertyProperty, Is.Not.Null);
580 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
581 }
582 }
583
584
585 [Test]
586 [TestCase(true)]
587 [TestCase(false)]
588 public void SerializeChatMessageWithEmptyStringContent(bool fromRawJson)
589 {
590 const string text = "";
591 AssistantChatMessage message;
592
593 if (fromRawJson)
594 {
595 BinaryData data = BinaryData.FromString($$"""
596 {
597 "role": "assistant",
598 "content": "{{text}}",
599 "additional_property": true
600 }
601 """);
602
603 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
604 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
605 }
606 else
607 {
608 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
609 message = new AssistantChatMessage([
610 ChatMessageContentPart.CreateTextPart(text),
611 ]);
612 }
613
614 BinaryData serializedMessage = ModelReaderWriter.Write(message);
615 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
616 Assert.That(messageAsJson.RootElement, Is.Not.Null);
617 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
618
619 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.True);
620 Assert.That(contentProperty, Is.Not.Null);
621 Assert.That(contentProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
622 Assert.That(contentProperty.ToString(), Is.EqualTo(text));
623
624 if (fromRawJson)
625 {
626 // Confirm that we also have the additional data.
627 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
628 Assert.That(additionalPropertyProperty, Is.Not.Null);
629 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
630 }
631 }
632
633 [Test]
634 [TestCase(true)]
635 [TestCase(false)]
636 public void SerializeChatMessageWithNoContent(bool fromRawJson)
637 {
638 string toolCallId = "fake_tool_call_id";
639 string toolCallType = "function";
640 string toolCallFunctionName = "fake_function_name";
641 string toolCallFunctionArguments = "{}";
642 AssistantChatMessage message;
643
644 if (fromRawJson)
645 {
646 BinaryData data = BinaryData.FromString($$"""
647 {
648 "role": "assistant",
649 "tool_calls": [{
650 "id": "{{toolCallId}}",
651 "type": "{{toolCallType}}",
652 "function": {
653 "name": "{{toolCallFunctionName}}",
654 "arguments": "{{toolCallFunctionArguments}}"
655 }
656 }],
657 "additional_property": true
658 }
659 """);
660
661 // We deserialize the raw JSON. Later, we serialize it back and confirm nothing was lost in the process.
662 message = ModelReaderWriter.Read<AssistantChatMessage>(data);
663 }
664 else
665 {
666 // We construct a new instance. Later, we serialize it and confirm it was constructed correctly.
667 message = new AssistantChatMessage([
668 ChatToolCall.CreateFunctionToolCall(toolCallId, toolCallFunctionName, "{}")
669 ]);
670 }
671
672 BinaryData serializedMessage = ModelReaderWriter.Write(message);
673 using JsonDocument messageAsJson = JsonDocument.Parse(serializedMessage);
674 Assert.That(messageAsJson.RootElement, Is.Not.Null);
675 Assert.That(messageAsJson.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Object));
676
677 Assert.That(messageAsJson.RootElement.TryGetProperty("content", out JsonElement contentProperty), Is.False);
678
679 Assert.That(messageAsJson.RootElement.TryGetProperty("tool_calls", out JsonElement toolCallsProperty), Is.True);
680 Assert.That(toolCallsProperty, Is.Not.Null);
681 Assert.That(toolCallsProperty.ValueKind, Is.EqualTo(JsonValueKind.Array));
682
683 foreach (JsonElement toolCall in toolCallsProperty.EnumerateArray())
684 {
685 Assert.That(toolCall.TryGetProperty("id", out JsonElement toolCallIdProperty), Is.True);
686 Assert.That(toolCallIdProperty, Is.Not.Null);
687 Assert.That(toolCallIdProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
688 Assert.That(toolCallIdProperty.ToString(), Is.EqualTo(toolCallId));
689
690 Assert.That(toolCall.TryGetProperty("type", out JsonElement toolCallTypeProperty), Is.True);
691 Assert.That(toolCallTypeProperty, Is.Not.Null);
692 Assert.That(toolCallTypeProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
693 Assert.That(toolCallTypeProperty.ToString(), Is.EqualTo(toolCallType));
694
695 Assert.That(toolCall.TryGetProperty("function", out JsonElement toolCallFunctionProperty), Is.True);
696 Assert.That(toolCallFunctionProperty, Is.Not.Null);
697 Assert.That(toolCallFunctionProperty.ValueKind, Is.EqualTo(JsonValueKind.Object));
698
699 Assert.That(toolCallFunctionProperty.TryGetProperty("name", out JsonElement toolCallFunctionNameProperty), Is.True);
700 Assert.That(toolCallFunctionNameProperty, Is.Not.Null);
701 Assert.That(toolCallFunctionNameProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
702 Assert.That(toolCallFunctionNameProperty.ToString(), Is.EqualTo(toolCallFunctionName));
703
704 Assert.That(toolCallFunctionProperty.TryGetProperty("arguments", out JsonElement toolCallFunctionArgumentsProperty), Is.True);
705 Assert.That(toolCallFunctionArgumentsProperty, Is.Not.Null);
706 Assert.That(toolCallFunctionArgumentsProperty.ValueKind, Is.EqualTo(JsonValueKind.String));
707 Assert.That(toolCallFunctionArgumentsProperty.ToString(), Is.EqualTo(toolCallFunctionArguments));
708 }
709
710 if (fromRawJson)
711 {
712 // Confirm that we also have the additional data.
713 Assert.That(messageAsJson.RootElement.TryGetProperty("additional_property", out JsonElement additionalPropertyProperty), Is.True);
714 Assert.That(additionalPropertyProperty, Is.Not.Null);
715 Assert.That(additionalPropertyProperty.ValueKind, Is.EqualTo(JsonValueKind.True));
716 }
717 }
718
719#pragma warning disable CS0618
720 [Test]
721 public void SerializeMessagesWithNullProperties()
722 {
723 AssistantChatMessage assistantMessage = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
724 {
725 "role": "assistant",
726 "content": null,
727 "refusal": null,
728 "function_call": null
729 }
730 """));
731 Assert.That(assistantMessage.Content, Has.Count.EqualTo(0));
732 Assert.That(assistantMessage.Refusal, Is.Null);
733 Assert.That(assistantMessage.FunctionCall, Is.Null);
734
735 foreach ((string role, Type messageType) in new List<(string, Type)>()
736 {
737 ("assistant", typeof(AssistantChatMessage)),
738 ("function", typeof(FunctionChatMessage)),
739 ("tool", typeof(ToolChatMessage)),
740 ("system", typeof(SystemChatMessage)),
741 ("user", typeof(UserChatMessage))
742 })
743 {
744 ChatMessage message = (ChatMessage)((object)ModelReaderWriter.Read(
745 BinaryData.FromString($$"""
746 {
747 "role": "{{role}}",
748 "content": [null]
749 }
750 """),
751 messageType));
752 Assert.That(message, Is.Not.Null);
753 Assert.That(message.Content, Has.Count.EqualTo(1));
754 Assert.That(message.Content[0], Is.Null);
755 }
756
757 assistantMessage = ModelReaderWriter.Read<AssistantChatMessage>(BinaryData.FromString("""
758 {
759 "role": "assistant",
760 "content": [null]
761 }
762 """));
763 Assert.That(assistantMessage.Content, Has.Count.EqualTo(1));
764 Assert.That(assistantMessage.Content[0], Is.Null);
765 FunctionChatMessage functionMessage = new("my_function", null);
766 BinaryData serializedMessage = ModelReaderWriter.Write(functionMessage);
767 Console.WriteLine(serializedMessage.ToString());
768
769 FunctionChatMessage deserializedMessage = ModelReaderWriter.Read<FunctionChatMessage>(serializedMessage);
770 }
771#pragma warning restore CS0618
772
773 [Test]
774 public void TopLevelClientOptionsPersistence()
775 {
776 MockPipelineTransport mockTransport = new(BinaryData.FromString("{}"), BinaryData.FromString("{}"));
777 OpenAIClientOptions options = new()
778 {
779 Transport = mockTransport,
780 Endpoint = new Uri("https://my.custom.com/expected/test/endpoint"),
781 };
782 Uri observedEndpoint = null;
783 options.AddPolicy(new TestPipelinePolicy(message =>
784 {
785 observedEndpoint = message?.Request?.Uri;
786 }),
787 PipelinePosition.PerCall);
788
789 OpenAIClient topLevelClient = new(new ApiKeyCredential("mock-credential"), options);
790 ChatClient firstClient = topLevelClient.GetChatClient("mock-model");
791 ClientResult first = firstClient.CompleteChat(new UserChatMessage("Hello, world"));
792
793 Assert.That(observedEndpoint, Is.Not.Null);
794 Assert.That(observedEndpoint.AbsoluteUri, Does.Contain("my.custom.com/expected/test/endpoint"));
795 }
796}
797