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();
}