From 057803a8f90ea724fd1084189943a2f7ad0ca4fb Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sun, 30 Jun 2024 14:42:10 -0500 Subject: [PATCH] feat: generate definition for complex types --- .../Utils/JsonSchemaGenerator.cs | 60 +++++++++++++++++-- .../Unit/Utils/JsonSchemaGeneratorTestData.cs | 54 +++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/AnthropicClient/Utils/JsonSchemaGenerator.cs b/src/AnthropicClient/Utils/JsonSchemaGenerator.cs index 61bebd0..f5101e3 100644 --- a/src/AnthropicClient/Utils/JsonSchemaGenerator.cs +++ b/src/AnthropicClient/Utils/JsonSchemaGenerator.cs @@ -46,7 +46,6 @@ static class JsonSchemaGenerator } var attribute = parameter.GetCustomAttribute(); - var paramName = attribute?.Name ?? parameter.Name; var paramDescription = attribute?.Description ?? string.Empty; var paramRequired = attribute?.Required ?? !parameter.HasDefaultValue; @@ -78,7 +77,7 @@ static class JsonSchemaGenerator { return new JsonObject() { - [RefKey] = $"#/definitions/{type.FullName}" + [RefKey] = GetDefinitionPath(type) }; } @@ -151,14 +150,65 @@ static class JsonSchemaGenerator private static JsonObject GenerateTypeDefinitionSchema(Type type, JsonObject inputSchema) { var definitions = inputSchema[DefinitionsKey] ?? new JsonObject(); - var typeSchema = new JsonObject(); + var typeSchema = new JsonObject() + { + [TypeKey] = ObjectType + }; - // TODO: Implement schema generation for complex types + List members = []; + var bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; + members.AddRange(type.GetProperties(bindingFlags)); + members.AddRange(type.GetFields(bindingFlags)); + var memberProperties = new JsonObject(); + var memberRequiredProperties = new JsonArray(); + + foreach (var member in members) + { + JsonObject memberProperty; + + var memberType = member switch + { + PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + _ => throw new InvalidOperationException("Member is not a property or field") + }; + + // TODO: Provide attribute to allow specifying... + // 1. Name + // 2. Description + // 3. Required + // 4. Default values + // 5. Possible values + var memberPropertyName = memberType.Name; + var memberDescription = string.Empty; + var memberRequired = Nullable.GetUnderlyingType(memberType) is null; + + memberProperty = definitions.AsObject().ContainsKey(memberType.FullName) + ? new JsonObject() + { + [RefKey] = GetDefinitionPath(memberType) + } + : GenerateParameterTypeSchema(memberType, inputSchema); + + if (memberRequired) + { + memberRequiredProperties.Add(memberPropertyName); + } + + memberProperty[DescriptionKey] = memberDescription; + memberProperties[memberPropertyName] = memberProperty; + } + + typeSchema[PropertiesKey] = memberProperties; + typeSchema[RequiredPropertiesKey] = memberRequiredProperties; definitions[type.FullName] = typeSchema; + inputSchema[DefinitionsKey] = definitions; return new JsonObject() { - [RefKey] = $"#/definitions/{type.FullName}" + [RefKey] = GetDefinitionPath(type) }; } + + private static string GetDefinitionPath(Type type) => $"#/definitions/{type.FullName}"; } \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs index 4350cde..dd0f89f 100644 --- a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs +++ b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTestData.cs @@ -447,7 +447,61 @@ public class JsonSchemaGeneratorTestData : IEnumerable }, } }; + + // class parameter type with no attributes + yield return new object[] + { + Tool.CreateFromFunction(TestToolName,TestToolDescription,(Person person) => person), + new JsonObject() + { + ["type"] = "object", + ["definitions"] = new JsonObject() + { + [$"{typeof(Person).FullName}"] = new JsonObject() + { + ["type"] = "object", + ["properties"] = new JsonObject() + { + ["String"] = new JsonObject() + { + ["type"] = "string", + ["description"] = string.Empty + }, + ["Int32"] = new JsonObject() + { + ["type"] = "integer", + ["description"] = string.Empty + } + }, + ["required"] = new JsonArray() + { + "String", + "Int32" + } + } + }, + ["properties"] = new JsonObject() + { + ["person"] = new JsonObject() + { + ["$ref"] = $"#/definitions/{typeof(Person).FullName}", + ["description"] = string.Empty + } + }, + ["required"] = new JsonArray() + { + "person", + }, + } + }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + + +class Person +{ + public string Name { get; } = string.Empty; + public int Age { get; } } \ No newline at end of file