diff --git a/api/OpenAI.net8.0.cs b/api/OpenAI.net8.0.cs index 6dd24833..20ef9a9a 100644 --- a/api/OpenAI.net8.0.cs +++ b/api/OpenAI.net8.0.cs @@ -5701,6 +5701,7 @@ public class ResponseContentPart : IJsonModel, IPersistable public string InputFilename { get; } public ResponseImageDetailLevel? InputImageDetailLevel { get; } public string InputImageFileId { get; } + public string InputImageUrl { get; } public ResponseContentPartKind Kind { get; } public IReadOnlyList OutputTextAnnotations { get; } [Serialization.JsonIgnore] diff --git a/api/OpenAI.netstandard2.0.cs b/api/OpenAI.netstandard2.0.cs index e42ae3d7..92f5b592 100644 --- a/api/OpenAI.netstandard2.0.cs +++ b/api/OpenAI.netstandard2.0.cs @@ -5006,6 +5006,7 @@ public class ResponseContentPart : IJsonModel, IPersistable public string InputFilename { get; } public ResponseImageDetailLevel? InputImageDetailLevel { get; } public string InputImageFileId { get; } + public string InputImageUrl { get; } public ResponseContentPartKind Kind { get; } public IReadOnlyList OutputTextAnnotations { get; } [Serialization.JsonIgnore] diff --git a/src/Custom/Responses/ResponseContentPart.cs b/src/Custom/Responses/ResponseContentPart.cs index 14ca0a9a..58e9af66 100644 --- a/src/Custom/Responses/ResponseContentPart.cs +++ b/src/Custom/Responses/ResponseContentPart.cs @@ -28,6 +28,7 @@ public string Text // CUSTOM: Exposed input image properties. public string InputImageFileId => (this as InternalItemContentInputImage)?.FileId; public ResponseImageDetailLevel? InputImageDetailLevel => (this as InternalItemContentInputImage)?.Detail; + public string InputImageUrl => (this as InternalItemContentInputImage)?.ImageUrl; // CUSTOM: Exposed input file properties. public string InputFileId => (this as InternalItemContentInputFile)?.FileId; diff --git a/tests/Responses/ResponsesSmokeTests.cs b/tests/Responses/ResponsesSmokeTests.cs index 89db6edd..7fb848f0 100644 --- a/tests/Responses/ResponsesSmokeTests.cs +++ b/tests/Responses/ResponsesSmokeTests.cs @@ -71,11 +71,12 @@ public void ItemSerialization() [Test] public void ToolChoiceSerialization() { - void AssertChoiceEqual(ResponseToolChoice choice, string expected) + static void AssertChoiceEqual(ResponseToolChoice choice, string expected) { string serialized = ModelReaderWriter.Write(choice).ToString(); Assert.That(serialized, Is.EqualTo(expected)); } + AssertChoiceEqual( ResponseToolChoice.CreateAutoChoice(), @"""auto"""); AssertChoiceEqual( @@ -124,7 +125,6 @@ public void ContentPartSerialization() Assert.That(textPart.Text, Is.EqualTo("hello")); }); - AssertSerializationRoundTrip( @"{""type"":""potato"",""potato_details"":{""cultivar"":""russet""}}", potatoPart => @@ -143,16 +143,15 @@ public void TextFormatSerialization() } [Test] - public void StableInputFileContentPartSerialization() + public void StableInputFileDataContentPartSerialization() { - string base64HelloWorld = Convert.ToBase64String(Encoding.UTF8.GetBytes("hello world")); - static void AssertExpectedFilePart(ResponseContentPart filePart) { Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputFile)); Assert.That(filePart.InputFileBytesMediaType, Is.EqualTo("text/plain")); Assert.That(filePart.InputFilename, Is.EqualTo("test_content_part.txt")); Assert.That(Convert.FromBase64String(Convert.ToBase64String(filePart.InputFileBytes.ToArray())), Is.EqualTo("hello world")); + Assert.That(filePart.InputFileId, Is.Null); } ResponseContentPart filePart = ResponseContentPart.CreateInputFilePart( @@ -170,6 +169,202 @@ static void AssertExpectedFilePart(ResponseContentPart filePart) AssertExpectedFilePart(deserializedFilePart); } + [Test] + public void StableInputFileReferenceContentPartSerialization() + { + static void AssertExpectedFilePart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputFile)); + Assert.That(filePart.InputFileId, Is.EqualTo("asst_123abc")); + Assert.That(filePart.InputFileBytes, Is.Null); + Assert.That(filePart.InputFileBytesMediaType, Is.Null); + Assert.That(filePart.InputFilename, Is.Null); + } + + ResponseContentPart filePart = ResponseContentPart.CreateInputFilePart("asst_123abc"); + + AssertExpectedFilePart(filePart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(filePart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedFilePart(deserializedFilePart); + } + + [Test] + public void StableInputImageDataContentPartSerialization() + { + static void AssertExpectedImagePart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputImage)); + Assert.That(filePart.InputImageDetailLevel, Is.EqualTo(ResponseImageDetailLevel.Low)); + Assert.That(filePart.InputImageUrl, Is.EqualTo($"data:image/png;base64,{Convert.ToBase64String(Encoding.UTF8.GetBytes("image data"))}")); + Assert.That(filePart.InputImageFileId, Is.Null); + } + + ResponseContentPart imagePart = ResponseContentPart.CreateInputImagePart( + BinaryData.FromBytes(Encoding.UTF8.GetBytes("image data")), + "image/png", + ResponseImageDetailLevel.Low); + + AssertExpectedImagePart(imagePart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(imagePart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedImagePart(deserializedFilePart); + } + + [Test] + public void StableInputImageFileContentPartSerialization() + { + static void AssertExpectedImagePart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputImage)); + Assert.That(filePart.InputImageDetailLevel, Is.EqualTo(ResponseImageDetailLevel.Auto)); + Assert.That(filePart.InputImageFileId, Is.EqualTo("image_123abc")); + Assert.That(filePart.InputImageUrl, Is.Null); + } + + ResponseContentPart imagePart = ResponseContentPart.CreateInputImagePart( + "image_123abc", + ResponseImageDetailLevel.Auto); + + AssertExpectedImagePart(imagePart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(imagePart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedImagePart(deserializedFilePart); + } + + [Test] + public void StableInputImageUrlContentPartSerialization() + { + static void AssertExpectedImagePart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputImage)); + Assert.That(filePart.InputImageDetailLevel, Is.EqualTo(ResponseImageDetailLevel.High)); + Assert.That(filePart.InputImageUrl, Is.EqualTo("https://example.com/image.jpg")); + Assert.That(filePart.InputImageFileId, Is.Null); + } + + ResponseContentPart imagePart = ResponseContentPart.CreateInputImagePart( + new Uri("https://example.com/image.jpg"), + ResponseImageDetailLevel.High); + + AssertExpectedImagePart(imagePart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(imagePart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedImagePart(deserializedFilePart); + } + + [Test] + public void StableInputTextContentPartSerialization() + { + static void AssertExpectedTextPart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.InputText)); + Assert.That(filePart.Text, Is.EqualTo("input message")); + } + + ResponseContentPart filePart = ResponseContentPart.CreateInputTextPart("input message"); + + AssertExpectedTextPart(filePart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(filePart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedTextPart(deserializedFilePart); + } + + [Test] + public void StableOutputTextContentPartSerialization() + { + static void AssertExpectedTextPart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.OutputText)); + Assert.That(filePart.Text, Is.EqualTo("output message")); + Assert.That(filePart.OutputTextAnnotations.Count, Is.EqualTo(4)); + + ContainerFileCitationMessageAnnotation containerFileAnnotation = filePart.OutputTextAnnotations.OfType().SingleOrDefault(); + Assert.That(containerFileAnnotation, Is.Not.Null); + Assert.That(containerFileAnnotation.ContainerId, Is.EqualTo("container67")); + Assert.That(containerFileAnnotation.FileId, Is.EqualTo("file_123abc")); + Assert.That(containerFileAnnotation.Filename, Is.EqualTo("example.txt")); + Assert.That(containerFileAnnotation.StartIndex, Is.EqualTo(789)); + Assert.That(containerFileAnnotation.EndIndex, Is.EqualTo(987)); + + FileCitationMessageAnnotation fileAnnotation = filePart.OutputTextAnnotations.OfType().SingleOrDefault(); + Assert.That(fileAnnotation, Is.Not.Null); + Assert.That(fileAnnotation.FileId, Is.EqualTo("file_123abc")); + Assert.That(fileAnnotation.Filename, Is.EqualTo("example.txt")); + Assert.That(fileAnnotation.Index, Is.EqualTo(789)); + + FilePathMessageAnnotation filePathAnnotation = filePart.OutputTextAnnotations.OfType().SingleOrDefault(); + Assert.That(filePathAnnotation, Is.Not.Null); + Assert.That(filePathAnnotation.FileId, Is.EqualTo("file_123abc")); + Assert.That(filePathAnnotation.Index, Is.EqualTo(789)); + + UriCitationMessageAnnotation uriAnnotation = filePart.OutputTextAnnotations.OfType().SingleOrDefault(); + Assert.That(uriAnnotation, Is.Not.Null); + Assert.That(uriAnnotation.Uri.AbsoluteUri, Is.EqualTo("https://example.com/document.md")); + Assert.That(uriAnnotation.StartIndex, Is.EqualTo(789)); + Assert.That(uriAnnotation.EndIndex, Is.EqualTo(987)); + } + + ResponseContentPart textPart = ResponseContentPart.CreateOutputTextPart( + "output message", + [ + new ContainerFileCitationMessageAnnotation("container67", "file_123abc", 789, 987, "example.txt"), + new FileCitationMessageAnnotation("file_123abc", 789, "example.txt"), + new FilePathMessageAnnotation("file_123abc", 789), + new UriCitationMessageAnnotation(new Uri("https://example.com/document.md"), 789, 987, "Example Title"), + ]); + + AssertExpectedTextPart(textPart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(textPart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedTextPart(deserializedFilePart); + } + + [Test] + public void StableRefusalContentPartSerialization() + { + static void AssertExpectedRefusalPart(ResponseContentPart filePart) + { + Assert.That(filePart.Kind, Is.EqualTo(ResponseContentPartKind.Refusal)); + Assert.That(filePart.Refusal, Is.EqualTo("no bueno")); + } + + ResponseContentPart refusalPart = ResponseContentPart.CreateRefusalPart("no bueno"); + + AssertExpectedRefusalPart(refusalPart); + + BinaryData serializedFilePart = ModelReaderWriter.Write(refusalPart); + Assert.That(serializedFilePart, Is.Not.Null); + + ResponseContentPart deserializedFilePart = ModelReaderWriter.Read(serializedFilePart); + + AssertExpectedRefusalPart(deserializedFilePart); + } + private static void AssertSerializationRoundTrip( string serializedJson, Action instanceAssertionsAction) @@ -387,7 +582,7 @@ public void SerializeCodeInterpreterToolContainerAsObject(bool fromRawJson) public void CanSerializeCodeInterpreterCallLogsOutput() { var customLogs = "Custom logs!"; - CodeInterpreterCallLogsOutput logOutput = new CodeInterpreterCallLogsOutput(customLogs); + CodeInterpreterCallLogsOutput logOutput = new(customLogs); logOutput.Patch.Set("$.custom_property"u8, "custom_property"); BinaryData serialized = ModelReaderWriter.Write(logOutput);