Skip to content

Commit ea282ad

Browse files
.Net: Allow hyphens in function name (#12389)
### Motivation, Context and Description This PR allows hyphens in function names for seamless integration with MCP tools and M.E.AI functions that support hyphens in their names. Out of the scope of the PR is support for functions with hyphens in SK prompt templates, which will need to be extended to support hyphens when/if needed.
1 parent d7a7699 commit ea282ad

File tree

3 files changed

+33
-20
lines changed

3 files changed

+33
-20
lines changed

dotnet/src/InternalUtilities/src/Diagnostics/KernelVerify.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@ internal static partial class KernelVerify
1313
{
1414
#if NET
1515
[GeneratedRegex("^[0-9A-Za-z_]*$")]
16-
private static partial Regex AsciiLettersDigitsUnderscoresRegex();
16+
private static partial Regex AllowedPluginNameSymbolsRegex();
17+
18+
[GeneratedRegex("^[0-9A-Za-z_-]*$")]
19+
private static partial Regex AllowedFunctionNameSymbolsRegex();
1720
#else
18-
private static Regex AsciiLettersDigitsUnderscoresRegex() => s_asciiLettersDigitsUnderscoresRegex;
19-
private static readonly Regex s_asciiLettersDigitsUnderscoresRegex = new("^[0-9A-Za-z_]*$", RegexOptions.Compiled);
21+
private static Regex AllowedPluginNameSymbolsRegex() => s_allowedPluginNameSymbolsRegex;
22+
private static readonly Regex s_allowedPluginNameSymbolsRegex = new("^[0-9A-Za-z_]*$", RegexOptions.Compiled);
23+
24+
private static Regex AllowedFunctionNameSymbolsRegex() => s_allowedFunctionNameSymbolsRegex;
25+
private static readonly Regex s_allowedFunctionNameSymbolsRegex = new("^[0-9A-Za-z_-]*$", RegexOptions.Compiled);
2026
#endif
2127

2228
internal static void ValidPluginName([NotNull] string? pluginName, IReadOnlyKernelPluginCollection? plugins = null, [CallerArgumentExpression(nameof(pluginName))] string? paramName = null)
2329
{
2430
Verify.NotNullOrWhiteSpace(pluginName);
25-
if (!AsciiLettersDigitsUnderscoresRegex().IsMatch(pluginName))
31+
if (!AllowedPluginNameSymbolsRegex().IsMatch(pluginName))
2632
{
2733
Verify.ThrowArgumentInvalidName("plugin name", pluginName, paramName);
2834
}
@@ -36,7 +42,7 @@ internal static void ValidPluginName([NotNull] string? pluginName, IReadOnlyKern
3642
internal static void ValidFunctionName([NotNull] string? functionName, [CallerArgumentExpression(nameof(functionName))] string? paramName = null)
3743
{
3844
Verify.NotNullOrWhiteSpace(functionName);
39-
if (!AsciiLettersDigitsUnderscoresRegex().IsMatch(functionName))
45+
if (!AllowedFunctionNameSymbolsRegex().IsMatch(functionName))
4046
{
4147
Verify.ThrowArgumentInvalidName("function name", functionName, paramName);
4248
}

dotnet/src/InternalUtilities/src/Diagnostics/Verify.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,9 @@ namespace Microsoft.SemanticKernel;
1515
internal static partial class Verify
1616
{
1717
#if NET
18-
[GeneratedRegex("^[0-9A-Za-z_]*$")]
19-
private static partial Regex AsciiLettersDigitsUnderscoresRegex();
20-
2118
[GeneratedRegex("^[^.]+\\.[^.]+$")]
2219
private static partial Regex FilenameRegex();
2320
#else
24-
private static Regex AsciiLettersDigitsUnderscoresRegex() => s_asciiLettersDigitsUnderscoresRegex;
25-
private static readonly Regex s_asciiLettersDigitsUnderscoresRegex = new("^[0-9A-Za-z_]*$", RegexOptions.Compiled);
26-
2721
private static Regex FilenameRegex() => s_filenameRegex;
2822
private static readonly Regex s_filenameRegex = new("^[^.]+\\.[^.]+$", RegexOptions.Compiled);
2923
#endif
@@ -75,15 +69,6 @@ public static void True(bool condition, string message, [CallerArgumentExpressio
7569
}
7670
}
7771

78-
internal static void ValidFunctionName([NotNull] string? functionName, [CallerArgumentExpression(nameof(functionName))] string? paramName = null)
79-
{
80-
NotNullOrWhiteSpace(functionName);
81-
if (!AsciiLettersDigitsUnderscoresRegex().IsMatch(functionName))
82-
{
83-
ThrowArgumentInvalidName("function name", functionName, paramName);
84-
}
85-
}
86-
8772
internal static void ValidFilename([NotNull] string? filename, [CallerArgumentExpression(nameof(filename))] string? paramName = null)
8873
{
8974
NotNullOrWhiteSpace(filename);

dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests1.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,6 +1556,28 @@ static TestReturnType StaticMethod(TestParameterType p1)
15561556
Assert.Equal("""{"type":"object","properties":{"Result":{"type":"integer"}}}""", metadata.ReturnParameter.Schema!.ToString());
15571557
}
15581558

1559+
[InlineData("f_1", true)]
1560+
[InlineData("f-1", true)]
1561+
[InlineData("f+1", false)]
1562+
[InlineData("f?1", false)]
1563+
[Theory]
1564+
public void ItShouldValidateFunctionName(string name, bool allowed)
1565+
{
1566+
// Arrange & Act & Assert
1567+
if (allowed)
1568+
{
1569+
// Should not throw
1570+
KernelFunctionFactory.CreateFromMethod(() => { }, functionName: name);
1571+
KernelFunctionFactory.CreateFromMethod(() => { }, new KernelFunctionFromMethodOptions() { FunctionName = name });
1572+
}
1573+
else
1574+
{
1575+
// Should throw
1576+
Assert.Throws<ArgumentException>(() => KernelFunctionFactory.CreateFromMethod(() => { }, functionName: name));
1577+
Assert.Throws<ArgumentException>(() => KernelFunctionFactory.CreateFromMethod(() => { }, new KernelFunctionFromMethodOptions() { FunctionName = name }));
1578+
}
1579+
}
1580+
15591581
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
15601582
private sealed class CustomTypeForJsonTests
15611583
#pragma warning restore CA1812 // Avoid uninstantiated internal classes

0 commit comments

Comments
 (0)