diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 7fb6459906..95d57ee7c4 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -17,6 +17,7 @@ + diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 6905fdf985..85196b953e 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -277,6 +277,7 @@ + @@ -306,6 +307,7 @@ + diff --git a/dotnet/nuget.config b/dotnet/nuget.config index 76d943ce16..62c87f185c 100644 --- a/dotnet/nuget.config +++ b/dotnet/nuget.config @@ -3,10 +3,14 @@ + + + + \ No newline at end of file diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj index 9fcbc4c83f..5300c98b14 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj @@ -11,7 +11,9 @@ + + diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/New/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/New/PersistentAgentsClientExtensions.cs new file mode 100644 index 0000000000..800324e66f --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/New/PersistentAgentsClientExtensions.cs @@ -0,0 +1,662 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using OpenAI.Responses; + +#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + +namespace Azure.AI.Agents.Persistent; + +/// +/// Provides extension methods for . +/// +public static class AgentsClientExtensions +{ + /* + /// + /// Gets a runnable agent instance from the provided response containing persistent agent metadata. + /// + /// The client used to interact with persistent agents. Cannot be . + /// The response containing the persistent agent to be converted. Cannot be . + /// The default to use when interacting with the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// A instance that can be used to perform operations on the persistent agent. + public static ChatClientAgent GetAIAgent(this AgentsClient client, Response persistentAgentResponse, ChatOptions? chatOptions = null, Func? clientFactory = null) + { + if (persistentAgentResponse is null) + { + throw new ArgumentNullException(nameof(persistentAgentResponse)); + } + + return GetAIAgent(client, persistentAgentResponse.Value, chatOptions, clientFactory); + } + + /// + /// Gets a runnable agent instance from a containing metadata about a persistent agent. + /// + /// The client used to interact with persistent agents. Cannot be . + /// The persistent agent metadata to be converted. Cannot be . + /// The default to use when interacting with the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// A instance that can be used to perform operations on the persistent agent. + public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatOptions? chatOptions = null, Func? clientFactory = null) + { + if (persistentAgentMetadata is null) + { + throw new ArgumentNullException(nameof(persistentAgentMetadata)); + } + + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + var chatClient = persistentAgentsClient.AsIChatClient(persistentAgentMetadata.Id); + + if (clientFactory is not null) + { + chatClient = clientFactory(chatClient); + } + + return new ChatClientAgent(chatClient, options: new() + { + Id = persistentAgentMetadata.Id, + Name = persistentAgentMetadata.Name, + Description = persistentAgentMetadata.Description, + Instructions = persistentAgentMetadata.Instructions, + ChatOptions = chatOptions + }); + } + + /// + /// Retrieves an existing server side agent, wrapped as a using the provided . + /// + /// The to create the with. + /// A for the persistent agent. + /// The ID of the server side agent to create a for. + /// Options that should apply to all runs of the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the persistent agent. + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + string agentId, + ChatOptions? chatOptions = null, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(agentId)) + { + throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); + } + + var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory); + } + + /// + /// Retrieves an existing server side agent, wrapped as a using the provided . + /// + /// The to create the with. + /// A for the persistent agent. + /// The ID of the server side agent to create a for. + /// Options that should apply to all runs of the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the persistent agent. + public static async Task GetAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string agentId, + ChatOptions? chatOptions = null, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(agentId)) + { + throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); + } + + var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory); + } + + /// + /// Gets a runnable agent instance from the provided response containing persistent agent metadata. + /// + /// The client used to interact with persistent agents. Cannot be . + /// The response containing the persistent agent to be converted. Cannot be . + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// A instance that can be used to perform operations on the persistent agent. + /// Thrown when or is . + public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, Response persistentAgentResponse, ChatClientAgentOptions options, Func? clientFactory = null) + { + if (persistentAgentResponse is null) + { + throw new ArgumentNullException(nameof(persistentAgentResponse)); + } + + return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, options, clientFactory); + } + + /// + /// Gets a runnable agent instance from a containing metadata about a persistent agent. + /// + /// The client used to interact with persistent agents. Cannot be . + /// The persistent agent metadata to be converted. Cannot be . + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// A instance that can be used to perform operations on the persistent agent. + /// Thrown when or is . + public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatClientAgentOptions options, Func? clientFactory = null) + { + if (persistentAgentMetadata is null) + { + throw new ArgumentNullException(nameof(persistentAgentMetadata)); + } + + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + var chatClient = persistentAgentsClient.AsIChatClient(persistentAgentMetadata.Id); + + if (clientFactory is not null) + { + chatClient = clientFactory(chatClient); + } + + var agentOptions = new ChatClientAgentOptions() + { + Id = persistentAgentMetadata.Id, + Name = options.Name ?? persistentAgentMetadata.Name, + Description = options.Description ?? persistentAgentMetadata.Description, + Instructions = options.Instructions ?? persistentAgentMetadata.Instructions, + ChatOptions = options.ChatOptions, + AIContextProviderFactory = options.AIContextProviderFactory, + ChatMessageStoreFactory = options.ChatMessageStoreFactory, + UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs + }; + + return new ChatClientAgent(chatClient, agentOptions); + } + + /// + /// Retrieves an existing server side agent, wrapped as a using the provided . + /// + /// The to create the with. + /// The ID of the server side agent to create a for. + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the persistent agent. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + string agentId, + ChatClientAgentOptions options, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(agentId)) + { + throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory); + } + + /// + /// Retrieves an existing server side agent, wrapped as a using the provided . + /// + /// The to create the with. + /// The ID of the server side agent to create a for. + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the persistent agent. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static async Task GetAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string agentId, + ChatClientAgentOptions options, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(agentId)) + { + throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory); + }*/ + + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// The name of the agent. + /// The instructions for the agent. + /// The tools to be used by the agent. + /// The temperature setting for the agent. + /// The top-p setting for the agent. + /// The response format for the agent. + /// The metadata for the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + public static async Task CreateAIAgentAsync( + this AgentsClient client, + string model, + string? name = null, + string? instructions = null, + IEnumerable? tools = null, + float? temperature = null, + float? topP = null, + RaiConfig raiConfig = null, + ResponseReasoningOptions? reasoningOptions = null, + ResponseTextOptions? textOptions = null, + IDictionary? structuredInputs = null, + IReadOnlyDictionary? metadata = null, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + var openAIClient = client.GetOpenAIClient(); + var chatClient = openAIClient.GetOpenAIResponseClient(model).AsIChatClient(); + + var promptAgentDefinition = new PromptAgentDefinition(model) + { + Instructions = instructions, + Temperature = temperature, + TopP = topP, + RaiConfig = raiConfig, + ReasoningOptions = reasoningOptions, + TextOptions = textOptions, + }; + + var versionCreation = new AgentVersionCreationOptions(); + if (metadata is not null) + { + foreach (var kvp in metadata) + { + versionCreation.Metadata.Add(kvp.Key, kvp.Value); + } + } + + AgentVersion newAgentVersion = await client.CreateAgentVersionAsync(name, promptAgentDefinition, versionCreation, cancellationToken).ConfigureAwait(false); + + if (tools is not null) + { + if (promptAgentDefinition.Tools is List toolsList) + { + toolsList.AddRange(tools); + } + else + { + foreach (var tool in tools) + { + promptAgentDefinition.Tools.Add(tool); + } + } + } + + if (structuredInputs is not null) + { + foreach (var kvp in structuredInputs) + { + promptAgentDefinition.StructuredInputs.Add(kvp.Key, kvp.Value); + } + } + + var agent = new ChatClientAgent(chatClient); + agent.AsBuilder().Use(FoundryAgentMiddlewareAsync).Build(); + + async Task FoundryAgentMiddlewareAsync(IEnumerable messages, AgentThread? thread, AgentRunOptions? options, Func, AgentThread?, AgentRunOptions?, CancellationToken, Task> sharedFunc, CancellationToken cancellationToken) + { + if (options is not ChatClientAgentRunOptions chatClientOptions) + { + throw new InvalidOperationException("The provided AgentRunOptions is not of type ChatClientAgentRunOptions."); + } + + ChatClientAgentThread? chatClientThread = null; + if (thread is not null) + { + if (thread is not ChatClientAgentThread asChatClientAgentThread) + { + throw new InvalidOperationException("The provided AgentThread is not of type ChatClientAgentThread."); + } + + if (string.IsNullOrWhiteSpace(asChatClientAgentThread.ConversationId)) + { + throw new InvalidOperationException("The ChatClientAgentThread does not have a valid ConversationId."); + } + + chatClientThread = asChatClientAgentThread; + } + + var conversation = (chatClientThread is not null) + ? await client.GetConversationsClient().GetConversationAsync(chatClientThread.ConversationId, cancellationToken).ConfigureAwait(false) + : await client.GetConversationsClient().CreateConversationAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + + chatClientOptions.ChatOptions ??= new(); + chatClientOptions.ChatOptions.RawRepresentationFactory = (client) => + { + var rawRepresentationFactory = chatClientOptions.ChatOptions?.RawRepresentationFactory; + ResponseCreationOptions? responseCreationOptions = null; + + if (rawRepresentationFactory is not null) + { + responseCreationOptions = rawRepresentationFactory.Invoke(chatClient) as ResponseCreationOptions; + + if (responseCreationOptions is null) + { + throw new InvalidOperationException("The RawRepresentationFactory did not return a valid ResponseCreationOptions instance."); + } + } + else + { + responseCreationOptions = new ResponseCreationOptions(); + } + + responseCreationOptions.SetAgentReference(name); + responseCreationOptions.SetConversationReference(conversation); + + return responseCreationOptions; + }; + + await sharedFunc(messages, thread, options, cancellationToken).ConfigureAwait(false); + } + + return agent; + } + + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// The name of the agent. + /// The description of the agent. + /// The instructions for the agent. + /// The tools to be used by the agent. + /// The resources for the tools. + /// The temperature setting for the agent. + /// The top-p setting for the agent. + /// The response format for the agent. + /// The metadata for the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + public static ChatClientAgent CreateAIAgent( + this PersistentAgentsClient persistentAgentsClient, + string model, + string? name = null, + string? description = null, + string? instructions = null, + IEnumerable? tools = null, + ToolResources? toolResources = null, + float? temperature = null, + float? topP = null, + BinaryData? responseFormat = null, + IReadOnlyDictionary? metadata = null, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + var createPersistentAgentResponse = persistentAgentsClient.Administration.CreateAgent( + model: model, + name: name, + description: description, + instructions: instructions, + tools: tools, + toolResources: toolResources, + temperature: temperature, + topP: topP, + responseFormat: responseFormat, + metadata: metadata, + cancellationToken: cancellationToken); + + // Get a local proxy for the agent to work with. + return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken); + } + + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + /// Thrown when or or is . + /// Thrown when is empty or whitespace. + public static ChatClientAgent CreateAIAgent( + this PersistentAgentsClient persistentAgentsClient, + string model, + ChatClientAgentOptions options, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(model)) + { + throw new ArgumentException($"{nameof(model)} should not be null or whitespace.", nameof(model)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + var toolDefinitionsAndResources = ConvertAIToolsToToolDefinitions(options.ChatOptions?.Tools); + + var createPersistentAgentResponse = persistentAgentsClient.Administration.CreateAgent( + model: model, + name: options.Name, + description: options.Description, + instructions: options.Instructions, + tools: toolDefinitionsAndResources.ToolDefinitions, + toolResources: toolDefinitionsAndResources.ToolResources, + temperature: null, + topP: null, + responseFormat: null, + metadata: null, + cancellationToken: cancellationToken); + + if (options.ChatOptions?.Tools is { Count: > 0 } && (toolDefinitionsAndResources.FunctionToolsAndOtherTools is null || options.ChatOptions.Tools.Count != toolDefinitionsAndResources.FunctionToolsAndOtherTools.Count)) + { + options = options.Clone(); + options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; + } + + // Get a local proxy for the agent to work with. + return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken); + } + + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// Full set of options to configure the agent. + /// Provides a way to customize the creation of the underlying used by the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + /// Thrown when or or is . + /// Thrown when is empty or whitespace. + public static async Task CreateAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string model, + ChatClientAgentOptions options, + Func? clientFactory = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + if (string.IsNullOrWhiteSpace(model)) + { + throw new ArgumentException($"{nameof(model)} should not be null or whitespace.", nameof(model)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + var toolDefinitionsAndResources = ConvertAIToolsToToolDefinitions(options.ChatOptions?.Tools); + + var createPersistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync( + model: model, + name: options.Name, + description: options.Description, + instructions: options.Instructions, + tools: toolDefinitionsAndResources.ToolDefinitions, + toolResources: toolDefinitionsAndResources.ToolResources, + temperature: null, + topP: null, + responseFormat: null, + metadata: null, + cancellationToken: cancellationToken).ConfigureAwait(false); + + if (options.ChatOptions?.Tools is { Count: > 0 } && (toolDefinitionsAndResources.FunctionToolsAndOtherTools is null || options.ChatOptions.Tools.Count != toolDefinitionsAndResources.FunctionToolsAndOtherTools.Count)) + { + options = options.Clone(); + options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; + } + + // Get a local proxy for the agent to work with. + return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools) + { + List? toolDefinitions = null; + ToolResources? toolResources = null; + List? functionToolsAndOtherTools = null; + + if (tools is not null) + { + foreach (AITool tool in tools) + { + switch (tool) + { + case HostedCodeInterpreterTool codeTool: + + toolDefinitions ??= new(); + toolDefinitions.Add(new CodeInterpreterToolDefinition()); + + if (codeTool.Inputs is { Count: > 0 }) + { + foreach (var input in codeTool.Inputs) + { + switch (input) + { + case HostedFileContent hostedFile: + // If the input is a HostedFileContent, we can use its ID directly. + toolResources ??= new(); + toolResources.CodeInterpreter ??= new(); + toolResources.CodeInterpreter.FileIds.Add(hostedFile.FileId); + break; + } + } + } + break; + + case HostedFileSearchTool fileSearchTool: + toolDefinitions ??= new(); + toolDefinitions.Add(new FileSearchToolDefinition + { + FileSearch = new() { MaxNumResults = fileSearchTool.MaximumResultCount } + }); + + if (fileSearchTool.Inputs is { Count: > 0 }) + { + foreach (var input in fileSearchTool.Inputs) + { + switch (input) + { + case HostedVectorStoreContent hostedVectorStore: + toolResources ??= new(); + toolResources.FileSearch ??= new(); + toolResources.FileSearch.VectorStoreIds.Add(hostedVectorStore.VectorStoreId); + break; + } + } + } + break; + + case HostedWebSearchTool webSearch when webSearch.AdditionalProperties?.TryGetValue("connectionId", out object? connectionId) is true: + toolDefinitions ??= new(); + toolDefinitions.Add(new BingGroundingToolDefinition(new BingGroundingSearchToolParameters([new BingGroundingSearchConfiguration(connectionId!.ToString())]))); + break; + + default: + functionToolsAndOtherTools ??= new(); + functionToolsAndOtherTools.Add(tool); + break; + } + } + } + + return (toolDefinitions, toolResources, functionToolsAndOtherTools); + } +}