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.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
using AnthropicClient.Models;
|
using AnthropicClient.Models;
|
||||||
|
using AnthropicClient.Json;
|
||||||
|
|
||||||
namespace AnthropicClient.Utils;
|
namespace AnthropicClient.Utils;
|
||||||
|
|
||||||
@@ -179,15 +181,11 @@ static class JsonSchemaGenerator
|
|||||||
_ => throw new InvalidOperationException("Member is not a property or field")
|
_ => throw new InvalidOperationException("Member is not a property or field")
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Provide attribute to allow specifying...
|
var attribute = member.GetCustomAttribute<FunctionPropertyAttribute>();
|
||||||
// 1. Name
|
|
||||||
// 2. Description
|
|
||||||
// 3. Required
|
|
||||||
// 4. Default values
|
|
||||||
// 5. Possible values
|
|
||||||
var memberPropertyName = member.Name;
|
var memberPropertyName = member.Name;
|
||||||
var memberDescription = string.Empty;
|
var memberDescription = attribute?.Description ?? string.Empty;
|
||||||
var memberRequired = Nullable.GetUnderlyingType(memberType) is null;
|
var memberRequired = attribute?.Required ?? Nullable.GetUnderlyingType(memberType) is null;
|
||||||
|
|
||||||
memberProperty = definitions.AsObject().ContainsKey(memberType.FullName)
|
memberProperty = definitions.AsObject().ContainsKey(memberType.FullName)
|
||||||
? new JsonObject()
|
? new JsonObject()
|
||||||
@@ -201,6 +199,38 @@ static class JsonSchemaGenerator
|
|||||||
memberRequiredProperties.Add(memberPropertyName);
|
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;
|
memberProperty[DescriptionKey] = memberDescription;
|
||||||
memberProperties[memberPropertyName] = memberProperty;
|
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();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
@@ -902,3 +960,22 @@ class Dad
|
|||||||
{
|
{
|
||||||
public string Role = "Father";
|
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