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

.Net: Replace stj-schema-mapper source code with M.E.AI schema generation #9807

Merged
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using JsonSchemaMapper;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;

namespace Step04;
Expand All @@ -14,55 +11,18 @@ internal static class JsonSchemaGenerator
/// <summary>
/// Wrapper for generating a JSON schema as string from a .NET type.
/// </summary>
public static string FromType<SchemaType>()
public static string FromType<TSchemaType>()
{
JsonSerializerOptions options = new(JsonSerializerOptions.Default)
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
};
JsonSchemaMapperConfiguration config = new()
AIJsonSchemaCreateOptions config = new()
{
TreatNullObliviousAsNonNullable = true,
TransformSchemaNode = (context, schema) =>
{
// NOTE: This can be replaced with `IncludeAdditionalProperties = false` when upgraded to System.Json.Text 9.0.0
if (context.TypeInfo.Type == typeof(SchemaType))
{
schema["additionalProperties"] = false;
}

// Determine if a type or property and extract the relevant attribute provider
ICustomAttributeProvider? attributeProvider = context.PropertyInfo is not null
? context.PropertyInfo.AttributeProvider
: context.TypeInfo.Type;

// Look up any description attributes
DescriptionAttribute? descriptionAttr = attributeProvider?
.GetCustomAttributes(inherit: true)
.Select(attr => attr as DescriptionAttribute)
.FirstOrDefault(attr => attr is not null);

// Apply description attribute to the generated schema
if (descriptionAttr != null)
{
if (schema is not JsonObject jObj)
{
// Handle the case where the schema is a boolean
JsonValueKind valueKind = schema.GetValueKind();
schema = jObj = new JsonObject();
if (valueKind is JsonValueKind.False)
{
jObj.Add("not", true);
}
}

jObj["description"] = descriptionAttr.Description;
}

return schema;
}
IncludeSchemaKeyword = false,
DisallowAdditionalProperties = true,
};

return KernelJsonSchemaBuilder.Build(typeof(SchemaType), "Intent Result", config).AsJson();
return KernelJsonSchemaBuilder.Build(typeof(TSchemaType), "Intent Result", config).AsJson();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ public void ItCanConvertToFunctionDefinitionsWithParameterTypesAndReturnParamete
{ "type": "object",
"required": ["param1", "param2"],
"properties": {
"param1": { "type": "string", "description": "String param 1" },
"param2": { "type": "integer", "description": "Int param 2" } } }
"param1": { "description": "String param 1", "type": "string" },
markwallace-microsoft marked this conversation as resolved.
Show resolved Hide resolved
"param2": { "description": "Int param 2" , "type": "integer"} } }
""";

KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("Tests", new[]
Expand Down Expand Up @@ -126,8 +126,8 @@ public void ItCanConvertToFunctionDefinitionsWithParameterTypesAndNoReturnParame
{ "type": "object",
"required": ["param1", "param2"],
"properties": {
"param1": { "type": "string", "description": "String param 1" },
"param2": { "type": "integer", "description": "Int param 2" } } }
"param1": { "description": "String param 1", "type": "string" },
"param2": { "description": "Int param 2", "type": "integer"} } }
""";

KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("Tests", new[]
Expand Down Expand Up @@ -180,7 +180,7 @@ public void ItCanConvertToFunctionDefinitionsWithNoParameterTypesButWithDescript

// Assert
Assert.Equal(
"""{"type":"object","required":[],"properties":{"param1":{"type":"string","description":"something neat"}}}""",
"""{"type":"object","required":[],"properties":{"param1":{"description":"something neat","type":"string"}}}""",
JsonSerializer.Serialize(result.Parameters));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public void ItCanCreateValidGeminiFunctionManualForPlugin()
// Assert
Assert.NotNull(result);
Assert.Equal(
"""{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"type":"string","description":"String parameter"},"parameter2":{"type":"string","enum":["Value1","Value2"],"description":"Enum parameter"},"parameter3":{"type":"string","format":"date-time","description":"DateTime parameter"}}}""",
"""{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string"}}}""",
JsonSerializer.Serialize(result.Parameters)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public void ItCanConvertToFunctionDefinitionWithPluginName()
[Fact]
public void ItCanConvertToFunctionDefinitionsWithParameterTypesAndReturnParameterType()
{
string expectedParameterSchema = """{ "type": "object", "required": ["param1", "param2"], "properties": { "param1": { "type": "string", "description": "String param 1" }, "param2": { "type": "integer", "description": "Int param 2" } } } """;
string expectedParameterSchema = """{ "type": "object", "required": ["param1", "param2"], "properties": { "param1": { "description": "String param 1", "type": "string" }, "param2": { "description": "Int param 2", "type": "integer" } } } """;

KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("Tests", new[]
{
Expand All @@ -118,7 +118,7 @@ public void ItCanConvertToFunctionDefinitionsWithParameterTypesAndReturnParamete
[Fact]
public void ItCanConvertToFunctionDefinitionsWithParameterTypesAndNoReturnParameterType()
{
string expectedParameterSchema = """{ "type": "object", "required": ["param1", "param2"], "properties": { "param1": { "type": "string", "description": "String param 1" }, "param2": { "type": "integer", "description": "Int param 2" } } } """;
string expectedParameterSchema = """{ "type": "object", "required": ["param1", "param2"], "properties": { "param1": { "description": "String param 1", "type": "string" }, "param2": { "description": "Int param 2", "type": "integer" } } } """;

KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("Tests", new[]
{
Expand Down Expand Up @@ -174,7 +174,7 @@ public void ItCanConvertToFunctionDefinitionsWithNoParameterTypesButWithDescript
Assert.NotNull(pd.properties);
Assert.Single(pd.properties);
Assert.Equal(
JsonSerializer.Serialize(KernelJsonSchema.Parse("""{ "type":"string", "description":"something neat" }""")),
JsonSerializer.Serialize(KernelJsonSchema.Parse("""{ "description":"something neat", "type":"string" }""")),
JsonSerializer.Serialize(pd.properties.First().Value.RootElement));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json;
using JsonSchemaMapper;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Core;

/// <summary>
/// Unit tests for <see cref="OpenAIJsonSchemaTransformer"/> class.
/// Unit tests for schema transformations used by OpenAI clients.
/// </summary>
public sealed class OpenAIJsonSchemaTransformerTests
{
private static readonly JsonSchemaMapperConfiguration s_jsonSchemaMapperConfiguration = new()
private static readonly AIJsonSchemaCreateOptions s_jsonSchemaCreateOptions = new()
markwallace-microsoft marked this conversation as resolved.
Show resolved Hide resolved
{
IncludeSchemaVersion = false,
IncludeTypeInEnums = true,
TreatNullObliviousAsNonNullable = true,
TransformSchemaNode = OpenAIJsonSchemaTransformer.Transform,
IncludeSchemaKeyword = false,
IncludeTypeInEnumSchemas = true,
DisallowAdditionalProperties = true,
RequireAllProperties = true,
};

private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
Expand Down Expand Up @@ -124,7 +123,7 @@ public void ItTransformsJsonSchemaCorrectly()
""";

// Act
var schema = KernelJsonSchemaBuilder.Build(type, configuration: s_jsonSchemaMapperConfiguration);
var schema = KernelJsonSchemaBuilder.Build(type, configuration: s_jsonSchemaCreateOptions);

// Assert
Assert.Equal(NormalizeJson(expectedSchema), NormalizeJson(schema.ToString()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public void ItCanCreateValidAzureOpenAIFunctionManualForPlugin()
// Assert
Assert.NotNull(result);
Assert.Equal(
"""{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"type":"string","description":"String parameter"},"parameter2":{"type":"string","enum":["Value1","Value2"],"description":"Enum parameter"},"parameter3":{"type":"string","format":"date-time","description":"DateTime parameter"}}}""",
"""{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string"}}}""",
result.FunctionParameters.ToString()
);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System;
using System.Text;
using System.Text.Json;
using JsonSchemaMapper;
using OpenAI.Chat;

namespace Microsoft.SemanticKernel.Connectors.OpenAI;
Expand All @@ -14,14 +13,14 @@ namespace Microsoft.SemanticKernel.Connectors.OpenAI;
internal static class OpenAIChatResponseFormatBuilder
{
/// <summary>
/// <see cref="JsonSchemaMapperConfiguration"/> for JSON schema format for structured outputs.
/// <see cref="Microsoft.Extensions.AI.AIJsonSchemaCreateOptions"/> for JSON schema format for structured outputs.
/// </summary>
private static readonly JsonSchemaMapperConfiguration s_jsonSchemaMapperConfiguration = new()
private static readonly Microsoft.Extensions.AI.AIJsonSchemaCreateOptions s_jsonSchemaCreateOptions = new()
{
IncludeSchemaVersion = false,
IncludeTypeInEnums = true,
TreatNullObliviousAsNonNullable = true,
TransformSchemaNode = OpenAIJsonSchemaTransformer.Transform
IncludeSchemaKeyword = false,
IncludeTypeInEnumSchemas = true,
DisallowAdditionalProperties = true,
RequireAllProperties = true,
};

/// <summary>
Expand Down Expand Up @@ -56,7 +55,7 @@ internal static ChatResponseFormat GetJsonSchemaResponseFormat(Type formatObject
{
var type = formatObjectType.IsGenericType && formatObjectType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(formatObjectType)! : formatObjectType;

var schema = KernelJsonSchemaBuilder.Build(type, configuration: s_jsonSchemaMapperConfiguration);
var schema = KernelJsonSchemaBuilder.Build(type, configuration: s_jsonSchemaCreateOptions);
var schemaBinaryData = BinaryData.FromString(schema.ToString());

var typeName = GetTypeName(type);
Expand Down
9 changes: 0 additions & 9 deletions dotnet/src/InternalUtilities/src/Schema/.editorconfig

This file was deleted.

Loading
Loading