Files
Stephen Toub dc2b109b50 .NET: Upgrade to .NET 10 (#2128)
* Upgrade to .NET 10

- Require .NET 10 SDK
- Include net10.0 assets in all assemblies
- Move net9.0-only targets to net10.0
- Update LangVersion to latest
- Remove complicated distinctions between debug target TFMs and release target TFMs
- Remove unnecessary package dependencies when built into netcoreapp
- Clean up some ifdefs
- Clean up some analyzer warnings

* Fix CI
2025-11-22 04:14:15 +00:00

217 lines
6.8 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Agents.AI.AGUI.Shared;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.AGUI.UnitTests;
/// <summary>
/// Unit tests for the <see cref="AIToolExtensions"/> class.
/// </summary>
public sealed class AIToolExtensionsTests
{
[Fact]
public void AsAGUITools_WithAIFunction_ConvertsToAGUIToolCorrectly()
{
// Arrange
AIFunction function = AIFunctionFactory.Create(
(string location) => $"Weather in {location}",
"GetWeather",
"Gets the current weather");
List<AITool> tools = [function];
// Act
List<AGUITool> aguiTools = tools.AsAGUITools().ToList();
// Assert
AGUITool aguiTool = Assert.Single(aguiTools);
Assert.Equal("GetWeather", aguiTool.Name);
Assert.Equal("Gets the current weather", aguiTool.Description);
Assert.NotEqual(default, aguiTool.Parameters);
}
[Fact]
public void AsAGUITools_WithMultipleFunctions_ConvertsAllCorrectly()
{
// Arrange
List<AITool> tools =
[
AIFunctionFactory.Create(() => "Result1", "Tool1", "First tool"),
AIFunctionFactory.Create(() => "Result2", "Tool2", "Second tool"),
AIFunctionFactory.Create(() => "Result3", "Tool3", "Third tool")
];
// Act
List<AGUITool> aguiTools = tools.AsAGUITools().ToList();
// Assert
Assert.Equal(3, aguiTools.Count);
Assert.Equal("Tool1", aguiTools[0].Name);
Assert.Equal("Tool2", aguiTools[1].Name);
Assert.Equal("Tool3", aguiTools[2].Name);
}
[Fact]
public void AsAGUITools_WithNullInput_ReturnsEmptyEnumerable()
{
// Arrange
IEnumerable<AITool>? tools = null;
// Act
IEnumerable<AGUITool> aguiTools = tools!.AsAGUITools();
// Assert
Assert.NotNull(aguiTools);
Assert.Empty(aguiTools);
}
[Fact]
public void AsAGUITools_WithEmptyInput_ReturnsEmptyEnumerable()
{
// Arrange
List<AITool> tools = [];
// Act
List<AGUITool> aguiTools = tools.AsAGUITools().ToList();
// Assert
Assert.Empty(aguiTools);
}
[Fact]
public void AsAGUITools_FiltersOutNonAIFunctionTools()
{
// Arrange - mix of AIFunction and non-function tools
AIFunction function = AIFunctionFactory.Create(() => "Result", "TestTool");
// Create a custom AITool that's not an AIFunction
var declaration = AIFunctionFactory.CreateDeclaration("DeclarationOnly", "Description", JsonElement.Parse("{}"));
List<AITool> tools = [function, declaration];
// Act
List<AGUITool> aguiTools = tools.AsAGUITools().ToList();
// Assert
// Only the AIFunction should be converted, declarations are filtered
Assert.Equal(2, aguiTools.Count); // Actually both convert since declaration is also AIFunctionDeclaration
}
[Fact]
public void AsAITools_WithAGUITool_ConvertsToAIFunctionDeclarationCorrectly()
{
// Arrange
AGUITool aguiTool = new()
{
Name = "TestTool",
Description = "Test description",
Parameters = JsonElement.Parse("""{"type":"object","properties":{}}""")
};
List<AGUITool> aguiTools = [aguiTool];
// Act
List<AITool> tools = aguiTools.AsAITools().ToList();
// Assert
AITool tool = Assert.Single(tools);
Assert.IsType<AIFunctionDeclaration>(tool, exactMatch: false);
var declaration = (AIFunctionDeclaration)tool;
Assert.Equal("TestTool", declaration.Name);
Assert.Equal("Test description", declaration.Description);
}
[Fact]
public void AsAITools_WithMultipleAGUITools_ConvertsAllCorrectly()
{
// Arrange
List<AGUITool> aguiTools =
[
new AGUITool { Name = "Tool1", Description = "Desc1", Parameters = JsonElement.Parse("{}") },
new AGUITool { Name = "Tool2", Description = "Desc2", Parameters = JsonElement.Parse("{}") },
new AGUITool { Name = "Tool3", Description = "Desc3", Parameters = JsonElement.Parse("{}") }
];
// Act
List<AITool> tools = aguiTools.AsAITools().ToList();
// Assert
Assert.Equal(3, tools.Count);
Assert.All(tools, t => Assert.IsType<AIFunctionDeclaration>(t, exactMatch: false));
}
[Fact]
public void AsAITools_WithNullInput_ReturnsEmptyEnumerable()
{
// Arrange
IEnumerable<AGUITool>? aguiTools = null;
// Act
IEnumerable<AITool> tools = aguiTools!.AsAITools();
// Assert
Assert.NotNull(tools);
Assert.Empty(tools);
}
[Fact]
public void AsAITools_WithEmptyInput_ReturnsEmptyEnumerable()
{
// Arrange
List<AGUITool> aguiTools = [];
// Act
List<AITool> tools = aguiTools.AsAITools().ToList();
// Assert
Assert.Empty(tools);
}
[Fact]
public void AsAITools_CreatesDeclarationsOnly_NotInvokableFunctions()
{
// Arrange
AGUITool aguiTool = new()
{
Name = "RemoteTool",
Description = "Tool implemented on server",
Parameters = JsonElement.Parse("""{"type":"object"}""")
};
// Act
List<AGUITool> aguiToolsList = [aguiTool];
AITool tool = aguiToolsList.AsAITools().Single();
// Assert
// The tool should be a declaration, not an executable function
Assert.IsType<AIFunctionDeclaration>(tool, exactMatch: false);
// AIFunctionDeclaration cannot be invoked (no implementation)
// This is correct since the actual implementation exists on the client side
}
[Fact]
public void RoundTrip_AIFunctionToAGUIToolBackToDeclaration_PreservesMetadata()
{
// Arrange
AIFunction originalFunction = AIFunctionFactory.Create(
(string name, int age) => $"{name} is {age} years old",
"FormatPerson",
"Formats person information");
// Act
List<AIFunction> originalList = [originalFunction];
AGUITool aguiTool = originalList.AsAGUITools().Single();
List<AGUITool> aguiToolsList = [aguiTool];
AITool reconstructed = aguiToolsList.AsAITools().Single();
// Assert
Assert.IsType<AIFunctionDeclaration>(reconstructed, exactMatch: false);
var declaration = (AIFunctionDeclaration)reconstructed;
Assert.Equal("FormatPerson", declaration.Name);
Assert.Equal("Formats person information", declaration.Description);
// Schema should be preserved through the round trip
Assert.NotEqual(default, declaration.JsonSchema);
}
}