From a356a165681d0ecd3583250aeff2159820269ce8 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:51:41 +0100 Subject: [PATCH] .NET: Remove OpenAIAssistantClientExtensions class (#5058) * Remove OpenAIAssistantClientExtensions class Remove the deprecated OpenAI Assistants API extension methods, along with their unit tests, integration tests, sample project, and related references. - Delete OpenAIAssistantClientExtensions.cs (source) - Delete OpenAIAssistantClientExtensionsTests.cs (unit + integration tests) - Delete Agent_With_OpenAIAssistants sample project - Remove sample from solution file, README, and verify-samples definitions - Remove AIOpenAIAssistants diagnostic ID constant Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * add removed extension methods to the suppression file --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/agent-framework-dotnet.slnx | 1 - dotnet/eng/verify-samples/AgentsSamples.cs | 13 - .../Agent_With_OpenAIAssistants.csproj | 15 - .../Agent_With_OpenAIAssistants/Program.cs | 41 - .../Agent_With_OpenAIAssistants/README.md | 16 - .../02-agents/AgentProviders/README.md | 1 - .../CompatibilitySuppressions.xml | 35 + .../OpenAIAssistantClientExtensions.cs | 433 ------- .../Shared/DiagnosticIds/DiagnosticsIds.cs | 1 - .../OpenAIAssistantClientExtensionsTests.cs | 1013 ----------------- .../OpenAIAssistantClientExtensionsTests.cs | 268 ----- 11 files changed, 35 insertions(+), 1802 deletions(-) delete mode 100644 dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj delete mode 100644 dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Program.cs delete mode 100644 dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/README.md delete mode 100644 dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs delete mode 100644 dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs delete mode 100644 dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 20ea58eaeb..22c37b1d5b 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -34,7 +34,6 @@ - diff --git a/dotnet/eng/verify-samples/AgentsSamples.cs b/dotnet/eng/verify-samples/AgentsSamples.cs index 87707a384e..f44dd2f1f6 100644 --- a/dotnet/eng/verify-samples/AgentsSamples.cs +++ b/dotnet/eng/verify-samples/AgentsSamples.cs @@ -781,19 +781,6 @@ internal static class AgentsSamples SkipReason = "Requires local Ollama server.", }, - new SampleDefinition - { - Name = "Agent_With_OpenAIAssistants", - ProjectPath = "samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants", - RequiredEnvironmentVariables = ["OPENAI_API_KEY"], - OptionalEnvironmentVariables = ["OPENAI_CHAT_MODEL_NAME"], - ExpectedOutputDescription = - [ - "The output should contain a joke about a pirate from the OpenAI Assistants API.", - "The output should not contain error messages or stack traces.", - ], - }, - new SampleDefinition { Name = "Agent_With_OpenAIChatCompletion", diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj b/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj deleted file mode 100644 index eeda3eef6f..0000000000 --- a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net10.0 - - enable - enable - - - - - - - diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Program.cs deleted file mode 100644 index 02d19ab52c..0000000000 --- a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -// This sample shows how to create and use a simple AI agent with OpenAI Assistants as the backend. - -// WARNING: The Assistants API is deprecated and will be shut down. -// For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration - -#pragma warning disable CS0618 // Type or member is obsolete - OpenAI Assistants API is deprecated but still used in this sample - -using Microsoft.Agents.AI; -using OpenAI; -using OpenAI.Assistants; - -var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set."); -var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini"; - -const string JokerName = "Joker"; -const string JokerInstructions = "You are good at telling jokes."; - -// Get a client to create/retrieve server side agents with. -var assistantClient = new OpenAIClient(apiKey).GetAssistantClient(); - -// You can create a server side assistant with the OpenAI SDK. -var createResult = await assistantClient.CreateAssistantAsync(model, new() { Name = JokerName, Instructions = JokerInstructions }); - -// You can retrieve an already created server side assistant as an AIAgent. -AIAgent agent1 = await assistantClient.GetAIAgentAsync(createResult.Value.Id); - -// You can also create a server side assistant and return it as an AIAgent directly. -AIAgent agent2 = await assistantClient.CreateAIAgentAsync( - model: model, - name: JokerName, - instructions: JokerInstructions); - -// You can invoke the agent like any other AIAgent. -AgentSession session = await agent1.CreateSessionAsync(); -Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session)); - -// Cleanup for sample purposes. -await assistantClient.DeleteAssistantAsync(agent1.Id); -await assistantClient.DeleteAssistantAsync(agent2.Id); diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/README.md deleted file mode 100644 index b0a7638ab5..0000000000 --- a/dotnet/samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Prerequisites - -WARNING: The Assistants API is deprecated and will be shut down. -For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration - -Before you begin, ensure you have the following prerequisites: - -- .NET 10 SDK or later -- OpenAI API key - -Set the following environment variables: - -```powershell -$env:OPENAI_API_KEY="*****" # Replace with your OpenAI API key -$env:OPENAI_CHAT_MODEL_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini -``` diff --git a/dotnet/samples/02-agents/AgentProviders/README.md b/dotnet/samples/02-agents/AgentProviders/README.md index ee83f8c08c..5584fdc810 100644 --- a/dotnet/samples/02-agents/AgentProviders/README.md +++ b/dotnet/samples/02-agents/AgentProviders/README.md @@ -25,7 +25,6 @@ See the README.md for each sample for the prerequisites for that sample. |[Creating an AIAgent with GitHub Copilot](./Agent_With_GitHubCopilot/)|This sample demonstrates how to create an AIAgent using GitHub Copilot SDK as the underlying inference service| |[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service| |[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service| -|[Creating an AIAgent with OpenAI Assistants](./Agent_With_OpenAIAssistants/)|This sample demonstrates how to create an AIAgent using OpenAI Assistants as the underlying inference service.
WARNING: The Assistants API is deprecated and will be shut down. For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration| |[Creating an AIAgent with OpenAI ChatCompletion](./Agent_With_OpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using OpenAI ChatCompletion as the underlying inference service| |[Creating an AIAgent with OpenAI Responses](./Agent_With_OpenAIResponses/)|This sample demonstrates how to create an AIAgent using OpenAI Responses as the underlying inference service| diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/CompatibilitySuppressions.xml b/dotnet/src/Microsoft.Agents.AI.OpenAI/CompatibilitySuppressions.xml index fe04c26eca..25439ed1a1 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/CompatibilitySuppressions.xml +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/CompatibilitySuppressions.xml @@ -1,6 +1,41 @@  + + CP0001 + T:OpenAI.Assistants.OpenAIAssistantClientExtensions + lib/net10.0/Microsoft.Agents.AI.OpenAI.dll + lib/net10.0/Microsoft.Agents.AI.OpenAI.dll + true + + + CP0001 + T:OpenAI.Assistants.OpenAIAssistantClientExtensions + lib/net472/Microsoft.Agents.AI.OpenAI.dll + lib/net472/Microsoft.Agents.AI.OpenAI.dll + true + + + CP0001 + T:OpenAI.Assistants.OpenAIAssistantClientExtensions + lib/net8.0/Microsoft.Agents.AI.OpenAI.dll + lib/net8.0/Microsoft.Agents.AI.OpenAI.dll + true + + + CP0001 + T:OpenAI.Assistants.OpenAIAssistantClientExtensions + lib/net9.0/Microsoft.Agents.AI.OpenAI.dll + lib/net9.0/Microsoft.Agents.AI.OpenAI.dll + true + + + CP0001 + T:OpenAI.Assistants.OpenAIAssistantClientExtensions + lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll + lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll + true + CP0002 M:OpenAI.Responses.OpenAIResponseClientExtensions.AsAIAgent(OpenAI.Responses.ResponsesClient,Microsoft.Agents.AI.ChatClientAgentOptions,System.Func{Microsoft.Extensions.AI.IChatClient,Microsoft.Extensions.AI.IChatClient},Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider) diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs deleted file mode 100644 index a1f083ae06..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.ClientModel; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Agents.AI; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Logging; -using Microsoft.Shared.DiagnosticIds; -using Microsoft.Shared.Diagnostics; - -namespace OpenAI.Assistants; - -/// -/// Provides extension methods for OpenAI -/// to simplify the creation of AI agents that work with OpenAI services. -/// -/// -/// These extensions bridge the gap between OpenAI SDK client objects and the Microsoft Agent Framework, -/// allowing developers to easily create AI agents that leverage OpenAI's chat completion and response services. -/// The methods handle the conversion from OpenAI clients to instances and then wrap them -/// in objects that implement the interface. -/// -[Experimental(DiagnosticIds.Experiments.AIOpenAIAssistants)] -public static class OpenAIAssistantClientExtensions -{ - /// - /// Gets a from a . - /// - /// The assistant client. - /// The client result containing the assistant. - /// Optional chat options. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// A instance that can be used to perform operations on the assistant. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static ChatClientAgent AsAIAgent( - this AssistantClient assistantClient, - ClientResult assistantClientResult, - ChatOptions? chatOptions = null, - Func? clientFactory = null, - IServiceProvider? services = null) - { - if (assistantClientResult is null) - { - throw new ArgumentNullException(nameof(assistantClientResult)); - } - - return assistantClient.AsAIAgent(assistantClientResult.Value, chatOptions, clientFactory, services); - } - - /// - /// Gets a from an . - /// - /// The assistant client. - /// The assistant metadata. - /// Optional chat options. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// A instance that can be used to perform operations on the assistant. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static ChatClientAgent AsAIAgent( - this AssistantClient assistantClient, - Assistant assistantMetadata, - ChatOptions? chatOptions = null, - Func? clientFactory = null, - IServiceProvider? services = null) - { - if (assistantMetadata is null) - { - throw new ArgumentNullException(nameof(assistantMetadata)); - } - if (assistantClient is null) - { - throw new ArgumentNullException(nameof(assistantClient)); - } - - var chatClient = assistantClient.AsIChatClient(assistantMetadata.Id); - - if (clientFactory is not null) - { - chatClient = clientFactory(chatClient); - } - - if (!string.IsNullOrWhiteSpace(assistantMetadata.Instructions) && chatOptions?.Instructions is null) - { - chatOptions ??= new ChatOptions(); - chatOptions.Instructions = assistantMetadata.Instructions; - } - - return new ChatClientAgent(chatClient, options: new() - { - Id = assistantMetadata.Id, - Name = assistantMetadata.Name, - Description = assistantMetadata.Description, - ChatOptions = chatOptions - }, services: services); - } - - /// - /// Retrieves an existing server side agent, wrapped as a using the provided . - /// - /// The to create the with. - /// The ID of the server side agent to create a for. - /// Options that should apply to all runs of the agent. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// The to monitor for cancellation requests. The default is . - /// A instance that can be used to perform operations on the assistant agent. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static async Task GetAIAgentAsync( - this AssistantClient assistantClient, - string agentId, - ChatOptions? chatOptions = null, - Func? clientFactory = null, - IServiceProvider? services = null, - CancellationToken cancellationToken = default) - { - if (assistantClient is null) - { - throw new ArgumentNullException(nameof(assistantClient)); - } - - if (string.IsNullOrWhiteSpace(agentId)) - { - throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); - } - - var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false); - return assistantClient.AsAIAgent(assistantResponse, chatOptions, clientFactory, services); - } - - /// - /// Gets a from a . - /// - /// The assistant client. - /// The client result containing the assistant. - /// Full set of options to configure the agent. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// A instance that can be used to perform operations on the assistant. - /// or is . - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static ChatClientAgent AsAIAgent( - this AssistantClient assistantClient, - ClientResult assistantClientResult, - ChatClientAgentOptions options, - Func? clientFactory = null, - IServiceProvider? services = null) - { - if (assistantClientResult is null) - { - throw new ArgumentNullException(nameof(assistantClientResult)); - } - - return assistantClient.AsAIAgent(assistantClientResult.Value, options, clientFactory, services); - } - - /// - /// Gets a from an . - /// - /// The assistant client. - /// The assistant metadata. - /// Full set of options to configure the agent. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// A instance that can be used to perform operations on the assistant. - /// or is . - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static ChatClientAgent AsAIAgent( - this AssistantClient assistantClient, - Assistant assistantMetadata, - ChatClientAgentOptions options, - Func? clientFactory = null, - IServiceProvider? services = null) - { - if (assistantMetadata is null) - { - throw new ArgumentNullException(nameof(assistantMetadata)); - } - - if (assistantClient is null) - { - throw new ArgumentNullException(nameof(assistantClient)); - } - - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - var chatClient = assistantClient.AsIChatClient(assistantMetadata.Id); - - if (clientFactory is not null) - { - chatClient = clientFactory(chatClient); - } - - if (string.IsNullOrWhiteSpace(options.ChatOptions?.Instructions) && !string.IsNullOrWhiteSpace(assistantMetadata.Instructions)) - { - options.ChatOptions ??= new ChatOptions(); - options.ChatOptions.Instructions = assistantMetadata.Instructions; - } - - var mergedOptions = new ChatClientAgentOptions() - { - Id = assistantMetadata.Id, - Name = options.Name ?? assistantMetadata.Name, - Description = options.Description ?? assistantMetadata.Description, - ChatOptions = options.ChatOptions, - AIContextProviders = options.AIContextProviders, - ChatHistoryProvider = options.ChatHistoryProvider, - UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs - }; - - return new ChatClientAgent(chatClient, mergedOptions, services: services); - } - - /// - /// Retrieves an existing server side agent, wrapped as a using the provided . - /// - /// The to create the with. - /// The ID of the server side agent to create a for. - /// Full set of options to configure the agent. - /// Provides a way to customize the creation of the underlying used by the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// The to monitor for cancellation requests. The default is . - /// A instance that can be used to perform operations on the assistant agent. - /// or is . - /// is empty or whitespace. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static async Task GetAIAgentAsync( - this AssistantClient assistantClient, - string agentId, - ChatClientAgentOptions options, - Func? clientFactory = null, - IServiceProvider? services = null, - CancellationToken cancellationToken = default) - { - if (assistantClient is null) - { - throw new ArgumentNullException(nameof(assistantClient)); - } - - if (string.IsNullOrWhiteSpace(agentId)) - { - throw new ArgumentException($"{nameof(agentId)} should not be null or whitespace.", nameof(agentId)); - } - - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false); - return assistantClient.AsAIAgent(assistantResponse, options, clientFactory, services); - } - - /// - /// Creates an AI agent from an using the OpenAI Assistant API. - /// - /// The OpenAI to use for the agent. - /// The model identifier to use (e.g., "gpt-4"). - /// Optional system instructions that define the agent's behavior and personality. - /// Optional name for the agent for identification purposes. - /// Optional description of the agent's capabilities and purpose. - /// Optional collection of AI tools that the agent can use during conversations. - /// Provides a way to customize the creation of the underlying used by the agent. - /// Optional logger factory for enabling logging within the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// The to monitor for cancellation requests. The default is . - /// An instance backed by the OpenAI Assistant service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static async Task CreateAIAgentAsync( - this AssistantClient client, - string model, - string? instructions = null, - string? name = null, - string? description = null, - IList? tools = null, - Func? clientFactory = null, - ILoggerFactory? loggerFactory = null, - IServiceProvider? services = null, - CancellationToken cancellationToken = default) => - await client.CreateAIAgentAsync(model, - new ChatClientAgentOptions() - { - Name = name, - Description = description, - ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions() - { - Tools = tools, - Instructions = instructions, - } - }, - clientFactory, - loggerFactory, - services, - cancellationToken).ConfigureAwait(false); - - /// - /// Creates an AI agent from an using the OpenAI Assistant API. - /// - /// The OpenAI to use for the agent. - /// The model identifier to use (e.g., "gpt-4"). - /// Full set of options to configure the agent. - /// Provides a way to customize the creation of the underlying used by the agent. - /// Optional logger factory for enabling logging within the agent. - /// An optional to use for resolving services required by the instances being invoked. - /// The to monitor for cancellation requests. The default is . - /// An instance backed by the OpenAI Assistant service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - [Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")] - public static async Task CreateAIAgentAsync( - this AssistantClient client, - string model, - ChatClientAgentOptions options, - Func? clientFactory = null, - ILoggerFactory? loggerFactory = null, - IServiceProvider? services = null, - CancellationToken cancellationToken = default) - { - Throw.IfNull(client); - Throw.IfNull(model); - Throw.IfNull(options); - - var assistantOptions = new AssistantCreationOptions() - { - Name = options.Name, - Description = options.Description, - Instructions = options.ChatOptions?.Instructions, - }; - - // Convert AITools to ToolDefinitions and ToolResources - var toolDefinitionsAndResources = ConvertAIToolsToToolDefinitions(options.ChatOptions?.Tools); - if (toolDefinitionsAndResources.ToolDefinitions is { Count: > 0 } toolDefinitions) - { - toolDefinitions.ForEach(x => assistantOptions.Tools.Add(x)); - } - if (toolDefinitionsAndResources.ToolResources is not null) - { - assistantOptions.ToolResources = toolDefinitionsAndResources.ToolResources; - } - - // Create the assistant in the assistant service. - var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions, cancellationToken).ConfigureAwait(false); - var assistantId = assistantCreateResult.Value.Id; - - // Build the local agent object. - var chatClient = client.AsIChatClient(assistantId); - if (clientFactory is not null) - { - chatClient = clientFactory(chatClient); - } - - var agentOptions = options.Clone(); - agentOptions.Id = assistantId; - options.ChatOptions ??= new ChatOptions(); - options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; - - return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services); - } - - private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools) - { - List? toolDefinitions = null; - ToolResources? toolResources = null; - List? functionToolsAndOtherTools = null; - - if (tools is not null) - { - foreach (AITool tool in tools) - { - switch (tool) - { - case HostedCodeInterpreterTool codeTool: - - toolDefinitions ??= []; - toolDefinitions.Add(new CodeInterpreterToolDefinition()); - - if (codeTool.Inputs is { Count: > 0 }) - { - foreach (var input in codeTool.Inputs) - { - switch (input) - { - case HostedFileContent hostedFile: - // If the input is a HostedFileContent, we can use its ID directly. - toolResources ??= new(); - toolResources.CodeInterpreter ??= new(); - toolResources.CodeInterpreter.FileIds.Add(hostedFile.FileId); - break; - } - } - } - break; - - case HostedFileSearchTool fileSearchTool: - toolDefinitions ??= []; - toolDefinitions.Add(new FileSearchToolDefinition - { - MaxResults = fileSearchTool.MaximumResultCount, - }); - - if (fileSearchTool.Inputs is { Count: > 0 }) - { - foreach (var input in fileSearchTool.Inputs) - { - switch (input) - { - case HostedVectorStoreContent hostedVectorStore: - toolResources ??= new(); - toolResources.FileSearch ??= new(); - toolResources.FileSearch.VectorStoreIds.Add(hostedVectorStore.VectorStoreId); - break; - } - } - } - break; - - default: - functionToolsAndOtherTools ??= []; - functionToolsAndOtherTools.Add(tool); - break; - } - } - } - - return (toolDefinitions, toolResources, functionToolsAndOtherTools); - } -} diff --git a/dotnet/src/Shared/DiagnosticIds/DiagnosticsIds.cs b/dotnet/src/Shared/DiagnosticIds/DiagnosticsIds.cs index 6316c6f607..2d4d02e3bf 100644 --- a/dotnet/src/Shared/DiagnosticIds/DiagnosticsIds.cs +++ b/dotnet/src/Shared/DiagnosticIds/DiagnosticsIds.cs @@ -26,7 +26,6 @@ internal static class DiagnosticIds // We use the same IDs so consumers do not need to suppress additional diagnostics // when using the experimental OpenAI APIs. internal const string AIOpenAIResponses = "OPENAI001"; - internal const string AIOpenAIAssistants = "OPENAI001"; private const string MEAIExperiments = "MEAI001"; } diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs deleted file mode 100644 index 44a4b73b52..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs +++ /dev/null @@ -1,1013 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -#pragma warning disable CS0618 // Type or member is obsolete - This is intentional as we are testing deprecated methods - -using System; -using System.ClientModel; -using System.ClientModel.Primitives; -using System.IO; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; -using OpenAI.Assistants; - -namespace Microsoft.Agents.AI.OpenAI.UnitTests.Extensions; - -/// -/// Unit tests for the class. -/// -public sealed class OpenAIAssistantClientExtensionsTests -{ - /// - /// Verify that CreateAIAgent with clientFactory parameter correctly applies the factory. - /// - [Fact] - public async Task CreateAIAgentAsync_WithClientFactory_AppliesFactoryCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var testChatClient = new TestChatClient(assistantClient.AsIChatClient("test-model")); - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - name: "Test Agent", - description: "Test description", - clientFactory: (innerClient) => testChatClient); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - Assert.Equal("Test description", agent.Description); - - // Verify that the custom chat client can be retrieved from the agent's service collection - var retrievedTestClient = agent.GetService(); - Assert.NotNull(retrievedTestClient); - Assert.Same(testChatClient, retrievedTestClient); - } - - /// - /// Verify that CreateAIAgent with clientFactory using AsBuilder pattern works correctly. - /// - [Fact] - public async Task CreateAIAgentAsync_WithClientFactoryUsingAsBuilder_AppliesFactoryCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - TestChatClient? testChatClient = null; - - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - clientFactory: (innerClient) => - innerClient.AsBuilder() - .Use((innerClient) => testChatClient = new TestChatClient(innerClient)) - .Build()); - - // Assert - Assert.NotNull(agent); - - // Verify that the custom chat client can be retrieved from the agent's service collection - var retrievedTestClient = agent.GetService(); - Assert.NotNull(retrievedTestClient); - Assert.Same(testChatClient, retrievedTestClient); - } - - /// - /// Verify that CreateAIAgent with options and clientFactory parameter correctly applies the factory. - /// - [Fact] - public async Task CreateAIAgentAsync_WithOptionsAndClientFactory_AppliesFactoryCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var testChatClient = new TestChatClient(assistantClient.AsIChatClient("test-model")); - const string ModelId = "test-model"; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - Description = "Test description", - ChatOptions = new() { Instructions = "Test instructions" } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - options, - clientFactory: (innerClient) => testChatClient); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - Assert.Equal("Test description", agent.Description); - - // Verify that the custom chat client can be retrieved from the agent's service collection - var retrievedTestClient = agent.GetService(); - Assert.NotNull(retrievedTestClient); - Assert.Same(testChatClient, retrievedTestClient); - } - - /// - /// Verify that CreateAIAgent without clientFactory works normally. - /// - [Fact] - public async Task CreateAIAgentAsync_WithoutClientFactory_WorksNormallyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - name: "Test Agent"); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - - // Verify that no TestChatClient is available since no factory was provided - var retrievedTestClient = agent.GetService(); - Assert.Null(retrievedTestClient); - } - - /// - /// Verify that CreateAIAgent with null clientFactory works normally. - /// - [Fact] - public async Task CreateAIAgentAsync_WithNullClientFactory_WorksNormallyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - name: "Test Agent", - clientFactory: null); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - - // Verify that no TestChatClient is available since no factory was provided - var retrievedTestClient = agent.GetService(); - Assert.Null(retrievedTestClient); - } - - /// - /// Verify that CreateAIAgent throws ArgumentNullException when client is null. - /// - [Fact] - public async Task CreateAIAgentAsync_WithNullClient_ThrowsArgumentNullExceptionAsync() - { - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - ((AssistantClient)null!).CreateAIAgentAsync("test-model")); - - Assert.Equal("client", exception.ParamName); - } - - /// - /// Verify that CreateAIAgent throws ArgumentNullException when model is null. - /// - [Fact] - public async Task CreateAIAgentAsync_WithNullModel_ThrowsArgumentNullExceptionAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient.CreateAIAgentAsync(null!)); - - Assert.Equal("model", exception.ParamName); - } - - /// - /// Verify that CreateAIAgent with options throws ArgumentNullException when options is null. - /// - [Fact] - public async Task CreateAIAgentAsync_WithNullOptions_ThrowsArgumentNullExceptionAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient.CreateAIAgentAsync("test-model", (ChatClientAgentOptions)null!)); - - Assert.Equal("options", exception.ParamName); - } - - /// - /// Verify that AsAIAgent with ClientResult and options works correctly. - /// - [Fact] - public void AsAIAgent_WithClientResultAndOptions_WorksCorrectly() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Original Name", "description": "Original Description", "instructions": "Original Instructions"}"""))!; - var clientResult = ClientResult.FromValue(assistant, new FakePipelineResponse()); - - var options = new ChatClientAgentOptions - { - Name = "Override Name", - Description = "Override Description", - ChatOptions = new() { Instructions = "Override Instructions" } - }; - - // Act - var agent = assistantClient.AsAIAgent(clientResult, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Override Name", agent.Name); - Assert.Equal("Override Description", agent.Description); - Assert.Equal("Override Instructions", agent.Instructions); - } - - /// - /// Verify that AsAIAgent with Assistant and options works correctly. - /// - [Fact] - public void AsAIAgent_WithAssistantAndOptions_WorksCorrectly() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Original Name", "description": "Original Description", "instructions": "Original Instructions"}"""))!; - - var options = new ChatClientAgentOptions - { - Name = "Override Name", - Description = "Override Description", - ChatOptions = new() { Instructions = "Override Instructions" } - }; - - // Act - var agent = assistantClient.AsAIAgent(assistant, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Override Name", agent.Name); - Assert.Equal("Override Description", agent.Description); - Assert.Equal("Override Instructions", agent.Instructions); - } - - /// - /// Verify that AsAIAgent with Assistant and options falls back to assistant metadata when options are null. - /// - [Fact] - public void AsAIAgent_WithAssistantAndOptionsWithNullFields_FallsBackToAssistantMetadata() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Original Name", "description": "Original Description", "instructions": "Original Instructions"}"""))!; - - var options = new ChatClientAgentOptions(); // Empty options - - // Act - var agent = assistantClient.AsAIAgent(assistant, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Original Name", agent.Name); - Assert.Equal("Original Description", agent.Description); - Assert.Equal("Original Instructions", agent.Instructions); - } - - /// - /// Verify that GetAIAgentAsync with agentId and options works correctly. - /// - [Fact] - public async Task GetAIAgentAsync_WithAgentIdAndOptions_WorksCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string AgentId = "asst_abc123"; - - var options = new ChatClientAgentOptions - { - Name = "Override Name", - Description = "Override Description", - ChatOptions = new() { Instructions = "Override Instructions" } - }; - - // Act - var agent = await assistantClient.GetAIAgentAsync(AgentId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Override Name", agent.Name); - Assert.Equal("Override Description", agent.Description); - Assert.Equal("Override Instructions", agent.Instructions); - } - - /// - /// Verify that AsAIAgent with clientFactory parameter correctly applies the factory. - /// - [Fact] - public void AsAIAgent_WithClientFactory_AppliesFactoryCorrectly() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent"}"""))!; - var testChatClient = new TestChatClient(assistantClient.AsIChatClient("asst_abc123")); - - var options = new ChatClientAgentOptions - { - Name = "Test Agent" - }; - - // Act - var agent = assistantClient.AsAIAgent( - assistant, - options, - clientFactory: (innerClient) => testChatClient); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - - // Verify that the custom chat client can be retrieved from the agent's service collection - var retrievedTestClient = agent.GetService(); - Assert.NotNull(retrievedTestClient); - Assert.Same(testChatClient, retrievedTestClient); - } - - /// - /// Verify that AsAIAgent throws ArgumentNullException when assistantClientResult is null. - /// - [Fact] - public void AsAIAgent_WithNullClientResult_ThrowsArgumentNullException() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var options = new ChatClientAgentOptions(); - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient.AsAIAgent(null!, options)); - - Assert.Equal("assistantClientResult", exception.ParamName); - } - - /// - /// Verify that AsAIAgent throws ArgumentNullException when assistant is null. - /// - [Fact] - public void AsAIAgent_WithNullAssistant_ThrowsArgumentNullException() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var options = new ChatClientAgentOptions(); - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient.AsAIAgent((Assistant)null!, options)); - - Assert.Equal("assistantMetadata", exception.ParamName); - } - - /// - /// Verify that AsAIAgent throws ArgumentNullException when options is null. - /// - [Fact] - public void AsAIAgent_WithNullOptions_ThrowsArgumentNullException() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}"""))!; - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient.AsAIAgent(assistant, (ChatClientAgentOptions)null!)); - - Assert.Equal("options", exception.ParamName); - } - - /// - /// Verify that GetAIAgentAsync throws ArgumentException when agentId is empty. - /// - [Fact] - public async Task GetAIAgentAsync_WithEmptyAgentId_ThrowsArgumentExceptionAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var options = new ChatClientAgentOptions(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient.GetAIAgentAsync(string.Empty, options)); - - Assert.Equal("agentId", exception.ParamName); - } - - /// - /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent. - /// - [Fact] - public async Task CreateAIAgentAsync_WithServices_PassesServicesToAgentAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var serviceProvider = new TestServiceProvider(); - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - name: "Test Agent", - services: serviceProvider); - - // Assert - Assert.NotNull(agent); - - // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient - var chatClient = agent.GetService(); - Assert.NotNull(chatClient); - var functionInvokingClient = chatClient.GetService(); - Assert.NotNull(functionInvokingClient); - Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); - } - - /// - /// Verify that CreateAIAgent with options and services parameter correctly passes it through to the ChatClientAgent. - /// - [Fact] - public async Task CreateAIAgentAsync_WithOptionsAndServices_PassesServicesToAgentAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var serviceProvider = new TestServiceProvider(); - const string ModelId = "test-model"; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new() { Instructions = "Test instructions" } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options, services: serviceProvider); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - - // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient - var chatClient = agent.GetService(); - Assert.NotNull(chatClient); - var functionInvokingClient = chatClient.GetService(); - Assert.NotNull(functionInvokingClient); - Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); - } - - /// - /// Verify that AsAIAgent with services parameter correctly passes it through to the ChatClientAgent. - /// - [Fact] - public void AsAIAgent_WithServices_PassesServicesToAgent() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var serviceProvider = new TestServiceProvider(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent"}"""))!; - - // Act - var agent = assistantClient.AsAIAgent(assistant, services: serviceProvider); - - // Assert - Assert.NotNull(agent); - - // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient - var chatClient = agent.GetService(); - Assert.NotNull(chatClient); - var functionInvokingClient = chatClient.GetService(); - Assert.NotNull(functionInvokingClient); - Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); - } - - /// - /// Verify that GetAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent. - /// - [Fact] - public async Task GetAIAgentAsync_WithServices_PassesServicesToAgentAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var serviceProvider = new TestServiceProvider(); - - // Act - var agent = await assistantClient.GetAIAgentAsync("asst_abc123", services: serviceProvider); - - // Assert - Assert.NotNull(agent); - - // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient - var chatClient = agent.GetService(); - Assert.NotNull(chatClient); - var functionInvokingClient = chatClient.GetService(); - Assert.NotNull(functionInvokingClient); - Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); - } - - /// - /// Verify that CreateAIAgent with both clientFactory and services works correctly. - /// - [Fact] - public async Task CreateAIAgentAsync_WithClientFactoryAndServices_AppliesBothCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var serviceProvider = new TestServiceProvider(); - var testChatClient = new TestChatClient(assistantClient.AsIChatClient("test-model")); - const string ModelId = "test-model"; - - // Act - var agent = await assistantClient.CreateAIAgentAsync( - ModelId, - instructions: "Test instructions", - name: "Test Agent", - clientFactory: (innerClient) => testChatClient, - services: serviceProvider); - - // Assert - Assert.NotNull(agent); - - // Verify the custom chat client was applied - var retrievedTestClient = agent.GetService(); - Assert.NotNull(retrievedTestClient); - Assert.Same(testChatClient, retrievedTestClient); - - // Verify the IServiceProvider was passed through - var chatClient = agent.GetService(); - Assert.NotNull(chatClient); - var functionInvokingClient = chatClient.GetService(); - Assert.NotNull(functionInvokingClient); - Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); - } - - /// - /// Uses reflection to access the FunctionInvocationServices property which is not public. - /// - private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client) - { - var property = typeof(FunctionInvokingChatClient).GetProperty( - "FunctionInvocationServices", - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - return property?.GetValue(client) as IServiceProvider; - } - - /// - /// Verify that CreateAIAgentAsync with HostedCodeInterpreterTool properly adds CodeInterpreter tool definition. - /// - [Fact] - public async Task CreateAIAgentAsync_WithHostedCodeInterpreterTool_CreatesAgentWithToolAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [new HostedCodeInterpreterTool()] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that CreateAIAgentAsync with HostedCodeInterpreterTool with HostedFileContent input properly creates agent. - /// - [Fact] - public async Task CreateAIAgentAsync_WithHostedCodeInterpreterToolAndHostedFileContent_CreatesAgentWithToolResourcesAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var codeInterpreterTool = new HostedCodeInterpreterTool - { - Inputs = [new HostedFileContent("test-file-id")] - }; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [codeInterpreterTool] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that CreateAIAgentAsync with HostedFileSearchTool properly adds FileSearch tool definition. - /// - [Fact] - public async Task CreateAIAgentAsync_WithHostedFileSearchTool_CreatesAgentWithToolAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [new HostedFileSearchTool()] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that CreateAIAgentAsync with HostedFileSearchTool with HostedVectorStoreContent input properly creates agent. - /// - [Fact] - public async Task CreateAIAgentAsync_WithHostedFileSearchToolAndHostedVectorStoreContent_CreatesAgentWithToolResourcesAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var fileSearchTool = new HostedFileSearchTool - { - MaximumResultCount = 10, - Inputs = [new HostedVectorStoreContent("test-vector-store-id")] - }; - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [fileSearchTool] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that CreateAIAgentAsync with multiple tools including functions properly creates agent. - /// - [Fact] - public async Task CreateAIAgentAsync_WithMixedTools_CreatesAgentWithAllToolsAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var testFunction = AIFunctionFactory.Create(() => "test", "TestFunction", "A test function"); - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [new HostedCodeInterpreterTool(), new HostedFileSearchTool(), testFunction] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that CreateAIAgentAsync with function tools properly categorizes them as other tools. - /// - [Fact] - public async Task CreateAIAgentAsync_WithFunctionTools_CategorizesAsOtherToolsAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string ModelId = "test-model"; - var testFunction = AIFunctionFactory.Create(() => "test", "TestFunction", "A test function"); - var options = new ChatClientAgentOptions - { - Name = "Test Agent", - ChatOptions = new ChatOptions - { - Instructions = "Test instructions", - Tools = [testFunction] - } - }; - - // Act - var agent = await assistantClient.CreateAIAgentAsync(ModelId, options); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that AsAIAgent with legacy overload works correctly when assistant instructions are set. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithAssistantInstructions_SetsInstructions() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!; - - // Act - var agent = assistantClient.AsAIAgent(assistant); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - Assert.Equal("Original Instructions", agent.Instructions); - } - - /// - /// Verify that AsAIAgent with legacy overload works correctly when chatOptions with instructions is provided. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithChatOptionsInstructions_UsesChatOptionsInstructions() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!; - var chatOptions = new ChatOptions { Instructions = "Override Instructions" }; - - // Act - var agent = assistantClient.AsAIAgent(assistant, chatOptions); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - Assert.Equal("Override Instructions", agent.Instructions); - } - - /// - /// Verify that AsAIAgent with legacy overload and ClientResult works correctly. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithClientResult_WorksCorrectly() - { - // Arrange - var assistantClient = new TestAssistantClient(); - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!; - var clientResult = ClientResult.FromValue(assistant, new FakePipelineResponse()); - - // Act - var agent = assistantClient.AsAIAgent(clientResult); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Test Agent", agent.Name); - } - - /// - /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when assistant client is null. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithNullAssistantClient_ThrowsArgumentNullException() - { - // Arrange - AssistantClient? assistantClient = null; - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}"""))!; - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient!.AsAIAgent(assistant)); - - Assert.Equal("assistantClient", exception.ParamName); - } - - /// - /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when assistantMetadata is null. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithNullAssistantMetadata_ThrowsArgumentNullException() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient.AsAIAgent((Assistant)null!)); - - Assert.Equal("assistantMetadata", exception.ParamName); - } - - /// - /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when clientResult is null. - /// - [Fact] - public void AsAIAgent_LegacyOverload_WithNullClientResult_ThrowsArgumentNullException() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient.AsAIAgent(null!, chatOptions: null)); - - Assert.Equal("assistantClientResult", exception.ParamName); - } - - /// - /// Verify that GetAIAgentAsync with legacy overload works correctly. - /// - [Fact] - public async Task GetAIAgentAsync_LegacyOverload_WorksCorrectlyAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - const string AgentId = "asst_abc123"; - - // Act - var agent = await assistantClient.GetAIAgentAsync(AgentId); - - // Assert - Assert.NotNull(agent); - Assert.Equal("Original Name", agent.Name); - } - - /// - /// Verify that GetAIAgentAsync with legacy overload throws ArgumentNullException when assistantClient is null. - /// - [Fact] - public async Task GetAIAgentAsync_LegacyOverload_WithNullAssistantClient_ThrowsArgumentNullExceptionAsync() - { - // Arrange - AssistantClient? assistantClient = null; - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient!.GetAIAgentAsync("asst_abc123")); - - Assert.Equal("assistantClient", exception.ParamName); - } - - /// - /// Verify that GetAIAgentAsync with legacy overload throws ArgumentException when agentId is empty. - /// - [Fact] - public async Task GetAIAgentAsync_LegacyOverload_WithEmptyAgentId_ThrowsArgumentExceptionAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient.GetAIAgentAsync(string.Empty)); - - Assert.Equal("agentId", exception.ParamName); - } - - /// - /// Verify that GetAIAgentAsync with options throws ArgumentNullException when assistantClient is null. - /// - [Fact] - public async Task GetAIAgentAsync_WithOptions_WithNullAssistantClient_ThrowsArgumentNullExceptionAsync() - { - // Arrange - AssistantClient? assistantClient = null; - var options = new ChatClientAgentOptions(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient!.GetAIAgentAsync("asst_abc123", options)); - - Assert.Equal("assistantClient", exception.ParamName); - } - - /// - /// Verify that GetAIAgentAsync with options throws ArgumentNullException when options is null. - /// - [Fact] - public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync() - { - // Arrange - var assistantClient = new TestAssistantClient(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - assistantClient.GetAIAgentAsync("asst_abc123", (ChatClientAgentOptions)null!)); - - Assert.Equal("options", exception.ParamName); - } - - /// - /// Verify that AsAIAgent with options throws ArgumentNullException when assistantClient is null. - /// - [Fact] - public void AsAIAgent_WithOptions_WithNullAssistantClient_ThrowsArgumentNullException() - { - // Arrange - AssistantClient? assistantClient = null; - var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}"""))!; - var options = new ChatClientAgentOptions(); - - // Act & Assert - var exception = Assert.Throws(() => - assistantClient!.AsAIAgent(assistant, options)); - - Assert.Equal("assistantClient", exception.ParamName); - } - - /// - /// Creates a test AssistantClient implementation for testing. - /// - private sealed class TestAssistantClient : AssistantClient - { - public TestAssistantClient() - { - } - - public override Task> CreateAssistantAsync(string model, AssistantCreationOptions? options = null, CancellationToken cancellationToken = default) - { - return Task.FromResult>(ClientResult.FromValue(ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}""")), new FakePipelineResponse())!); - } - - public override async Task> GetAssistantAsync(string assistantId, CancellationToken cancellationToken = default) - { - await Task.Delay(1, cancellationToken); // Simulate async operation - return ClientResult.FromValue(ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Original Name", "description": "Original Description", "instructions": "Original Instructions"}""")), new FakePipelineResponse())!; - } - } - - private sealed class TestChatClient : DelegatingChatClient - { - public TestChatClient(IChatClient innerClient) : base(innerClient) - { - } - } - - private sealed class TestServiceProvider : IServiceProvider - { - public object? GetService(Type serviceType) => null; - } - - private sealed class FakePipelineResponse : PipelineResponse - { - public override int Status => throw new NotImplementedException(); - - public override string ReasonPhrase => throw new NotImplementedException(); - - public override Stream? ContentStream { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public override BinaryData Content => throw new NotImplementedException(); - - protected override PipelineResponseHeaders HeadersCore => throw new NotImplementedException(); - - public override BinaryData BufferContent(CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public override ValueTask BufferContentAsync(CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public override void Dispose() - { - throw new NotImplementedException(); - } - } -} diff --git a/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs deleted file mode 100644 index 9441d9534b..0000000000 --- a/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -#pragma warning disable CS0618 // Type or member is obsolete - Testing deprecated OpenAI Assistants API extension methods - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using AgentConformance.IntegrationTests.Support; -using Microsoft.Agents.AI; -using Microsoft.Extensions.AI; -using OpenAI; -using OpenAI.Assistants; -using OpenAI.Files; -using OpenAI.VectorStores; -using Shared.IntegrationTests; - -namespace OpenAIAssistant.IntegrationTests; - -public class OpenAIAssistantClientExtensionsTests -{ - private const string SkipCodeInterpreterReason = "OpenAI Assistant Code Interpreter intermittently fails in CI"; - - private readonly AssistantClient _assistantClient = new OpenAIClient(TestConfiguration.GetRequiredValue(TestSettings.OpenAIApiKey)).GetAssistantClient(); - private readonly OpenAIFileClient _fileClient = new OpenAIClient(TestConfiguration.GetRequiredValue(TestSettings.OpenAIApiKey)).GetOpenAIFileClient(); - - [Theory] - [InlineData("CreateWithChatClientAgentOptionsAsync")] - [InlineData("CreateWithChatClientAgentOptionsSync")] - [InlineData("CreateWithParamsAsync")] - public async Task CreateAIAgentAsync_WithAIFunctionTool_InvokesFunctionAsync(string createMechanism) - { - // Arrange - const string AgentInstructions = "You are a helpful weather assistant. Always call the GetWeather function to answer questions about weather."; - - static string GetWeather(string location) => $"The weather in {location} is sunny with a high of 23C."; - var weatherFunction = AIFunctionFactory.Create(GetWeather, nameof(GetWeather)); - - // Act - var agent = createMechanism switch - { - "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = AgentInstructions, - Tools = [weatherFunction] - } - }), - "CreateWithChatClientAgentOptionsSync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = AgentInstructions, - Tools = [weatherFunction] - } - }), - "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - instructions: AgentInstructions, - tools: [weatherFunction]), - _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}") - }; - - try - { - // Trigger function call. - var response = await agent.RunAsync("What is the weather like in Amsterdam?"); - var text = response.Text; - - // Assert - Assert.Contains("Amsterdam", text, StringComparison.OrdinalIgnoreCase); - Assert.Contains("sunny", text, StringComparison.OrdinalIgnoreCase); - Assert.Contains("23", text, StringComparison.OrdinalIgnoreCase); - } - finally - { - await this._assistantClient.DeleteAssistantAsync(agent.Id); - } - } - - [Theory(Skip = SkipCodeInterpreterReason)] - [InlineData("CreateWithChatClientAgentOptionsAsync")] - [InlineData("CreateWithChatClientAgentOptionsSync")] - [InlineData("CreateWithParamsAsync")] - public async Task CreateAIAgentAsync_WithHostedCodeInterpreter_RunsCodeAsync(string createMechanism) - { - // Arrange - const string Instructions = "Use the Code Interpreter Tool to run the uploaded python file and respond only with the secret number."; - - // Create a python file that prints a known value. - var codeFilePath = Path.GetTempFileName() + "openai_secret_number.py"; - File.WriteAllText( - path: codeFilePath, - contents: "print(\"OPENAI_SECRET=13579\")" // Deterministic output we will look for. - ); - - // Upload file to OpenAI Assistants file store for use with the Code Interpreter. - var uploadResult = await this._fileClient.UploadFileAsync(codeFilePath, FileUploadPurpose.Assistants); - string uploadedFileId = uploadResult.Value.Id; - var codeInterpreterTool = new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedFileId)] }; - - var agent = createMechanism switch - { - "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = Instructions, - Tools = [codeInterpreterTool] - } - }), - "CreateWithChatClientAgentOptionsSync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = Instructions, - Tools = [codeInterpreterTool] - } - }), - "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - instructions: Instructions, - tools: [codeInterpreterTool]), - _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}") - }; - - try - { - var response = await agent.RunAsync("What is the OPENAI_SECRET number?"); - var text = response.ToString(); - Assert.Contains("13579", text); - } - finally - { - await this._assistantClient.DeleteAssistantAsync(agent.Id); - await this._fileClient.DeleteFileAsync(uploadedFileId); - File.Delete(codeFilePath); - } - } - - [Theory(Skip = "For manual testing only")] - [InlineData("CreateWithChatClientAgentOptionsAsync")] - [InlineData("CreateWithChatClientAgentOptionsSync")] - [InlineData("CreateWithParamsAsync")] - public async Task CreateAIAgentAsync_WithHostedFileSearchTool_SearchesFilesAsync(string createMechanism) - { - // Arrange. - const string Instructions = """ - You are a helpful agent that can help fetch data from files you know about. - Use the File Search Tool to look up codes for words. - Do not answer a question unless you can find the answer using the File Search Tool. - """; - - // Create a local file with deterministic content and upload it. - var searchFilePath = Path.GetTempFileName() + "wordcodelookup.txt"; - File.WriteAllText( - path: searchFilePath, - contents: "The word 'apple' uses the code 442345, while the word 'banana' uses the code 673457."); - var uploadResult = await this._fileClient.UploadFileAsync(searchFilePath, FileUploadPurpose.Assistants); - string uploadedFileId = uploadResult.Value.Id; - - // Create a vector store backing the file search (HostedFileSearchTool requires a vector store id). - var vectorStoreClient = new OpenAIClient(TestConfiguration.GetRequiredValue(TestSettings.OpenAIApiKey)).GetVectorStoreClient(); - var vectorStoreCreate = await vectorStoreClient.CreateVectorStoreAsync(options: new VectorStoreCreationOptions() - { - Name = "WordCodeLookup_VectorStore", - FileIds = { uploadedFileId } - }); - string vectorStoreId = vectorStoreCreate.Value.Id; - - // Wait for vector store indexing to complete before using it - await WaitForVectorStoreReadyAsync(vectorStoreClient, vectorStoreId); - - var fileSearchTool = new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreId)] }; - - var agent = createMechanism switch - { - "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = Instructions, - Tools = [fileSearchTool] - } - }), - "CreateWithChatClientAgentOptionsSync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - options: new ChatClientAgentOptions() - { - ChatOptions = new() - { - Instructions = Instructions, - Tools = [fileSearchTool] - } - }), - "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( - model: TestConfiguration.GetRequiredValue(TestSettings.OpenAIChatModelName), - instructions: Instructions, - tools: [fileSearchTool]), - _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}") - }; - - try - { - // Act - ask about banana code which must be retrieved via file search. - var response = await agent.RunAsync("Can you give me the documented code for 'banana'?"); - var text = response.ToString(); - Assert.Contains("673457", text); - } - finally - { - await this._assistantClient.DeleteAssistantAsync(agent.Id); - await vectorStoreClient.DeleteVectorStoreAsync(vectorStoreId); - await this._fileClient.DeleteFileAsync(uploadedFileId); - File.Delete(searchFilePath); - } - } - - /// - /// Waits for a vector store to complete indexing by polling its status. - /// - /// The vector store client. - /// The ID of the vector store. - /// Maximum time to wait in seconds (default: 30). - /// A task that completes when the vector store is ready or throws on timeout/failure. - private static async Task WaitForVectorStoreReadyAsync( - VectorStoreClient client, - string vectorStoreId, - int maxWaitSeconds = 30) - { - Stopwatch sw = Stopwatch.StartNew(); - while (sw.Elapsed.TotalSeconds < maxWaitSeconds) - { - VectorStore vectorStore = await client.GetVectorStoreAsync(vectorStoreId); - VectorStoreStatus status = vectorStore.Status; - - if (status == VectorStoreStatus.Completed) - { - if (vectorStore.FileCounts.Failed > 0) - { - throw new InvalidOperationException("Vector store indexing failed for some files"); - } - - return; - } - - if (status == VectorStoreStatus.Expired) - { - throw new InvalidOperationException("Vector store has expired"); - } - - await Task.Delay(1000); - } - - throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s"); - } -}