feat: add function property attribute
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user