.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>
This commit is contained in:
SergeyMenshykh
2026-04-02 14:51:41 +01:00
committed by GitHub
Unverified
parent 95fd5ec658
commit a356a16568
11 changed files with 35 additions and 1802 deletions
-1
View File
@@ -34,7 +34,6 @@
<Project Path="samples/02-agents/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj" />
<Project Path="samples/02-agents/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
</Folder>
@@ -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",
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -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);
@@ -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
```
@@ -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.</br>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|
@@ -1,6 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
<Left>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Left>
<Right>lib/net10.0/Microsoft.Agents.AI.OpenAI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
<Left>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Left>
<Right>lib/net472/Microsoft.Agents.AI.OpenAI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
<Left>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Left>
<Right>lib/net8.0/Microsoft.Agents.AI.OpenAI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
<Left>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Left>
<Right>lib/net9.0/Microsoft.Agents.AI.OpenAI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:OpenAI.Assistants.OpenAIAssistantClientExtensions</Target>
<Left>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Left>
<Right>lib/netstandard2.0/Microsoft.Agents.AI.OpenAI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>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)</Target>
@@ -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;
/// <summary>
/// Provides extension methods for OpenAI <see cref="AssistantClient"/>
/// to simplify the creation of AI agents that work with OpenAI services.
/// </summary>
/// <remarks>
/// 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 <see cref="IChatClient"/> instances and then wrap them
/// in <see cref="ChatClientAgent"/> objects that implement the <see cref="AIAgent"/> interface.
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AIOpenAIAssistants)]
public static class OpenAIAssistantClientExtensions
{
/// <summary>
/// Gets a <see cref="ChatClientAgent"/> from a <see cref="ClientResult{Assistant}"/>.
/// </summary>
/// <param name="assistantClient">The assistant client.</param>
/// <param name="assistantClientResult">The client result containing the assistant.</param>
/// <param name="chatOptions">Optional chat options.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static ChatClientAgent AsAIAgent(
this AssistantClient assistantClient,
ClientResult<Assistant> assistantClientResult,
ChatOptions? chatOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
IServiceProvider? services = null)
{
if (assistantClientResult is null)
{
throw new ArgumentNullException(nameof(assistantClientResult));
}
return assistantClient.AsAIAgent(assistantClientResult.Value, chatOptions, clientFactory, services);
}
/// <summary>
/// Gets a <see cref="ChatClientAgent"/> from an <see cref="Assistant"/>.
/// </summary>
/// <param name="assistantClient">The assistant client.</param>
/// <param name="assistantMetadata">The assistant metadata.</param>
/// <param name="chatOptions">Optional chat options.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
[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<IChatClient, IChatClient>? 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);
}
/// <summary>
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AssistantClient"/>.
/// </summary>
/// <param name="assistantClient">The <see cref="AssistantClient"/> to create the <see cref="ChatClientAgent"/> with.</param>
/// <param name="agentId"> The ID of the server side agent to create a <see cref="ChatClientAgent"/> for.</param>
/// <param name="chatOptions">Options that should apply to all runs of the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant agent.</returns>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static async Task<ChatClientAgent> GetAIAgentAsync(
this AssistantClient assistantClient,
string agentId,
ChatOptions? chatOptions = null,
Func<IChatClient, IChatClient>? 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);
}
/// <summary>
/// Gets a <see cref="ChatClientAgent"/> from a <see cref="ClientResult{Assistant}"/>.
/// </summary>
/// <param name="assistantClient">The assistant client.</param>
/// <param name="assistantClientResult">The client result containing the assistant.</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
/// <exception cref="ArgumentNullException"><paramref name="assistantClientResult"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static ChatClientAgent AsAIAgent(
this AssistantClient assistantClient,
ClientResult<Assistant> assistantClientResult,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
IServiceProvider? services = null)
{
if (assistantClientResult is null)
{
throw new ArgumentNullException(nameof(assistantClientResult));
}
return assistantClient.AsAIAgent(assistantClientResult.Value, options, clientFactory, services);
}
/// <summary>
/// Gets a <see cref="ChatClientAgent"/> from an <see cref="Assistant"/>.
/// </summary>
/// <param name="assistantClient">The assistant client.</param>
/// <param name="assistantMetadata">The assistant metadata.</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant.</returns>
/// <exception cref="ArgumentNullException"><paramref name="assistantMetadata"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
[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<IChatClient, IChatClient>? 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);
}
/// <summary>
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AssistantClient"/>.
/// </summary>
/// <param name="assistantClient">The <see cref="AssistantClient"/> to create the <see cref="ChatClientAgent"/> with.</param>
/// <param name="agentId"> The ID of the server side agent to create a <see cref="ChatClientAgent"/> for.</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the assistant agent.</returns>
/// <exception cref="ArgumentNullException"><paramref name="assistantClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="agentId"/> is empty or whitespace.</exception>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static async Task<ChatClientAgent> GetAIAgentAsync(
this AssistantClient assistantClient,
string agentId,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? 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);
}
/// <summary>
/// Creates an AI agent from an <see cref="AssistantClient"/> using the OpenAI Assistant API.
/// </summary>
/// <param name="client">The OpenAI <see cref="AssistantClient" /> to use for the agent.</param>
/// <param name="model">The model identifier to use (e.g., "gpt-4").</param>
/// <param name="instructions">Optional system instructions that define the agent's behavior and personality.</param>
/// <param name="name">Optional name for the agent for identification purposes.</param>
/// <param name="description">Optional description of the agent's capabilities and purpose.</param>
/// <param name="tools">Optional collection of AI tools that the agent can use during conversations.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the OpenAI Assistant service.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="model"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace.</exception>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static async Task<ChatClientAgent> CreateAIAgentAsync(
this AssistantClient client,
string model,
string? instructions = null,
string? name = null,
string? description = null,
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? 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);
/// <summary>
/// Creates an AI agent from an <see cref="AssistantClient"/> using the OpenAI Assistant API.
/// </summary>
/// <param name="client">The OpenAI <see cref="AssistantClient" /> to use for the agent.</param>
/// <param name="model">The model identifier to use (e.g., "gpt-4").</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the OpenAI Assistant service.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="model"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace.</exception>
[Obsolete("The Assistants API has been deprecated. Please use the Responses API instead.")]
public static async Task<ChatClientAgent> CreateAIAgentAsync(
this AssistantClient client,
string model,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? 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<ToolDefinition>? ToolDefinitions, ToolResources? ToolResources, List<AITool>? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList<AITool>? tools)
{
List<ToolDefinition>? toolDefinitions = null;
ToolResources? toolResources = null;
List<AITool>? 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);
}
}
@@ -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";
}
@@ -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);
}
}
/// <summary>
/// Waits for a vector store to complete indexing by polling its status.
/// </summary>
/// <param name="client">The vector store client.</param>
/// <param name="vectorStoreId">The ID of the vector store.</param>
/// <param name="maxWaitSeconds">Maximum time to wait in seconds (default: 30).</param>
/// <returns>A task that completes when the vector store is ready or throws on timeout/failure.</returns>
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");
}
}