diff --git a/readme.md b/readme.md index a6c26df..ae6f3bf 100644 --- a/readme.md +++ b/readme.md @@ -264,6 +264,11 @@ else } ``` +> [!IMPORTANT] +> The `ToolFactory` will also automatically sanitize the tool name +> when using local functions to avoid invalid characters and honor +> its original name. + ## Console Logging Additional `UseJsonConsoleLogging` extension for rich JSON-formatted console logging of AI requests diff --git a/src/AI.Tests/ToolsTests.cs b/src/AI.Tests/ToolsTests.cs index c59ac08..c7fc277 100644 --- a/src/AI.Tests/ToolsTests.cs +++ b/src/AI.Tests/ToolsTests.cs @@ -9,6 +9,16 @@ public class ToolsTests(ITestOutputHelper output) { public record ToolResult(string Name, string Description, string Content); + [Fact] + public void SanitizesToolName() + { + static void DoSomething() { } + + var tool = ToolFactory.Create(DoSomething); + + Assert.Equal("do_something", tool.Name); + } + [SecretsFact("OPENAI_API_KEY")] public async Task RunToolResult() { diff --git a/src/AI/ToolFactory.cs b/src/AI/ToolFactory.cs index 994a411..9ad827f 100644 --- a/src/AI/ToolFactory.cs +++ b/src/AI/ToolFactory.cs @@ -6,14 +6,37 @@ namespace Devlooped.Extensions.AI; /// Creates tools for function calling that can leverage the /// extension methods for locating invocations and their results. /// -public static class ToolFactory +public static partial class ToolFactory { /// /// Invokes - /// using the method name following the naming convention and serialization options from . + /// using the method name following the naming convention and serialization options from + /// so that FindCalls extension methods on can be used. /// - public static AIFunction Create(Delegate method) + public static AIFunction Create(Delegate method, string? name = default) => AIFunctionFactory.Create(method, - ToolJsonOptions.Default.PropertyNamingPolicy!.ConvertName(method.Method.Name), + name ?? ToolJsonOptions.Default.PropertyNamingPolicy!.ConvertName(SanitizeName(method.Method.Name)), serializerOptions: ToolJsonOptions.Default); + + static string SanitizeName(string name) + { + if (!name.Contains('<')) + return name; + + // i.e.: g__SetCandidates|0 > SetCandidates + var match = AnonymousMethodExpr().Match(name); + if (match.Success) + { + return match.Groups[1].Value; + } + + return name + .Replace("<", string.Empty) + .Replace(">", string.Empty) + .Replace("g__", "_") + .Replace("|", string.Empty); + } + + [System.Text.RegularExpressions.GeneratedRegex(@"__(.+?)\|")] + private static partial System.Text.RegularExpressions.Regex AnonymousMethodExpr(); }