feat: add function property attribute

This commit is contained in:
Stevan Freeborn
2024-07-01 22:45:54 -05:00
parent ff649cb3d1
commit f848d4b792
3 changed files with 166 additions and 10 deletions
@@ -0,0 +1,49 @@
namespace AnthropicClient.Models;
/// <summary>
/// Attribute to describe a property of a type that is used as a function parameter.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class FunctionPropertyAttribute : Attribute
{
/// <summary>
/// Description of the property.
/// </summary>
public string Description { get; } = string.Empty;
/// <summary>
/// Whether the property is required.
/// </summary>
public bool Required { get; } = false;
/// <summary>
/// Default value of the property.
/// </summary>
public object? DefaultValue { get; } = null;
/// <summary>
/// Possible values of the property.
/// </summary>
public object[]? PossibleValues { get; } = null;
/// <summary>
/// Initializes a new instance of the <see cref="FunctionPropertyAttribute"/> class.
/// </summary>
/// <param name="description">Description of the property.</param>
/// <param name="required">Whether the property is required.</param>
/// <param name="defaultValue">Default value of the property.</param>
/// <param name="possibleValues">Possible values of the property.</param>
/// <returns>A new instance of the <see cref="FunctionPropertyAttribute"/> class.</returns>
public FunctionPropertyAttribute(
string description,
bool required = false,
object? defaultValue = null,
object[]? possibleValues = null
)
{
Description = description;
Required = required;
DefaultValue = defaultValue;
PossibleValues = possibleValues;
}
}
@@ -1,7 +1,9 @@
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using AnthropicClient.Models;
using AnthropicClient.Json;
namespace AnthropicClient.Utils;
@@ -179,15 +181,11 @@ static class JsonSchemaGenerator
_ => 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 attribute = member.GetCustomAttribute<FunctionPropertyAttribute>();
var memberPropertyName = member.Name;
var memberDescription = string.Empty;
var memberRequired = Nullable.GetUnderlyingType(memberType) is null;
var memberDescription = attribute?.Description ?? string.Empty;
var memberRequired = attribute?.Required ?? Nullable.GetUnderlyingType(memberType) is null;
memberProperty = definitions.AsObject().ContainsKey(memberType.FullName)
? new JsonObject()
@@ -201,6 +199,38 @@ static class JsonSchemaGenerator
memberRequiredProperties.Add(memberPropertyName);
}
JsonNode? defaultValue = null;
if (attribute?.DefaultValue is not null)
{
defaultValue = JsonNode.Parse(JsonSerializer.Serialize(attribute.DefaultValue, JsonSerializationOptions.DefaultOptions));
memberProperty["default"] = defaultValue;
}
if (attribute?.PossibleValues is { Length: > 0 })
{
var enumValues = new JsonArray();
foreach (var value in attribute.PossibleValues)
{
var enumValue = JsonNode.Parse(JsonSerializer.Serialize(value, JsonSerializationOptions.DefaultOptions));
if (defaultValue is null || JsonNode.DeepEquals(enumValue, defaultValue) is false)
{
enumValues.Add(enumValue);
}
}
var containsDefaultValue = enumValues.Where(value => JsonNode.DeepEquals(value, defaultValue)).Any();
if (defaultValue is not null && containsDefaultValue is false)
{
enumValues.Add(JsonNode.Parse(defaultValue.ToJsonString(JsonSerializationOptions.DefaultOptions)));
}
memberProperty[EnumKey] = enumValues;
}
memberProperty[DescriptionKey] = memberDescription;
memberProperties[memberPropertyName] = memberProperty;
}
@@ -585,7 +585,7 @@ public class JsonSchemaGeneratorTestData : IEnumerable<object[]>
}
};
var family =new Family();
var family = new Family();
// parameters from instance method
yield return new object[]
@@ -868,6 +868,64 @@ public class JsonSchemaGeneratorTestData : IEnumerable<object[]>
},
}
};
// complex type with attributes
yield return new object[]
{
Tool.CreateFromFunction(TestToolName,TestToolDescription,(Rule rule) => rule),
new JsonObject()
{
["type"] = "object",
["definitions"] = new JsonObject()
{
[$"{typeof(Rule).FullName}"] = new JsonObject()
{
["type"] = "object",
["properties"] = new JsonObject()
{
["Status"] = new JsonObject()
{
["type"] = "string",
["description"] = "Indicates the current status of the rule.",
["default"] = "Active",
["enum"] = new JsonArray()
{
"Inactive",
"Active",
}
},
["Type"] = new JsonObject()
{
["type"] = "string",
["description"] = "The type of the rule.",
["default"] = "Type A",
["enum"] = new JsonArray()
{
"Type B",
"Type A",
}
}
},
["required"] = new JsonArray()
{
"Status"
}
}
},
["properties"] = new JsonObject()
{
["rule"] = new JsonObject()
{
["$ref"] = $"#/definitions/{typeof(Rule).FullName}",
["description"] = string.Empty
}
},
["required"] = new JsonArray()
{
"rule",
},
}
};
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@@ -902,3 +960,22 @@ class Dad
{
public string Role = "Father";
}
class Rule
{
[FunctionProperty(
"Indicates the current status of the rule.",
true,
"Active",
new object[] { "Active", "Inactive" }
)]
public string Status { get; } = "Active";
[FunctionProperty(
"The type of the rule.",
false,
"Type A",
new object[] { "Type B" }
)]
public string Type { get; } = "Type A";
}