Skip to content

Commit 21a3352

Browse files
committed
Support for file inputs in chat completions
1 parent 68b326b commit 21a3352

13 files changed

+220
-25
lines changed

api/OpenAI.net8.0.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,10 @@ public class ChatMessageContent : ObjectModel.Collection<ChatMessageContentPart>
14121412
public ChatMessageContent(string content);
14131413
}
14141414
public class ChatMessageContentPart : IJsonModel<ChatMessageContentPart>, IPersistableModel<ChatMessageContentPart> {
1415+
public BinaryData FileBytes { get; }
1416+
public string FileBytesMediaType { get; }
1417+
public string FileId { get; }
1418+
public string Filename { get; }
14151419
public BinaryData ImageBytes { get; }
14161420
public string ImageBytesMediaType { get; }
14171421
public ChatImageDetailLevel? ImageDetailLevel { get; }
@@ -1421,6 +1425,8 @@ public class ChatMessageContentPart : IJsonModel<ChatMessageContentPart>, IPersi
14211425
public ChatMessageContentPartKind Kind { get; }
14221426
public string Refusal { get; }
14231427
public string Text { get; }
1428+
public static ChatMessageContentPart CreateFilePart(BinaryData fileBytes, string fileBytesMediaType, string filename);
1429+
public static ChatMessageContentPart CreateFilePart(string fileId);
14241430
public static ChatMessageContentPart CreateImagePart(BinaryData imageBytes, string imageBytesMediaType, ChatImageDetailLevel? imageDetailLevel = null);
14251431
public static ChatMessageContentPart CreateImagePart(Uri imageUri, ChatImageDetailLevel? imageDetailLevel = null);
14261432
public static ChatMessageContentPart CreateInputAudioPart(BinaryData inputAudioBytes, ChatInputAudioFormat inputAudioFormat);
@@ -1434,7 +1440,8 @@ public enum ChatMessageContentPartKind {
14341440
Text = 0,
14351441
Refusal = 1,
14361442
Image = 2,
1437-
InputAudio = 3
1443+
InputAudio = 3,
1444+
File = 4
14381445
}
14391446
public enum ChatMessageRole {
14401447
System = 0,

api/OpenAI.netstandard2.0.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,10 @@ public class ChatMessageContent : ObjectModel.Collection<ChatMessageContentPart>
13231323
public ChatMessageContent(string content);
13241324
}
13251325
public class ChatMessageContentPart : IJsonModel<ChatMessageContentPart>, IPersistableModel<ChatMessageContentPart> {
1326+
public BinaryData FileBytes { get; }
1327+
public string FileBytesMediaType { get; }
1328+
public string FileId { get; }
1329+
public string Filename { get; }
13261330
public BinaryData ImageBytes { get; }
13271331
public string ImageBytesMediaType { get; }
13281332
public ChatImageDetailLevel? ImageDetailLevel { get; }
@@ -1332,6 +1336,8 @@ public class ChatMessageContentPart : IJsonModel<ChatMessageContentPart>, IPersi
13321336
public ChatMessageContentPartKind Kind { get; }
13331337
public string Refusal { get; }
13341338
public string Text { get; }
1339+
public static ChatMessageContentPart CreateFilePart(BinaryData fileBytes, string fileBytesMediaType, string filename);
1340+
public static ChatMessageContentPart CreateFilePart(string fileId);
13351341
public static ChatMessageContentPart CreateImagePart(BinaryData imageBytes, string imageBytesMediaType, ChatImageDetailLevel? imageDetailLevel = null);
13361342
public static ChatMessageContentPart CreateImagePart(Uri imageUri, ChatImageDetailLevel? imageDetailLevel = null);
13371343
public static ChatMessageContentPart CreateInputAudioPart(BinaryData inputAudioBytes, ChatInputAudioFormat inputAudioFormat);
@@ -1345,7 +1351,8 @@ public enum ChatMessageContentPartKind {
13451351
Text = 0,
13461352
Refusal = 1,
13471353
Image = 2,
1348-
InputAudio = 3
1354+
InputAudio = 3,
1355+
File = 4
13491356
}
13501357
public enum ChatMessageRole {
13511358
System = 0,

src/Custom/Chat/ChatMessageContentPart.Serialization.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ internal static void WriteCoreContentPart(ChatMessageContentPart instance, Utf8J
3838
writer.WritePropertyName("input_audio"u8);
3939
writer.WriteObjectValue(instance._inputAudio, options);
4040
}
41+
else if (instance._kind == ChatMessageContentPartKind.File)
42+
{
43+
writer.WritePropertyName("file"u8);
44+
writer.WriteObjectValue(instance._fileFile, options);
45+
}
4146
writer.WriteSerializedAdditionalRawData(instance._additionalBinaryDataProperties, options);
4247
writer.WriteEndObject();
4348
}
@@ -56,6 +61,7 @@ internal static ChatMessageContentPart DeserializeChatMessageContentPart(JsonEle
5661
string refusal = default;
5762
InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default;
5863
InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default;
64+
InternalChatCompletionRequestMessageContentPartFileFile fileFile = default;
5965
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
6066
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
6167
foreach (var property in element.EnumerateObject())
@@ -86,12 +92,18 @@ internal static ChatMessageContentPart DeserializeChatMessageContentPart(JsonEle
8692
.DeserializeInternalChatCompletionRequestMessageContentPartAudioInputAudio(property.Value, options);
8793
continue;
8894
}
95+
if (property.NameEquals("file"u8))
96+
{
97+
fileFile = InternalChatCompletionRequestMessageContentPartFileFile
98+
.DeserializeInternalChatCompletionRequestMessageContentPartFileFile(property.Value, options);
99+
continue;
100+
}
89101
if (true)
90102
{
91103
rawDataDictionary.Add(property.Name, BinaryData.FromString(property.Value.GetRawText()));
92104
}
93105
}
94106
serializedAdditionalRawData = rawDataDictionary;
95-
return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, serializedAdditionalRawData);
107+
return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, fileFile, serializedAdditionalRawData);
96108
}
97109
}

src/Custom/Chat/ChatMessageContentPart.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public partial class ChatMessageContentPart
3333
private readonly string _text;
3434
private readonly InternalChatCompletionRequestMessageContentPartImageImageUrl _imageUri;
3535
private readonly InternalChatCompletionRequestMessageContentPartAudioInputAudio _inputAudio;
36+
private readonly InternalChatCompletionRequestMessageContentPartFileFile _fileFile;
3637
private readonly string _refusal;
3738

3839
// CUSTOM: Made internal.
@@ -47,13 +48,15 @@ internal ChatMessageContentPart(
4748
InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default,
4849
string refusal = default,
4950
InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default,
51+
InternalChatCompletionRequestMessageContentPartFileFile fileFile = default,
5052
IDictionary<string, BinaryData> serializedAdditionalRawData = default)
5153
{
5254
_kind = kind;
5355
_text = text;
5456
_imageUri = imageUri;
5557
_refusal = refusal;
5658
_inputAudio = inputAudio;
59+
_fileFile = fileFile;
5760
_additionalBinaryDataProperties = serializedAdditionalRawData;
5861
}
5962

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

104+
// CUSTOM: Spread.
105+
/// <summary> The ID of the previously uploaded file that the content part represents. </summary>
106+
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content part refers to a previously uploaded file. </remarks>
107+
public string FileId => _fileFile?.FileId;
108+
109+
// CUSTOM: Spread.
110+
/// <summary> The binary file content of the file content part. </summary>
111+
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
112+
public BinaryData FileBytes => _fileFile?.FileBytes;
113+
114+
// CUSTOM: Spread.
115+
/// <summary> The MIME type of the file, e.g., <c>application/pdf</c>. </summary>
116+
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
117+
public string FileBytesMediaType => _fileFile?.FileBytesMediaType;
118+
119+
// CUSTOM: Spread.
120+
/// <summary> The filename for the new file content creation that the content part encapsulates. </summary>
121+
/// <remarks> Present when <see cref="Kind"/> is <see cref="ChatMessageContentPartKind.File"/> and the content refers to data for a new file. </remarks>
122+
public string Filename => _fileFile?.Filename;
123+
101124
// CUSTOM: Spread.
102125
/// <summary>
103126
/// The level of detail with which the model should process the image and generate its textual understanding of
@@ -184,6 +207,40 @@ public static ChatMessageContentPart CreateInputAudioPart(BinaryData inputAudioB
184207
inputAudio: new(inputAudioBytes, inputAudioFormat));
185208
}
186209

210+
/// <summary> Creates a new <see cref="ChatMessageContentPart"/> that represents a previously uploaded file. </summary>
211+
/// <exception cref="ArgumentException"> <paramref name="fileId"/> is null or empty. </exception>
212+
public static ChatMessageContentPart CreateFilePart(string fileId)
213+
{
214+
Argument.AssertNotNullOrEmpty(fileId, nameof(fileId));
215+
216+
return new ChatMessageContentPart(
217+
kind: ChatMessageContentPartKind.File,
218+
fileFile: new()
219+
{
220+
FileId = fileId,
221+
});
222+
}
223+
224+
/// <summary> Creates a new <see cref="ChatMessageContentPart"/> that encapsulates new file data to upload. </summary>
225+
/// <param name="fileBytes"> The binary content of the file. </param>
226+
/// <param name="fileBytesMediaType"> The MIME type of the file, e.g., <c>application/pdf</c>. </param>
227+
/// <param name="filename"> The filename to use for the file that will be created. </param>
228+
/// <exception cref="ArgumentNullException"> <paramref name="fileBytes"/> or <paramref name="fileBytesMediaType"/> is null. </exception>
229+
/// <exception cref="ArgumentException"> <paramref name="fileBytesMediaType"/> or <paramref name="filename"/>> is an empty string, and was expected to be non-empty. </exception>
230+
public static ChatMessageContentPart CreateFilePart(BinaryData fileBytes, string fileBytesMediaType, string filename)
231+
{
232+
Argument.AssertNotNull(fileBytes, nameof(fileBytes));
233+
Argument.AssertNotNullOrEmpty(fileBytesMediaType, nameof(fileBytesMediaType));
234+
Argument.AssertNotNullOrEmpty(filename, nameof(filename));
235+
236+
return new ChatMessageContentPart(
237+
kind: ChatMessageContentPartKind.File,
238+
fileFile: new(fileBytes, fileBytesMediaType)
239+
{
240+
Filename = filename,
241+
});
242+
}
243+
187244
/// <summary>
188245
/// Implicitly instantiates a new <see cref="ChatMessageContentPart"/> from a <see cref="string"/>. As such,
189246
/// using a <see cref="string"/> in place of a <see cref="ChatMessageContentPart"/> is equivalent to calling the

src/Custom/Chat/ChatMessageContentPartKind.Serialization.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ internal static partial class ChatMessageContentPartKindExtensions
1414
ChatMessageContentPartKind.Refusal => "refusal",
1515
ChatMessageContentPartKind.Image => "image_url",
1616
ChatMessageContentPartKind.InputAudio => "input_audio",
17+
ChatMessageContentPartKind.File => "file",
1718
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown ChatMessageContentPartKind value.")
1819
};
1920

@@ -23,6 +24,7 @@ public static ChatMessageContentPartKind ToChatMessageContentPartKind(this strin
2324
if (StringComparer.OrdinalIgnoreCase.Equals(value, "refusal")) return ChatMessageContentPartKind.Refusal;
2425
if (StringComparer.OrdinalIgnoreCase.Equals(value, "image_url")) return ChatMessageContentPartKind.Image;
2526
if (StringComparer.OrdinalIgnoreCase.Equals(value, "input_audio")) return ChatMessageContentPartKind.InputAudio;
27+
if (StringComparer.OrdinalIgnoreCase.Equals(value, "file")) return ChatMessageContentPartKind.File;
2628
throw new ArgumentOutOfRangeException(nameof(value), value, "Unknown ChatMessageContentPartKind value.");
2729
}
2830
}

src/Custom/Chat/ChatMessageContentPartKind.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ public enum ChatMessageContentPartKind
1212
Image,
1313

1414
InputAudio,
15+
16+
File,
1517
}

src/Custom/Chat/Internal/GeneratorStubs.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ internal readonly partial struct InternalCreateChatCompletionStreamResponseObjec
9292
[CodeGenType("CreateChatCompletionStreamResponseServiceTier")]
9393
internal readonly partial struct InternalCreateChatCompletionStreamResponseServiceTier { }
9494

95-
[CodeGenType("CreateChatCompletionStreamResponseUsage1")]
95+
[CodeGenType("CreateChatCompletionStreamResponseUsage")]
9696
internal partial class InternalCreateChatCompletionStreamResponseUsage { }
9797

9898
[CodeGenType("CreateChatCompletionRequestModality")]
@@ -122,9 +122,6 @@ internal readonly partial struct InternalChatCompletionResponseMessageAnnotation
122122
[CodeGenType("ChatCompletionRequestMessageContentPartFile")]
123123
internal partial class InternalChatCompletionRequestMessageContentPartFile { }
124124

125-
[CodeGenType("ChatCompletionRequestMessageContentPartFileFile")]
126-
internal partial class InternalChatCompletionRequestMessageContentPartFileFile { }
127-
128125
[CodeGenType("CreateChatCompletionRequestWebSearchOptionsUserLocation1")]
129126
internal partial class InternalCreateChatCompletionRequestWebSearchOptionsUserLocation1 { }
130127

@@ -133,5 +130,3 @@ internal partial class InternalChatCompletionResponseMessageAnnotationUrlCitatio
133130

134131
[CodeGenType("CreateChatCompletionStreamResponseChoiceFinishReason1")]
135132
internal readonly partial struct InternalCreateChatCompletionStreamResponseChoiceFinishReason1 { }
136-
137-
[CodeGenType("CreateChatCompletionStreamResponseUsage")] internal partial class InternalCreateChatCompletionStreamResponseUsage { }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
4+
namespace OpenAI.Chat;
5+
6+
[CodeGenType("ChatCompletionRequestMessageContentPartFileFile")]
7+
internal partial class InternalChatCompletionRequestMessageContentPartFileFile
8+
{
9+
private readonly BinaryData _fileBytes;
10+
private readonly string _fileBytesMediaType;
11+
12+
// CUSTOM: Changed type from Uri to string to be able to support data URIs properly.
13+
/// <summary> Either a URL of the image or the base64 encoded image data. </summary>
14+
[CodeGenMember("FileData")]
15+
internal string FileData { get; }
16+
17+
public InternalChatCompletionRequestMessageContentPartFileFile(BinaryData fileBytes, string fileBytesMediaType)
18+
{
19+
Argument.AssertNotNull(fileBytes, nameof(fileBytes));
20+
Argument.AssertNotNull(fileBytesMediaType, nameof(fileBytesMediaType));
21+
22+
_fileBytes = fileBytes;
23+
_fileBytesMediaType = fileBytesMediaType;
24+
25+
string base64EncodedData = Convert.ToBase64String(_fileBytes.ToArray());
26+
FileData = $"data:{_fileBytesMediaType};base64,{base64EncodedData}";
27+
}
28+
29+
public BinaryData FileBytes => _fileBytes;
30+
31+
public string FileBytesMediaType => _fileBytesMediaType;
32+
}

src/Generated/Models/InternalChatCompletionRequestMessageContentPartFileFile.Serialization.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit
3232
writer.WritePropertyName("filename"u8);
3333
writer.WriteStringValue(Filename);
3434
}
35-
if (Optional.IsDefined(FileData) && _additionalBinaryDataProperties?.ContainsKey("file_data") != true)
36-
{
37-
writer.WritePropertyName("file_data"u8);
38-
writer.WriteStringValue(FileData);
39-
}
4035
if (Optional.IsDefined(FileId) && _additionalBinaryDataProperties?.ContainsKey("file_id") != true)
4136
{
4237
writer.WritePropertyName("file_id"u8);
4338
writer.WriteStringValue(FileId);
4439
}
40+
if (Optional.IsDefined(FileData) && _additionalBinaryDataProperties?.ContainsKey("file_data") != true)
41+
{
42+
writer.WritePropertyName("file_data"u8);
43+
writer.WriteStringValue(FileData);
44+
}
4545
if (_additionalBinaryDataProperties != null)
4646
{
4747
foreach (var item in _additionalBinaryDataProperties)
@@ -83,8 +83,8 @@ internal static InternalChatCompletionRequestMessageContentPartFileFile Deserial
8383
return null;
8484
}
8585
string filename = default;
86-
string fileData = default;
8786
string fileId = default;
87+
string fileData = default;
8888
IDictionary<string, BinaryData> additionalBinaryDataProperties = new ChangeTrackingDictionary<string, BinaryData>();
8989
foreach (var prop in element.EnumerateObject())
9090
{
@@ -93,19 +93,19 @@ internal static InternalChatCompletionRequestMessageContentPartFileFile Deserial
9393
filename = prop.Value.GetString();
9494
continue;
9595
}
96-
if (prop.NameEquals("file_data"u8))
96+
if (prop.NameEquals("file_id"u8))
9797
{
98-
fileData = prop.Value.GetString();
98+
fileId = prop.Value.GetString();
9999
continue;
100100
}
101-
if (prop.NameEquals("file_id"u8))
101+
if (prop.NameEquals("file_data"u8))
102102
{
103-
fileId = prop.Value.GetString();
103+
fileData = prop.Value.GetString();
104104
continue;
105105
}
106106
additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText()));
107107
}
108-
return new InternalChatCompletionRequestMessageContentPartFileFile(filename, fileData, fileId, additionalBinaryDataProperties);
108+
return new InternalChatCompletionRequestMessageContentPartFileFile(filename, fileId, fileData, additionalBinaryDataProperties);
109109
}
110110

111111
BinaryData IPersistableModel<InternalChatCompletionRequestMessageContentPartFileFile>.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options);

src/Generated/Models/InternalChatCompletionRequestMessageContentPartFileFile.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,16 @@ public InternalChatCompletionRequestMessageContentPartFileFile()
1515
{
1616
}
1717

18-
internal InternalChatCompletionRequestMessageContentPartFileFile(string filename, string fileData, string fileId, IDictionary<string, BinaryData> additionalBinaryDataProperties)
18+
internal InternalChatCompletionRequestMessageContentPartFileFile(string filename, string fileId, string fileData, IDictionary<string, BinaryData> additionalBinaryDataProperties)
1919
{
2020
Filename = filename;
21-
FileData = fileData;
2221
FileId = fileId;
22+
FileData = fileData;
2323
_additionalBinaryDataProperties = additionalBinaryDataProperties;
2424
}
2525

2626
public string Filename { get; set; }
2727

28-
public string FileData { get; set; }
29-
3028
public string FileId { get; set; }
3129

3230
internal IDictionary<string, BinaryData> SerializedAdditionalRawData

0 commit comments

Comments
 (0)