diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs new file mode 100644 index 0000000000..e45fe4c5e5 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class CodeInterpreterToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(this CodeInterpreterTool tool) + { + Throw.IfNull(tool); + + return new CodeInterpreterToolDefinition(); + } + + /// + /// Converts a to an . + /// + /// Instance of + /// A new instance configured with the container ID from the tool's extension data. + internal static OpenAI.Responses.CodeInterpreterTool CreateCodeInterpreterTool(this CodeInterpreterTool tool) + { + Throw.IfNull(tool); + + var containerId = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("containerId"))?.Value; + Throw.IfNull(containerId, "The 'containerId' property must be specified in the CodeInterpreterTool's extension data to create a code interpreter tool."); + + return new OpenAI.Responses.CodeInterpreterTool(new OpenAI.Responses.CodeInterpreterToolContainer(containerId)); + } + + /// + /// Collects the file IDs from the extension data of a . + /// + /// Instance of + internal static List? GetFileIds(this CodeInterpreterTool tool) + { + var fileIds = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("fileIds")); + return fileIds is not null + ? [.. fileIds.Values.Select(fileId => fileId.GetPropertyOrNull(InitializablePropertyPath.Create("value"))?.Value)] + : null; + } + + /// + /// Collects the data sources from the extension data of a . + /// + /// Instance of + internal static List? GetDataSources(this CodeInterpreterTool tool) + { + var dataSources = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("dataSources")); + return dataSources is not null + ? dataSources.Values.Select(dataSource => dataSource.CreateDataSource()).ToList() + : null; + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs new file mode 100644 index 0000000000..e582672f93 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class FileSearchToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this FileSearchTool tool) + { + Throw.IfNull(tool); + + // TODO: Add support for FileSearchToolDefinitionDetails. + + return new FileSearchToolDefinition(); + } + + /// + /// Creates an from a . + /// + /// Instance of + /// A new instance configured with the vector store IDs. + internal static OpenAI.Responses.FileSearchTool CreateFileSearchTool(this FileSearchTool tool) + { + Throw.IfNull(tool); + + return new OpenAI.Responses.FileSearchTool(tool.GetVectorStoreIds()); + } + + /// + /// Get the vector store IDs for the specified . + /// + /// Instance of + internal static List? GetVectorStoreIds(this FileSearchTool tool) + { + return tool.VectorStoreIds?.LiteralValue.ToList(); + } + + internal static IList? GetVectorStoreConfigurations(this FileSearchTool tool) + { + var dataSources = tool.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("options.configurations")); + return dataSources?.Values.Select(value => value.CreateVectorStoreConfiguration()).ToList(); + } + + internal static VectorStoreConfigurations CreateVectorStoreConfiguration(this RecordDataValue value) + { + Throw.IfNull(value); + + var storeName = value.GetPropertyOrNull(InitializablePropertyPath.Create("storeName"))?.Value; + Throw.IfNullOrEmpty(storeName); + + var dataSources = value.GetDataSources(); + Throw.IfNull(dataSources); + + return new VectorStoreConfigurations(storeName, new VectorStoreConfiguration(dataSources)); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs new file mode 100644 index 0000000000..73bd9b41b2 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +public static class FunctionToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static FunctionToolDefinition CreateFunctionToolDefinition(this InvokeClientTaskAction tool) + { + Throw.IfNull(tool); + Throw.IfNull(tool.Name); + + BinaryData parameters = tool.GetParameters(); + + return new FunctionToolDefinition( + name: tool.Name, + description: tool.Description, + parameters: parameters); + } + + /// + /// Creates a from a . + /// + /// Instance of + /// A new instance configured with the function name, parameters, and description. + internal static FunctionTool CreateFunctionTool(this InvokeClientTaskAction tool) + { + Throw.IfNull(tool); + Throw.IfNull(tool.Name); + + BinaryData parameters = tool.GetParameters(); + + return new FunctionTool( + functionName: tool.Name, + functionParameters: parameters, + strictModeEnabled: null) + { + FunctionDescription = tool.Description + }; + } + + /// + /// Creates the parameters schema for a . + /// + /// Instance of + internal static BinaryData GetParameters(this InvokeClientTaskAction tool) + { + Throw.IfNull(tool); + + var parameters = tool.ClientActionInputSchema?.GetSchema().ToString() ?? DefaultSchema; + + return new BinaryData(parameters); + } + + private const string DefaultSchema = "{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}"; +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs new file mode 100644 index 0000000000..da769856dd --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Microsoft.Bot.ObjectModel; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.AI; + +/// +/// Extension methods for . +/// +internal static class HostedCodeInterpreterToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static CodeInterpreterToolDefinition CreateHostedCodeInterpreterToolDefinition(this HostedCodeInterpreterTool tool) + { + Throw.IfNull(tool); + + return new CodeInterpreterToolDefinition(); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs new file mode 100644 index 0000000000..846e28f226 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.AI; + +/// +/// Extension methods for . +/// +internal static class HostedFileSearchToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static FileSearchToolDefinition CreateFileSearchToolDefinition(this HostedFileSearchTool tool) + { + Throw.IfNull(tool); + + // TODO: Add support for FileSearchToolDefinitionDetails. + + return new FileSearchToolDefinition(); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs new file mode 100644 index 0000000000..a879a105bc --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.AI; + +/// +/// Extension methods for . +/// +internal static class HostedMcpServerToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static MCPToolDefinition CreateMcpToolDefinition(this HostedMcpServerTool tool) + { + Throw.IfNull(tool); + Throw.IfNull(tool.ServerName); + Throw.IfNull(tool.ServerAddress); + + var definition = new MCPToolDefinition(tool.ServerName, tool.ServerAddress); + tool.AllowedTools?.ToList().ForEach(definition.AllowedTools.Add); + return definition; + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs new file mode 100644 index 0000000000..f13c0ec2d4 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.AI; + +/// +/// Extension methods for . +/// +internal static class HostedWebSearchToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this HostedWebSearchTool tool) + { + Throw.IfNull(tool); + + // TODO: Add support for BingGroundingSearchToolParameters. + var parameters = new BingGroundingSearchToolParameters([]); + + return new BingGroundingToolDefinition(parameters); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs new file mode 100644 index 0000000000..0e74f53e9a --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class McpServerToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static MCPToolDefinition CreateMcpToolDefinition(this McpServerTool tool) + { + Throw.IfNull(tool); + Throw.IfNull(tool.ServerName?.LiteralValue); + Throw.IfNull(tool.Connection); + + // TODO: Add support for additional properties + + var connection = tool.Connection as AnonymousConnection ?? throw new ArgumentException($"Only AnonymousConnection is supported for MCP Server Tool connections. Actual connection type: {tool.Connection.GetType().Name}", nameof(tool)); + var serverUrl = connection.Endpoint?.LiteralValue; + Throw.IfNullOrEmpty(serverUrl, nameof(connection.Endpoint)); + + return new MCPToolDefinition(tool.ServerName?.LiteralValue, serverUrl); + } + + /// + /// Creates a from a . + /// + /// Instance of + /// A new instance configured with the server name and URL. + internal static McpTool CreateMcpTool(this McpServerTool tool) + { + Throw.IfNull(tool); + Throw.IfNull(tool.ServerName?.LiteralValue); + Throw.IfNull(tool.Connection); + + // TODO: Add support for headers + + var connection = tool.Connection as AnonymousConnection ?? throw new ArgumentException("Only AnonymousConnection is supported for MCP Server Tool connections.", nameof(tool)); + var serverUrl = connection.Endpoint?.LiteralValue; + Throw.IfNullOrEmpty(serverUrl, nameof(connection.Endpoint)); + + return new McpTool(tool.ServerName?.LiteralValue, serverUrl); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs new file mode 100644 index 0000000000..3fb7c64c1b --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Linq; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class PromptAgentExtensions +{ + /// + /// Return the Foundry tool definitions which corresponds with the provided . + /// + /// Instance of + internal static IEnumerable GetToolDefinitions(this GptComponentMetadata promptAgent) + { + Throw.IfNull(promptAgent); + + return promptAgent.Tools.Select(tool => + { + return tool switch + { + CodeInterpreterTool codeInterpreterTool => codeInterpreterTool.CreateCodeInterpreterToolDefinition(), + InvokeClientTaskAction functionTool => functionTool.CreateFunctionToolDefinition(), + FileSearchTool fileSearchTool => fileSearchTool.CreateFileSearchToolDefinition(), + WebSearchTool webSearchTool => webSearchTool.CreateBingGroundingToolDefinition(), + McpServerTool mcpServerTool => mcpServerTool.CreateMcpToolDefinition(), + // TODO: Add other tool types as custom tools + // AzureAISearch + // AzureFunction + // OpenApi + _ => throw new NotSupportedException($"Unable to create tool definition because of unsupported tool type: {tool.Kind}"), + }; + }).ToList(); + } + + /// + /// Return the Foundry tool resources which corresponds with the provided . + /// + /// Instance of + internal static ToolResources GetToolResources(this GptComponentMetadata promptAgent) + { + Throw.IfNull(promptAgent); + + var toolResources = new ToolResources(); + + var codeInterpreter = promptAgent.GetCodeInterpreterToolResource(); + if (codeInterpreter is not null) + { + toolResources.CodeInterpreter = codeInterpreter; + } + + var fileSearch = promptAgent.GetFileSearchToolResource(); + if (fileSearch is not null) + { + toolResources.FileSearch = fileSearch; + } + + // TODO Handle MCP tool resources + + return toolResources; + } + + /// + /// Returns the Foundry response tools which correspond with the provided . + /// + /// Instance of . + /// A collection of instances corresponding to the tools defined in the agent. + internal static IEnumerable GetResponseTools(this GptComponentMetadata promptAgent) + { + Throw.IfNull(promptAgent); + + return promptAgent.Tools.Select(tool => + { + return tool switch + { + CodeInterpreterTool codeInterpreterTool => codeInterpreterTool.CreateCodeInterpreterTool(), + InvokeClientTaskAction functionTool => functionTool.CreateFunctionTool(), + FileSearchTool fileSearchTool => fileSearchTool.CreateFileSearchTool(), + WebSearchTool webSearchTool => webSearchTool.CreateWebSearchTool(), + McpServerTool mcpServerTool => mcpServerTool.CreateMcpTool(), + // TODO: Add other tool types as custom tools + // AzureAISearch + // AzureFunction + // OpenApi + _ => throw new NotSupportedException($"Unable to create response tool because of unsupported tool type: {tool.Kind}"), + }; + }).ToList(); + } + + #region private + private static CodeInterpreterToolResource? GetCodeInterpreterToolResource(this GptComponentMetadata promptAgent) + { + Throw.IfNull(promptAgent); + + CodeInterpreterToolResource? resource = null; + + var codeInterpreter = (CodeInterpreterTool?)promptAgent.GetFirstAgentTool(); + if (codeInterpreter is not null) + { + var fileIds = codeInterpreter.GetFileIds(); + var dataSources = codeInterpreter.GetDataSources(); + if (fileIds is not null || dataSources is not null) + { + resource = new CodeInterpreterToolResource(); + fileIds?.ForEach(id => resource.FileIds.Add(id)); + dataSources?.ForEach(ds => resource.DataSources.Add(ds)); + } + } + + return resource; + } + + private static FileSearchToolResource? GetFileSearchToolResource(this GptComponentMetadata promptAgent) + { + Throw.IfNull(promptAgent); + + var fileSearch = (FileSearchTool?)promptAgent.GetFirstAgentTool(); + if (fileSearch is not null) + { + var vectorStoreIds = fileSearch.GetVectorStoreIds(); + var vectorStores = fileSearch.GetVectorStoreConfigurations(); + if (vectorStoreIds is not null || vectorStores is not null) + { + return new FileSearchToolResource(vectorStoreIds, vectorStores); + } + } + + return null; + } + + private static TaskAction? GetFirstAgentTool(this GptComponentMetadata promptAgent) + { + return promptAgent.Tools.FirstOrDefault(tool => tool is T); + } + #endregion +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs new file mode 100644 index 0000000000..24c172c888 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.Extensions.AI; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class RecordDataTypeExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + internal static BinaryData? AsBinaryData(this RecordDataType recordDataType) + { + Throw.IfNull(recordDataType); + + if (recordDataType.Properties.Count == 0) + { + return null; + } + + return BinaryData.FromObjectAsJson( + new + { + type = "json_schema", + schema = + new + { + type = "object", + properties = recordDataType.Properties.AsObjectDictionary(), + additionalProperties = false + } + } + ); + } +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs new file mode 100644 index 0000000000..a2c4d490d6 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class RecordDataValueExtensions +{ + /// + /// Gets the data sources from the specified . + /// + internal static List? GetDataSources(this RecordDataValue value) + { + var dataSources = value.GetPropertyOrNull(InitializablePropertyPath.Create("options.data_sources")); + return dataSources?.Values.Select(dataSource => dataSource.CreateDataSource()).ToList(); + } + + /// + /// Creates a new instance of using the specified . + /// + internal static VectorStoreDataSource CreateDataSource(this RecordDataValue value) + { + Throw.IfNull(value); + + string? assetIdentifier = value.GetPropertyOrNull(InitializablePropertyPath.Create("assetIdentifier"))?.Value; + Throw.IfNullOrEmpty(assetIdentifier); + + string? assetType = value.GetPropertyOrNull(InitializablePropertyPath.Create("assetType"))?.Value; + Throw.IfNullOrEmpty(assetType); + + return new VectorStoreDataSource(assetIdentifier, new VectorStoreDataSourceAssetType(assetType)); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs new file mode 100644 index 0000000000..b0f65442af --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Bot.ObjectModel; + +/// +/// Extension methods for . +/// +internal static class WebSearchToolExtensions +{ + /// + /// Creates a from a . + /// + /// Instance of + internal static BingGroundingToolDefinition CreateBingGroundingToolDefinition(this WebSearchTool tool) + { + Throw.IfNull(tool); + + // TODO: Add support for BingGroundingSearchToolParameters. + var parameters = new BingGroundingSearchToolParameters([]); + + return new BingGroundingToolDefinition(parameters); + } + + /// + /// Creates a from a . + /// + /// Instance of + /// A new instance. + internal static OpenAI.Responses.WebSearchTool CreateWebSearchTool(this WebSearchTool tool) + { + Throw.IfNull(tool); + + return new OpenAI.Responses.WebSearchTool(); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs new file mode 100644 index 0000000000..5d18c7a25c --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Agents; +using Azure.AI.Agents.Persistent; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class FoundryAgentFactory : AgentFactory +{ + private readonly AgentClient? _agentClient; + private readonly TokenCredential? _tokenCredential; + + /// + /// Creates a new instance of the class with an associated . + /// + /// The instance to use for creating agents. + /// The instance to use for configuration. + public FoundryAgentFactory(AgentClient agentClient, IConfiguration? configuration = null) : base(configuration) + { + Throw.IfNull(agentClient); + + this._agentClient = agentClient; + } + + /// + /// Creates a new instance of the class with an associated . + /// + /// The to use for authenticating requests. + /// The instance to use for configuration. + public FoundryAgentFactory(TokenCredential tokenCredential, IConfiguration? configuration = null) : base(configuration) + { + Throw.IfNull(tokenCredential); + + this._tokenCredential = tokenCredential; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + + var agentClient = this._agentClient ?? this.CreateAgentClient(promptAgent); + + var modelId = promptAgent.Model?.ModelNameHint; + if (string.IsNullOrEmpty(modelId)) + { + throw new InvalidOperationException("The model id must be specified in the agent definition model to create a foundry agent."); + } + + var modelOptions = promptAgent.Model?.Options; + + var promptAgentDefinition = new PromptAgentDefinition(model: modelId) + { + Instructions = promptAgent.Instructions?.ToTemplateString(), + Temperature = (float?)modelOptions?.Temperature?.LiteralValue, + TopP = (float?)modelOptions?.TopP?.LiteralValue, + }; + + foreach (var tool in promptAgent.GetResponseTools()) + { + promptAgentDefinition.Tools.Add(tool); + } + + var agentVersionCreationOptions = new AgentVersionCreationOptions(promptAgentDefinition); + + var metadata = promptAgent.Metadata?.ToDictionary(); + if (metadata is not null) + { + foreach (var kvp in metadata) + { + agentVersionCreationOptions.Metadata.Add(kvp.Key, kvp.Value); + } + } + + var agentVersion = await agentClient.CreateAgentVersionAsync(agentName: promptAgent.Name, options: agentVersionCreationOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + + return agentClient.GetAIAgent(agentVersion, cancellationToken: cancellationToken); + } + + private AgentClient CreateAgentClient(GptComponentMetadata promptAgent) + { + var externalModel = promptAgent.Model as CurrentModels; + var connection = externalModel?.Connection as RemoteConnection; + if (connection is not null) + { + var endpoint = connection.Endpoint?.Eval(this.Engine); + if (string.IsNullOrEmpty(endpoint)) + { + throw new InvalidOperationException("The endpoint must be specified in the agent definition model connection to create an AgentClient."); + } + + if (this._tokenCredential is null) + { + throw new InvalidOperationException("A TokenCredential must be registered in the service provider to create an AgentClient."); + } + + return new AgentClient(new Uri(endpoint), this._tokenCredential); + } + + throw new InvalidOperationException("An AgentClient must be registered in the service provider or a RemoteConnection must be specified in the agent definition model connection to create an AgentClient."); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs new file mode 100644 index 0000000000..ae9bc7756e --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Agents.Persistent; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class FoundryPersistentAgentFactory : AgentFactory +{ + private readonly PersistentAgentsClient? _agentClient; + private readonly TokenCredential? _tokenCredential; + + /// + /// Creates a new instance of the class with an associated . + /// + /// The instance to use for creating agents. + /// The instance to use for configuration. + public FoundryPersistentAgentFactory(PersistentAgentsClient agentClient, IConfiguration? configuration = null) : base(configuration) + { + Throw.IfNull(agentClient); + + this._agentClient = agentClient; + } + + /// + /// Creates a new instance of the class with an associated . + /// + /// The to use for authenticating requests. + /// The instance to use for configuration. + public FoundryPersistentAgentFactory(TokenCredential tokenCredential, IConfiguration? configuration = null) : base(configuration) + { + Throw.IfNull(tokenCredential); + + this._tokenCredential = tokenCredential; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + + var agentClient = this._agentClient ?? this.CreatePersistentAgentClient(promptAgent); + + var modelId = promptAgent.Model?.ModelNameHint; + if (string.IsNullOrEmpty(modelId)) + { + throw new InvalidOperationException("The model id must be specified in the agent definition model to create a foundry agent."); + } + + //var outputSchema = promptAgent.OutputType; TODO: Fix converting RecordDataType to BinaryData + var modelOptions = promptAgent.Model?.Options; + + return await agentClient.CreateAIAgentAsync( + model: modelId, + name: promptAgent.Name, + instructions: promptAgent.Instructions?.ToTemplateString(), + tools: promptAgent.GetToolDefinitions(), + toolResources: promptAgent.GetToolResources(), + temperature: (float?)modelOptions?.Temperature?.LiteralValue, + topP: (float?)modelOptions?.TopP?.LiteralValue, + //responseFormat: outputSchema.AsBinaryData(), TODO: Fix converting RecordDataType to BinaryData + metadata: promptAgent.Metadata?.ToDictionary(), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private PersistentAgentsClient CreatePersistentAgentClient(GptComponentMetadata promptAgent) + { + var externalModel = promptAgent.Model as CurrentModels; + var connection = externalModel?.Connection as RemoteConnection; + if (connection is not null) + { + var endpoint = connection.Endpoint?.Eval(this.Engine); + if (string.IsNullOrEmpty(endpoint)) + { + throw new InvalidOperationException("The endpoint must be specified in the agent definition model connection to create an PersistentAgentsClient."); + } + if (this._tokenCredential is null) + { + throw new InvalidOperationException("A TokenCredential must be registered in the service provider to create an PersistentAgentsClient."); + } + return new PersistentAgentsClient(endpoint, this._tokenCredential); + } + + throw new InvalidOperationException("A PersistentAgentsClient must be registered in the service provider or a FoundryConnection must be specified in the agent definition model connection to create an PersistentAgentsClient."); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs new file mode 100644 index 0000000000..c406825d5b --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.AI; + +namespace Microsoft.Agents.AI.Declarative.AzureAI; + +/// +/// A class to describe the parameters of an in a JSON Schema friendly way. +/// +internal sealed class JsonSchemaFunctionParameters +{ + /// + /// The type of schema which is always "object" when describing function parameters. + /// + [JsonPropertyName("type")] + public string Type => "object"; + + /// + /// The list of required properties. + /// + [JsonPropertyName("required")] + public List Required { get; set; } = []; + + /// + /// A dictionary of properties, keyed by name => JSON Schema. + /// + [JsonPropertyName("properties")] + public Dictionary Properties { get; set; } = []; +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj new file mode 100644 index 0000000000..05705a5675 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj @@ -0,0 +1,52 @@ + + + + $(ProjectsTargetFrameworks) + $(ProjectsDebugTargetFrameworks) + preview + $(NoWarn);MEAI001;OPENAI001 + + + + true + true + true + true + + + + + + + Microsoft Agent Framework Declarative AzureAI + Provides Microsoft Agent Framework support for declarative AzureAI agents. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs new file mode 100644 index 0000000000..a598292e75 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.ClientModel; +using Azure.AI.OpenAI; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI; +using OpenAI.Assistants; +using OpenAI.Chat; +using OpenAI.Responses; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an abstract base class. +/// +public abstract class OpenAIAgentFactory : AgentFactory +{ + /// + /// Creates a new instance of the class. + /// + protected OpenAIAgentFactory(IConfiguration? configuration, ILoggerFactory? loggerFactory) : base(configuration) + { + this.LoggerFactory = loggerFactory; + } + + /// + /// Creates a new instance of the class. + /// + protected OpenAIAgentFactory(Uri endpoint, TokenCredential tokenCredential, IConfiguration? configuration, ILoggerFactory? loggerFactory) : base(configuration) + { + Throw.IfNull(endpoint); + Throw.IfNull(tokenCredential); + + this._endpoint = endpoint; + this._tokenCredential = tokenCredential; + this.LoggerFactory = loggerFactory; + } + + /// + /// Gets the instance used for creating loggers. + /// + protected ILoggerFactory? LoggerFactory { get; } + + /// + /// Creates a new instance of the class. + /// + protected ChatClient? CreateChatClient(GptComponentMetadata promptAgent) + { + var model = promptAgent.Model as CurrentModels; + var provider = model?.Provider?.Value ?? ModelProvider.OpenAI; + if (provider == ModelProvider.OpenAI) + { + return this.CreateOpenAIChatClient(promptAgent); + } + else if (provider == ModelProvider.AzureOpenAI) + { + Throw.IfNull(this._endpoint, "A endpoint must be specified to create an Azure OpenAI client"); + Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client"); + return CreateAzureOpenAIChatClient(promptAgent, this._endpoint, this._tokenCredential); + } + + return null; + } + + /// + /// Creates a new instance of the class. + /// + protected AssistantClient? CreateAssistantClient(GptComponentMetadata promptAgent) + { + var model = promptAgent.Model as CurrentModels; + var provider = model?.Provider?.Value ?? ModelProvider.OpenAI; + if (provider == ModelProvider.OpenAI) + { + return this.CreateOpenAIAssistantClient(promptAgent); + } + else if (provider == ModelProvider.AzureOpenAI) + { + Throw.IfNull(this._endpoint, "The connection endpoint must be specified to create an Azure OpenAI client."); + Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client"); + return CreateAzureOpenAIAssistantClient(promptAgent, this._endpoint, this._tokenCredential); + } + + return null; + } + + /// + /// Creates a new instance of the class. + /// + protected OpenAIResponseClient? CreateResponseClient(GptComponentMetadata promptAgent) + { + var model = promptAgent.Model as CurrentModels; + var provider = model?.Provider?.Value ?? ModelProvider.OpenAI; + if (provider == ModelProvider.OpenAI) + { + return this.CreateOpenAIResponseClient(promptAgent); + } + else if (provider == ModelProvider.AzureOpenAI) + { + Throw.IfNull(this._endpoint, "The connection endpoint must be specified to create an Azure OpenAI client."); + Throw.IfNull(this._tokenCredential, "A token credential must be specified to create an Azure OpenAI client"); + return CreateAzureOpenAIResponseClient(promptAgent, this._endpoint, this._tokenCredential); + } + + return null; + } + + #region private + private readonly Uri? _endpoint; + private readonly TokenCredential? _tokenCredential; + + private ChatClient CreateOpenAIChatClient(GptComponentMetadata promptAgent) + { + var modelId = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent."); + + return this.CreateOpenAIClient(promptAgent).GetChatClient(modelId); + } + + private static ChatClient CreateAzureOpenAIChatClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential) + { + var deploymentName = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent."); + + return new AzureOpenAIClient(endpoint, tokenCredential).GetChatClient(deploymentName); + } + + private AssistantClient CreateOpenAIAssistantClient(GptComponentMetadata promptAgent) + { + var modelId = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent."); + + return this.CreateOpenAIClient(promptAgent).GetAssistantClient(); + } + + private static AssistantClient CreateAzureOpenAIAssistantClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential) + { + var deploymentName = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent."); + + return new AzureOpenAIClient(endpoint, tokenCredential).GetAssistantClient(); + } + + private OpenAIResponseClient CreateOpenAIResponseClient(GptComponentMetadata promptAgent) + { + var modelId = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI agent."); + + return this.CreateOpenAIClient(promptAgent).GetOpenAIResponseClient(modelId); + } + + private static OpenAIResponseClient CreateAzureOpenAIResponseClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential) + { + var deploymentName = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(deploymentName, "The deployment name (using model.id) must be specified in the agent definition to create an Azure OpenAI agent."); + + return new AzureOpenAIClient(endpoint, tokenCredential).GetOpenAIResponseClient(deploymentName); + } + + private OpenAIClient CreateOpenAIClient(GptComponentMetadata promptAgent) + { + var model = promptAgent.Model as CurrentModels; + + var keyConnection = model?.Connection as ApiKeyConnection; + Throw.IfNull(keyConnection, "A key connection must be specified when create an OpenAI client"); + + var apiKey = keyConnection.Key!.Eval(this.Engine); + Throw.IfNullOrEmpty(apiKey, "The connection key must be specified in the agent definition to create an OpenAI client."); + + var clientOptions = new OpenAIClientOptions(); + var endpoint = keyConnection.Endpoint?.Eval(this.Engine); + if (!string.IsNullOrEmpty(endpoint)) + { + clientOptions.Endpoint = new Uri(endpoint); + } + + return new OpenAIClient(new ApiKeyCredential(apiKey), clientOptions); + } + #endregion +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs new file mode 100644 index 0000000000..621736e6dd --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Agents.Persistent; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI; +using OpenAI.Assistants; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class OpenAIAssistantAgentFactory : OpenAIAgentFactory +{ + /// + /// Creates a new instance of the class. + /// + public OpenAIAssistantAgentFactory(IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIAssistantAgentFactory(AssistantClient assistantClient, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + Throw.IfNull(assistantClient); + + this._assistantClient = assistantClient; + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIAssistantAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, configuration, loggerFactory) + { + this._functions = functions; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + + var model = promptAgent.Model as CurrentModels; + var apiType = model?.ApiType; + if (apiType?.IsUnknown() == false || apiType?.UnknownValue?.Equals(API_TYPE_ASSISTANTS, StringComparison.OrdinalIgnoreCase) == false) + { + return null; + } + + var options = new ChatClientAgentOptions() + { + Name = promptAgent.Name, + Description = promptAgent.Description, + Instructions = promptAgent.Instructions?.ToTemplateString(), + ChatOptions = promptAgent.GetChatOptions(this._functions), + }; + + AssistantClient? assistantClient = this._assistantClient ?? this.CreateAssistantClient(promptAgent); + if (assistantClient is not null) + { + var modelId = promptAgent.Model?.ModelNameHint; + Throw.IfNullOrEmpty(modelId, "The model id must be specified in the agent definition to create an OpenAI Assistant."); + Throw.IfNullOrEmpty(promptAgent.Instructions?.ToTemplateString(), "The instructions must be specified in the agent definition to create an OpenAI Assistant."); + + return await assistantClient.CreateAIAgentAsync( + modelId, + options + ).ConfigureAwait(false); + } + + return null; + } + + #region private + private readonly AssistantClient? _assistantClient; + private readonly IList? _functions; + + private const string API_TYPE_ASSISTANTS = "ASSISTANTS"; + #endregion +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs new file mode 100644 index 0000000000..f27c8ca6a5 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Agents.Persistent; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Chat; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class OpenAIChatAgentFactory : OpenAIAgentFactory +{ + /// + /// Creates a new instance of the class. + /// + public OpenAIChatAgentFactory(IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIChatAgentFactory(ChatClient chatClient, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + Throw.IfNull(chatClient); + + this._chatClient = chatClient; + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIChatAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, configuration, loggerFactory) + { + this._functions = functions; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + + var model = promptAgent.Model as CurrentModels; + var apiType = model?.ApiType; + if (apiType?.IsUnknown() == true || apiType?.Value != ModelApiType.Chat) + { + return null; + } + + var options = new ChatClientAgentOptions() + { + Name = promptAgent.Name, + Description = promptAgent.Description, + Instructions = promptAgent.Instructions?.ToTemplateString(), + ChatOptions = promptAgent.GetChatOptions(this._functions), + }; + + ChatClient? chatClient = this._chatClient ?? this.CreateChatClient(promptAgent); + if (chatClient is not null) + { + return new ChatClientAgent( + chatClient.AsIChatClient(), + options, + this.LoggerFactory); + } + + return null; + } + + #region private + private readonly ChatClient? _chatClient; + private readonly IList? _functions; + #endregion +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs new file mode 100644 index 0000000000..6da009135d --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Agents.Persistent; +using Azure.Core; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class OpenAIResponseAgentFactory : OpenAIAgentFactory +{ + /// + /// Creates a new instance of the class. + /// + public OpenAIResponseAgentFactory(IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIResponseAgentFactory(OpenAIResponseClient responseClient, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + { + Throw.IfNull(responseClient); + + this._responseClient = responseClient; + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIResponseAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, configuration, loggerFactory) + { + this._functions = functions; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + + var model = promptAgent.Model as CurrentModels; + var apiType = model?.ApiType; + if (apiType?.IsUnknown() == true || apiType?.Value != ModelApiType.Responses) + { + return null; + } + + var options = new ChatClientAgentOptions() + { + Name = promptAgent.Name, + Description = promptAgent.Description, + Instructions = promptAgent.Instructions?.ToTemplateString(), + ChatOptions = promptAgent.GetChatOptions(this._functions), + }; + + var responseClient = this._responseClient ?? this.CreateResponseClient(promptAgent); + if (responseClient is not null) + { + return new ChatClientAgent( + responseClient.AsIChatClient(), + options, + this.LoggerFactory); + } + + return null; + } + + #region private + private readonly OpenAIResponseClient? _responseClient; + private readonly IList? _functions; + #endregion +}