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

OpenAI-DotNet 8.3.0 #369

Merged
merged 12 commits into from
Sep 19, 2024
2 changes: 1 addition & 1 deletion OpenAI-DotNet-Proxy/OpenAI-DotNet-Proxy.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<Title>OpenAI API Proxy</Title>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<IsPackable>false</IsPackable>
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet-Tests/OpenAI-DotNet-Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<SignAssembly>false</SignAssembly>
<LangVersion>latest</LangVersion>
Expand Down
47 changes: 47 additions & 0 deletions OpenAI-DotNet-Tests/TestFixture_00_02_Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
Expand Down Expand Up @@ -146,5 +147,51 @@ public void Test_02_01_GenerateJsonSchema()
JsonSchema mathSchema = typeof(MathResponse);
Console.WriteLine(mathSchema.ToString());
}

[Test]
public void Test_02_02_GenerateJsonSchema_PrimitiveTypes()
{
JsonSchema schema = typeof(TestSchema);
Console.WriteLine(schema.ToString());
}

private class TestSchema
{
// test all primitive types can be serialized
public bool Bool { get; set; }
public byte Byte { get; set; }
public sbyte SByte { get; set; }
public short Short { get; set; }
public ushort UShort { get; set; }
public int Integer { get; set; }
public uint UInteger { get; set; }
public long Long { get; set; }
public ulong ULong { get; set; }
public float Float { get; set; }
public double Double { get; set; }
public decimal Decimal { get; set; }
public char Char { get; set; }
public string String { get; set; }
public DateTime DateTime { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
public Guid Guid { get; set; }
// test nullables
public int? NullInt { get; set; }
public DateTime? NullDateTime { get; set; }
public TestEnum TestEnum { get; set; }
public TestEnum? NullEnum { get; set; }
public Dictionary<string, object> Dictionary { get; set; }
public IDictionary<string, int> IntDictionary { get; set; }
public IReadOnlyDictionary<string, string> StringDictionary { get; set; }
public Dictionary<string, MathResponse> CustomDictionary { get; set; }
}

private enum TestEnum
{
Enum1,
Enum2,
Enum3,
Enum4
}
}
}
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Audio/SpeechRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public SpeechRequest(string input, Model model = null, SpeechVoice voice = Speec
/// </summary>
[JsonPropertyName("response_format")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonConverter(typeof(JsonStringEnumConverter<SpeechResponseFormat>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<SpeechResponseFormat>))]
public SpeechResponseFormat ResponseFormat { get; }

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Batch/BatchResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public sealed class BatchResponse : BaseResponse
/// </summary>
[JsonInclude]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter<BatchStatus>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<BatchStatus>))]
public BatchStatus Status { get; private set; }

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Common/Annotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class Annotation : IAppendable<Annotation>

[JsonInclude]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter<AnnotationType>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<AnnotationType>))]
public AnnotationType Type { get; private set; }

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Common/Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Content(ContentType type, string input)

[JsonInclude]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter<ContentType>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<ContentType>))]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public ContentType Type { get; private set; }

Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Common/ResponseFormatObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public ResponseFormatObject(JsonSchema schema)

[JsonInclude]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter<ChatResponseFormat>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<ChatResponseFormat>))]
public ChatResponseFormat Type { get; private set; }

[JsonInclude]
Expand Down
188 changes: 148 additions & 40 deletions OpenAI-DotNet/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
Expand Down Expand Up @@ -87,6 +88,7 @@ public static JsonObject GenerateJsonSchema(this Type type, JsonObject rootSchem
{
options ??= OpenAIClient.JsonSerializationOptions;
var schema = new JsonObject();
type = UnwrapNullableType(type);

if (!type.IsPrimitive &&
type != typeof(Guid) &&
Expand All @@ -98,60 +100,45 @@ public static JsonObject GenerateJsonSchema(this Type type, JsonObject rootSchem
return new JsonObject { ["$ref"] = $"#/definitions/{type.FullName}" };
}

if (type == typeof(string) || type == typeof(char))
if (type.TryGetSimpleTypeSchema(out var schemaType))
{
schema["type"] = "string";
}
else if (type == typeof(int) ||
type == typeof(long) ||
type == typeof(uint) ||
type == typeof(byte) ||
type == typeof(sbyte) ||
type == typeof(ulong) ||
type == typeof(short) ||
type == typeof(ushort))
{
schema["type"] = "integer";
}
else if (type == typeof(float) ||
type == typeof(double) ||
type == typeof(decimal))
{
schema["type"] = "number";
}
else if (type == typeof(bool))
{
schema["type"] = "boolean";
schema["type"] = schemaType;

if (type == typeof(DateTime) ||
type == typeof(DateTimeOffset))
{
schema["format"] = "date-time";
}
else if (type == typeof(Guid))
{
schema["format"] = "uuid";
}
}
else if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
else if (type.IsEnum)
{
schema["type"] = "string";
schema["format"] = "date-time";
schema["enum"] = new JsonArray(Enum.GetNames(type).Select(name => JsonValue.Create(name)).ToArray<JsonNode>());
}
else if (type == typeof(Guid))
else if (type.TryGetDictionaryValueType(out var valueType))
{
schema["type"] = "string";
schema["format"] = "uuid";
}
else if (type.IsEnum)
{
schema["type"] = "string";
schema["enum"] = new JsonArray();
schema["type"] = "object";

foreach (var value in Enum.GetValues(type))
if (rootSchema["definitions"] != null &&
rootSchema["definitions"].AsObject().ContainsKey(valueType!.FullName!))
{
schema["enum"].AsArray().Add(JsonNode.Parse(JsonSerializer.Serialize(value, options)));
schema["additionalProperties"] = new JsonObject { ["$ref"] = $"#/definitions/{valueType.FullName}" };
}
else
{
schema["additionalProperties"] = GenerateJsonSchema(valueType, rootSchema);
}
}
else if (type.IsArray ||
type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>) ||
type.GetGenericTypeDefinition() == typeof(IReadOnlyList<>)))
else if (type.TryGetCollectionElementType(out var elementType))
{
schema["type"] = "array";
var elementType = type.GetElementType() ?? type.GetGenericArguments()[0];

if (rootSchema["definitions"] != null &&
rootSchema["definitions"].AsObject().ContainsKey(elementType.FullName!))
rootSchema["definitions"].AsObject().ContainsKey(elementType!.FullName!))
{
schema["items"] = new JsonObject { ["$ref"] = $"#/definitions/{elementType.FullName}" };
}
Expand Down Expand Up @@ -282,6 +269,127 @@ public static JsonObject GenerateJsonSchema(this Type type, JsonObject rootSchem
return schema;
}

private static bool TryGetSimpleTypeSchema(this Type type, out string schemaType)
{
switch (type)
{
case not null when type == typeof(object):
schemaType = "object";
return true;
case not null when type == typeof(bool):
schemaType = "boolean";
return true;
case not null when type == typeof(float) ||
type == typeof(double) ||
type == typeof(decimal):
schemaType = "number";
return true;
case not null when type == typeof(char) ||
type == typeof(string) ||
type == typeof(Guid) ||
type == typeof(DateTime) ||
type == typeof(DateTimeOffset):
schemaType = "string";
return true;
case not null when type == typeof(int) ||
type == typeof(long) ||
type == typeof(uint) ||
type == typeof(byte) ||
type == typeof(sbyte) ||
type == typeof(ulong) ||
type == typeof(short) ||
type == typeof(ushort):
schemaType = "integer";
return true;
default:
schemaType = null;
return false;
}
}

private static bool TryGetDictionaryValueType(this Type type, out Type valueType)
{
valueType = null;

if (!type.IsGenericType) { return false; }

var genericTypeDefinition = type.GetGenericTypeDefinition();

if (genericTypeDefinition == typeof(Dictionary<,>) ||
genericTypeDefinition == typeof(IDictionary<,>) ||
genericTypeDefinition == typeof(IReadOnlyDictionary<,>))
{
return InternalTryGetDictionaryValueType(type, out valueType);
}

// Check implemented interfaces for dictionary types
foreach (var @interface in type.GetInterfaces())
{
if (!@interface.IsGenericType) { continue; }

var interfaceTypeDefinition = @interface.GetGenericTypeDefinition();

if (interfaceTypeDefinition == typeof(IDictionary<,>) ||
interfaceTypeDefinition == typeof(IReadOnlyDictionary<,>))
{
return InternalTryGetDictionaryValueType(@interface, out valueType);
}
}

return false;

bool InternalTryGetDictionaryValueType(Type dictType, out Type dictValueType)
{
dictValueType = null;
var genericArgs = dictType.GetGenericArguments();

// The key type is not string, which cannot be represented in JSON object property names
if (genericArgs[0] != typeof(string))
{
throw new InvalidOperationException($"Cannot generate schema for dictionary type '{dictType.FullName}' with non-string key type.");
}

dictValueType = genericArgs[1].UnwrapNullableType();
return true;
}
}

private static readonly Type[] arrayTypes =
[
typeof(IEnumerable<>),
typeof(ICollection<>),
typeof(IReadOnlyCollection<>),
typeof(List<>),
typeof(IList<>),
typeof(IReadOnlyList<>),
typeof(HashSet<>),
typeof(ISet<>),
typeof(IReadOnlySet<>)
];

private static bool TryGetCollectionElementType(this Type type, out Type elementType)
{
elementType = null;

if (type.IsArray)
{
elementType = type.GetElementType();
return true;
}

if (!type.IsGenericType) { return false; }

var genericTypeDefinition = type.GetGenericTypeDefinition();

if (!arrayTypes.Contains(genericTypeDefinition)) { return false; }

elementType = type.GetGenericArguments()[0].UnwrapNullableType();
return true;
}

private static Type UnwrapNullableType(this Type type)
=> Nullable.GetUnderlyingType(type) ?? type;

private static Type GetMemberType(MemberInfo member)
=> member switch
{
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/FineTuning/FineTuneJobResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public DateTime? FinishedAt

[JsonInclude]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter<JobStatus>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<JobStatus>))]
public JobStatus Status { get; private set; }

[JsonInclude]
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Images/AbstractBaseImageRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected AbstractBaseImageRequest(Model model = null, int numberOfResults = 1,
/// <para/> Defaults to <see cref="ImageResponseFormat.Url"/>
/// </summary>
[JsonPropertyName("response_format")]
[JsonConverter(typeof(JsonStringEnumConverter<ImageResponseFormat>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<ImageResponseFormat>))]
[FunctionProperty("The format in which the generated images are returned. Must be one of url or b64_json.")]
public ImageResponseFormat ResponseFormat { get; }

Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Images/ImageGenerationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public ImageGenerationRequest(
/// <para/> Defaults to <see cref="ImageResponseFormat.Url"/>
/// </summary>
[JsonPropertyName("response_format")]
[JsonConverter(typeof(JsonStringEnumConverter<ImageResponseFormat>))]
[JsonConverter(typeof(Extensions.JsonStringEnumConverter<ImageResponseFormat>))]
[FunctionProperty("The format in which the generated images are returned. Must be one of url or b64_json.", true)]
public ImageResponseFormat ResponseFormat { get; }

Expand Down
Loading