Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare 2.2.0-beta.4 release (Part 1) #378

Merged
merged 2 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 214 additions & 96 deletions api/OpenAI.net8.0.cs

Large diffs are not rendered by default.

298 changes: 202 additions & 96 deletions api/OpenAI.netstandard2.0.cs

Large diffs are not rendered by default.

45 changes: 0 additions & 45 deletions examples/Responses/Example01_CuaFlow.cs

This file was deleted.

18 changes: 18 additions & 0 deletions examples/Responses/Example01_HelloWorld.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;

namespace OpenAI.Examples;

public partial class ResponseExamples
{
[Test]
public void Example01_HelloWorld()
{
OpenAIResponseClient client = new(model: "gpt-4o", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

OpenAIResponse response = client.CreateResponse("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]: {response.GetOutputText()}");
}
}
19 changes: 19 additions & 0 deletions examples/Responses/Example01_HelloWorldAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;
using System.Threading.Tasks;

namespace OpenAI.Examples;

public partial class ResponseExamples
{
[Test]
public async Task Example01_HelloWorldAsync()
{
OpenAIResponseClient client = new(model: "gpt-4o", apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

OpenAIResponse response = await client.CreateResponseAsync("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]: {response.GetOutputText()}");
}
}
34 changes: 34 additions & 0 deletions examples/Responses/Example02_HelloWorldStreaming.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;

namespace OpenAI.Examples;

public partial class ResponseExamples
{
[Test]
public void Example02_HelloWorldStreaming()
{
OpenAIResponseClient client = new(
model: "gpt-4o-mini",
apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

Console.Write($"Streaming text: ");

foreach (StreamingResponseUpdate update
in client.CreateResponseStreaming("Hello, world!"))
{
if (update is StreamingResponseOutputTextDeltaUpdate outputTextUpdate)
{
// Streamed text will arrive as it's generated via delta events
Console.Write(outputTextUpdate.Delta);
}
else if (update is StreamingResponseCompletedUpdate responseCompletedUpdate)
{
// Item and response completed events have aggregated text available
Console.WriteLine();
Console.WriteLine($"Final text: {responseCompletedUpdate.Response.GetOutputText()}");
}
}
}
}
35 changes: 35 additions & 0 deletions examples/Responses/Example02_HelloWorldStreamingAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using NUnit.Framework;
using OpenAI.Responses;
using System;
using System.Threading.Tasks;

namespace OpenAI.Examples;

public partial class ResponseExamples
{
[Test]
public async Task Example02_HelloWorldStreamingAsync()
{
OpenAIResponseClient client = new(
model: "gpt-4o-mini",
apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

Console.Write($"Streaming text: ");

await foreach (StreamingResponseUpdate update
in client.CreateResponseStreamingAsync("Hello, world!"))
{
if (update is StreamingResponseOutputTextDeltaUpdate outputTextUpdate)
{
// Streamed text will arrive as it's generated via delta events
Console.Write(outputTextUpdate.Delta);
}
else if (update is StreamingResponseCompletedUpdate responseCompletedUpdate)
{
// Item and response completed events have aggregated text available
Console.WriteLine();
Console.WriteLine($"Final text: {responseCompletedUpdate.Response.GetOutputText()}");
}
}
}
}
14 changes: 13 additions & 1 deletion src/Custom/Chat/ChatMessageContentPart.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ internal static void WriteCoreContentPart(ChatMessageContentPart instance, Utf8J
writer.WritePropertyName("input_audio"u8);
writer.WriteObjectValue(instance._inputAudio, options);
}
else if (instance._kind == ChatMessageContentPartKind.File)
{
writer.WritePropertyName("file"u8);
writer.WriteObjectValue(instance._fileFile, options);
}
writer.WriteSerializedAdditionalRawData(instance._additionalBinaryDataProperties, options);
writer.WriteEndObject();
}
Expand All @@ -56,6 +61,7 @@ internal static ChatMessageContentPart DeserializeChatMessageContentPart(JsonEle
string refusal = default;
InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default;
InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default;
InternalChatCompletionRequestMessageContentPartFileFile fileFile = default;
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
foreach (var property in element.EnumerateObject())
Expand Down Expand Up @@ -86,12 +92,18 @@ internal static ChatMessageContentPart DeserializeChatMessageContentPart(JsonEle
.DeserializeInternalChatCompletionRequestMessageContentPartAudioInputAudio(property.Value, options);
continue;
}
if (property.NameEquals("file"u8))
{
fileFile = InternalChatCompletionRequestMessageContentPartFileFile
.DeserializeInternalChatCompletionRequestMessageContentPartFileFile(property.Value, options);
continue;
}
if (true)
{
rawDataDictionary.Add(property.Name, BinaryData.FromString(property.Value.GetRawText()));
}
}
serializedAdditionalRawData = rawDataDictionary;
return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, serializedAdditionalRawData);
return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, fileFile, serializedAdditionalRawData);
}
}
57 changes: 57 additions & 0 deletions src/Custom/Chat/ChatMessageContentPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public partial class ChatMessageContentPart
private readonly string _text;
private readonly InternalChatCompletionRequestMessageContentPartImageImageUrl _imageUri;
private readonly InternalChatCompletionRequestMessageContentPartAudioInputAudio _inputAudio;
private readonly InternalChatCompletionRequestMessageContentPartFileFile _fileFile;
private readonly string _refusal;

// CUSTOM: Made internal.
Expand All @@ -47,13 +48,15 @@ internal ChatMessageContentPart(
InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default,
string refusal = default,
InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default,
InternalChatCompletionRequestMessageContentPartFileFile fileFile = default,
IDictionary<string, BinaryData> serializedAdditionalRawData = default)
{
_kind = kind;
_text = text;
_imageUri = imageUri;
_refusal = refusal;
_inputAudio = inputAudio;
_fileFile = fileFile;
_additionalBinaryDataProperties = serializedAdditionalRawData;
}

Expand Down Expand Up @@ -98,6 +101,26 @@ internal ChatMessageContentPart(
/// </remarks>
public ChatInputAudioFormat? InputAudioFormat => _inputAudio?.Format;

// CUSTOM: Spread.
/// <summary> The ID of the previously uploaded file that the content part represents. </summary>
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content part refers to a previously uploaded file. </remarks>
public string FileId => _fileFile?.FileId;

// CUSTOM: Spread.
/// <summary> The binary file content of the file content part. </summary>
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
public BinaryData FileBytes => _fileFile?.FileBytes;

// CUSTOM: Spread.
/// <summary> The MIME type of the file, e.g., <c>application/pdf</c>. </summary>
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
public string FileBytesMediaType => _fileFile?.FileBytesMediaType;

// CUSTOM: Spread.
/// <summary> The filename for the new file content creation that the content part encapsulates. </summary>
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
public string Filename => _fileFile?.Filename;

// CUSTOM: Spread.
/// <summary>
/// The level of detail with which the model should process the image and generate its textual understanding of
Expand Down Expand Up @@ -184,6 +207,40 @@ public static ChatMessageContentPart CreateInputAudioPart(BinaryData inputAudioB
inputAudio: new(inputAudioBytes, inputAudioFormat));
}

/// <summary> Creates a new <see cref="ChatMessageContentPart"/> that represents a previously uploaded file. </summary>
/// <exception cref="ArgumentException"> <paramref name="fileId"/> is null or empty. </exception>
public static ChatMessageContentPart CreateFilePart(string fileId)
{
Argument.AssertNotNullOrEmpty(fileId, nameof(fileId));

return new ChatMessageContentPart(
kind: ChatMessageContentPartKind.File,
fileFile: new()
{
FileId = fileId,
});
}

/// <summary> Creates a new <see cref="ChatMessageContentPart"/> that encapsulates new file data to upload. </summary>
/// <param name="fileBytes"> The binary content of the file. </param>
/// <param name="fileBytesMediaType"> The MIME type of the file, e.g., <c>application/pdf</c>. </param>
/// <param name="filename"> The filename to use for the file that will be created. </param>
/// <exception cref="ArgumentNullException"> <paramref name="fileBytes"/> or <paramref name="fileBytesMediaType"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="fileBytesMediaType"/> or <paramref name="filename"/>> is an empty string, and was expected to be non-empty. </exception>
public static ChatMessageContentPart CreateFilePart(BinaryData fileBytes, string fileBytesMediaType, string filename)
{
Argument.AssertNotNull(fileBytes, nameof(fileBytes));
Argument.AssertNotNullOrEmpty(fileBytesMediaType, nameof(fileBytesMediaType));
Argument.AssertNotNullOrEmpty(filename, nameof(filename));

return new ChatMessageContentPart(
kind: ChatMessageContentPartKind.File,
fileFile: new(fileBytes, fileBytesMediaType)
{
Filename = filename,
});
}

/// <summary>
/// Implicitly instantiates a new <see cref="ChatMessageContentPart"/> from a <see cref="string"/>. As such,
/// using a <see cref="string"/> in place of a <see cref="ChatMessageContentPart"/> is equivalent to calling the
Expand Down
2 changes: 2 additions & 0 deletions src/Custom/Chat/ChatMessageContentPartKind.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal static partial class ChatMessageContentPartKindExtensions
ChatMessageContentPartKind.Refusal => "refusal",
ChatMessageContentPartKind.Image => "image_url",
ChatMessageContentPartKind.InputAudio => "input_audio",
ChatMessageContentPartKind.File => "file",
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown ChatMessageContentPartKind value.")
};

Expand All @@ -23,6 +24,7 @@ public static ChatMessageContentPartKind ToChatMessageContentPartKind(this strin
if (StringComparer.OrdinalIgnoreCase.Equals(value, "refusal")) return ChatMessageContentPartKind.Refusal;
if (StringComparer.OrdinalIgnoreCase.Equals(value, "image_url")) return ChatMessageContentPartKind.Image;
if (StringComparer.OrdinalIgnoreCase.Equals(value, "input_audio")) return ChatMessageContentPartKind.InputAudio;
if (StringComparer.OrdinalIgnoreCase.Equals(value, "file")) return ChatMessageContentPartKind.File;
throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown ChatMessageContentPartKind value.");
}
}
2 changes: 2 additions & 0 deletions src/Custom/Chat/ChatMessageContentPartKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public enum ChatMessageContentPartKind
Image,

InputAudio,

File,
}
5 changes: 1 addition & 4 deletions src/Custom/Chat/Internal/GeneratorStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal readonly partial struct InternalCreateChatCompletionStreamResponseObjec
[CodeGenType("CreateChatCompletionStreamResponseServiceTier")]
internal readonly partial struct InternalCreateChatCompletionStreamResponseServiceTier { }

[CodeGenType("CreateChatCompletionStreamResponseUsage1")]
[CodeGenType("CreateChatCompletionStreamResponseUsage")]
internal partial class InternalCreateChatCompletionStreamResponseUsage { }

[CodeGenType("CreateChatCompletionRequestModality")]
Expand Down Expand Up @@ -122,9 +122,6 @@ internal readonly partial struct InternalChatCompletionResponseMessageAnnotation
[CodeGenType("ChatCompletionRequestMessageContentPartFile")]
internal partial class InternalChatCompletionRequestMessageContentPartFile { }

[CodeGenType("ChatCompletionRequestMessageContentPartFileFile")]
internal partial class InternalChatCompletionRequestMessageContentPartFileFile { }

[CodeGenType("CreateChatCompletionRequestWebSearchOptionsUserLocation1")]
internal partial class InternalCreateChatCompletionRequestWebSearchOptionsUserLocation1 { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ internal partial class InternalChatCompletionMessageToolCallFunction : IJsonMode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SerializeArgumentsValue(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
writer.WriteStringValue(Arguments.ToString());
string value = Arguments.ToMemory().IsEmpty
? string.Empty
: Arguments.ToString();
writer.WriteStringValue(value);
}

// CUSTOM: Replaced the call to GetRawText() for a call to GetString() because otherwise the starting and ending
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Text.RegularExpressions;

namespace OpenAI.Chat;

[CodeGenType("ChatCompletionRequestMessageContentPartFileFile")]
internal partial class InternalChatCompletionRequestMessageContentPartFileFile
{
private readonly BinaryData _fileBytes;
private readonly string _fileBytesMediaType;

// CUSTOM: Changed type from Uri to string to be able to support data URIs properly.
/// <summary> Either a URL of the image or the base64 encoded image data. </summary>
[CodeGenMember("FileData")]
internal string FileData { get; }

public InternalChatCompletionRequestMessageContentPartFileFile(BinaryData fileBytes, string fileBytesMediaType)
{
Argument.AssertNotNull(fileBytes, nameof(fileBytes));
Argument.AssertNotNull(fileBytesMediaType, nameof(fileBytesMediaType));

_fileBytes = fileBytes;
_fileBytesMediaType = fileBytesMediaType;

string base64EncodedData = Convert.ToBase64String(_fileBytes.ToArray());
FileData = $"data:{_fileBytesMediaType};base64,{base64EncodedData}";
}

public BinaryData FileBytes => _fileBytes;

public string FileBytesMediaType => _fileBytesMediaType;
}
4 changes: 4 additions & 0 deletions src/Custom/FineTuning/Internal/GeneratorStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ internal readonly partial struct InternalFineTuneChatCompletionRequestAssistantM
[CodeGenType("FineTuneChatCompletionRequestAssistantMessageRole")]
internal readonly partial struct InternalFineTuneChatCompletionRequestAssistantMessageRole { }

[CodeGenType("CreateFineTuningCheckpointPermissionRequest")] internal partial class InternalCreateFineTuningCheckpointPermissionRequest { }
[CodeGenType("DeleteFineTuningCheckpointPermissionResponse")] internal partial class InternalDeleteFineTuningCheckpointPermissionResponse { }
[CodeGenType("FineTuningCheckpointPermission")] internal partial class InternalFineTuningCheckpointPermission { }
[CodeGenType("ListFineTuningCheckpointPermissionResponse")] internal partial class InternalListFineTuningCheckpointPermissionResponse { }
Loading