// 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 }