.NET: Add WithAITool extensions for Hosting AIAgents (#1990)

* add extensions to register tools via fluentAPI

* fix
This commit is contained in:
Korolev Dmitry
2025-11-07 16:28:28 +00:00
committed by GitHub
parent b7cddaaba8
commit 32b984b09b
8 changed files with 272 additions and 8 deletions
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Hosting.Local;
using Microsoft.Extensions.AI;
@@ -29,7 +30,8 @@ public static class AgentHostingServiceCollectionExtensions
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = sp.GetRequiredService<IChatClient>();
return new ChatClientAgent(chatClient, instructions, key);
var tools = GetRegisteredToolsForAgent(sp, name);
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
});
}
@@ -46,7 +48,11 @@ public static class AgentHostingServiceCollectionExtensions
{
Throw.IfNull(services);
Throw.IfNullOrEmpty(name);
return services.AddAIAgent(name, (sp, key) => new ChatClientAgent(chatClient, instructions, key));
return services.AddAIAgent(name, (sp, key) =>
{
var tools = GetRegisteredToolsForAgent(sp, name);
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
});
}
/// <summary>
@@ -65,7 +71,8 @@ public static class AgentHostingServiceCollectionExtensions
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = chatClientServiceKey is null ? sp.GetRequiredService<IChatClient>() : sp.GetRequiredKeyedService<IChatClient>(chatClientServiceKey);
return new ChatClientAgent(chatClient, instructions, key);
var tools = GetRegisteredToolsForAgent(sp, name);
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
});
}
@@ -86,7 +93,8 @@ public static class AgentHostingServiceCollectionExtensions
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = chatClientServiceKey is null ? sp.GetRequiredService<IChatClient>() : sp.GetRequiredKeyedService<IChatClient>(chatClientServiceKey);
return new ChatClientAgent(chatClient, instructions: instructions, name: key, description: description);
var tools = GetRegisteredToolsForAgent(sp, name);
return new ChatClientAgent(chatClient, instructions: instructions, name: key, description: description, tools: tools);
});
}
@@ -142,4 +150,10 @@ public static class AgentHostingServiceCollectionExtensions
services.Add(ServiceDescriptor.Singleton(agentHostBuilderContext));
services.AddSingleton<AgentCatalog, LocalAgentCatalog>();
}
private static IList<AITool> GetRegisteredToolsForAgent(IServiceProvider serviceProvider, string agentName)
{
var registry = serviceProvider.GetService<LocalAgentToolRegistry>();
return registry?.GetTools(agentName) ?? [];
}
}
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Microsoft.Agents.AI.Hosting.Local;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Shared.Diagnostics;
@@ -59,4 +62,52 @@ public static class HostedAgentBuilderExtensions
});
return builder;
}
/// <summary>
/// Adds an AI tool to an agent being configured with the service collection.
/// </summary>
/// <param name="builder">The hosted agent builder.</param>
/// <param name="tool">The AI tool to add to the agent.</param>
/// <returns>The same <see cref="IHostedAgentBuilder"/> instance so that additional calls can be chained.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="tool"/> is <see langword="null"/>.</exception>
public static IHostedAgentBuilder WithAITool(this IHostedAgentBuilder builder, AITool tool)
{
Throw.IfNull(builder);
Throw.IfNull(tool);
var agentName = builder.Name;
var services = builder.ServiceCollection;
// Get or create the agent tool registry
var descriptor = services.FirstOrDefault(sd => !sd.IsKeyedService && sd.ServiceType.Equals(typeof(LocalAgentToolRegistry)));
if (descriptor?.ImplementationInstance is not LocalAgentToolRegistry toolRegistry)
{
toolRegistry = new();
services.Add(ServiceDescriptor.Singleton(toolRegistry));
}
toolRegistry.AddTool(agentName, tool);
return builder;
}
/// <summary>
/// Adds multiple AI tools to an agent being configured with the service collection.
/// </summary>
/// <param name="builder">The hosted agent builder.</param>
/// <param name="tools">The collection of AI tools to add to the agent.</param>
/// <returns>The same <see cref="IHostedAgentBuilder"/> instance so that additional calls can be chained.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="tools"/> is <see langword="null"/>.</exception>
public static IHostedAgentBuilder WithAITools(this IHostedAgentBuilder builder, params AITool[] tools)
{
Throw.IfNull(builder);
Throw.IfNull(tools);
foreach (var tool in tools)
{
builder.WithAITool(tool);
}
return builder;
}
}
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Hosting.Local;
internal sealed class LocalAgentToolRegistry
{
private readonly Dictionary<string, List<AITool>> _toolsByAgentName = new();
public void AddTool(string agentName, AITool tool)
{
if (!this._toolsByAgentName.TryGetValue(agentName, out var tools))
{
tools = [];
this._toolsByAgentName[agentName] = tools;
}
tools.Add(tool);
}
public IList<AITool> GetTools(string agentName)
{
return this._toolsByAgentName.TryGetValue(agentName, out var tools) ? tools : [];
}
}
@@ -31,8 +31,10 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.Hosting.UnitTests"/>
</ItemGroup>
</Project>