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);