// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Hosting;
///
/// Provides extension methods for configuring AI agents in a service collection.
///
public static class AgentHostingServiceCollectionExtensions
{
///
/// Adds an AI agent to the service collection using only a name and instructions, resolving the chat client from dependency injection.
///
/// The service collection to configure.
/// The name of the agent.
/// The instructions for the agent.
/// The DI service lifetime for the agent registration. Defaults to .
/// The same instance so that additional calls can be chained.
/// Thrown when or is .
public static IHostedAgentBuilder AddAIAgent(this IServiceCollection services, string name, string? instructions, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
Throw.IfNull(services);
Throw.IfNullOrEmpty(name);
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = sp.GetRequiredService();
var tools = sp.GetKeyedServices(name).ToList();
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
}, lifetime);
}
///
/// Adds an AI agent to the service collection with a provided chat client instance.
///
/// The service collection to configure.
/// The name of the agent.
/// The instructions for the agent.
/// The chat client which the agent will use for inference.
/// The DI service lifetime for the agent registration. Defaults to .
/// The same instance so that additional calls can be chained.
/// Thrown when or is .
public static IHostedAgentBuilder AddAIAgent(this IServiceCollection services, string name, string? instructions, IChatClient chatClient, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
Throw.IfNull(services);
Throw.IfNullOrEmpty(name);
return services.AddAIAgent(name, (sp, key) =>
{
var tools = sp.GetKeyedServices(name).ToList();
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
}, lifetime);
}
///
/// Adds an AI agent to the service collection using a chat client resolved by an optional keyed service.
///
/// The service collection to configure.
/// The name of the agent.
/// The instructions for the agent.
/// The key to use when resolving the chat client from the service provider. If , a non-keyed service will be resolved.
/// The DI service lifetime for the agent registration. Defaults to .
/// The same instance so that additional calls can be chained.
/// Thrown when or is .
public static IHostedAgentBuilder AddAIAgent(this IServiceCollection services, string name, string? instructions, object? chatClientServiceKey, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
Throw.IfNull(services);
Throw.IfNullOrEmpty(name);
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = chatClientServiceKey is null ? sp.GetRequiredService() : sp.GetRequiredKeyedService(chatClientServiceKey);
var tools = sp.GetKeyedServices(name).ToList();
return new ChatClientAgent(chatClient, instructions, key, tools: tools);
}, lifetime);
}
///
/// Adds an AI agent to the service collection using a chat client (optionally keyed) and a description.
///
/// The service collection to configure.
/// The name of the agent.
/// The instructions for the agent.
/// A description of the agent.
/// The key to use when resolving the chat client from the service provider. If , a non-keyed service will be resolved.
/// The DI service lifetime for the agent registration. Defaults to .
/// The same instance so that additional calls can be chained.
/// Thrown when or is .
public static IHostedAgentBuilder AddAIAgent(this IServiceCollection services, string name, string? instructions, string? description, object? chatClientServiceKey, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
Throw.IfNull(services);
Throw.IfNullOrEmpty(name);
return services.AddAIAgent(name, (sp, key) =>
{
var chatClient = chatClientServiceKey is null ? sp.GetRequiredService() : sp.GetRequiredKeyedService(chatClientServiceKey);
var tools = sp.GetKeyedServices(name).ToList();
return new ChatClientAgent(chatClient, instructions: instructions, name: key, description: description, tools: tools);
}, lifetime);
}
///
/// Adds an AI agent to the service collection using a custom factory delegate.
///
/// The service collection to configure.
/// The name of the agent.
/// A factory delegate that creates the AI agent instance. The delegate receives the service provider and agent key as parameters.
/// The DI service lifetime for the agent registration. Defaults to .
/// The same instance so that additional calls can be chained.
/// Thrown when , , or is .
/// Thrown when the agent factory delegate returns or an agent whose does not match .
public static IHostedAgentBuilder AddAIAgent(this IServiceCollection services, string name, Func createAgentDelegate, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
Throw.IfNull(services);
Throw.IfNull(name);
Throw.IfNull(createAgentDelegate);
services.AddKeyedService(name, (sp, key) =>
{
Throw.IfNull(key);
var keyString = key as string;
Throw.IfNullOrEmpty(keyString);
var agent = createAgentDelegate(sp, keyString) ?? throw new InvalidOperationException($"The agent factory did not return a valid {nameof(AIAgent)} instance for key '{keyString}'.");
if (!string.Equals(agent.Name, keyString, StringComparison.Ordinal))
{
throw new InvalidOperationException($"The agent factory returned an agent with name '{agent.Name}', but the expected name is '{keyString}'.");
}
return agent;
}, lifetime);
return new HostedAgentBuilder(name, services, lifetime);
}
///
/// Registers a keyed service with the specified lifetime.
///
internal static void AddKeyedService(this IServiceCollection services, object? serviceKey, Func factory, ServiceLifetime lifetime)
where T : class
{
var descriptor = new ServiceDescriptor(typeof(T), serviceKey, (sp, key) => factory(sp, key), lifetime);
services.Add(descriptor);
}
}