// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
///
/// Provides extensions for .
///
public static partial class AIAgentExtensions
{
///
/// Creates a new using the specified agent as the foundation for the builder pipeline.
///
/// The instance to use as the inner agent.
/// A new instance configured with the specified inner agent.
/// is .
///
/// This method provides a convenient way to convert an existing instance into
/// a builder pattern, enabling easily wrapping the agent in layers of additional functionality.
/// It is functionally equivalent to using the constructor directly,
/// but provides a more fluent API when working with existing agent instances.
///
public static AIAgentBuilder AsBuilder(this AIAgent innerAgent)
{
_ = Throw.IfNull(innerAgent);
return new AIAgentBuilder(innerAgent);
}
///
/// Creates an that runs the provided .
///
/// The to be represented as an invocable function.
///
/// Optional metadata to customize the function representation, such as name and description.
/// If not provided, defaults will be inferred from the agent's properties.
///
///
/// Optional to use for function invocations. If not provided, a new session
/// will be created for each function call, which may not preserve conversation context.
///
///
/// An that can be used as a tool by other agents or AI models to invoke this agent.
///
/// is .
///
///
/// This extension method enables agents to participate in function calling scenarios, where they can be
/// invoked as tools by other agents or AI models. The resulting function accepts a query string as input and
/// returns the agent's response as a string, making it compatible with standard function calling interfaces
/// used by AI models.
///
///
/// The resulting is stateful, referencing both the and the optional
/// . Especially if a specific session is provided, avoid using the resulting function concurrently
/// in multiple conversations or in requests where the parallel function calls may result in concurrent usage of the session,
/// as that could lead to undefined and unpredictable behavior.
///
///
public static AIFunction AsAIFunction(this AIAgent agent, AIFunctionFactoryOptions? options = null, AgentSession? session = null)
{
Throw.IfNull(agent);
[Description("Invoke an agent to retrieve some information.")]
async Task InvokeAgentAsync(
[Description("Input query to invoke the agent.")] string query,
CancellationToken cancellationToken)
{
// Propagate any additional properties from the parent agent's run to the child agent if the parent is using a FunctionInvokingChatClient.
AgentRunOptions? agentRunOptions = FunctionInvokingChatClient.CurrentContext?.Options?.AdditionalProperties is AdditionalPropertiesDictionary dict
? new AgentRunOptions { AdditionalProperties = dict }
: null;
var response = await agent.RunAsync(query, session: session, options: agentRunOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
return response.Text;
}
options ??= new();
options.Name ??= SanitizeAgentName(agent.Name);
options.Description ??= agent.Description;
return AIFunctionFactory.Create(InvokeAgentAsync, options);
}
///
/// Removes characters from AI agent name that shouldn't be used in an AI function name.
///
/// The AI agent name to sanitize.
///
/// The sanitized agent name with invalid characters replaced by underscores, or null if the input is null.
///
private static string? SanitizeAgentName(string? agentName)
{
return agentName is null
? agentName
: InvalidNameCharsRegex().Replace(agentName, "_");
}
/// Regex that flags any character other than ASCII digits or letters.
#if NET
[GeneratedRegex("[^0-9A-Za-z]+")]
private static partial Regex InvalidNameCharsRegex();
#else
private static Regex InvalidNameCharsRegex() => s_invalidNameCharsRegex;
private static readonly Regex s_invalidNameCharsRegex = new("[^0-9A-Za-z]+", RegexOptions.Compiled);
#endif
}