From e3ba6a62874079519751c1a4b6f7d9d2397165a2 Mon Sep 17 00:00:00 2001
From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com>
Date: Wed, 3 Jul 2024 13:57:51 -0500
Subject: [PATCH] feat: create tool from class that implements ITool
---
src/AnthropicClient/Models/Tool.cs | 52 +++++++
.../Unit/Models/ToolTests.cs | 140 ++++++++++++++++++
2 files changed, 192 insertions(+)
diff --git a/src/AnthropicClient/Models/Tool.cs b/src/AnthropicClient/Models/Tool.cs
index a459c51..5476436 100644
--- a/src/AnthropicClient/Models/Tool.cs
+++ b/src/AnthropicClient/Models/Tool.cs
@@ -7,6 +7,27 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models;
+///
+/// Interface that a class can implement to be used to create a tool.
+///
+public interface ITool
+{
+ ///
+ /// Gets the name of the tool. Should not be null or empty.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the description of the tool. Should not be null or empty.
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets the input schema of the tool. Should not be null.
+ ///
+ public MethodInfo Function { get; }
+}
+
///
/// Represents a tool that can be used in the chat.
///
@@ -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);
}
+ ///
+ /// Creates a tool from a type that implements .
+ ///
+ /// The type that implements .
+ /// Thrown when the name or description of the tool is null or empty.
+ /// Thrown when the function of the tool is null.
+ /// The created tool as instance of .
+ /// The implementation of must have a parameterless constructor.
+ public static Tool CreateFromClass() 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));
+ }
+
///
/// Creates a tool from a static method.
///
diff --git a/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs
index b84a11e..9e4b48d 100644
--- a/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs
+++ b/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs
@@ -246,6 +246,67 @@ public class ToolTests : SerializationTest
t => t.IgnoringCyclicReferences()
);
}
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithToolWhoseNameIsNull_ItShouldThrowException()
+ {
+ var action = () => Tool.CreateFromClass();
+
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithToolWhoseNameIsEmpty_ItShouldThrowException()
+ {
+ var action = () => Tool.CreateFromClass();
+
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithToolWhoseDescriptionIsNull_ItShouldThrowException()
+ {
+ var action = () => Tool.CreateFromClass();
+
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithToolWhoseDescriptionIsEmpty_ItShouldThrowException()
+ {
+ var action = () => Tool.CreateFromClass();
+
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithToolWhoseFunctionIsNull_ItShouldThrowException()
+ {
+ var action = () => Tool.CreateFromClass();
+
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void CreateFromClass_WhenCalledWithProperTool_ItShouldReturnTool()
+ {
+ var tool = Tool.CreateFromClass();
+
+ 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
@@ -254,4 +315,83 @@ 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!;
}
\ No newline at end of file