// Copyright (c) Microsoft. All rights reserved. using System.ClientModel; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.Shared.Diagnostics; using OpenAI.Assistants; namespace OpenAI; /// /// 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. /// 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. /// A instance that can be used to perform operations on the assistant. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, ClientResult assistantClientResult, ChatOptions? chatOptions = null, Func? clientFactory = null) { if (assistantClientResult is null) { throw new ArgumentNullException(nameof(assistantClientResult)); } return assistantClient.GetAIAgent(assistantClientResult.Value, chatOptions, clientFactory); } /// /// 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. /// A instance that can be used to perform operations on the assistant. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, Assistant assistantMetadata, ChatOptions? chatOptions = null, Func? clientFactory = 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); } return new ChatClientAgent(chatClient, options: new() { Id = assistantMetadata.Id, Name = assistantMetadata.Name, Description = assistantMetadata.Description, Instructions = assistantMetadata.Instructions, ChatOptions = chatOptions }); } /// /// 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. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, string agentId, ChatOptions? chatOptions = null, Func? clientFactory = 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 assistant = assistantClient.GetAssistant(agentId, cancellationToken); return assistantClient.GetAIAgent(assistant, chatOptions, clientFactory); } /// /// Retrieves an existing server side agent, wrapped as a using the provided . /// /// The to create the with. /// The ID of the server side agent to create a for. /// Options that should apply to all runs of the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. public static async Task GetAIAgentAsync( this AssistantClient assistantClient, string agentId, ChatOptions? chatOptions = null, Func? clientFactory = 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.GetAIAgent(assistantResponse, chatOptions, clientFactory); } /// /// 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. /// A instance that can be used to perform operations on the assistant. /// or is . public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, ClientResult assistantClientResult, ChatClientAgentOptions options, Func? clientFactory = null) { if (assistantClientResult is null) { throw new ArgumentNullException(nameof(assistantClientResult)); } return assistantClient.GetAIAgent(assistantClientResult.Value, options, clientFactory); } /// /// 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. /// A instance that can be used to perform operations on the assistant. /// or is . public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, Assistant assistantMetadata, ChatClientAgentOptions options, Func? clientFactory = 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); } var mergedOptions = new ChatClientAgentOptions() { Id = assistantMetadata.Id, Name = options.Name ?? assistantMetadata.Name, Description = options.Description ?? assistantMetadata.Description, Instructions = options.Instructions ?? assistantMetadata.Instructions, ChatOptions = options.ChatOptions, AIContextProviderFactory = options.AIContextProviderFactory, ChatMessageStoreFactory = options.ChatMessageStoreFactory, UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs }; return new ChatClientAgent(chatClient, mergedOptions); } /// /// Retrieves an existing server side agent, wrapped as a using the provided . /// /// The to create the with. /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. /// or is . /// is empty or whitespace. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, string agentId, ChatClientAgentOptions options, Func? clientFactory = 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 assistant = assistantClient.GetAssistant(agentId, cancellationToken); return assistantClient.GetAIAgent(assistant, options, clientFactory); } /// /// Retrieves an existing server side agent, wrapped as a using the provided . /// /// The to create the with. /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. /// or is . /// is empty or whitespace. public static async Task GetAIAgentAsync( this AssistantClient assistantClient, string agentId, ChatClientAgentOptions options, Func? clientFactory = 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.GetAIAgent(assistantResponse, options, clientFactory); } /// /// 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 instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. public static ChatClientAgent CreateAIAgent( this AssistantClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, Func? clientFactory = null, ILoggerFactory? loggerFactory = null) => client.CreateAIAgent( model, new ChatClientAgentOptions() { Name = name, Description = description, Instructions = instructions, ChatOptions = tools is null ? null : new ChatOptions() { Tools = tools, } }, clientFactory, loggerFactory); /// /// 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 instance backed by the OpenAI Assistant service. /// Thrown when or or is . /// Thrown when is empty or whitespace. public static ChatClientAgent CreateAIAgent( this AssistantClient client, string model, ChatClientAgentOptions options, Func? clientFactory = null, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); Throw.IfNullOrEmpty(model); Throw.IfNull(options); var assistantOptions = new AssistantCreationOptions() { Name = options.Name, Description = options.Description, Instructions = options.Instructions, }; // Convert AITools to ToolDefinitions and ToolResources var toolDefinitionsAndResources = ConvertAIToolsToToolDefinitions(options.ChatOptions?.Tools); if (toolDefinitionsAndResources.ToolDefinitions is { Count: > 0 }) { toolDefinitionsAndResources.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 = client.CreateAssistant(model, assistantOptions); 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); } /// /// 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 instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. 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) => await client.CreateAIAgentAsync(model, new ChatClientAgentOptions() { Name = name, Description = description, Instructions = instructions, ChatOptions = tools is null ? null : new ChatOptions() { Tools = tools, } }, clientFactory, loggerFactory).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 instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. public static async Task CreateAIAgentAsync( this AssistantClient client, string model, ChatClientAgentOptions options, Func? clientFactory = null, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); Throw.IfNull(model); Throw.IfNull(options); var assistantOptions = new AssistantCreationOptions() { Name = options.Name, Description = options.Description, Instructions = options.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).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); } 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); } }