From e360297c8f1e5b5d31f5f6a9f99356204dcb0047 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:44:00 +0100 Subject: [PATCH 01/16] Add some OpenAI specific extensions --- .../Extensions/AgentRunResponseExtensions.cs | 36 ++++++ .../Extensions/OpenAIAgentExtensions.cs | 101 +++++++++++++++ .../Extensions/OpenAIClientExtensions.cs | 43 +++++++ ...lientAgent_With_AzureAIAgentsPersistent.cs | 13 +- ...nt_With_OpenAIChatCompletion_Extensions.cs | 118 ++++++++++++++++++ .../PersistentAgentsClientExtensions.cs | 54 +++++++- 6 files changed, 356 insertions(+), 9 deletions(-) create mode 100644 dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs create mode 100644 dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs create mode 100644 dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs create mode 100644 dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs diff --git a/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs new file mode 100644 index 000000000..d65d0dff3 --- /dev/null +++ b/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using OpenAI.Chat; +using OpenAI.Responses; + +namespace OpenAI; + +/// +/// Extension methods for +/// +internal static class AgentRunResponseExtensions +{ + internal static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse) + { + if (agentResponse.RawRepresentation is ChatCompletion chatCompletion) + { + return chatCompletion; + } + throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + } + + internal static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentResponse) + { + if (agentResponse.RawRepresentation is ChatResponse chatResponse) + { + if (chatResponse.RawRepresentation is OpenAIResponse openAIResponse) + { + return openAIResponse; + } + throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + } + throw new ArgumentException("AgentRunResponse.RawRepresentation must be a OpenAIResponse"); + } +} diff --git a/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs b/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs new file mode 100644 index 000000000..a6fa268a7 --- /dev/null +++ b/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using OpenAI.Chat; + +namespace OpenAI; + +/// +/// Provides extension methods for to simplify interaction with OpenAI chat messages +/// and return native OpenAI responses. +/// +/// +/// These extensions bridge the gap between the Microsoft Extensions AI framework and the OpenAI SDK, +/// allowing developers to work with native OpenAI types while leveraging the AI Agent framework. +/// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types, +/// and return OpenAI objects directly from the agent's . +/// +internal static class OpenAIAgentExtensions +{ + /// + /// Runs the AI agent with a single OpenAI chat message and returns the response as a native OpenAI . + /// + /// The AI agent to run. + /// The OpenAI chat message to send to the agent. + /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided message and agent response. + /// Optional parameters for agent invocation. + /// The to monitor for cancellation requests. The default is . + /// A representing the asynchronous operation that returns a native OpenAI response. + /// Thrown when or is . + /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. + /// Thrown when the type is not supported by the conversion method. + /// + /// This method converts the OpenAI chat message to the Microsoft Extensions AI format using , + /// runs the agent, and then extracts the native OpenAI from the response using . + /// Currently only types are supported for conversion. + /// + internal static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + var response = await agent.RunAsync([message.ToChatMessage()], thread, options, cancellationToken); + + var chatCompletion = response.AsChatCompletion(); + return chatCompletion; + } + + /// + /// Runs the AI agent with a collection of OpenAI chat messages and returns the response as a native OpenAI . + /// + /// The AI agent to run. + /// The collection of OpenAI chat messages to send to the agent. + /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response. + /// Optional parameters for agent invocation. + /// The to monitor for cancellation requests. The default is . + /// A representing the asynchronous operation that returns a native OpenAI response. + /// Thrown when or is . + /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. + /// Thrown when any message in has a type that is not supported by the conversion method. + /// + /// This method converts each OpenAI chat message to the Microsoft Extensions AI format using , + /// runs the agent with the converted message collection, and then extracts the native OpenAI from the response using . + /// Currently only types are supported for conversion. + /// + internal static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + var response = await agent.RunAsync(messages.Select(m => m.ToChatMessage()).ToList(), thread, options, cancellationToken); + + var chatCompletion = response.AsChatCompletion(); + return chatCompletion; + } + + /// + /// Converts an OpenAI chat message to a Microsoft Extensions AI . + /// + /// The OpenAI chat message to convert. + /// A equivalent of the input OpenAI message. + /// Thrown when is . + /// Thrown when the type is not supported for conversion. + /// + /// + /// This method provides a bridge between OpenAI SDK message types and Microsoft Extensions AI message types. + /// It enables seamless integration when working with both frameworks in the same application. + /// + /// + /// Currently, only is supported for conversion. The method extracts the text content + /// from the first content item in the user message and creates a corresponding Microsoft Extensions AI user message. + /// + /// + /// Future versions may extend support to additional OpenAI message types such as AssistantChatMessage and SystemChatMessage. + /// + /// + internal static Microsoft.Extensions.AI.ChatMessage ToChatMessage(this OpenAI.Chat.ChatMessage chatMessage) + { + switch (chatMessage) + { + case UserChatMessage userMessage: + return new Microsoft.Extensions.AI.ChatMessage(ChatRole.User, userMessage.Content.First().Text); + default: + throw new ArgumentException("Only support for user messages is implemented."); + } + } +} diff --git a/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs b/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs new file mode 100644 index 000000000..87d105cdb --- /dev/null +++ b/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using OpenAI.Chat; +using OpenAI.Responses; + +namespace OpenAI; + +/// +/// Extension methods for . +/// +internal static class OpenAIClientExtensions +{ + internal static AIAgent GetChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.GetChatClient(model).AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + internal static AIAgent GetAgent(this ChatClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + internal static AIAgent GetOpenAIResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.GetOpenAIResponseClient(model).AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + internal static AIAgent GetAgent(this OpenAIResponseClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } +} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs index 4bbb3df0d..e3b0004dd 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs @@ -25,14 +25,11 @@ public async Task RunWithAzureAIAgentsPersistent() var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); // Create a server side persistent agent. - var createPersistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync( - model: TestConfiguration.AzureAI.DeploymentName, + AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName!, name: JokerName, instructions: JokerInstructions); - // Get a local proxy for the agent to work with. - AIAgent agent = await persistentAgentsClient.GetRunnableAgentAsync(createPersistentAgentResponse.Value.Id); - // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -43,15 +40,15 @@ public async Task RunWithAzureAIAgentsPersistent() // Local function to run agent and display the conversation messages for the thread. async Task RunAgentAsync(string input) { - this.WriteUserMessage(input); + Console.WriteLine(input); var response = await agent.RunAsync(input, thread); - this.WriteResponseOutput(response); + Console.WriteLine(response.Messages.Last().Text); } // Cleanup await persistentAgentsClient.Threads.DeleteThreadAsync(thread.Id); - await persistentAgentsClient.Administration.DeleteAgentAsync(createPersistentAgentResponse.Value.Id); + await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id); } } diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs new file mode 100644 index 000000000..d9d8032f3 --- /dev/null +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Samples; +using OpenAI; + +namespace Providers; + +/// +/// End-to-end sample showing how to use with OpenAI Chat Completion. +/// +public sealed class ChatClientAgent_With_OpenAIChatCompletion_Extensions(ITestOutputHelper output) : AgentSample(output) +{ + private const string JokerName = "Joker"; + private const string JokerInstructions = "You are good at telling jokes."; + + [Fact] + public async Task RunWithChatCompletion() + { + // Get the agent directly from OpenAIClient. + AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input. + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to invoke agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + Console.WriteLine(input); + + var response = await agent.RunAsync(input, thread); + + Console.WriteLine(response.Messages.Last().Text); + } + } + + [Fact] + public async Task RunWithResponses() + { + // Get the agent directly from OpenAIClient. + AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .GetOpenAIResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input. + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to invoke agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + Console.WriteLine(input); + + var response = await agent.RunAsync(input, thread); + + Console.WriteLine(response.Messages.Last().Text); + } + } + + [Fact] + public async Task RunWithChatCompletionReturnChatCompletion() + { + // Get the agent directly from OpenAIClient. + var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input. + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to invoke agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + Console.WriteLine(input); + + var response = await agent.RunAsync(input, thread); + var chatCompletion = response.AsChatCompletion(); + + Console.WriteLine(chatCompletion.Content.Last().Text); + } + } + + [Fact] + public async Task RunWithChatCompletionWithOpenAIChatMessage() + { + // Get the agent directly from OpenAIClient. + var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input. + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to invoke agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + Console.WriteLine(input); + + var chatMessage = new OpenAI.Chat.UserChatMessage(input); + var chatCompletion = await agent.RunAsync(chatMessage, thread); + + Console.WriteLine(chatCompletion.Content.Last().Text); + } + } +} diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs index fb2a4ed57..6224bc62a 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -22,7 +23,7 @@ public static class PersistentAgentsClientExtensions /// Options that should apply to all runs of the agent. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the persistent agent. - public static async Task GetRunnableAgentAsync( + public static async Task GetAIAgentAsync( this PersistentAgentsClient persistentAgentsClient, string agentId, ChatOptions? chatOptions = null, @@ -41,4 +42,55 @@ public static async Task GetRunnableAgentAsync( var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); return persistentAgentResponse.AsRunnableAgent(persistentAgentsClient, chatOptions); } + + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// The name of the agent. + /// The description of the agent. + /// The instructions for the agent. + /// The tools to be used by the agent. + /// The resources for the tools. + /// The temperature setting for the agent. + /// The top-p setting for the agent. + /// The response format for the agent. + /// The metadata for the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + public static async Task CreateAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string model, + string? name = null, + string? description = null, + string? instructions = null, + IEnumerable? tools = null, + ToolResources? toolResources = null, + float? temperature = null, + float? topP = null, + BinaryData? responseFormat = null, + IReadOnlyDictionary? metadata = null, + CancellationToken cancellationToken = default) + { + if (persistentAgentsClient is null) + { + throw new ArgumentNullException(nameof(persistentAgentsClient)); + } + + var createPersistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync( + model, + name, + instructions, + tools: tools, + toolResources: toolResources, + temperature: temperature, + topP: topP, + responseFormat: responseFormat, + metadata: metadata, + cancellationToken: cancellationToken).ConfigureAwait(false); + + // Get a local proxy for the agent to work with. + return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, cancellationToken: cancellationToken).ConfigureAwait(false); + } } From 48ad3cf579077556f6ac3f2363db91b1eb2caaae Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:13:12 +0100 Subject: [PATCH 02/16] Update samples and extension methods --- .../Extensions/AgentRunResponseExtensions.cs | 36 -- .../Extensions/OpenAIAgentExtensions.cs | 101 ------ .../Extensions/OpenAIClientExtensions.cs | 43 --- .../MEAI.OpenAI/AgentRunResponseExtensions.cs | 100 ++++++ .../MEAI.OpenAI/OpenAIAgentExtensions.cs | 331 ++++++++++++++++++ .../MEAI.OpenAI/OpenAIClientExtensions.cs | 154 ++++++++ ...lientAgent_With_AzureAIAgentsPersistent.cs | 2 +- ...entAgent_With_AzureOpenAIChatCompletion.cs | 2 +- ...atClientAgent_With_OpenAIChatCompletion.cs | 46 --- ...s => ChatClientAgent_With_OpenAIClient.cs} | 4 +- 10 files changed, 589 insertions(+), 230 deletions(-) delete mode 100644 dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs delete mode 100644 dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs delete mode 100644 dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs create mode 100644 dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs create mode 100644 dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs create mode 100644 dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs delete mode 100644 dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs => ChatClientAgent_With_OpenAIClient.cs} (94%) diff --git a/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs deleted file mode 100644 index d65d0dff3..000000000 --- a/dotnet/samples/GettingStarted/Extensions/AgentRunResponseExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Agents; -using OpenAI.Chat; -using OpenAI.Responses; - -namespace OpenAI; - -/// -/// Extension methods for -/// -internal static class AgentRunResponseExtensions -{ - internal static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse) - { - if (agentResponse.RawRepresentation is ChatCompletion chatCompletion) - { - return chatCompletion; - } - throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); - } - - internal static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentResponse) - { - if (agentResponse.RawRepresentation is ChatResponse chatResponse) - { - if (chatResponse.RawRepresentation is OpenAIResponse openAIResponse) - { - return openAIResponse; - } - throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); - } - throw new ArgumentException("AgentRunResponse.RawRepresentation must be a OpenAIResponse"); - } -} diff --git a/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs b/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs deleted file mode 100644 index a6fa268a7..000000000 --- a/dotnet/samples/GettingStarted/Extensions/OpenAIAgentExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Agents; -using OpenAI.Chat; - -namespace OpenAI; - -/// -/// Provides extension methods for to simplify interaction with OpenAI chat messages -/// and return native OpenAI responses. -/// -/// -/// These extensions bridge the gap between the Microsoft Extensions AI framework and the OpenAI SDK, -/// allowing developers to work with native OpenAI types while leveraging the AI Agent framework. -/// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types, -/// and return OpenAI objects directly from the agent's . -/// -internal static class OpenAIAgentExtensions -{ - /// - /// Runs the AI agent with a single OpenAI chat message and returns the response as a native OpenAI . - /// - /// The AI agent to run. - /// The OpenAI chat message to send to the agent. - /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided message and agent response. - /// Optional parameters for agent invocation. - /// The to monitor for cancellation requests. The default is . - /// A representing the asynchronous operation that returns a native OpenAI response. - /// Thrown when or is . - /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. - /// Thrown when the type is not supported by the conversion method. - /// - /// This method converts the OpenAI chat message to the Microsoft Extensions AI format using , - /// runs the agent, and then extracts the native OpenAI from the response using . - /// Currently only types are supported for conversion. - /// - internal static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) - { - var response = await agent.RunAsync([message.ToChatMessage()], thread, options, cancellationToken); - - var chatCompletion = response.AsChatCompletion(); - return chatCompletion; - } - - /// - /// Runs the AI agent with a collection of OpenAI chat messages and returns the response as a native OpenAI . - /// - /// The AI agent to run. - /// The collection of OpenAI chat messages to send to the agent. - /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response. - /// Optional parameters for agent invocation. - /// The to monitor for cancellation requests. The default is . - /// A representing the asynchronous operation that returns a native OpenAI response. - /// Thrown when or is . - /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. - /// Thrown when any message in has a type that is not supported by the conversion method. - /// - /// This method converts each OpenAI chat message to the Microsoft Extensions AI format using , - /// runs the agent with the converted message collection, and then extracts the native OpenAI from the response using . - /// Currently only types are supported for conversion. - /// - internal static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) - { - var response = await agent.RunAsync(messages.Select(m => m.ToChatMessage()).ToList(), thread, options, cancellationToken); - - var chatCompletion = response.AsChatCompletion(); - return chatCompletion; - } - - /// - /// Converts an OpenAI chat message to a Microsoft Extensions AI . - /// - /// The OpenAI chat message to convert. - /// A equivalent of the input OpenAI message. - /// Thrown when is . - /// Thrown when the type is not supported for conversion. - /// - /// - /// This method provides a bridge between OpenAI SDK message types and Microsoft Extensions AI message types. - /// It enables seamless integration when working with both frameworks in the same application. - /// - /// - /// Currently, only is supported for conversion. The method extracts the text content - /// from the first content item in the user message and creates a corresponding Microsoft Extensions AI user message. - /// - /// - /// Future versions may extend support to additional OpenAI message types such as AssistantChatMessage and SystemChatMessage. - /// - /// - internal static Microsoft.Extensions.AI.ChatMessage ToChatMessage(this OpenAI.Chat.ChatMessage chatMessage) - { - switch (chatMessage) - { - case UserChatMessage userMessage: - return new Microsoft.Extensions.AI.ChatMessage(ChatRole.User, userMessage.Content.First().Text); - default: - throw new ArgumentException("Only support for user messages is implemented."); - } - } -} diff --git a/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs b/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs deleted file mode 100644 index 87d105cdb..000000000 --- a/dotnet/samples/GettingStarted/Extensions/OpenAIClientExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Agents; -using Microsoft.Extensions.Logging; -using OpenAI.Chat; -using OpenAI.Responses; - -namespace OpenAI; - -/// -/// Extension methods for . -/// -internal static class OpenAIClientExtensions -{ - internal static AIAgent GetChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - var chatClient = client.GetChatClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; - } - - internal static AIAgent GetAgent(this ChatClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - var chatClient = client.AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; - } - - internal static AIAgent GetOpenAIResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - var chatClient = client.GetOpenAIResponseClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; - } - - internal static AIAgent GetAgent(this OpenAIResponseClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - var chatClient = client.AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; - } -} diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs new file mode 100644 index 000000000..1d4d4c459 --- /dev/null +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using OpenAI.Chat; +using OpenAI.Responses; + +namespace OpenAI; + +/// +/// Provides extension methods for to extract native OpenAI response objects +/// from the Microsoft Extensions AI Agent framework responses. +/// +/// +/// These extensions enable developers to access the underlying OpenAI SDK objects when working with +/// AI agents that are backed by OpenAI services. The methods extract strongly-typed OpenAI responses +/// from the property, providing a bridge between +/// the Microsoft Extensions AI framework and the native OpenAI SDK types. +/// +public static class AgentRunResponseExtensions +{ + /// + /// Extracts a native OpenAI object from an . + /// + /// The agent response containing the raw OpenAI representation. + /// The native OpenAI object. + /// Thrown when is . + /// + /// Thrown when the is not a object. + /// This typically occurs when the agent response was not generated by an OpenAI chat completion service + /// or when the underlying representation has been modified or corrupted. + /// + /// + /// + /// This method provides access to the native OpenAI object that was used + /// to generate the agent response. This is useful when you need to access OpenAI-specific properties + /// or metadata that are not exposed through the Microsoft Extensions AI abstractions. + /// + /// + /// The method expects that the property contains + /// a object directly. If the response was generated through a different + /// OpenAI API (such as the Responses API), use instead. + /// + /// + public static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse) + { + if (agentResponse.RawRepresentation is ChatCompletion chatCompletion) + { + return chatCompletion; + } + throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + } + + /// + /// Extracts a native OpenAI object from an . + /// + /// The agent response containing the raw OpenAI representation. + /// The native OpenAI object. + /// Thrown when is . + /// + /// Thrown in the following scenarios: + /// + /// When the is not a object. + /// When the is not an object. + /// + /// This typically occurs when the agent response was not generated by an OpenAI service that uses the Responses API, + /// or when the underlying representation has been modified or corrupted. + /// + /// + /// + /// This method provides access to the native OpenAI object that was used + /// to generate the agent response. This is useful when working with OpenAI's Responses API and you need + /// to access OpenAI-specific properties or metadata that are not exposed through the Microsoft Extensions AI abstractions. + /// + /// + /// The method follows a two-level extraction pattern: + /// + /// First, it extracts a from the . + /// Then, it extracts an from the . + /// + /// This pattern accommodates the layered architecture where the Microsoft Extensions AI framework wraps OpenAI responses. + /// + /// + /// If the response was generated through the standard OpenAI Chat Completion API (not the Responses API), + /// use instead. + /// + /// + public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentResponse) + { + if (agentResponse.RawRepresentation is ChatResponse chatResponse) + { + if (chatResponse.RawRepresentation is OpenAIResponse openAIResponse) + { + return openAIResponse; + } + throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + } + throw new ArgumentException("AgentRunResponse.RawRepresentation must be a OpenAIResponse"); + } +} diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs new file mode 100644 index 000000000..e87dfee24 --- /dev/null +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs @@ -0,0 +1,331 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using OpenAI.Chat; + +namespace OpenAI; + +/// +/// Provides extension methods for to simplify interaction with OpenAI chat messages +/// and return native OpenAI responses. +/// +/// +/// These extensions bridge the gap between the Microsoft Extensions AI framework and the OpenAI SDK, +/// allowing developers to work with native OpenAI types while leveraging the AI Agent framework. +/// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types, +/// and return OpenAI objects directly from the agent's . +/// +internal static class OpenAIAgentExtensions +{ + /// + /// Runs the AI agent with a single OpenAI chat message and returns the response as a native OpenAI . + /// + /// The AI agent to run. + /// The OpenAI chat message to send to the agent. + /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided message and agent response. + /// Optional parameters for agent invocation. + /// The to monitor for cancellation requests. The default is . + /// A representing the asynchronous operation that returns a native OpenAI response. + /// Thrown when or is . + /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. + /// Thrown when the type is not supported by the message conversion method. + /// + /// This method converts the OpenAI chat message to the Microsoft Extensions AI format using the appropriate conversion method, + /// runs the agent, and then extracts the native OpenAI from the response using . + /// + internal static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + var response = await agent.RunAsync(message.AsChatMessage(), thread, options, cancellationToken); + + var chatCompletion = response.AsChatCompletion(); + return chatCompletion; + } + + /// + /// Runs the AI agent with a collection of OpenAI chat messages and returns the response as a native OpenAI . + /// + /// The AI agent to run. + /// The collection of OpenAI chat messages to send to the agent. + /// The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response. + /// Optional parameters for agent invocation. + /// The to monitor for cancellation requests. The default is . + /// A representing the asynchronous operation that returns a native OpenAI response. + /// Thrown when or is . + /// Thrown when the agent's response cannot be converted to a , typically when the underlying representation is not an OpenAI response. + /// Thrown when any message in has a type that is not supported by the message conversion method. + /// + /// This method converts each OpenAI chat message to the Microsoft Extensions AI format using , + /// runs the agent with the converted message collection, and then extracts the native OpenAI from the response using . + /// + internal static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + var response = await agent.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken); + + var chatCompletion = response.AsChatCompletion(); + return chatCompletion; + } + + /// + /// Creates a sequence of instances from the specified OpenAI input messages. + /// + /// The OpenAI input messages to convert. + /// A sequence of Microsoft Extensions AI chat messages converted from the OpenAI messages. + /// Thrown when is . + /// Thrown when a message type is encountered that cannot be converted. + /// + /// This method supports conversion of the following OpenAI message types: + /// + /// + /// + /// (obsolete) + /// + /// + /// + /// + /// + public static IEnumerable AsChatMessages(this IEnumerable messages) + { + foreach (OpenAI.Chat.ChatMessage message in messages) + { + switch (message) + { + case OpenAI.Chat.AssistantChatMessage assistantMessage: + yield return assistantMessage.AsChatMessage(); + break; + case OpenAI.Chat.DeveloperChatMessage developerMessage: + yield return developerMessage.AsChatMessage(); + break; +#pragma warning disable CS0618 // Type or member is obsolete + case OpenAI.Chat.FunctionChatMessage functionMessage: + yield return functionMessage.AsChatMessage(); + break; +#pragma warning restore CS0618 // Type or member is obsolete + case OpenAI.Chat.SystemChatMessage systemMessage: + yield return systemMessage.AsChatMessage(); + break; + case OpenAI.Chat.ToolChatMessage toolMessage: + yield return toolMessage.AsChatMessage(); + break; + case OpenAI.Chat.UserChatMessage userMessage: + yield return userMessage.AsChatMessage(); + break; + } + } + } + + /// + /// Converts an OpenAI chat message to a Microsoft Extensions AI . + /// + /// The OpenAI chat message to convert. + /// A equivalent of the input OpenAI message. + /// Thrown when is . + /// Thrown when the type is not supported for conversion. + /// + /// This method provides a bridge between OpenAI SDK message types and Microsoft Extensions AI message types. + /// It handles the conversion by switching on the concrete type of the OpenAI message and calling the appropriate + /// specialized conversion method. + /// + public static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this OpenAI.Chat.ChatMessage chatMessage) + { + return chatMessage switch + { + AssistantChatMessage assistantMessage => assistantMessage.AsChatMessage(), + DeveloperChatMessage developerMessage => developerMessage.AsChatMessage(), + SystemChatMessage systemMessage => systemMessage.AsChatMessage(), + ToolChatMessage toolMessage => toolMessage.AsChatMessage(), + UserChatMessage userMessage => userMessage.AsChatMessage(), + _ => throw new NotSupportedException($"Message type {chatMessage.GetType().Name} is not supported for conversion.") + }; + } + + /// + /// Converts OpenAI chat message content to Microsoft Extensions AI content items. + /// + /// The OpenAI chat message content to convert. + /// A sequence of items converted from the OpenAI content. + /// + /// This method supports conversion of the following OpenAI content part types: + /// + /// Text content (converted to ) + /// Refusal content (converted to ) + /// Image content (converted to or ) + /// Input audio content (converted to ) + /// File content (converted to ) + /// + /// + private static IEnumerable AsAIContent(this OpenAI.Chat.ChatMessageContent content) + { + foreach (OpenAI.Chat.ChatMessageContentPart part in content) + { + switch (part.Kind) + { + case OpenAI.Chat.ChatMessageContentPartKind.Text: + yield return new TextContent(part.Text) + { + RawRepresentation = content + }; + break; + case OpenAI.Chat.ChatMessageContentPartKind.Refusal: + yield return new TextContent(part.Refusal) + { + RawRepresentation = content + }; + break; + case OpenAI.Chat.ChatMessageContentPartKind.Image: + if (part.ImageBytes is not null) + { + yield return new DataContent(part.ImageBytes, part.ImageBytesMediaType) + { + RawRepresentation = content + }; + } + else + { + yield return new UriContent(part.ImageUri, "image/*") + { + RawRepresentation = content + }; + } + break; + case OpenAI.Chat.ChatMessageContentPartKind.InputAudio: + yield return new DataContent(part.InputAudioBytes, "audio/*") + { + RawRepresentation = content + }; + break; + case OpenAI.Chat.ChatMessageContentPartKind.File: + yield return new DataContent(part.FileBytes, part.FileBytesMediaType) + { + RawRepresentation = content + }; + break; + default: + throw new NotSupportedException($"Content part kind '{part.Kind}' is not supported for conversion to AIContent."); + } + } + } + + /// + /// Converts OpenAI chat message content to text. + /// + /// The OpenAI chat message content to convert. + /// A string created from the text and refusal parts of the OpenAI content. + /// + /// Using when converting OpenAI For tool messages, the contents can only be of type text. + /// + private static string AsText(this OpenAI.Chat.ChatMessageContent content) + { + StringBuilder text = new(); + foreach (OpenAI.Chat.ChatMessageContentPart part in content) + { + switch (part.Kind) + { + case OpenAI.Chat.ChatMessageContentPartKind.Text: + text.Append(part.Text); + break; + case OpenAI.Chat.ChatMessageContentPartKind.Refusal: + text.Append(part.Refusal); + break; + default: + throw new NotSupportedException($"Content part kind '{part.Kind}' is not supported for conversion to text."); + } + } + return text.ToString(); + } + + /// + /// Converts an OpenAI to a Microsoft Extensions AI . + /// + /// The OpenAI assistant message to convert. + /// A Microsoft Extensions AI chat message with assistant role. + /// + /// This method converts the assistant message content using and preserves + /// the participant name as the author name in the resulting message. + /// + private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this AssistantChatMessage assistantMessage) + { + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.Assistant, [.. assistantMessage.Content.AsAIContent()]) + { + AuthorName = assistantMessage.ParticipantName, + RawRepresentation = assistantMessage + }; + } + + /// + /// Converts an OpenAI to a Microsoft Extensions AI . + /// + /// The OpenAI developer message to convert. + /// A Microsoft Extensions AI chat message with system role. + /// + /// Developer messages are treated as system messages in the Microsoft Extensions AI framework. + /// The participant name is preserved as the author name. + /// + private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this DeveloperChatMessage developerMessage) + { + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.System, [.. developerMessage.Content.AsAIContent()]) + { + AuthorName = developerMessage.ParticipantName, + RawRepresentation = developerMessage + }; + } + + /// + /// Converts an OpenAI to a Microsoft Extensions AI . + /// + /// The OpenAI system message to convert. + /// A Microsoft Extensions AI chat message with system role. + /// + /// This method converts the system message content using and preserves + /// the participant name as the author name in the resulting message. + /// + private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this SystemChatMessage systemMessage) + { + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.System, [.. systemMessage.Content.AsAIContent()]) + { + AuthorName = systemMessage.ParticipantName, + RawRepresentation = systemMessage + }; + } + + /// + /// Converts an OpenAI to a Microsoft Extensions AI . + /// + /// The OpenAI tool message to convert. + /// A Microsoft Extensions AI chat message with tool role. + /// + /// This method converts tool message content using and includes the tool call ID + /// in the resulting message's additional properties for traceability. + /// + private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this ToolChatMessage toolMessage) + { + var content = new FunctionResultContent(toolMessage.ToolCallId, toolMessage.Content.AsText()) + { + RawRepresentation = toolMessage + }; + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.Tool, [content]) + { + RawRepresentation = toolMessage, + AdditionalProperties = new() { ["tool_call_id"] = toolMessage.ToolCallId } + }; + } + + /// + /// Converts an OpenAI to a Microsoft Extensions AI . + /// + /// The OpenAI user message to convert. + /// A Microsoft Extensions AI chat message with user role. + /// + /// This method converts the user message content using and preserves + /// the participant name as the author name in the resulting message. + /// + private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this UserChatMessage userMessage) + { + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.User, [.. userMessage.Content.AsAIContent()]) + { + AuthorName = userMessage.ParticipantName, + RawRepresentation = userMessage + }; + } +} diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs new file mode 100644 index 000000000..68797f88f --- /dev/null +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using OpenAI.Chat; +using OpenAI.Responses; + +namespace OpenAI; + +/// +/// Provides extension methods for , , and +/// 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 Extensions AI 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 OpenAIClientExtensions +{ + /// + /// Creates an AI agent from an using the OpenAI Chat Completion API. + /// + /// The OpenAI client to use for the agent. + /// The model identifier to use for chat completions (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Chat Completion service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + /// + /// + /// This method creates an agent that uses OpenAI's Chat Completion API, which is suitable for most conversational AI scenarios. + /// The agent will be able to maintain conversation context and use any provided tools to enhance its capabilities. + /// + /// + /// The parameter serves as the system message that guides the agent's behavior. + /// This should contain clear instructions about the agent's role, personality, and any specific guidelines it should follow. + /// + /// + /// If are provided, the agent will be able to call these functions during conversation + /// to retrieve information, perform calculations, or interact with external systems. + /// + /// + public static AIAgent GetChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.GetChatClient(model).AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + /// + /// Creates an AI agent from an OpenAI . + /// + /// The OpenAI chat client to use for the agent. + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the provided chat client. + /// Thrown when is . + /// + /// + /// This method is useful when you already have a configured instance and want to + /// wrap it in an AI agent. This provides more control over the chat client configuration compared to + /// . + /// + /// + /// The resulting agent will inherit all the configuration and behavior of the underlying chat client, + /// including any pre-configured options for model selection, temperature, token limits, etc. + /// + /// + public static AIAgent GetAgent(this ChatClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + /// + /// Creates an AI agent from an using the OpenAI Response API. + /// + /// The OpenAI client to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + /// + /// + /// This method creates an agent that uses OpenAI's Response API, which provides additional features + /// and response formats compared to the standard Chat Completion API. This is particularly useful + /// for scenarios that require structured outputs or specific response formats. + /// + /// + /// The Response API may offer different capabilities than the Chat Completion API, such as + /// enhanced structured output support, different streaming behaviors, or access to specialized + /// response formats that are not available through the standard chat completion endpoint. + /// + /// + /// Choose this method over + /// when you specifically need the features provided by the OpenAI Response API. + /// + /// + public static AIAgent GetOpenAIResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.GetOpenAIResponseClient(model).AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } + + /// + /// Creates an AI agent from an . + /// + /// The OpenAI response client to use for the agent. + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the provided response client. + /// Thrown when is . + /// + /// + /// This method is useful when you already have a configured instance + /// and want to wrap it in an AI agent. This provides more control over the response client configuration + /// compared to . + /// + /// + /// The resulting agent will inherit all the configuration and behavior of the underlying response client, + /// including any pre-configured options for response formats, model parameters, and API-specific settings. + /// + /// + /// Use this method when you need fine-grained control over the OpenAI Response API configuration + /// or when you want to reuse an existing response client instance across multiple agents. + /// + /// + public static AIAgent GetAgent(this OpenAIResponseClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + return agent; + } +} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs index e3b0004dd..8a2bb2d64 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs @@ -8,7 +8,7 @@ namespace Providers; /// -/// Shows how to use with Azure AI Persistent Agents. +/// Shows how to use with Azure AI Persistent Agents. /// /// /// Running "az login" command in terminal is required for authentication with Azure AI service. diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs index e52c7a5d3..633554b7f 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs @@ -29,7 +29,7 @@ public async Task RunWithChatCompletion() .AsIChatClient(); // Define the agent - ChatClientAgent agent = new(chatClient, JokerInstructions, JokerName); + AIAgent agent = new ChatClientAgent(chatClient, JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs deleted file mode 100644 index e21cf5e3a..000000000 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Agents; -using Microsoft.Shared.Samples; -using OpenAI; - -namespace Providers; - -/// -/// End-to-end sample showing how to use with OpenAI Chat Completion. -/// -public sealed class ChatClientAgent_With_OpenAIChatCompletion(ITestOutputHelper output) : AgentSample(output) -{ - private const string JokerName = "Joker"; - private const string JokerInstructions = "You are good at telling jokes."; - - [Fact] - public async Task RunWithChatCompletion() - { - // Get the chat client to use for the agent. - using var chatClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetChatClient(TestConfiguration.OpenAI.ChatModelId) - .AsIChatClient(); - - // Define the agent - ChatClientAgent agent = new(chatClient, JokerInstructions, JokerName); - - // Start a new thread for the agent conversation. - AgentThread thread = agent.GetNewThread(); - - // Respond to user input. - await RunAgentAsync("Tell me a joke about a pirate."); - await RunAgentAsync("Now add some emojis to the joke."); - - // Local function to invoke agent and display the conversation messages for the thread. - async Task RunAgentAsync(string input) - { - this.WriteUserMessage(input); - - var response = await agent.RunAsync(input, thread); - - this.WriteResponseOutput(response); - } - } -} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs similarity index 94% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs rename to dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs index d9d8032f3..1fdca8778 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion_Extensions.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs @@ -7,9 +7,9 @@ namespace Providers; /// -/// End-to-end sample showing how to use with OpenAI Chat Completion. +/// End-to-end sample showing how to use with OpenAI Chat Completion and Responses. /// -public sealed class ChatClientAgent_With_OpenAIChatCompletion_Extensions(ITestOutputHelper output) : AgentSample(output) +public sealed class ChatClientAgent_With_OpenAIClient(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; From f798ca4bb36ae85f49f6e9fed0fee503fff05a14 Mon Sep 17 00:00:00 2001 From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:43:34 +0100 Subject: [PATCH 03/16] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../External/MEAI.OpenAI/AgentRunResponseExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs index 1d4d4c459..b18f31320 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs @@ -93,7 +93,7 @@ public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentRespons { return openAIResponse; } - throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + throw new ArgumentException("ChatResponse.RawRepresentation must be an OpenAIResponse"); } throw new ArgumentException("AgentRunResponse.RawRepresentation must be a OpenAIResponse"); } From c781aca2746f74d72c640ce9c62bd1640e1426b9 Mon Sep 17 00:00:00 2001 From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:44:01 +0100 Subject: [PATCH 04/16] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../External/MEAI.OpenAI/AgentRunResponseExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs index b18f31320..d60f55987 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs @@ -95,6 +95,6 @@ public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentRespons } throw new ArgumentException("ChatResponse.RawRepresentation must be an OpenAIResponse"); } - throw new ArgumentException("AgentRunResponse.RawRepresentation must be a OpenAIResponse"); + throw new ArgumentException("AgentRunResponse.RawRepresentation must be a ChatResponse"); } } From 4b7858a2cff8e56d6cfa5e551ebe72f1db714045 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:59:24 +0100 Subject: [PATCH 05/16] Add extension methods for creating agents using the Assistant API --- .../MEAI.OpenAI/AgentRunResponseExtensions.cs | 21 +- .../NewOpenAIAssistantChatClient.cs | 2 +- .../MEAI.OpenAI/OpenAIAgentExtensions.cs | 27 ++- .../MEAI.OpenAI/OpenAIClientExtensions.cs | 222 +++++++++++------- .../ChatClientAgent_With_OpenAIAssistant.cs | 33 +-- .../ChatClientAgent_With_OpenAIClient.cs | 31 +-- ...tClientAgent_With_OpenAIResponseClient.cs} | 48 ++-- 7 files changed, 215 insertions(+), 169 deletions(-) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_OpenAIResponseChatCompletion.cs => ChatClientAgent_With_OpenAIResponseClient.cs} (57%) diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs index d60f55987..33df0463d 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AgentRunResponseExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Diagnostics; using OpenAI.Chat; using OpenAI.Responses; @@ -44,11 +45,11 @@ public static class AgentRunResponseExtensions /// public static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse) { - if (agentResponse.RawRepresentation is ChatCompletion chatCompletion) - { - return chatCompletion; - } - throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + Throw.IfNull(agentResponse); + + return agentResponse.RawRepresentation is ChatCompletion chatCompletion + ? chatCompletion + : throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); } /// @@ -87,13 +88,13 @@ public static ChatCompletion AsChatCompletion(this AgentRunResponse agentRespons /// public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentResponse) { + Throw.IfNull(agentResponse); + if (agentResponse.RawRepresentation is ChatResponse chatResponse) { - if (chatResponse.RawRepresentation is OpenAIResponse openAIResponse) - { - return openAIResponse; - } - throw new ArgumentException("ChatResponse.RawRepresentation must be an OpenAIResponse"); + return chatResponse.RawRepresentation is OpenAIResponse openAIResponse + ? openAIResponse + : throw new ArgumentException("ChatResponse.RawRepresentation must be an OpenAIResponse"); } throw new ArgumentException("AgentRunResponse.RawRepresentation must be a ChatResponse"); } diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/NewOpenAIAssistantChatClient.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/NewOpenAIAssistantChatClient.cs index 769eb74d7..ba7c04764 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/NewOpenAIAssistantChatClient.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/NewOpenAIAssistantChatClient.cs @@ -49,7 +49,7 @@ public sealed class NewOpenAIAssistantChatClient : IChatClient private IReadOnlyList? _assistantTools; /// Initializes a new instance of the class for the specified . - public NewOpenAIAssistantChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId) + public NewOpenAIAssistantChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId = null) { _client = Throw.IfNull(assistantClient); _assistantId = Throw.IfNullOrWhitespace(assistantId); diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs index e87dfee24..024807a66 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs @@ -3,6 +3,7 @@ using System.Text; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Diagnostics; using OpenAI.Chat; namespace OpenAI; @@ -17,7 +18,7 @@ namespace OpenAI; /// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types, /// and return OpenAI objects directly from the agent's . /// -internal static class OpenAIAgentExtensions +public static class OpenAIAgentExtensions { /// /// Runs the AI agent with a single OpenAI chat message and returns the response as a native OpenAI . @@ -37,6 +38,9 @@ internal static class OpenAIAgentExtensions /// internal static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { + Throw.IfNull(agent); + Throw.IfNull(message); + var response = await agent.RunAsync(message.AsChatMessage(), thread, options, cancellationToken); var chatCompletion = response.AsChatCompletion(); @@ -61,6 +65,9 @@ internal static async Task RunAsync(this AIAgent agent, OpenAI.C /// internal static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { + Throw.IfNull(agent); + Throw.IfNull(messages); + var response = await agent.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken); var chatCompletion = response.AsChatCompletion(); @@ -87,6 +94,8 @@ internal static async Task RunAsync(this AIAgent agent, IEnumera /// public static IEnumerable AsChatMessages(this IEnumerable messages) { + Throw.IfNull(messages); + foreach (OpenAI.Chat.ChatMessage message in messages) { switch (message) @@ -129,6 +138,8 @@ internal static async Task RunAsync(this AIAgent agent, IEnumera /// public static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this OpenAI.Chat.ChatMessage chatMessage) { + Throw.IfNull(chatMessage); + return chatMessage switch { AssistantChatMessage assistantMessage => assistantMessage.AsChatMessage(), @@ -157,6 +168,8 @@ public static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this OpenAI.Chat /// private static IEnumerable AsAIContent(this OpenAI.Chat.ChatMessageContent content) { + Throw.IfNull(content); + foreach (OpenAI.Chat.ChatMessageContentPart part in content) { switch (part.Kind) @@ -217,6 +230,8 @@ private static IEnumerable AsAIContent(this OpenAI.Chat.ChatMessageCo /// private static string AsText(this OpenAI.Chat.ChatMessageContent content) { + Throw.IfNull(content); + StringBuilder text = new(); foreach (OpenAI.Chat.ChatMessageContentPart part in content) { @@ -246,6 +261,8 @@ private static string AsText(this OpenAI.Chat.ChatMessageContent content) /// private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this AssistantChatMessage assistantMessage) { + Throw.IfNull(assistantMessage); + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.Assistant, [.. assistantMessage.Content.AsAIContent()]) { AuthorName = assistantMessage.ParticipantName, @@ -264,6 +281,8 @@ private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this AssistantC /// private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this DeveloperChatMessage developerMessage) { + Throw.IfNull(developerMessage); + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.System, [.. developerMessage.Content.AsAIContent()]) { AuthorName = developerMessage.ParticipantName, @@ -282,6 +301,8 @@ private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this DeveloperC /// private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this SystemChatMessage systemMessage) { + Throw.IfNull(systemMessage); + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.System, [.. systemMessage.Content.AsAIContent()]) { AuthorName = systemMessage.ParticipantName, @@ -300,6 +321,8 @@ private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this SystemChat /// private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this ToolChatMessage toolMessage) { + Throw.IfNull(toolMessage); + var content = new FunctionResultContent(toolMessage.ToolCallId, toolMessage.Content.AsText()) { RawRepresentation = toolMessage @@ -322,6 +345,8 @@ private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this ToolChatMe /// private static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this UserChatMessage userMessage) { + Throw.IfNull(userMessage); + return new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.User, [.. userMessage.Content.AsAIContent()]) { AuthorName = userMessage.ParticipantName, diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs index 68797f88f..cfd599ece 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIClientExtensions.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Assistants; using OpenAI.Chat; using OpenAI.Responses; @@ -33,54 +35,71 @@ public static class OpenAIClientExtensions /// An instance backed by the OpenAI Chat Completion service. /// Thrown when or is . /// Thrown when is empty or whitespace. - /// - /// - /// This method creates an agent that uses OpenAI's Chat Completion API, which is suitable for most conversational AI scenarios. - /// The agent will be able to maintain conversation context and use any provided tools to enhance its capabilities. - /// - /// - /// The parameter serves as the system message that guides the agent's behavior. - /// This should contain clear instructions about the agent's role, personality, and any specific guidelines it should follow. - /// - /// - /// If are provided, the agent will be able to call these functions during conversation - /// to retrieve information, perform calculations, or interact with external systems. - /// - /// - public static AIAgent GetChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + public static AIAgent CreateChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) { + return client.CreateChatClientAgent( + model, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); + } + + /// + /// Creates an AI agent from an using the OpenAI Chat Completion API. + /// + /// The OpenAI client to use for the agent. + /// The model identifier to use for chat completions (e.g., "gpt-4", "gpt-3.5-turbo"). + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Chat Completion service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static AIAgent CreateChatClientAgent(this OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) + { + Throw.IfNull(client); + Throw.IfNull(model); + var chatClient = client.GetChatClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + ChatClientAgent agent = new(chatClient, options, loggerFactory); return agent; } /// - /// Creates an AI agent from an OpenAI . + /// Creates an AI agent from an using the OpenAI Response API. /// - /// The OpenAI chat client to use for the agent. + /// The OpenAI client to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). /// 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. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the provided chat client. - /// Thrown when is . - /// - /// - /// This method is useful when you already have a configured instance and want to - /// wrap it in an AI agent. This provides more control over the chat client configuration compared to - /// . - /// - /// - /// The resulting agent will inherit all the configuration and behavior of the underlying chat client, - /// including any pre-configured options for model selection, temperature, token limits, etc. - /// - /// - public static AIAgent GetAgent(this ChatClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static AIAgent CreateResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) { - var chatClient = client.AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; + return client.CreateResponseClientAgent( + model, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); } /// @@ -88,67 +107,110 @@ public static AIAgent GetAgent(this ChatClient client, string? instructions = nu /// /// The OpenAI client to use for the agent. /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). - /// 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. + /// Full set of options to configure the agent. /// Optional logger factory for enabling logging within the agent. /// An instance backed by the OpenAI Response service. /// Thrown when or is . /// Thrown when is empty or whitespace. - /// - /// - /// This method creates an agent that uses OpenAI's Response API, which provides additional features - /// and response formats compared to the standard Chat Completion API. This is particularly useful - /// for scenarios that require structured outputs or specific response formats. - /// - /// - /// The Response API may offer different capabilities than the Chat Completion API, such as - /// enhanced structured output support, different streaming behaviors, or access to specialized - /// response formats that are not available through the standard chat completion endpoint. - /// - /// - /// Choose this method over - /// when you specifically need the features provided by the OpenAI Response API. - /// - /// - public static AIAgent GetOpenAIResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + public static AIAgent CreateResponseClientAgent(this OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) { + Throw.IfNull(client); + Throw.IfNull(model); + var chatClient = client.GetOpenAIResponseClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); + ChatClientAgent agent = new(chatClient, options, loggerFactory); return agent; } /// - /// Creates an AI agent from an . + /// Creates an AI agent from an using the OpenAI Assistant API. /// - /// The OpenAI response client to use for the agent. + /// The OpenAI client to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). /// 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. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the provided response client. - /// Thrown when is . - /// - /// - /// This method is useful when you already have a configured instance - /// and want to wrap it in an AI agent. This provides more control over the response client configuration - /// compared to . - /// - /// - /// The resulting agent will inherit all the configuration and behavior of the underlying response client, - /// including any pre-configured options for response formats, model parameters, and API-specific settings. - /// - /// - /// Use this method when you need fine-grained control over the OpenAI Response API configuration - /// or when you want to reuse an existing response client instance across multiple agents. - /// - /// - public static AIAgent GetAgent(this OpenAIResponseClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static async Task CreateAssistantClientAgentAsync(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) { - var chatClient = client.AsIChatClient(); - ChatClientAgent agent = new(chatClient, instructions, name, description, tools, loggerFactory); - return agent; + return await client.CreateAssistantClientAgentAsync( + model, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); + } + + /// + /// Creates an AI agent from an using the OpenAI Assistant API. + /// + /// The OpenAI client to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static async Task CreateAssistantClientAgentAsync(this OpenAIClient client, string model, ChatClientAgentOptions options, 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, + }; + + if (options.ChatOptions?.Tools is not null) + { + foreach (AITool tool in options.ChatOptions.Tools) + { + switch (tool) + { + case AIFunction aiFunction: + assistantOptions.Tools.Add(NewOpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(aiFunction)); + break; + + case HostedCodeInterpreterTool: + var codeInterpreterToolDefinition = new CodeInterpreterToolDefinition(); + assistantOptions.Tools.Add(codeInterpreterToolDefinition); + break; + } + } + } + + var assistantClient = client.GetAssistantClient(); + + var assistantCreateResult = await assistantClient.CreateAssistantAsync(model, assistantOptions); + var assistantId = assistantCreateResult.Value.Id; + + var agentOptions = new ChatClientAgentOptions() + { + Id = assistantId, + Name = options.Name, + Description = options.Description, + Instructions = options.Instructions, + ChatOptions = options.ChatOptions?.Tools is null ? null : new ChatOptions() + { + Tools = options.ChatOptions.Tools, + } + }; + + var chatClient = new NewOpenAIAssistantChatClient(assistantClient, assistantId); + return new ChatClientAgent(chatClient, agentOptions, loggerFactory); } } diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs index f3c12a3ff..41acdd449 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Samples; using OpenAI; @@ -10,7 +9,7 @@ namespace Providers; /// -/// End-to-end sample showing how to use with OpenAI Assistants. +/// End-to-end sample showing how to use with OpenAI Assistants. /// public sealed class ChatClientAgent_With_OpenAIAssistant(ITestOutputHelper output) : AgentSample(output) { @@ -18,28 +17,19 @@ public sealed class ChatClientAgent_With_OpenAIAssistant(ITestOutputHelper outpu private const string JokerInstructions = "You are good at telling jokes."; [Fact] - public async Task RunWithOpenAIAssistant() + public async Task RunWithAssistant() { // Get a client to create server side agents with. var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); - var assistantClient = openAIClient.GetAssistantClient(); - - // Create a server side agent to work with. - var assistantCreateResult = await assistantClient.CreateAssistantAsync( - TestConfiguration.OpenAI.ChatModelId, - new() - { - Name = JokerName, - Instructions = JokerInstructions - }); - - var assistantId = assistantCreateResult.Value.Id; - // Get the chat client to use for the agent. - using var chatClient = assistantClient.AsIChatClient(assistantId); - - // Define the agent. - ChatClientAgent agent = new(chatClient); + // Get the agent directly from OpenAIClient. + AIAgent agent = await new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .CreateAssistantClientAgentAsync(TestConfiguration.OpenAI.ChatModelId, + options: new() + { + Name = JokerName, + Instructions = JokerInstructions, + }); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -59,7 +49,8 @@ async Task RunAgentAsync(string input) } // Cleanup + var assistantClient = openAIClient.GetAssistantClient(); await assistantClient.DeleteThreadAsync(thread.Id); - await assistantClient.DeleteAssistantAsync(assistantId); + await assistantClient.DeleteAssistantAsync(agent.Id); } } diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs index 1fdca8778..6d8dd9993 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs @@ -19,32 +19,7 @@ public async Task RunWithChatCompletion() { // Get the agent directly from OpenAIClient. AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); - - // Start a new thread for the agent conversation. - AgentThread thread = agent.GetNewThread(); - - // Respond to user input. - await RunAgentAsync("Tell me a joke about a pirate."); - await RunAgentAsync("Now add some emojis to the joke."); - - // Local function to invoke agent and display the conversation messages for the thread. - async Task RunAgentAsync(string input) - { - Console.WriteLine(input); - - var response = await agent.RunAsync(input, thread); - - Console.WriteLine(response.Messages.Last().Text); - } - } - - [Fact] - public async Task RunWithResponses() - { - // Get the agent directly from OpenAIClient. - AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetOpenAIResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -69,7 +44,7 @@ public async Task RunWithChatCompletionReturnChatCompletion() { // Get the agent directly from OpenAIClient. var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -95,7 +70,7 @@ public async Task RunWithChatCompletionWithOpenAIChatMessage() { // Get the agent directly from OpenAIClient. var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs similarity index 57% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseChatCompletion.cs rename to dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs index 3e3489da4..181bf6be3 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseChatCompletion.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs @@ -11,7 +11,7 @@ namespace Providers; /// /// End-to-end sample showing how to use with OpenAI Chat Completion. /// -public sealed class ChatClientAgent_With_OpenAIResponsesChatCompletion(ITestOutputHelper output) : AgentSample(output) +public sealed class ChatClientAgent_With_OpenAIResponseClient(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; @@ -20,15 +20,11 @@ public sealed class ChatClientAgent_With_OpenAIResponsesChatCompletion(ITestOutp /// This will use the conversation id to reference the thread state on the server side. /// [Fact] - public async Task RunWithChatCompletionServiceManagedThread() + public async Task RunWithResponses() { - // Get the chat client to use for the agent. - using var chatClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId) - .AsIChatClient(); - - // Define the agent - ChatClientAgent agent = new(chatClient, JokerInstructions, JokerName); + // Get the agent directly from OpenAIClient. + AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .CreateResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); // Start a new thread for the agent conversation based on the type. AgentThread thread = agent.GetNewThread(); @@ -52,27 +48,23 @@ async Task RunAgentAsync(string input) /// This will use in-memory messages to store the thread state. /// [Fact] - public async Task RunWithChatCompletionInMemoryThread() + public async Task RunWithResponsesAndStoreOutputDisabled() { - // Get the chat client to use for the agent. - using var chatClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId) - .AsIChatClient(); - - // Define the agent - ChatClientAgent agent = - new(chatClient, options: new() - { - Name = JokerName, - Instructions = JokerInstructions, - ChatOptions = new ChatOptions + // Get the agent directly from OpenAIClient. + AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) + .CreateResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, + options: new() { - // We can use the RawRepresentationFactory to provide Response service specific - // options. Here we can indicate that we do not want the service to store the - // conversation in a service managed thread. - RawRepresentationFactory = (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } - } - }); + Name = JokerName, + Instructions = JokerInstructions, + ChatOptions = new ChatOptions + { + // We can use the RawRepresentationFactory to provide Response service specific + // options. Here we can indicate that we do not want the service to store the + // conversation in a service managed thread. + RawRepresentationFactory = (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } + } + }); // Start a new thread for the agent conversation based on the type. AgentThread thread = agent.GetNewThread(); From c42226c7d280af7cc3b74b609803d7b4d1dec3ac Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:19:03 +0100 Subject: [PATCH 06/16] Add orchestration sample --- .../SequentialOrchestration_Multi_Agent.cs | 84 +++++++++++++++++++ .../ChatClientAgent_With_OpenAIAssistant.cs | 14 ++-- 2 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs diff --git a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs new file mode 100644 index 000000000..82cedf5b4 --- /dev/null +++ b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.Orchestration; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Samples; +using OpenAI; + +namespace Orchestration; + +/// +/// Demonstrates how to use the for +/// executing multiple heterogeneous agents in sequence. +/// +public class SequentialOrchestration_Multi_Agent(ITestOutputHelper output) : OrchestrationSample(output) +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOrchestrationAsync(bool streamedResponse) + { + var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); + var model = TestConfiguration.OpenAI.ChatModelId; + + // Define the agents + AIAgent analystAgent = + openAIClient.CreateChatClientAgent( + model, + name: "Analyst", + instructions: + """ + You are a marketing analyst. Given a product description, identify: + - Key features + - Target audience + - Unique selling points + """, + description: "A agent that extracts key concepts from a product description."); + AIAgent writerAgent = + openAIClient.CreateResponseClientAgent( + model, + name: "copywriter", + instructions: + """ + You are a marketing copywriter. Given a block of text describing features, audience, and USPs, + compose a compelling marketing copy (like a newsletter section) that highlights these points. + Output should be short (around 150 words), output just the copy as a single text block. + """, + description: "An agent that writes a marketing copy based on the extracted concepts."); + AIAgent editorAgent = + await openAIClient.CreateAssistantClientAgentAsync( + model, + name: "editor", + instructions: + """ + You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, + give format and make it polished. Output the final improved copy as a single text block. + """, + description: "An agent that formats and proofreads the marketing copy."); + + // Create a monitor to capturing agent responses (via ResponseCallback) + // to display at the end of this sample. (optional) + // NOTE: Create your own callback to capture responses in your application or service. + OrchestrationMonitor monitor = new(); + // Define the orchestration + SequentialOrchestration orchestration = + new(analystAgent, writerAgent, editorAgent) + { + LoggerFactory = this.LoggerFactory, + ResponseCallback = monitor.ResponseCallback, + StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null, + }; + + // Run the orchestration + string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours"; + Console.WriteLine($"\n# INPUT: {input}\n"); + AgentRunResponse result = await orchestration.RunAsync(input); + Console.WriteLine($"\n# RESULT: {result}"); + + this.DisplayHistory(monitor.History); + + // Cleanup + var assistantClient = openAIClient.GetAssistantClient(); + await assistantClient.DeleteAssistantAsync(editorAgent.Id); + } +} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs index 41acdd449..e8e457b61 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs @@ -23,13 +23,13 @@ public async Task RunWithAssistant() var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); // Get the agent directly from OpenAIClient. - AIAgent agent = await new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateAssistantClientAgentAsync(TestConfiguration.OpenAI.ChatModelId, - options: new() - { - Name = JokerName, - Instructions = JokerInstructions, - }); + AIAgent agent = await openAIClient.CreateAssistantClientAgentAsync( + TestConfiguration.OpenAI.ChatModelId, + options: new() + { + Name = JokerName, + Instructions = JokerInstructions, + }); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); From 2269b1abbf7834c8285974da4128a2d269e012fd Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:19:03 +0100 Subject: [PATCH 07/16] Add orchestration sample --- ...ions.cs => AIAgentWithOpenAIExtensions.cs} | 6 +- .../SequentialOrchestration_Multi_Agent.cs | 84 +++++++++++++++++++ .../ChatClientAgent_With_OpenAIAssistant.cs | 14 ++-- 3 files changed, 94 insertions(+), 10 deletions(-) rename dotnet/samples/GettingStarted/External/MEAI.OpenAI/{OpenAIAgentExtensions.cs => AIAgentWithOpenAIExtensions.cs} (97%) create mode 100644 dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AIAgentWithOpenAIExtensions.cs similarity index 97% rename from dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs rename to dotnet/samples/GettingStarted/External/MEAI.OpenAI/AIAgentWithOpenAIExtensions.cs index 024807a66..d829093d4 100644 --- a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIAgentExtensions.cs +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/AIAgentWithOpenAIExtensions.cs @@ -18,7 +18,7 @@ namespace OpenAI; /// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types, /// and return OpenAI objects directly from the agent's . /// -public static class OpenAIAgentExtensions +public static class AIAgentWithOpenAIExtensions { /// /// Runs the AI agent with a single OpenAI chat message and returns the response as a native OpenAI . @@ -36,7 +36,7 @@ public static class OpenAIAgentExtensions /// This method converts the OpenAI chat message to the Microsoft Extensions AI format using the appropriate conversion method, /// runs the agent, and then extracts the native OpenAI from the response using . /// - internal static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public static async Task RunAsync(this AIAgent agent, OpenAI.Chat.ChatMessage message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(agent); Throw.IfNull(message); @@ -63,7 +63,7 @@ internal static async Task RunAsync(this AIAgent agent, OpenAI.C /// This method converts each OpenAI chat message to the Microsoft Extensions AI format using , /// runs the agent with the converted message collection, and then extracts the native OpenAI from the response using . /// - internal static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public static async Task RunAsync(this AIAgent agent, IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(agent); Throw.IfNull(messages); diff --git a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs new file mode 100644 index 000000000..82cedf5b4 --- /dev/null +++ b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.Orchestration; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Samples; +using OpenAI; + +namespace Orchestration; + +/// +/// Demonstrates how to use the for +/// executing multiple heterogeneous agents in sequence. +/// +public class SequentialOrchestration_Multi_Agent(ITestOutputHelper output) : OrchestrationSample(output) +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOrchestrationAsync(bool streamedResponse) + { + var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); + var model = TestConfiguration.OpenAI.ChatModelId; + + // Define the agents + AIAgent analystAgent = + openAIClient.CreateChatClientAgent( + model, + name: "Analyst", + instructions: + """ + You are a marketing analyst. Given a product description, identify: + - Key features + - Target audience + - Unique selling points + """, + description: "A agent that extracts key concepts from a product description."); + AIAgent writerAgent = + openAIClient.CreateResponseClientAgent( + model, + name: "copywriter", + instructions: + """ + You are a marketing copywriter. Given a block of text describing features, audience, and USPs, + compose a compelling marketing copy (like a newsletter section) that highlights these points. + Output should be short (around 150 words), output just the copy as a single text block. + """, + description: "An agent that writes a marketing copy based on the extracted concepts."); + AIAgent editorAgent = + await openAIClient.CreateAssistantClientAgentAsync( + model, + name: "editor", + instructions: + """ + You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, + give format and make it polished. Output the final improved copy as a single text block. + """, + description: "An agent that formats and proofreads the marketing copy."); + + // Create a monitor to capturing agent responses (via ResponseCallback) + // to display at the end of this sample. (optional) + // NOTE: Create your own callback to capture responses in your application or service. + OrchestrationMonitor monitor = new(); + // Define the orchestration + SequentialOrchestration orchestration = + new(analystAgent, writerAgent, editorAgent) + { + LoggerFactory = this.LoggerFactory, + ResponseCallback = monitor.ResponseCallback, + StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null, + }; + + // Run the orchestration + string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours"; + Console.WriteLine($"\n# INPUT: {input}\n"); + AgentRunResponse result = await orchestration.RunAsync(input); + Console.WriteLine($"\n# RESULT: {result}"); + + this.DisplayHistory(monitor.History); + + // Cleanup + var assistantClient = openAIClient.GetAssistantClient(); + await assistantClient.DeleteAssistantAsync(editorAgent.Id); + } +} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs index 41acdd449..e8e457b61 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs @@ -23,13 +23,13 @@ public async Task RunWithAssistant() var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); // Get the agent directly from OpenAIClient. - AIAgent agent = await new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateAssistantClientAgentAsync(TestConfiguration.OpenAI.ChatModelId, - options: new() - { - Name = JokerName, - Instructions = JokerInstructions, - }); + AIAgent agent = await openAIClient.CreateAssistantClientAgentAsync( + TestConfiguration.OpenAI.ChatModelId, + options: new() + { + Name = JokerName, + Instructions = JokerInstructions, + }); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); From be94f1de2468d411967f75190b50cb3ed7d10c77 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:32:48 +0100 Subject: [PATCH 08/16] Sample for the Foundry alignment document --- .../SequentialOrchestration_Foundry_Agents.cs | 87 +++++++++++++++++++ .../SequentialOrchestration_Multi_Agent.cs | 1 + 2 files changed, 88 insertions(+) create mode 100644 dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Foundry_Agents.cs diff --git a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Foundry_Agents.cs b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Foundry_Agents.cs new file mode 100644 index 000000000..a9ff1ee20 --- /dev/null +++ b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Foundry_Agents.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.Agents.Persistent; +using Azure.Identity; +using Microsoft.Agents.Orchestration; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Shared.Samples; + +namespace Orchestration; + +/// +/// Demonstrates how to use the for +/// executing multiple Foundry agents in sequence. +/// +public class SequentialOrchestration_Foundry_Agents(ITestOutputHelper output) : OrchestrationSample(output) +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RunOrchestrationAsync(bool streamedResponse) + { + // Get a client to create server side agents with. + var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); + var model = TestConfiguration.OpenAI.ChatModelId; + + // Define the agents + AIAgent analystAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "Analyst", + instructions: + """ + You are a marketing analyst. Given a product description, identify: + - Key features + - Target audience + - Unique selling points + """, + description: "A agent that extracts key concepts from a product description."); + AIAgent writerAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "copywriter", + instructions: + """ + You are a marketing copywriter. Given a block of text describing features, audience, and USPs, + compose a compelling marketing copy (like a newsletter section) that highlights these points. + Output should be short (around 150 words), output just the copy as a single text block. + """, + description: "An agent that writes a marketing copy based on the extracted concepts."); + AIAgent editorAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "editor", + instructions: + """ + You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, + give format and make it polished. Output the final improved copy as a single text block. + """, + description: "An agent that formats and proofreads the marketing copy."); + + // Create a monitor to capturing agent responses (via ResponseCallback) + // to display at the end of this sample. (optional) + // NOTE: Create your own callback to capture responses in your application or service. + OrchestrationMonitor monitor = new(); + // Define the orchestration + SequentialOrchestration orchestration = + new(analystAgent, writerAgent, editorAgent) + { + LoggerFactory = this.LoggerFactory, + ResponseCallback = monitor.ResponseCallback, + StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null, + }; + + // Run the orchestration + string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours"; + Console.WriteLine($"\n# INPUT: {input}\n"); + AgentRunResponse result = await orchestration.RunAsync(input); + Console.WriteLine($"\n# RESULT: {result}"); + + this.DisplayHistory(monitor.History); + + // Cleanup + await persistentAgentsClient.Administration.DeleteAgentAsync(editorAgent.Id); + await persistentAgentsClient.Administration.DeleteAgentAsync(writerAgent.Id); + await persistentAgentsClient.Administration.DeleteAgentAsync(analystAgent.Id); + } +} diff --git a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs index 82cedf5b4..e82d4e02d 100644 --- a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs +++ b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs @@ -80,5 +80,6 @@ give format and make it polished. Output the final improved copy as a single tex // Cleanup var assistantClient = openAIClient.GetAssistantClient(); await assistantClient.DeleteAssistantAsync(editorAgent.Id); + // Need to know how to get the assistant thread ID to delete the thread (issue #260) } } From 8aed6e5679c3c6e62164046d5c8801da85f3339c Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:49:08 +0100 Subject: [PATCH 09/16] Address code review feedback --- .../Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs | 2 +- .../Providers/ChatClientAgent_With_OpenAIClient.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs index 8a2bb2d64..5703bb1d3 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs @@ -44,7 +44,7 @@ async Task RunAgentAsync(string input) var response = await agent.RunAsync(input, thread); - Console.WriteLine(response.Messages.Last().Text); + Console.WriteLine(response); } // Cleanup diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs index 6d8dd9993..a8d8efdeb 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Samples; using OpenAI; +using OpenAI.Chat; namespace Providers; @@ -84,7 +85,8 @@ async Task RunAgentAsync(string input) { Console.WriteLine(input); - var chatMessage = new OpenAI.Chat.UserChatMessage(input); + // Use the OpenAI.Chat message types directly + var chatMessage = new UserChatMessage(input); var chatCompletion = await agent.RunAsync(chatMessage, thread); Console.WriteLine(chatCompletion.Content.Last().Text); From d221ef5eaf895db3873607a610796a451a0e526e Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:41:26 +0100 Subject: [PATCH 10/16] Rename provider samples --- ...ntsPersistent.cs => AIAgent_With_AzureAIAgentsPersistent.cs} | 2 +- ...tCompletion.cs => AIAgent_With_AzureOpenAIChatCompletion.cs} | 2 +- ..._With_OpenAIAssistant.cs => AIAgent_With_OpenAIAssistant.cs} | 2 +- ...tAgent_With_OpenAIClient.cs => AIAgent_With_OpenAIClient.cs} | 2 +- ...AIResponseClient.cs => AIAgent_With_OpenAIResponseClient.cs} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_AzureAIAgentsPersistent.cs => AIAgent_With_AzureAIAgentsPersistent.cs} (94%) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_AzureOpenAIChatCompletion.cs => AIAgent_With_AzureOpenAIChatCompletion.cs} (93%) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_OpenAIAssistant.cs => AIAgent_With_OpenAIAssistant.cs} (94%) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_OpenAIClient.cs => AIAgent_With_OpenAIClient.cs} (97%) rename dotnet/samples/GettingStarted/Providers/{ChatClientAgent_With_OpenAIResponseClient.cs => AIAgent_With_OpenAIResponseClient.cs} (96%) diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs similarity index 94% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs rename to dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs index 5703bb1d3..8d3979468 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureAIAgentsPersistent.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs @@ -13,7 +13,7 @@ namespace Providers; /// /// Running "az login" command in terminal is required for authentication with Azure AI service. /// -public sealed class ChatClientAgent_With_AzureAIAgentsPersistent(ITestOutputHelper output) : AgentSample(output) +public sealed class AIAgent_With_AzureAIAgentsPersistent(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs similarity index 93% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs rename to dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs index 14717b727..a78ee097a 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs @@ -12,7 +12,7 @@ namespace Providers; /// /// End-to-end sample showing how to use with Azure OpenAI Chat Completion. /// -public sealed class ChatClientAgent_With_AzureOpenAIChatCompletion(ITestOutputHelper output) : AgentSample(output) +public sealed class AIAgent_With_AzureOpenAIChatCompletion(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs similarity index 94% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs rename to dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs index e8e457b61..31ed4c07a 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs @@ -11,7 +11,7 @@ namespace Providers; /// /// End-to-end sample showing how to use with OpenAI Assistants. /// -public sealed class ChatClientAgent_With_OpenAIAssistant(ITestOutputHelper output) : AgentSample(output) +public sealed class AIAgent_With_OpenAIAssistant(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs similarity index 97% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs rename to dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs index a8d8efdeb..de65bf65c 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIClient.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs @@ -10,7 +10,7 @@ namespace Providers; /// /// End-to-end sample showing how to use with OpenAI Chat Completion and Responses. /// -public sealed class ChatClientAgent_With_OpenAIClient(ITestOutputHelper output) : AgentSample(output) +public sealed class AIAgent_With_OpenAIClient(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs similarity index 96% rename from dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs rename to dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs index 181bf6be3..f41bbcd47 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIResponseClient.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs @@ -11,7 +11,7 @@ namespace Providers; /// /// End-to-end sample showing how to use with OpenAI Chat Completion. /// -public sealed class ChatClientAgent_With_OpenAIResponseClient(ITestOutputHelper output) : AgentSample(output) +public sealed class AIAgent_With_OpenAIResponseClient(ITestOutputHelper output) : AgentSample(output) { private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; From e33a6b78f1bf7bd8334b0152fdbf97628ed9be05 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 29 Jul 2025 17:54:44 +0100 Subject: [PATCH 11/16] Sample showing how to get an AI agent for Foundry SDK --- .../AIAgent_With_AzureAIAgentsPersistent.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs index 8d3979468..89f4d704d 100644 --- a/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureAIAgentsPersistent.cs @@ -19,7 +19,44 @@ public sealed class AIAgent_With_AzureAIAgentsPersistent(ITestOutputHelper outpu private const string JokerInstructions = "You are good at telling jokes."; [Fact] - public async Task RunWithAzureAIAgentsPersistent() + public async Task GetWithAzureAIAgentsPersistent() + { + // Get a client to create server side agents with. + var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); + + // Create a service side persistent agent. + var persistentAgent = await persistentAgentsClient.Administration.CreateAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName!, + name: JokerName, + instructions: JokerInstructions); + + // Get a server side agent. + AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(persistentAgent.Value.Id); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to run agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + Console.WriteLine(input); + + var response = await agent.RunAsync(input, thread); + + Console.WriteLine(response); + } + + // Cleanup + await persistentAgentsClient.Threads.DeleteThreadAsync(thread.Id); + await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id); + } + + [Fact] + public async Task CreateWithAzureAIAgentsPersistent() { // Get a client to create server side agents with. var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); From 43f303fb8eafd4395efc01bc98ce378fdd039638 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 29 Jul 2025 19:50:53 +0100 Subject: [PATCH 12/16] Add OpenAI chat completion based implementation of AIAgent --- .../Custom/Custom_OpenAIChatClientAgent.cs | 42 +++++++++++ .../MEAI.OpenAI/OpenAIChatClientAgent.cs | 70 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs create mode 100644 dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIChatClientAgent.cs diff --git a/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs b/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs new file mode 100644 index 000000000..35767f1d5 --- /dev/null +++ b/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Samples; +using OpenAI; +using OpenAI.Chat; + +namespace Custom; + +/// +/// End-to-end sample showing how to use a custom . +/// +public sealed class Custom_OpenAIChatClientAgent(ITestOutputHelper output) : AgentSample(output) +{ + /// + /// This will create an instance of and run it. + /// + [Fact] + public async Task RunCustomChatClientAgent() + { + var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); + var model = TestConfiguration.OpenAI.ChatModelId; + + var agent = new MyOpenAIChatClientAgent(openAIClient, model); + + var chatMessage = new UserChatMessage("Tell me a joke about a pirate."); + var chatCompletion = await agent.RunAsync(chatMessage); + + Console.WriteLine(chatCompletion.Content.Last().Text); + } +} + +public class MyOpenAIChatClientAgent : OpenAIChatClientAgent +{ + private const string JokerName = "Joker"; + private const string JokerInstructions = "You are good at telling jokes."; + + public MyOpenAIChatClientAgent(OpenAIClient client, string model, ILoggerFactory? loggerFactory = null) : + base(client, model, instructions: JokerInstructions, name: JokerName, loggerFactory: loggerFactory) + { + } +} diff --git a/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIChatClientAgent.cs b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIChatClientAgent.cs new file mode 100644 index 000000000..7e648a181 --- /dev/null +++ b/dotnet/samples/GettingStarted/External/MEAI.OpenAI/OpenAIChatClientAgent.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Chat; +using ChatMessage = OpenAI.Chat.ChatMessage; + +namespace OpenAI; + +/// +/// OpenAI chat completion based implementation of . +/// +public class OpenAIChatClientAgent : AIAgent +{ + private readonly ChatClientAgent _chatClientAgent; + + public OpenAIChatClientAgent(OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null) + { + Throw.IfNull(client); + Throw.IfNull(model); + + var chatClient = client.GetChatClient(model).AsIChatClient(); + this._chatClientAgent = new( + chatClient, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + }, + loggerFactory); + } + + public OpenAIChatClientAgent(OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) + { + Throw.IfNull(client); + Throw.IfNull(model); + + var chatClient = client.GetChatClient(model).AsIChatClient(); + this._chatClientAgent = new(chatClient, options, loggerFactory); + } + + public async Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + var response = await this.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken); + + var chatCompletion = response.AsChatCompletion(); + return chatCompletion; + } + + /// + public override AgentThread GetNewThread() + { + return this._chatClientAgent.GetNewThread(); + } + + /// + public override Task RunAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + return this._chatClientAgent.RunAsync(messages, thread, options, cancellationToken); + } + + /// + public override IAsyncEnumerable RunStreamingAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + return this._chatClientAgent.RunStreamingAsync(messages, thread, options, cancellationToken); + } +} From 6fbc11ec51c5c256d41c0cca39d68d18dc2ea794 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:47:13 +0100 Subject: [PATCH 13/16] Split OpenAI client extension methods by client type --- .../AgentRunResponseExtensions.cs | 54 --------- .../OpenAIAssistantClientExtensions.cs | 114 ++++++++++++++++++ .../OpenAIChatClientExtensions.cs | 66 ++++++++++ .../OpenAIResponseClientExtensions.cs | 66 ++++++++++ 4 files changed, 246 insertions(+), 54 deletions(-) create mode 100644 dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs create mode 100644 dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs create mode 100644 dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs index 33df0463d..e66544860 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Diagnostics; using OpenAI.Chat; -using OpenAI.Responses; namespace OpenAI; @@ -37,11 +35,6 @@ public static class AgentRunResponseExtensions /// to generate the agent response. This is useful when you need to access OpenAI-specific properties /// or metadata that are not exposed through the Microsoft Extensions AI abstractions. /// - /// - /// The method expects that the property contains - /// a object directly. If the response was generated through a different - /// OpenAI API (such as the Responses API), use instead. - /// /// public static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse) { @@ -51,51 +44,4 @@ public static ChatCompletion AsChatCompletion(this AgentRunResponse agentRespons ? chatCompletion : throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); } - - /// - /// Extracts a native OpenAI object from an . - /// - /// The agent response containing the raw OpenAI representation. - /// The native OpenAI object. - /// Thrown when is . - /// - /// Thrown in the following scenarios: - /// - /// When the is not a object. - /// When the is not an object. - /// - /// This typically occurs when the agent response was not generated by an OpenAI service that uses the Responses API, - /// or when the underlying representation has been modified or corrupted. - /// - /// - /// - /// This method provides access to the native OpenAI object that was used - /// to generate the agent response. This is useful when working with OpenAI's Responses API and you need - /// to access OpenAI-specific properties or metadata that are not exposed through the Microsoft Extensions AI abstractions. - /// - /// - /// The method follows a two-level extraction pattern: - /// - /// First, it extracts a from the . - /// Then, it extracts an from the . - /// - /// This pattern accommodates the layered architecture where the Microsoft Extensions AI framework wraps OpenAI responses. - /// - /// - /// If the response was generated through the standard OpenAI Chat Completion API (not the Responses API), - /// use instead. - /// - /// - public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse agentResponse) - { - Throw.IfNull(agentResponse); - - if (agentResponse.RawRepresentation is ChatResponse chatResponse) - { - return chatResponse.RawRepresentation is OpenAIResponse openAIResponse - ? openAIResponse - : throw new ArgumentException("ChatResponse.RawRepresentation must be an OpenAIResponse"); - } - throw new ArgumentException("AgentRunResponse.RawRepresentation must be a ChatResponse"); - } } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs new file mode 100644 index 000000000..beae011f8 --- /dev/null +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +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 Extensions AI 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 +{ + /// + /// Creates an AI agent from an using the OpenAI Assistant API. + /// + /// The OpenAI to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response 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, ILoggerFactory? loggerFactory = null) + { + return await client.CreateAIAgentAsync( + model, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + 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 for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static async Task CreateAIAgentAsync(this AssistantClient client, string model, ChatClientAgentOptions options, 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, + }; + + if (options.ChatOptions?.Tools is not null) + { + foreach (AITool tool in options.ChatOptions.Tools) + { + switch (tool) + { + case AIFunction aiFunction: + assistantOptions.Tools.Add(NewOpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(aiFunction)); + break; + + case HostedCodeInterpreterTool: + var codeInterpreterToolDefinition = new CodeInterpreterToolDefinition(); + assistantOptions.Tools.Add(codeInterpreterToolDefinition); + break; + } + } + } + + var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions).ConfigureAwait(false); + var assistantId = assistantCreateResult.Value.Id; + + var agentOptions = new ChatClientAgentOptions() + { + Id = assistantId, + Name = options.Name, + Description = options.Description, + Instructions = options.Instructions, + ChatOptions = options.ChatOptions?.Tools is null ? null : new ChatOptions() + { + Tools = options.ChatOptions.Tools, + } + }; + +#pragma warning disable CA2000 // Dispose objects before losing scope + var chatClient = new NewOpenAIAssistantChatClient(client, assistantId); +#pragma warning restore CA2000 // Dispose objects before losing scope + return new ChatClientAgent(chatClient, agentOptions, loggerFactory); + } +} diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs new file mode 100644 index 000000000..7bc3f7fab --- /dev/null +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Chat; + +namespace OpenAI; + +/// +/// Provides extension methods for +/// 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 Extensions AI 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 OpenAIChatClientExtensions +{ + /// + /// Creates an AI agent from an using the OpenAI Chat Completion API. + /// + /// The OpenAI to use for the agent. + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Chat Completion service. + /// Thrown when is . + public static AIAgent CreateAIAgent(this ChatClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + return client.CreateAIAgent( + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); + } + + /// + /// Creates an AI agent from an using the OpenAI Chat Completion API. + /// + /// The OpenAI to use for the agent. + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Chat Completion service. + /// Thrown when is . + public static AIAgent CreateAIAgent(this ChatClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) + { + Throw.IfNull(client); + + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, options, loggerFactory); + return agent; + } +} diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs new file mode 100644 index 000000000..b01860358 --- /dev/null +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; +using OpenAI.Responses; + +namespace OpenAI; + +/// +/// Provides extension methods for +/// 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 Extensions AI 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 OpenAIResponseClientExtensions +{ + /// + /// Creates an AI agent from an using the OpenAI Response API. + /// + /// The to use for the agent. + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when is . + public static AIAgent CreateAIAgent(this OpenAIResponseClient client, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + return client.CreateAIAgent( + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); + } + + /// + /// Creates an AI agent from an using the OpenAI Response API. + /// + /// The to use for the agent. + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when is . + public static AIAgent CreateAIAgent(this OpenAIResponseClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) + { + Throw.IfNull(client); + + var chatClient = client.AsIChatClient(); + ChatClientAgent agent = new(chatClient, options, loggerFactory); + return agent; + } +} From 800869915d5bbaeb3e890b35336033bbd50380bc Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:57:17 +0100 Subject: [PATCH 14/16] Remove OpenAIClient extension methods --- .../SequentialOrchestration_Multi_Agent.cs | 8 +- .../AIAgent_With_AzureOpenAIChatCompletion.cs | 2 +- .../Providers/AIAgent_With_OpenAIAssistant.cs | 16 +- .../Providers/AIAgent_With_OpenAIClient.cs | 9 +- .../AIAgent_With_OpenAIResponseClient.cs | 27 +-- .../AIAgentWithOpenAIExtensions.cs | 2 +- .../AgentRunResponseExtensions.cs | 11 +- .../OpenAIAssistantClientExtensions.cs | 92 ++++++++ .../OpenAIClientExtensions.cs | 218 ------------------ 9 files changed, 134 insertions(+), 251 deletions(-) delete mode 100644 dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIClientExtensions.cs diff --git a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs index e82d4e02d..f79b6ad61 100644 --- a/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs +++ b/dotnet/samples/GettingStarted/Orchestration/SequentialOrchestration_Multi_Agent.cs @@ -23,8 +23,7 @@ public async Task RunOrchestrationAsync(bool streamedResponse) // Define the agents AIAgent analystAgent = - openAIClient.CreateChatClientAgent( - model, + openAIClient.GetChatClient(model).CreateAIAgent( name: "Analyst", instructions: """ @@ -35,8 +34,7 @@ public async Task RunOrchestrationAsync(bool streamedResponse) """, description: "A agent that extracts key concepts from a product description."); AIAgent writerAgent = - openAIClient.CreateResponseClientAgent( - model, + openAIClient.GetOpenAIResponseClient(model).CreateAIAgent( name: "copywriter", instructions: """ @@ -46,7 +44,7 @@ Output should be short (around 150 words), output just the copy as a single text """, description: "An agent that writes a marketing copy based on the extracted concepts."); AIAgent editorAgent = - await openAIClient.CreateAssistantClientAgentAsync( + openAIClient.GetAssistantClient().CreateAIAgent( model, name: "editor", instructions: diff --git a/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs index a78ee097a..86b33c35f 100644 --- a/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_AzureOpenAIChatCompletion.cs @@ -27,7 +27,7 @@ public async Task RunWithChatCompletion() : new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey)); // Create the agent - AIAgent agent = openAIClient.CreateChatClientAgent(TestConfiguration.AzureOpenAI.DeploymentName, JokerInstructions, JokerName); + AIAgent agent = openAIClient.GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName).CreateAIAgent(JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs index 31ed4c07a..df9772922 100644 --- a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIAssistant.cs @@ -23,13 +23,15 @@ public async Task RunWithAssistant() var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); // Get the agent directly from OpenAIClient. - AIAgent agent = await openAIClient.CreateAssistantClientAgentAsync( - TestConfiguration.OpenAI.ChatModelId, - options: new() - { - Name = JokerName, - Instructions = JokerInstructions, - }); + AIAgent agent = openAIClient + .GetAssistantClient() + .CreateAIAgent( + TestConfiguration.OpenAI.ChatModelId, + options: new() + { + Name = JokerName, + Instructions = JokerInstructions, + }); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs index de65bf65c..2c85b93bf 100644 --- a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIClient.cs @@ -20,7 +20,8 @@ public async Task RunWithChatCompletion() { // Get the agent directly from OpenAIClient. AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .GetChatClient(TestConfiguration.OpenAI.ChatModelId) + .CreateAIAgent(JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -45,7 +46,8 @@ public async Task RunWithChatCompletionReturnChatCompletion() { // Get the agent directly from OpenAIClient. var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .GetChatClient(TestConfiguration.OpenAI.ChatModelId) + .CreateAIAgent(JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); @@ -71,7 +73,8 @@ public async Task RunWithChatCompletionWithOpenAIChatMessage() { // Get the agent directly from OpenAIClient. var agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateChatClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .GetChatClient(TestConfiguration.OpenAI.ChatModelId) + .CreateAIAgent(JokerInstructions, JokerName); // Start a new thread for the agent conversation. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs index f41bbcd47..314a49c76 100644 --- a/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs +++ b/dotnet/samples/GettingStarted/Providers/AIAgent_With_OpenAIResponseClient.cs @@ -24,7 +24,8 @@ public async Task RunWithResponses() { // Get the agent directly from OpenAIClient. AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, JokerInstructions, JokerName); + .GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId) + .CreateAIAgent(JokerInstructions, JokerName); // Start a new thread for the agent conversation based on the type. AgentThread thread = agent.GetNewThread(); @@ -52,19 +53,19 @@ public async Task RunWithResponsesAndStoreOutputDisabled() { // Get the agent directly from OpenAIClient. AIAgent agent = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) - .CreateResponseClientAgent(TestConfiguration.OpenAI.ChatModelId, - options: new() + .GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId) + .CreateAIAgent(options: new() + { + Name = JokerName, + Instructions = JokerInstructions, + ChatOptions = new ChatOptions { - Name = JokerName, - Instructions = JokerInstructions, - ChatOptions = new ChatOptions - { - // We can use the RawRepresentationFactory to provide Response service specific - // options. Here we can indicate that we do not want the service to store the - // conversation in a service managed thread. - RawRepresentationFactory = (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } - } - }); + // We can use the RawRepresentationFactory to provide Response service specific + // options. Here we can indicate that we do not want the service to store the + // conversation in a service managed thread. + RawRepresentationFactory = (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } + } + }); // Start a new thread for the agent conversation based on the type. AgentThread thread = agent.GetNewThread(); diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs index fc547ab7d..8c2d8c29f 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs @@ -92,7 +92,7 @@ public static async Task RunAsync(this AIAgent agent, IEnumerabl /// /// /// - public static IEnumerable AsChatMessages(this IEnumerable messages) + internal static IEnumerable AsChatMessages(this IEnumerable messages) { Throw.IfNull(messages); diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs index e66544860..b3e5c5c93 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AgentRunResponseExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Diagnostics; using OpenAI.Chat; @@ -40,8 +41,12 @@ public static ChatCompletion AsChatCompletion(this AgentRunResponse agentRespons { Throw.IfNull(agentResponse); - return agentResponse.RawRepresentation is ChatCompletion chatCompletion - ? chatCompletion - : throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + if (agentResponse.RawRepresentation is ChatResponse chatResponse) + { + return chatResponse.RawRepresentation is ChatCompletion chatCompletion + ? chatCompletion + : throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion"); + } + throw new ArgumentException("AgentRunResponse.RawRepresentation must be a ChatResponse"); } } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs index beae011f8..e57f444f3 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs @@ -20,6 +20,98 @@ namespace OpenAI; /// public static class OpenAIAssistantClientExtensions { + /// + /// Creates an AI agent from an using the OpenAI Assistant API. + /// + /// The OpenAI to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static AIAgent CreateAIAgent(this AssistantClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) + { + return client.CreateAIAgent( + model, + new ChatClientAgentOptions() + { + Name = name, + Description = description, + Instructions = instructions, + ChatOptions = tools is null ? null : new ChatOptions() + { + Tools = tools, + } + }, + loggerFactory); + } + + /// + /// Creates an AI agent from an using the OpenAI Assistant API. + /// + /// The OpenAI to use for the agent. + /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// Full set of options to configure the agent. + /// Optional logger factory for enabling logging within the agent. + /// An instance backed by the OpenAI Response service. + /// Thrown when or is . + /// Thrown when is empty or whitespace. + public static AIAgent CreateAIAgent(this AssistantClient client, string model, ChatClientAgentOptions options, 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, + }; + + if (options.ChatOptions?.Tools is not null) + { + foreach (AITool tool in options.ChatOptions.Tools) + { + switch (tool) + { + case AIFunction aiFunction: + assistantOptions.Tools.Add(NewOpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(aiFunction)); + break; + + case HostedCodeInterpreterTool: + var codeInterpreterToolDefinition = new CodeInterpreterToolDefinition(); + assistantOptions.Tools.Add(codeInterpreterToolDefinition); + break; + } + } + } + + var assistantCreateResult = client.CreateAssistant(model, assistantOptions); + var assistantId = assistantCreateResult.Value.Id; + + var agentOptions = new ChatClientAgentOptions() + { + Id = assistantId, + Name = options.Name, + Description = options.Description, + Instructions = options.Instructions, + ChatOptions = options.ChatOptions?.Tools is null ? null : new ChatOptions() + { + Tools = options.ChatOptions.Tools, + } + }; + +#pragma warning disable CA2000 // Dispose objects before losing scope + var chatClient = new NewOpenAIAssistantChatClient(client, assistantId); +#pragma warning restore CA2000 // Dispose objects before losing scope + return new ChatClientAgent(chatClient, agentOptions, loggerFactory); + } + /// /// Creates an AI agent from an using the OpenAI Assistant API. /// diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIClientExtensions.cs deleted file mode 100644 index 40a4b8bd7..000000000 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIClientExtensions.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Agents; -using Microsoft.Extensions.Logging; -using Microsoft.Shared.Diagnostics; -using OpenAI.Assistants; -using OpenAI.Chat; -using OpenAI.Responses; - -namespace OpenAI; - -/// -/// Provides extension methods for , , and -/// 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 Extensions AI 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 OpenAIClientExtensions -{ - /// - /// Creates an AI agent from an using the OpenAI Chat Completion API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for chat completions (e.g., "gpt-4", "gpt-3.5-turbo"). - /// 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. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Chat Completion service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static AIAgent CreateChatClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - return client.CreateChatClientAgent( - model, - new ChatClientAgentOptions() - { - Name = name, - Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() - { - Tools = tools, - } - }, - loggerFactory); - } - - /// - /// Creates an AI agent from an using the OpenAI Chat Completion API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for chat completions (e.g., "gpt-4", "gpt-3.5-turbo"). - /// Full set of options to configure the agent. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Chat Completion service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static AIAgent CreateChatClientAgent(this OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) - { - Throw.IfNull(client); - Throw.IfNull(model); - - var chatClient = client.GetChatClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, options, loggerFactory); - return agent; - } - - /// - /// Creates an AI agent from an using the OpenAI Response API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). - /// 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. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static AIAgent CreateResponseClientAgent(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - return client.CreateResponseClientAgent( - model, - new ChatClientAgentOptions() - { - Name = name, - Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() - { - Tools = tools, - } - }, - loggerFactory); - } - - /// - /// Creates an AI agent from an using the OpenAI Response API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). - /// Full set of options to configure the agent. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static AIAgent CreateResponseClientAgent(this OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) - { - Throw.IfNull(client); - Throw.IfNull(model); - - var chatClient = client.GetOpenAIResponseClient(model).AsIChatClient(); - ChatClientAgent agent = new(chatClient, options, loggerFactory); - return agent; - } - - /// - /// Creates an AI agent from an using the OpenAI Assistant API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). - /// 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. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static async Task CreateAssistantClientAgentAsync(this OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) - { - return await client.CreateAssistantClientAgentAsync( - model, - new ChatClientAgentOptions() - { - Name = name, - Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() - { - Tools = tools, - } - }, - loggerFactory).ConfigureAwait(false); - } - - /// - /// Creates an AI agent from an using the OpenAI Assistant API. - /// - /// The OpenAI client to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). - /// Full set of options to configure the agent. - /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. - /// Thrown when or is . - /// Thrown when is empty or whitespace. - public static async Task CreateAssistantClientAgentAsync(this OpenAIClient client, string model, ChatClientAgentOptions options, 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, - }; - - if (options.ChatOptions?.Tools is not null) - { - foreach (AITool tool in options.ChatOptions.Tools) - { - switch (tool) - { - case AIFunction aiFunction: - assistantOptions.Tools.Add(NewOpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(aiFunction)); - break; - - case HostedCodeInterpreterTool: - var codeInterpreterToolDefinition = new CodeInterpreterToolDefinition(); - assistantOptions.Tools.Add(codeInterpreterToolDefinition); - break; - } - } - } - - var assistantClient = client.GetAssistantClient(); - - var assistantCreateResult = await assistantClient.CreateAssistantAsync(model, assistantOptions).ConfigureAwait(false); - var assistantId = assistantCreateResult.Value.Id; - - var agentOptions = new ChatClientAgentOptions() - { - Id = assistantId, - Name = options.Name, - Description = options.Description, - Instructions = options.Instructions, - ChatOptions = options.ChatOptions?.Tools is null ? null : new ChatOptions() - { - Tools = options.ChatOptions.Tools, - } - }; - -#pragma warning disable CA2000 // Dispose objects before losing scope - var chatClient = new NewOpenAIAssistantChatClient(assistantClient, assistantId); -#pragma warning restore CA2000 // Dispose objects before losing scope - return new ChatClientAgent(chatClient, agentOptions, loggerFactory); - } -} From 786b5e3062ff8ed499f640df72ad9e1c1562d8ec Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:27:06 +0100 Subject: [PATCH 15/16] Rename AsRunnableAgent --- .../Custom/Custom_OpenAIChatClientAgent.cs | 9 ++++--- ...s => PersistentAgentResponseExtensions.cs} | 6 ++--- .../PersistentAgentsClientExtensions.cs | 2 +- .../AIAgentWithOpenAIExtensions.cs | 2 +- .../OpenAIChatClientAgent.cs | 24 ++++++++----------- 5 files changed, 19 insertions(+), 24 deletions(-) rename dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/{ResponseExtensions.cs => PersistentAgentResponseExtensions.cs} (85%) diff --git a/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs b/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs index 35767f1d5..2363b06cb 100644 --- a/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs +++ b/dotnet/samples/GettingStarted/Custom/Custom_OpenAIChatClientAgent.cs @@ -18,10 +18,9 @@ public sealed class Custom_OpenAIChatClientAgent(ITestOutputHelper output) : Age [Fact] public async Task RunCustomChatClientAgent() { - var openAIClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey); - var model = TestConfiguration.OpenAI.ChatModelId; + var chatClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey).GetChatClient(TestConfiguration.OpenAI.ChatModelId); - var agent = new MyOpenAIChatClientAgent(openAIClient, model); + var agent = new MyOpenAIChatClientAgent(chatClient); var chatMessage = new UserChatMessage("Tell me a joke about a pirate."); var chatCompletion = await agent.RunAsync(chatMessage); @@ -35,8 +34,8 @@ public class MyOpenAIChatClientAgent : OpenAIChatClientAgent private const string JokerName = "Joker"; private const string JokerInstructions = "You are good at telling jokes."; - public MyOpenAIChatClientAgent(OpenAIClient client, string model, ILoggerFactory? loggerFactory = null) : - base(client, model, instructions: JokerInstructions, name: JokerName, loggerFactory: loggerFactory) + public MyOpenAIChatClientAgent(ChatClient client, ILoggerFactory? loggerFactory = null) : + base(client, instructions: JokerInstructions, name: JokerName, loggerFactory: loggerFactory) { } } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/ResponseExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentResponseExtensions.cs similarity index 85% rename from dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/ResponseExtensions.cs rename to dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentResponseExtensions.cs index 517350da1..d63cfb915 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/ResponseExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentResponseExtensions.cs @@ -17,14 +17,14 @@ internal static class PersistentAgentResponseExtensions /// The client used to interact with persistent agents. Cannot be . /// The default to use when interacting with the agent. /// A instance that can be used to perform operations on the persistent agent. - public static ChatClientAgent AsRunnableAgent(this Response persistentAgentResponse, PersistentAgentsClient persistentAgentsClient, ChatOptions? chatOptions = null) + public static ChatClientAgent AsAIAgent(this Response persistentAgentResponse, PersistentAgentsClient persistentAgentsClient, ChatOptions? chatOptions = null) { if (persistentAgentResponse is null) { throw new ArgumentNullException(nameof(persistentAgentResponse)); } - return AsRunnableAgent(persistentAgentResponse.Value, persistentAgentsClient, chatOptions); + return AsAIAgent(persistentAgentResponse.Value, persistentAgentsClient, chatOptions); } /// @@ -34,7 +34,7 @@ public static ChatClientAgent AsRunnableAgent(this Response per /// The client used to interact with persistent agents. Cannot be . /// The default to use when interacting with the agent. /// A instance that can be used to perform operations on the persistent agent. - public static ChatClientAgent AsRunnableAgent(this PersistentAgent persistentAgentMetadata, PersistentAgentsClient persistentAgentsClient, ChatOptions? chatOptions = null) + public static ChatClientAgent AsAIAgent(this PersistentAgent persistentAgentMetadata, PersistentAgentsClient persistentAgentsClient, ChatOptions? chatOptions = null) { if (persistentAgentMetadata is null) { diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs index 60daa8d60..acfa0a11e 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/PersistentAgentsClientExtensions.cs @@ -36,7 +36,7 @@ public static async Task GetAIAgentAsync( } var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); - return persistentAgentResponse.AsRunnableAgent(persistentAgentsClient, chatOptions); + return persistentAgentResponse.AsAIAgent(persistentAgentsClient, chatOptions); } /// diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs index 8c2d8c29f..dc23ad1df 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/AIAgentWithOpenAIExtensions.cs @@ -136,7 +136,7 @@ public static async Task RunAsync(this AIAgent agent, IEnumerabl /// It handles the conversion by switching on the concrete type of the OpenAI message and calling the appropriate /// specialized conversion method. /// - public static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this OpenAI.Chat.ChatMessage chatMessage) + internal static Microsoft.Extensions.AI.ChatMessage AsChatMessage(this OpenAI.Chat.ChatMessage chatMessage) { Throw.IfNull(chatMessage); diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientAgent.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientAgent.cs index 45063ff9f..61c3b12fa 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientAgent.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientAgent.cs @@ -19,18 +19,16 @@ public class OpenAIChatClientAgent : AIAgent /// /// Initialize an instance of /// - /// Instance of - /// Id of the model. + /// Instance of /// Optional instructions for the agent. /// Optional name for the agent. /// Optional description for the agent. /// Optional instance of - public OpenAIChatClientAgent(OpenAIClient client, string model, string? instructions = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null) + public OpenAIChatClientAgent(ChatClient client, string? instructions = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); - Throw.IfNull(model); - var chatClient = client.GetChatClient(model).AsIChatClient(); + var chatClient = client.AsIChatClient(); this._chatClientAgent = new( chatClient, new ChatClientAgentOptions() @@ -45,16 +43,14 @@ public OpenAIChatClientAgent(OpenAIClient client, string model, string? instruct /// /// Initialize an instance of /// - /// Instance of - /// Id of the model. + /// Instance of /// Options to create the agent. /// Optional instance of - public OpenAIChatClientAgent(OpenAIClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) + public OpenAIChatClientAgent(ChatClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); - Throw.IfNull(model); - var chatClient = client.GetChatClient(model).AsIChatClient(); + var chatClient = client.AsIChatClient(); this._chatClientAgent = new(chatClient, options, loggerFactory); } @@ -66,7 +62,7 @@ public OpenAIChatClientAgent(OpenAIClient client, string model, ChatClientAgentO /// Optional parameters for agent invocation. /// The to monitor for cancellation requests. The default is . /// A containing the list of items. - public async Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public virtual async Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { var response = await this.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken).ConfigureAwait(false); @@ -75,19 +71,19 @@ public async Task RunAsync(IEnumerable messages, Ag } /// - public override AgentThread GetNewThread() + public sealed override AgentThread GetNewThread() { return this._chatClientAgent.GetNewThread(); } /// - public override Task RunAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public sealed override Task RunAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { return this._chatClientAgent.RunAsync(messages, thread, options, cancellationToken); } /// - public override IAsyncEnumerable RunStreamingAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public sealed override IAsyncEnumerable RunStreamingAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { return this._chatClientAgent.RunStreamingAsync(messages, thread, options, cancellationToken); } From 4361c6406a5cd4a747471547be0fe74a82f431a4 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 31 Jul 2025 18:57:19 +0100 Subject: [PATCH 16/16] Fix XML comments --- .../OpenAIAssistantClientExtensions.cs | 20 +++++++++---------- .../OpenAIChatClientExtensions.cs | 3 ++- .../OpenAIResponseClientExtensions.cs | 3 ++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs index e57f444f3..71277db74 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIAssistantClientExtensions.cs @@ -24,13 +24,13 @@ public static class OpenAIAssistantClientExtensions /// Creates an AI agent from an using the OpenAI Assistant API. /// /// The OpenAI to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. + /// An instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. public static AIAgent CreateAIAgent(this AssistantClient client, string model, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null) @@ -54,16 +54,16 @@ public static AIAgent CreateAIAgent(this AssistantClient client, string model, s /// Creates an AI agent from an using the OpenAI Assistant API. /// /// The OpenAI to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// The model identifier to use (e.g., "gpt-4"). /// Full set of options to configure the agent. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. - /// Thrown when or is . + /// An instance backed by the OpenAI Assistant service. + /// Thrown when or or is . /// Thrown when is empty or whitespace. public static AIAgent CreateAIAgent(this AssistantClient client, string model, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); - Throw.IfNull(model); + Throw.IfNullOrEmpty(model); Throw.IfNull(options); var assistantOptions = new AssistantCreationOptions() @@ -116,13 +116,13 @@ public static AIAgent CreateAIAgent(this AssistantClient client, string model, C /// Creates an AI agent from an using the OpenAI Assistant API. /// /// The OpenAI to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// 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. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. + /// 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, ILoggerFactory? loggerFactory = null) @@ -146,10 +146,10 @@ public static async Task CreateAIAgentAsync(this AssistantClient client /// Creates an AI agent from an using the OpenAI Assistant API. /// /// The OpenAI to use for the agent. - /// The model identifier to use for responses (e.g., "gpt-4", "gpt-3.5-turbo"). + /// The model identifier to use (e.g., "gpt-4"). /// Full set of options to configure the agent. /// Optional logger factory for enabling logging within the agent. - /// An instance backed by the OpenAI Response service. + /// 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, ILoggerFactory? loggerFactory = null) diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs index 7bc3f7fab..90e279eab 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIChatClientExtensions.cs @@ -54,10 +54,11 @@ public static AIAgent CreateAIAgent(this ChatClient client, string? instructions /// Full set of options to configure the agent. /// Optional logger factory for enabling logging within the agent. /// An instance backed by the OpenAI Chat Completion service. - /// Thrown when is . + /// Thrown when or is . public static AIAgent CreateAIAgent(this ChatClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); + Throw.IfNull(options); var chatClient = client.AsIChatClient(); ChatClientAgent agent = new(chatClient, options, loggerFactory); diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs index b01860358..37f27f304 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.OpenAI/OpenAIResponseClientExtensions.cs @@ -54,10 +54,11 @@ public static AIAgent CreateAIAgent(this OpenAIResponseClient client, string? in /// Full set of options to configure the agent. /// Optional logger factory for enabling logging within the agent. /// An instance backed by the OpenAI Response service. - /// Thrown when is . + /// Thrown when or is . public static AIAgent CreateAIAgent(this OpenAIResponseClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) { Throw.IfNull(client); + Throw.IfNull(options); var chatClient = client.AsIChatClient(); ChatClientAgent agent = new(chatClient, options, loggerFactory);