From 0e922de9be666e4a02fcdc9c009b5a989be796f3 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:43:37 +0000 Subject: [PATCH 1/8] Declarative support for AzureAI Persistent agents --- .../CodeInterpreterToolExtensions.cs | 64 ++++++ .../Extensions/FileSearchToolExtensions.cs | 67 +++++++ .../Extensions/FunctionToolExtensions.cs | 67 +++++++ .../HostedCodeInterpreterToolExtensions.cs | 24 +++ .../HostedFileSearchToolExtensions.cs | 25 +++ .../HostedMcpServerToolExtensions.cs | 28 +++ .../HostedWebSearchToolExtensions.cs | 26 +++ .../Extensions/McpServerToolExtensions.cs | 53 +++++ .../Extensions/PromptAgentExtensions.cs | 142 ++++++++++++++ .../Extensions/RecordDataTypeExtensions.cs | 45 +++++ .../Extensions/RecordDataValueExtensions.cs | 39 ++++ .../Extensions/WebSearchToolExtensions.cs | 38 ++++ .../FoundryAgentFactory.cs | 111 +++++++++++ .../FoundryPersistentAgentFactory.cs | 94 +++++++++ .../JsonSchemaFunctionParameters.cs | 32 +++ ...s.AI.Declarative.AzureAI.Persistent.csproj | 52 +++++ .../OpenAIAgentFactory.cs | 183 ++++++++++++++++++ .../OpenAIAssistantAgentFactory.cs | 92 +++++++++ .../OpenAIChatAgentFactory.cs | 85 ++++++++ .../OpenAIResponseAgentFactory.cs | 85 ++++++++ 20 files changed, 1352 insertions(+) create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs 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 +} From 2ba1a257a54ebd57e4fa9fede20f15f923cae52e Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:23:42 +0000 Subject: [PATCH 2/8] Start to fix compile errors due to refactoring --- dotnet/agent-framework-dotnet.slnx | 1 + .../FoundryAgentFactory.cs | 41 ++++++++++++++----- ...s.AI.Declarative.AzureAI.Persistent.csproj | 23 +++++------ 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 002efdbab1..3ee33ae909 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -381,6 +381,7 @@ + diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs index 5d18c7a25c..148e251b18 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs @@ -2,29 +2,30 @@ 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.PowerFx; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// -/// Provides an which creates instances of using a . +/// Provides an which creates instances of using a . /// -public sealed class FoundryAgentFactory : AgentFactory +public sealed class FoundryAgentFactory : PromptAgentFactory { - private readonly AgentClient? _agentClient; + private readonly PersistentAgentsClient? _agentClient; private readonly TokenCredential? _tokenCredential; /// - /// Creates a new instance of the class with an associated . + /// Creates a new instance of the class with an associated . /// - /// The instance to use for creating agents. + /// The instance to use for creating agents. + /// Optional , if none is provided a default instance will be created. /// The instance to use for configuration. - public FoundryAgentFactory(AgentClient agentClient, IConfiguration? configuration = null) : base(configuration) + public FoundryAgentFactory(PersistentAgentsClient agentClient, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, configuration) { Throw.IfNull(agentClient); @@ -35,8 +36,9 @@ public FoundryAgentFactory(AgentClient agentClient, IConfiguration? configuratio /// Creates a new instance of the class with an associated . /// /// The to use for authenticating requests. + /// Optional , if none is provided a default instance will be created. /// The instance to use for configuration. - public FoundryAgentFactory(TokenCredential tokenCredential, IConfiguration? configuration = null) : base(configuration) + public FoundryAgentFactory(TokenCredential tokenCredential, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, configuration) { Throw.IfNull(tokenCredential); @@ -58,6 +60,7 @@ public FoundryAgentFactory(TokenCredential tokenCredential, IConfiguration? conf var modelOptions = promptAgent.Model?.Options; + /* var promptAgentDefinition = new PromptAgentDefinition(model: modelId) { Instructions = promptAgent.Instructions?.ToTemplateString(), @@ -84,9 +87,25 @@ public FoundryAgentFactory(TokenCredential tokenCredential, IConfiguration? conf var agentVersion = await agentClient.CreateAgentVersionAsync(agentName: promptAgent.Name, options: agentVersionCreationOptions, cancellationToken: cancellationToken).ConfigureAwait(false); return agentClient.GetAIAgent(agentVersion, cancellationToken: cancellationToken); + */ + + var createPersistentAgentResponse = agentClient.Administration.CreateAgent( + model: modelId, + name: promptAgent.Name, + instructions: promptAgent.Instructions?.ToTemplateString(), + tools: tools, + toolResources: toolResources, + temperature: (float?)modelOptions?.Temperature?.LiteralValue, + topP: (float?)modelOptions?.TopP?.LiteralValue, + responseFormat: responseFormat, + metadata: promptAgent.Metadata?.ToDictionary(), + cancellationToken: cancellationToken); + + // Get a local proxy for the agent to work with. + return agentClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: null, services: null, cancellationToken: cancellationToken); } - private AgentClient CreateAgentClient(GptComponentMetadata promptAgent) + private PersistentAgentsClient CreateAgentClient(GptComponentMetadata promptAgent) { var externalModel = promptAgent.Model as CurrentModels; var connection = externalModel?.Connection as RemoteConnection; @@ -103,9 +122,9 @@ private AgentClient CreateAgentClient(GptComponentMetadata promptAgent) throw new InvalidOperationException("A TokenCredential must be registered in the service provider to create an AgentClient."); } - return new AgentClient(new Uri(endpoint), this._tokenCredential); + return new PersistentAgentsClient(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."); + throw new InvalidOperationException("A PersistentAgentsClient must be registered in the service provider or a RemoteConnection must be specified in the agent definition model connection to create a PersistentAgentsClient."); } } 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 index 05705a5675..a74a845fef 100644 --- 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 @@ -1,33 +1,30 @@  - $(ProjectsTargetFrameworks) - $(ProjectsDebugTargetFrameworks) preview $(NoWarn);MEAI001;OPENAI001 + false true - true - true true + true - Microsoft Agent Framework Declarative AzureAI - Provides Microsoft Agent Framework support for declarative AzureAI agents. + Microsoft Agent Framework Declarative Foundry Agents + Provides Microsoft Agent Framework support for declarative Foundry agents. - + - @@ -39,14 +36,16 @@ - - - + - + + + + + From 6ae3bdae4d9c20a1994e19bc3e4edf12c6a15384 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:05:46 +0000 Subject: [PATCH 3/8] Remove declarative support for persistent agents --- dotnet/agent-framework-dotnet.slnx | 2 +- .../CodeInterpreterToolExtensions.cs | 64 -------- .../Extensions/FileSearchToolExtensions.cs | 67 --------- .../Extensions/FunctionToolExtensions.cs | 67 --------- .../HostedCodeInterpreterToolExtensions.cs | 24 --- .../HostedFileSearchToolExtensions.cs | 25 --- .../HostedMcpServerToolExtensions.cs | 28 ---- .../HostedWebSearchToolExtensions.cs | 26 ---- .../Extensions/McpServerToolExtensions.cs | 53 ------- .../Extensions/PromptAgentExtensions.cs | 142 ------------------ .../Extensions/RecordDataTypeExtensions.cs | 45 ------ .../Extensions/RecordDataValueExtensions.cs | 39 ----- .../Extensions/WebSearchToolExtensions.cs | 38 ----- .../FoundryAgentFactory.cs | 130 ---------------- .../FoundryPersistentAgentFactory.cs | 94 ------------ .../OpenAIAssistantAgentFactory.cs | 92 ------------ .../OpenAIResponseAgentFactory.cs | 85 ----------- .../AzureAIPromptAgentFactory.cs | 91 +++++++++++ .../BaseOpenAIPromptAgentFactory.cs} | 31 ++-- .../JsonSchemaFunctionParameters.cs | 0 ...soft.Agents.AI.Declarative.AzureAI.csproj} | 10 +- .../OpenAIPromptAgentFactory.cs} | 23 ++- .../OpenAIResponsesPromptAgentFactory.cs | 84 +++++++++++ .../Extensions/PromptAgentExtensions.cs | 2 +- 24 files changed, 210 insertions(+), 1052 deletions(-) delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/AzureAIPromptAgentFactory.cs rename dotnet/src/{Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs => Microsoft.Agents.AI.Declarative.AzureAI/BaseOpenAIPromptAgentFactory.cs} (81%) rename dotnet/src/{Microsoft.Agents.AI.Declarative.AzureAI.Persistent => Microsoft.Agents.AI.Declarative.AzureAI}/JsonSchemaFunctionParameters.cs (100%) rename dotnet/src/{Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj => Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj} (81%) rename dotnet/src/{Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs => Microsoft.Agents.AI.Declarative.AzureAI/OpenAIPromptAgentFactory.cs} (56%) create mode 100644 dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponsesPromptAgentFactory.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 7baeecd6f2..5358c02f92 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -402,7 +402,7 @@ - + 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 deleted file mode 100644 index e45fe4c5e5..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/CodeInterpreterToolExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 deleted file mode 100644 index e582672f93..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FileSearchToolExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index 73bd9b41b2..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/FunctionToolExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index da769856dd..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedCodeInterpreterToolExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 846e28f226..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedFileSearchToolExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index a879a105bc..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedMcpServerToolExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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 deleted file mode 100644 index f13c0ec2d4..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/HostedWebSearchToolExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index 0e74f53e9a..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/McpServerToolExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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 deleted file mode 100644 index 3fb7c64c1b..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/PromptAgentExtensions.cs +++ /dev/null @@ -1,142 +0,0 @@ -// 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 deleted file mode 100644 index 24c172c888..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataTypeExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 deleted file mode 100644 index a2c4d490d6..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/RecordDataValueExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index b0f65442af..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Extensions/WebSearchToolExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index 148e251b18..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryAgentFactory.cs +++ /dev/null @@ -1,130 +0,0 @@ -// 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.PowerFx; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Agents.AI; - -/// -/// Provides an which creates instances of using a . -/// -public sealed class FoundryAgentFactory : PromptAgentFactory -{ - 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. - /// Optional , if none is provided a default instance will be created. - /// The instance to use for configuration. - public FoundryAgentFactory(PersistentAgentsClient agentClient, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, configuration) - { - Throw.IfNull(agentClient); - - this._agentClient = agentClient; - } - - /// - /// Creates a new instance of the class with an associated . - /// - /// The to use for authenticating requests. - /// Optional , if none is provided a default instance will be created. - /// The instance to use for configuration. - public FoundryAgentFactory(TokenCredential tokenCredential, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, 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); - */ - - var createPersistentAgentResponse = agentClient.Administration.CreateAgent( - model: modelId, - name: promptAgent.Name, - instructions: promptAgent.Instructions?.ToTemplateString(), - tools: tools, - toolResources: toolResources, - temperature: (float?)modelOptions?.Temperature?.LiteralValue, - topP: (float?)modelOptions?.TopP?.LiteralValue, - responseFormat: responseFormat, - metadata: promptAgent.Metadata?.ToDictionary(), - cancellationToken: cancellationToken); - - // Get a local proxy for the agent to work with. - return agentClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: null, services: null, cancellationToken: cancellationToken); - } - - private PersistentAgentsClient 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 PersistentAgentsClient(endpoint, this._tokenCredential); - } - - throw new InvalidOperationException("A PersistentAgentsClient must be registered in the service provider or a RemoteConnection must be specified in the agent definition model connection to create a PersistentAgentsClient."); - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs deleted file mode 100644 index ae9bc7756e..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/FoundryPersistentAgentFactory.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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/OpenAIAssistantAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs deleted file mode 100644 index 621736e6dd..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAssistantAgentFactory.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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/OpenAIResponseAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs deleted file mode 100644 index 6da009135d..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIResponseAgentFactory.cs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 -} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/AzureAIPromptAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/AzureAIPromptAgentFactory.cs new file mode 100644 index 0000000000..e8ec6d7028 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/AzureAIPromptAgentFactory.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Projects; +using Azure.Core; +using Microsoft.Agents.ObjectModel; +using Microsoft.Extensions.Configuration; +using Microsoft.PowerFx; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class AzureAIPromptAgentFactory : PromptAgentFactory +{ + private readonly AIProjectClient? _projectClient; + private readonly TokenCredential? _tokenCredential; + + /// + /// Creates a new instance of the class with an associated . + /// + /// The instance to use for creating agents. + /// Optional , if none is provided a default instance will be created. + /// The instance to use for configuration. + public AzureAIPromptAgentFactory(AIProjectClient projectClient, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, configuration) + { + Throw.IfNull(projectClient); + + this._projectClient = projectClient; + } + + /// + /// Creates a new instance of the class with an associated . + /// + /// The to use for authenticating requests. + /// Optional , if none is provided a default instance will be created. + /// The instance to use for configuration. + public AzureAIPromptAgentFactory(TokenCredential tokenCredential, RecalcEngine? engine = null, IConfiguration? configuration = null) : base(engine, configuration) + { + Throw.IfNull(tokenCredential); + + this._tokenCredential = tokenCredential; + } + + /// + public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default) + { + Throw.IfNull(promptAgent); + Throw.IfNullOrEmpty(promptAgent.Name); + + var projectClient = this._projectClient ?? this.CreateAIProjectClient(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."); + } + + return await projectClient.CreateAIAgentAsync( + name: promptAgent.Name, + model: modelId, + instructions: promptAgent.Instructions?.ToTemplateString() ?? string.Empty, + description: promptAgent.Description, + tools: promptAgent.GetAITools(), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private AIProjectClient CreateAIProjectClient(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 AIProjectClient."); + } + if (this._tokenCredential is null) + { + throw new InvalidOperationException("A TokenCredential must be registered in the service provider to create an AIProjectClient."); + } + return new AIProjectClient(new Uri(endpoint), this._tokenCredential); + } + + throw new InvalidOperationException("A AIProjectClient must be registered in the service provider or a FoundryConnection must be specified in the agent definition model connection to create an AIProjectClient."); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/BaseOpenAIPromptAgentFactory.cs similarity index 81% rename from dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs rename to dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/BaseOpenAIPromptAgentFactory.cs index a598292e75..3f84776d67 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIAgentFactory.cs +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/BaseOpenAIPromptAgentFactory.cs @@ -3,9 +3,10 @@ using System.ClientModel; using Azure.AI.OpenAI; using Azure.Core; -using Microsoft.Bot.ObjectModel; +using Microsoft.Agents.ObjectModel; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.PowerFx; using Microsoft.Shared.Diagnostics; using OpenAI; using OpenAI.Assistants; @@ -15,22 +16,22 @@ namespace Microsoft.Agents.AI; /// -/// Provides an abstract base class. +/// Provides an abstract base class. /// -public abstract class OpenAIAgentFactory : AgentFactory +public abstract class BaseOpenAIPromptAgentFactory : PromptAgentFactory { /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - protected OpenAIAgentFactory(IConfiguration? configuration, ILoggerFactory? loggerFactory) : base(configuration) + protected BaseOpenAIPromptAgentFactory(RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration) { this.LoggerFactory = loggerFactory; } /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - protected OpenAIAgentFactory(Uri endpoint, TokenCredential tokenCredential, IConfiguration? configuration, ILoggerFactory? loggerFactory) : base(configuration) + protected BaseOpenAIPromptAgentFactory(Uri endpoint, TokenCredential tokenCredential, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration) { Throw.IfNull(endpoint); Throw.IfNull(tokenCredential); @@ -88,21 +89,21 @@ protected OpenAIAgentFactory(Uri endpoint, TokenCredential tokenCredential, ICon } /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - protected OpenAIResponseClient? CreateResponseClient(GptComponentMetadata promptAgent) + protected ResponsesClient? CreateResponseClient(GptComponentMetadata promptAgent) { var model = promptAgent.Model as CurrentModels; var provider = model?.Provider?.Value ?? ModelProvider.OpenAI; if (provider == ModelProvider.OpenAI) { - return this.CreateOpenAIResponseClient(promptAgent); + return this.CreateResponsesClient(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 CreateAzureResponsesClient(promptAgent, this._endpoint, this._tokenCredential); } return null; @@ -144,20 +145,20 @@ private static AssistantClient CreateAzureOpenAIAssistantClient(GptComponentMeta return new AzureOpenAIClient(endpoint, tokenCredential).GetAssistantClient(); } - private OpenAIResponseClient CreateOpenAIResponseClient(GptComponentMetadata promptAgent) + private ResponsesClient CreateResponsesClient(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); + return this.CreateOpenAIClient(promptAgent).GetResponsesClient(modelId); } - private static OpenAIResponseClient CreateAzureOpenAIResponseClient(GptComponentMetadata promptAgent, Uri endpoint, TokenCredential tokenCredential) + private static ResponsesClient CreateAzureResponsesClient(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); + return new AzureOpenAIClient(endpoint, tokenCredential).GetResponsesClient(deploymentName); } private OpenAIClient CreateOpenAIClient(GptComponentMetadata promptAgent) diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/JsonSchemaFunctionParameters.cs similarity index 100% rename from dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/JsonSchemaFunctionParameters.cs rename to dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/JsonSchemaFunctionParameters.cs 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/Microsoft.Agents.AI.Declarative.AzureAI.csproj similarity index 81% rename from dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj rename to dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj index a74a845fef..f2b63182ec 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/Microsoft.Agents.AI.Declarative.AzureAI.Persistent.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj @@ -22,20 +22,22 @@ - + + - - - + + + + diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIPromptAgentFactory.cs similarity index 56% rename from dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs rename to dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIPromptAgentFactory.cs index f27c8ca6a5..162e9dcebb 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI.Persistent/OpenAIChatAgentFactory.cs +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIPromptAgentFactory.cs @@ -3,34 +3,34 @@ 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.Agents.ObjectModel; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.PowerFx; using Microsoft.Shared.Diagnostics; using OpenAI.Chat; namespace Microsoft.Agents.AI; /// -/// Provides an which creates instances of using a . +/// Provides an which creates instances of using a . /// -public sealed class OpenAIChatAgentFactory : OpenAIAgentFactory +public sealed class OpenAIPromptAgentFactory : BaseOpenAIPromptAgentFactory { /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - public OpenAIChatAgentFactory(IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + public OpenAIPromptAgentFactory(IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration, loggerFactory) { this._functions = functions; } /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - public OpenAIChatAgentFactory(ChatClient chatClient, IList? functions = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(configuration, loggerFactory) + public OpenAIPromptAgentFactory(ChatClient chatClient, IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration, loggerFactory) { Throw.IfNull(chatClient); @@ -39,9 +39,9 @@ public OpenAIChatAgentFactory(ChatClient chatClient, IList? function } /// - /// Creates a new instance of the class. + /// 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) + public OpenAIPromptAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, engine, configuration, loggerFactory) { this._functions = functions; } @@ -62,8 +62,7 @@ public OpenAIChatAgentFactory(Uri endpoint, TokenCredential tokenCredential, ILi { Name = promptAgent.Name, Description = promptAgent.Description, - Instructions = promptAgent.Instructions?.ToTemplateString(), - ChatOptions = promptAgent.GetChatOptions(this._functions), + ChatOptions = promptAgent.GetChatOptions(this.Engine, this._functions), }; ChatClient? chatClient = this._chatClient ?? this.CreateChatClient(promptAgent); diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponsesPromptAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponsesPromptAgentFactory.cs new file mode 100644 index 0000000000..d71365a855 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/OpenAIResponsesPromptAgentFactory.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.Agents.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.PowerFx; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace Microsoft.Agents.AI; + +/// +/// Provides an which creates instances of using a . +/// +public sealed class OpenAIResponsesPromptAgentFactory : BaseOpenAIPromptAgentFactory +{ + /// + /// Creates a new instance of the class. + /// + public OpenAIResponsesPromptAgentFactory(IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration, loggerFactory) + { + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIResponsesPromptAgentFactory(ResponsesClient responsesClient, IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration, loggerFactory) + { + Throw.IfNull(responsesClient); + + this._responsesClient = responsesClient; + this._functions = functions; + } + + /// + /// Creates a new instance of the class. + /// + public OpenAIResponsesPromptAgentFactory(Uri endpoint, TokenCredential tokenCredential, IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(endpoint, tokenCredential, engine, 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, + ChatOptions = promptAgent.GetChatOptions(this.Engine, this._functions), + }; + + var responseClient = this._responsesClient ?? this.CreateResponseClient(promptAgent); + if (responseClient is not null) + { + return new ChatClientAgent( + responseClient.AsIChatClient(), + options, + this.LoggerFactory); + } + + return null; + } + + #region private + private readonly ResponsesClient? _responsesClient; + private readonly IList? _functions; + #endregion +} diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs index 0da3f18f85..4255738c96 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs @@ -58,7 +58,7 @@ public static class PromptAgentExtensions /// /// Instance of /// Instance of - internal static List? GetAITools(this GptComponentMetadata promptAgent, IList? functions) + public static List? GetAITools(this GptComponentMetadata promptAgent, IList? functions = null) { return promptAgent.Tools.Select(tool => { From a1d5a3546651786b6e18cf2a947fa616bda6571e Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:26:49 +0000 Subject: [PATCH 4/8] Add unit test project for Microsoft.Agents.AI.Declarative.AzureAI --- dotnet/agent-framework-dotnet.slnx | 1 + .../Microsoft.Agents.AI.Declarative.AzureAI.csproj | 1 + ...oft.Agents.AI.Declarative.AzureAI.UnitTests.csproj | 11 +++++++++++ 3 files changed, 13 insertions(+) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 60276808fd..2dc7ef14f9 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -447,6 +447,7 @@ + diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj index f2b63182ec..3af7c1d385 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Declarative.AzureAI/Microsoft.Agents.AI.Declarative.AzureAI.csproj @@ -47,6 +47,7 @@ + diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj new file mode 100644 index 0000000000..5c304f1572 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj @@ -0,0 +1,11 @@ + + + + $(NoWarn);IDE1006;VSTHRD200 + + + + + + + From 668bc7dbf7ce4876b491c53e1690a10f00b454ed Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:35:05 +0000 Subject: [PATCH 5/8] Add unit tests for Microsoft.Agents.AI.Declarative.AzureAI --- .../Microsoft.Agents.AI.Declarative.csproj | 1 + .../AzureAIPromptAgentFactoryTests.cs | 137 +++++++++++++++++ ...ts.AI.Declarative.AzureAI.UnitTests.csproj | 2 +- .../OpenAIPromptAgentFactoryTests.cs | 135 +++++++++++++++++ .../OpenAIResponsesPromptAgentFactoryTests.cs | 138 ++++++++++++++++++ 5 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs create mode 100644 dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs create mode 100644 dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj index 3b75b63236..c9f09ce90d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj @@ -39,6 +39,7 @@ + diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs new file mode 100644 index 0000000000..bc4f6cfb9d --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading.Tasks; +using Azure.AI.Projects; +using Azure.Core; +using Microsoft.Agents.ObjectModel; +using Moq; + +namespace Microsoft.Agents.AI.Declarative.AzureAI.UnitTests; + +/// +/// Unit tests for . +/// +public sealed class AzureAIPromptAgentFactoryTests +{ + [Fact] + public void Constructor_WithProjectClient_ThrowsForNull() + { + // Arrange & Act & Assert + Assert.Throws(() => new AzureAIPromptAgentFactory(projectClient: null!)); + } + + [Fact] + public void Constructor_WithTokenCredential_ThrowsForNull() + { + // Arrange & Act & Assert + Assert.Throws(() => new AzureAIPromptAgentFactory(tokenCredential: null!)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsForNullPromptAgentAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(null!)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsForNullOrEmptyNameAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + GptComponentMetadata promptAgent = new(name: null!); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(promptAgent)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsForEmptyNameAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + GptComponentMetadata promptAgent = new(name: string.Empty); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(promptAgent)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsWhenModelIdIsNullAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + GptComponentMetadata promptAgent = new("TestAgent"); + + // Act & Assert + InvalidOperationException exception = await Assert.ThrowsAsync(() => factory.TryCreateAsync(promptAgent)); + Assert.Contains("model id must be specified", exception.Message); + } + + [Fact] + public async Task TryCreateAsync_ThrowsWhenNoProjectClientAndNoConnectionAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + GptComponentMetadata promptAgent = CreateTestPromptAgentWithoutConnection(); + + // Act & Assert + InvalidOperationException exception = await Assert.ThrowsAsync(() => factory.TryCreateAsync(promptAgent)); + Assert.Contains("AIProjectClient must be registered", exception.Message); + } + + [Fact] + public async Task TryCreateAsync_ThrowsWhenEndpointIsEmptyAsync() + { + // Arrange + Mock mockCredential = new(); + AzureAIPromptAgentFactory factory = new(mockCredential.Object); + GptComponentMetadata promptAgent = CreateTestPromptAgentWithEmptyEndpoint(); + + // Act & Assert + InvalidOperationException exception = await Assert.ThrowsAsync(() => factory.TryCreateAsync(promptAgent)); + Assert.Contains("endpoint must be specified", exception.Message); + } + + private static GptComponentMetadata CreateTestPromptAgentWithoutConnection() + { + string agentYaml = + """ + kind: Prompt + name: Test Agent + description: Test Description + instructions: You are a helpful assistant. + model: + id: gpt-4o + """; + + return AgentBotElementYaml.FromYaml(agentYaml); + } + + private static GptComponentMetadata CreateTestPromptAgentWithEmptyEndpoint() + { + string agentYaml = + """ + kind: Prompt + name: Test Agent + description: Test Description + instructions: You are a helpful assistant. + model: + id: gpt-4o + connection: + kind: Remote + endpoint: "" + """; + + return AgentBotElementYaml.FromYaml(agentYaml); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj index 5c304f1572..fe9fb91041 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests.csproj @@ -1,4 +1,4 @@ - + $(NoWarn);IDE1006;VSTHRD200 diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs new file mode 100644 index 0000000000..3bdc654d4e --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading.Tasks; +using Microsoft.Agents.ObjectModel; +using Moq; +using OpenAI.Chat; + +namespace Microsoft.Agents.AI.Declarative.AzureAI.UnitTests; + +/// +/// Unit tests for . +/// +public sealed class OpenAIPromptAgentFactoryTests +{ + [Fact] + public void Constructor_WithChatClient_ThrowsForNull() + { + // Arrange & Act & Assert + Assert.Throws(() => new OpenAIPromptAgentFactory(chatClient: null!)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsForNullPromptAgentAsync() + { + // Arrange + OpenAIPromptAgentFactory factory = new(); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(null!)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsWhenModelIsNullAsync() + { + // Arrange + OpenAIPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = new("TestAgent"); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(null!)); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsUnknownAsync() + { + // Arrange + OpenAIPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Unknown"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsResponsesAsync() + { + // Arrange + OpenAIPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Responses"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsAssistantsAsync() + { + // Arrange + OpenAIPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Assistants"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsChatClientAgent_WhenChatClientProvidedAsync() + { + // Arrange + ChatClient chatClient = new("gpt-4o", "test-api-key"); + OpenAIPromptAgentFactory factory = new(chatClient); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Chat"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsChatClientAgent_WithCorrectOptionsAsync() + { + // Arrange + ChatClient chatClient = new("gpt-4o", "test-api-key"); + OpenAIPromptAgentFactory factory = new(chatClient); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Chat"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.NotNull(result); + ChatClientAgent agent = Assert.IsType(result); + Assert.Equal("Test Agent", agent.Name); + Assert.Equal("Test Description", agent.Description); + } + + private static GptComponentMetadata CreateTestPromptAgent(string apiType) + { + string agentYaml = + $""" + kind: Prompt + name: Test Agent + description: Test Description + instructions: You are a helpful assistant. + model: + id: gpt-4o + apiType: {apiType} + """; + + return AgentBotElementYaml.FromYaml(agentYaml); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs new file mode 100644 index 0000000000..f799eb543e --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading.Tasks; +using Microsoft.Agents.ObjectModel; +using OpenAI; +using OpenAI.Responses; + +namespace Microsoft.Agents.AI.Declarative.AzureAI.UnitTests; + +/// +/// Unit tests for . +/// +public sealed class OpenAIResponsesPromptAgentFactoryTests +{ + [Fact] + public void Constructor_WithResponsesClient_ThrowsForNull() + { + // Arrange & Act & Assert + Assert.Throws(() => new OpenAIResponsesPromptAgentFactory(responsesClient: null!)); + } + + [Fact] + public async Task TryCreateAsync_ThrowsForNullPromptAgentAsync() + { + // Arrange + OpenAIResponsesPromptAgentFactory factory = new(); + + // Act & Assert + await Assert.ThrowsAsync(() => factory.TryCreateAsync(null!)); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenModelIsNullAsync() + { + // Arrange + OpenAIResponsesPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = new("TestAgent"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsUnknownAsync() + { + // Arrange + OpenAIResponsesPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Unknown"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsChatAsync() + { + // Arrange + OpenAIResponsesPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Chat"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsNull_WhenApiTypeIsAssistantsAsync() + { + // Arrange + OpenAIResponsesPromptAgentFactory factory = new(); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Assistants"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsChatClientAgent_WhenResponsesClientProvidedAsync() + { + // Arrange + ResponsesClient responsesClient = new OpenAIClient("test-api-key").GetResponsesClient("gpt-4o"); + OpenAIResponsesPromptAgentFactory factory = new(responsesClient); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Responses"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task TryCreateAsync_ReturnsChatClientAgent_WithCorrectOptionsAsync() + { + // Arrange + ResponsesClient responsesClient = new OpenAIClient("test-api-key").GetResponsesClient("gpt-4o"); + OpenAIResponsesPromptAgentFactory factory = new(responsesClient); + GptComponentMetadata promptAgent = CreateTestPromptAgent(apiType: "Responses"); + + // Act + AIAgent? result = await factory.TryCreateAsync(promptAgent); + + // Assert + Assert.NotNull(result); + ChatClientAgent agent = Assert.IsType(result); + Assert.Equal("Test Agent", agent.Name); + Assert.Equal("Test Description", agent.Description); + } + + private static GptComponentMetadata CreateTestPromptAgent(string apiType) + { + string agentYaml = + $""" + kind: Prompt + name: Test Agent + description: Test Description + instructions: You are a helpful assistant. + model: + id: gpt-4o + apiType: {apiType} + """; + + return AgentBotElementYaml.FromYaml(agentYaml); + } +} From 14401f7c73f2e95edaee4871a6cbc97adff092ca Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:49:31 +0000 Subject: [PATCH 6/8] Fix formatting --- .../AzureAIPromptAgentFactoryTests.cs | 7 +++---- .../OpenAIPromptAgentFactoryTests.cs | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs index bc4f6cfb9d..270b109e56 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/AzureAIPromptAgentFactoryTests.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading.Tasks; -using Azure.AI.Projects; using Azure.Core; using Microsoft.Agents.ObjectModel; using Moq; @@ -104,7 +103,7 @@ public async Task TryCreateAsync_ThrowsWhenEndpointIsEmptyAsync() private static GptComponentMetadata CreateTestPromptAgentWithoutConnection() { - string agentYaml = + const string agentYaml = """ kind: Prompt name: Test Agent @@ -119,7 +118,7 @@ private static GptComponentMetadata CreateTestPromptAgentWithoutConnection() private static GptComponentMetadata CreateTestPromptAgentWithEmptyEndpoint() { - string agentYaml = + const string agentYaml = """ kind: Prompt name: Test Agent diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs index 3bdc654d4e..4cde729a88 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIPromptAgentFactoryTests.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.Agents.ObjectModel; -using Moq; using OpenAI.Chat; namespace Microsoft.Agents.AI.Declarative.AzureAI.UnitTests; From e92425e54f17558a7ad121edf0f307ef33892cde Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:01:40 +0000 Subject: [PATCH 7/8] More formatting --- .../OpenAIResponsesPromptAgentFactoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs index f799eb543e..16c6402f8f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.AzureAI.UnitTests/OpenAIResponsesPromptAgentFactoryTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading.Tasks; From 1d19b694888e610cf8e0c5ae0d38088f42ba2fd6 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:29:21 +0000 Subject: [PATCH 8/8] New sample for declarative OpenAI --- agent-samples/openai/OpenAIAssistants.yaml | 28 --------- dotnet/agent-framework-dotnet.slnx | 1 + .../OpenAI/DeclarativeOpenAIAgents.csproj | 25 ++++++++ .../DeclarativeAgents/OpenAI/Program.cs | 61 ++++++++++++++++++ .../OpenAI/Properties/launchSettings.json | 16 +++++ .../DeclarativeAgents/OpenAI/README.md | 63 +++++++++++++++++++ 6 files changed, 166 insertions(+), 28 deletions(-) delete mode 100644 agent-samples/openai/OpenAIAssistants.yaml create mode 100644 dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj create mode 100644 dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs create mode 100644 dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Properties/launchSettings.json create mode 100644 dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/README.md diff --git a/agent-samples/openai/OpenAIAssistants.yaml b/agent-samples/openai/OpenAIAssistants.yaml deleted file mode 100644 index 1318051120..0000000000 --- a/agent-samples/openai/OpenAIAssistants.yaml +++ /dev/null @@ -1,28 +0,0 @@ -kind: Prompt -name: Assistant -description: Helpful assistant -instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response. -model: - id: gpt-4.1-mini - provider: OpenAI - apiType: Assistants - options: - temperature: 0.9 - topP: 0.95 - connection: - kind: ApiKey - key: =Env.OPENAI_API_KEY -outputSchema: - properties: - language: - type: string - required: true - description: The language of the answer. - answer: - type: string - required: true - description: The answer text. - type: - type: string - required: true - description: The type of the response. diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 2dc7ef14f9..333ba4262d 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -98,6 +98,7 @@ + diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj new file mode 100644 index 0000000000..7fd4be0da4 --- /dev/null +++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/DeclarativeOpenAIAgents.csproj @@ -0,0 +1,25 @@ + + + + Exe + net10.0 + + enable + enable + + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs new file mode 100644 index 0000000000..2e66f08916 --- /dev/null +++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Program.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to load an AI agent from a YAML file and process a prompt using Azure OpenAI as the backend. +// Unlike the ChatClient sample, this uses the OpenAIPromptAgentFactory which can create a ChatClient from the YAML model definition. + +using System.ComponentModel; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; + +string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); + +// Read command-line arguments +if (args.Length < 2) +{ + Console.WriteLine("Usage: DeclarativeOpenAIAgents "); + Console.WriteLine(" : The path to the YAML file containing the agent definition"); + Console.WriteLine(" : The prompt to send to the agent"); + return; +} + +string yamlFilePath = args[0]; +string prompt = args[1]; + +// Verify the YAML file exists +if (!File.Exists(yamlFilePath)) +{ + Console.WriteLine($"Error: File not found: {yamlFilePath}"); + return; +} + +// Read the YAML content from the file +string text = await File.ReadAllTextAsync(yamlFilePath); + +// Example function tool that can be used by the agent. +[Description("Get the weather for a given location.")] +static string GetWeather( + [Description("The city and state, e.g. San Francisco, CA")] string location, + [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit) + => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}."; + +// Create the configuration with the Azure OpenAI endpoint +IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["AzureOpenAI:Endpoint"] = endpoint, + }) + .Build(); + +// Create the agent from the YAML definition. +// OpenAIPromptAgentFactory can create a ChatClient based on the model defined in the YAML file. +OpenAIPromptAgentFactory agentFactory = new( + new Uri(endpoint), + new AzureCliCredential(), + [AIFunctionFactory.Create(GetWeather, "GetWeather")], + configuration: configuration); +AIAgent? agent = await agentFactory.CreateFromYamlAsync(text); + +// Invoke the agent and output the text result. +Console.WriteLine(await agent!.RunAsync(prompt)); diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Properties/launchSettings.json b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Properties/launchSettings.json new file mode 100644 index 0000000000..d70b1eb090 --- /dev/null +++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "OpenAI": { + "commandName": "Project", + "commandLineArgs": "..\\..\\..\\..\\..\\..\\..\\..\\agent-samples\\openai\\OpenAI.yaml \"What is the weather in Cambridge, MA in °C?\"" + }, + "OpenAIChat": { + "commandName": "Project", + "commandLineArgs": "..\\..\\..\\..\\..\\..\\..\\..\\agent-samples\\openai\\OpenAIChat.yaml \"What is the weather in Cambridge, MA in °C?\"" + }, + "OpenAIResponses": { + "commandName": "Project", + "commandLineArgs": "..\\..\\..\\..\\..\\..\\..\\..\\agent-samples\\openai\\OpenAIResponses.yaml \"What is the weather in Cambridge, MA in °C?\"" + } + } +} \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/README.md b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/README.md new file mode 100644 index 0000000000..2b93f40fe6 --- /dev/null +++ b/dotnet/samples/GettingStarted/DeclarativeAgents/OpenAI/README.md @@ -0,0 +1,63 @@ +# Declarative OpenAI Agents + +This sample demonstrates how to use the `OpenAIPromptAgentFactory` to create AI agents from YAML definitions. + +## Overview + +Unlike the `ChatClientPromptAgentFactory` which requires you to create an `IChatClient` upfront, the `OpenAIPromptAgentFactory` can create the chat client based on the model definition in the YAML file. This is useful when: + +- You want the model to be defined declaratively in the YAML file +- You need to support multiple models without changing code +- You want to use Azure OpenAI endpoints with token-based authentication + +## Prerequisites + +- .NET 10.0 SDK +- Azure OpenAI endpoint access +- Azure CLI installed and authenticated (`az login`) + +## Configuration + +Set the following environment variable: + +```bash +# Required: Azure OpenAI endpoint URL +set AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/ +``` + +## Running the Sample + +```bash +dotnet run -- +``` + +### Example + +```bash +dotnet run -- agent.yaml "What is the weather in Seattle?" +``` + +## Sample YAML Agent Definition + +Create a file named `agent.yaml` with the following content: + +```yaml +name: WeatherAgent +description: An agent that can provide weather information +model: + api: chat + configuration: + azure_deployment: gpt-4o-mini +instructions: | + You are a helpful assistant that provides weather information. + Use the GetWeather function when asked about weather conditions. +``` + +## Key Differences from ChatClient Sample + +| Feature | ChatClient Sample | OpenAI Sample | +|---------|------------------|---------------| +| Chat client creation | Manual (in code) | Automatic (from YAML model definition) | +| Model selection | Code-specified | YAML-specified | +| Factory class | `ChatClientPromptAgentFactory` | `OpenAIPromptAgentFactory` | +| Authentication | Passed to `AzureOpenAIClient` | Passed to factory constructor |