feat: create tool from class that implements ITool

This commit is contained in:
Stevan Freeborn
2024-07-03 13:57:51 -05:00
parent 917719d36e
commit e3ba6a6287
2 changed files with 192 additions and 0 deletions
+52
View File
@@ -7,6 +7,27 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models;
/// <summary>
/// Interface that a class can implement to be used to create a tool.
/// </summary>
public interface ITool
{
/// <summary>
/// Gets the name of the tool. Should not be null or empty.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the description of the tool. Should not be null or empty.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets the input schema of the tool. Should not be null.
/// </summary>
public MethodInfo Function { get; }
}
/// <summary>
/// Represents a tool that can be used in the chat.
/// </summary>
@@ -40,6 +61,18 @@ public class Tool
[JsonIgnore]
public string DisplayName { get; }
[JsonConstructor]
internal Tool()
{
var func = () => {};
Name = string.Empty;
Description = string.Empty;
InputSchema = [];
Function = new AnthropicFunction(func.Method);
DisplayName = string.Empty;
}
internal Tool(string name, string description, AnthropicFunction function)
{
ArgumentValidator.ThrowIfNullOrWhitespace(name, nameof(name));
@@ -58,6 +91,25 @@ public class Tool
InputSchema = JsonSchemaGenerator.GenerateInputSchema(function);
}
/// <summary>
/// Creates a tool from a type that implements <see cref="ITool"/>.
/// </summary>
/// <typeparam name="T">The type that implements <see cref="ITool"/>.</typeparam>
/// <exception cref="ArgumentException">Thrown when the name or description of the tool is null or empty.</exception>
/// <exception cref="ArgumentNullException">Thrown when the function of the tool is null.</exception>
/// <returns>The created tool as instance of <see cref="Tool"/>.</returns>
/// <remarks>The implementation of <see cref="ITool"/> must have a parameterless constructor.</remarks>
public static Tool CreateFromClass<T>() where T : ITool, new()
{
var tool = new T();
ArgumentValidator.ThrowIfNullOrWhitespace(tool.Name, nameof(tool.Name));
ArgumentValidator.ThrowIfNullOrWhitespace(tool.Description, nameof(tool.Description));
ArgumentValidator.ThrowIfNull(tool.Function, nameof(tool.Function));
return new Tool(tool.Name, tool.Description, new AnthropicFunction(tool.Function));
}
/// <summary>
/// Creates a tool from a static method.
/// </summary>
@@ -246,6 +246,67 @@ public class ToolTests : SerializationTest
t => t.IgnoringCyclicReferences()
);
}
[Fact]
public void CreateFromClass_WhenCalledWithToolWhoseNameIsNull_ItShouldThrowException()
{
var action = () => Tool.CreateFromClass<ToolWithNullName>();
action.Should().Throw<ArgumentException>();
}
[Fact]
public void CreateFromClass_WhenCalledWithToolWhoseNameIsEmpty_ItShouldThrowException()
{
var action = () => Tool.CreateFromClass<ToolWithEmptyName>();
action.Should().Throw<ArgumentException>();
}
[Fact]
public void CreateFromClass_WhenCalledWithToolWhoseDescriptionIsNull_ItShouldThrowException()
{
var action = () => Tool.CreateFromClass<ToolWithNullDescription>();
action.Should().Throw<ArgumentException>();
}
[Fact]
public void CreateFromClass_WhenCalledWithToolWhoseDescriptionIsEmpty_ItShouldThrowException()
{
var action = () => Tool.CreateFromClass<ToolWithEmptyDescription>();
action.Should().Throw<ArgumentException>();
}
[Fact]
public void CreateFromClass_WhenCalledWithToolWhoseFunctionIsNull_ItShouldThrowException()
{
var action = () => Tool.CreateFromClass<ToolWithNullFunction>();
action.Should().Throw<ArgumentNullException>();
}
[Fact]
public void CreateFromClass_WhenCalledWithProperTool_ItShouldReturnTool()
{
var tool = Tool.CreateFromClass<ProperTool>();
var expectedSchema = new JsonObject()
{
["type"] = "object",
};
tool.Name.Should().Be("Name");
tool.DisplayName.Should().Be("Name");
tool.Description.Should().Be("Description");
tool.Function.Method.Name.Should().Be(nameof(ProperTool.GetWeather));
tool.Function.Instance.Should().BeNull();
tool.InputSchema.Should().BeEquivalentTo(
expectedSchema,
t => t.IgnoringCyclicReferences()
);
}
}
class TestClass
@@ -255,3 +316,82 @@ class TestClass
public static bool TestStaticMethod() => true;
public bool TestInstanceMethod() => _result;
}
class ProperTool : ITool
{
public string Name => "Name";
public string Description { get; } = "Description";
public MethodInfo Function => typeof(ProperTool).GetMethod(nameof(GetWeather))!;
public static string GetWeather()
{
return "Sunny";
}
}
class ToolWithNullName : ITool
{
public string Name => null!;
public string Description { get; } = "Description";
public MethodInfo Function => typeof(ToolWithNullName).GetMethod(nameof(Tool))!;
public static string GetWeather()
{
return "Sunny";
}
}
class ToolWithEmptyName : ITool
{
public string Name => string.Empty;
public string Description { get; } = "Description";
public MethodInfo Function => typeof(ToolWithEmptyName).GetMethod(nameof(Tool))!;
public static string GetWeather()
{
return "Sunny";
}
}
class ToolWithNullDescription : ITool
{
public string Name => "Name";
public string Description => null!;
public MethodInfo Function => typeof(ToolWithNullDescription).GetMethod(nameof(Tool))!;
public static string GetWeather()
{
return "Sunny";
}
}
class ToolWithEmptyDescription : ITool
{
public string Name => "Name";
public string Description => string.Empty;
public MethodInfo Function => typeof(ToolWithEmptyDescription).GetMethod(nameof(Tool))!;
public static string GetWeather()
{
return "Sunny";
}
}
class ToolWithNullFunction : ITool
{
public string Name => "Name";
public string Description => "Description";
public MethodInfo Function => null!;
}